mirror of
https://github.com/dptech-corp/Uni-Lab-OS.git
synced 2025-12-17 13:01:12 +00:00
Merge remote-tracking branch 'origin/dev' into workstation_dev_YB3
This commit is contained in:
@@ -6,6 +6,8 @@ HTTP客户端模块
|
||||
|
||||
import json
|
||||
import os
|
||||
import time
|
||||
from threading import Thread
|
||||
from typing import List, Dict, Any, Optional
|
||||
|
||||
import requests
|
||||
@@ -84,14 +86,14 @@ class HTTPClient:
|
||||
f"{self.remote_addr}/edge/material",
|
||||
json={"nodes": [x for xs in resources.dump() for x in xs], "mount_uuid": mount_uuid},
|
||||
headers={"Authorization": f"Lab {self.auth}"},
|
||||
timeout=100,
|
||||
timeout=60,
|
||||
)
|
||||
else:
|
||||
response = requests.put(
|
||||
f"{self.remote_addr}/edge/material",
|
||||
json={"nodes": [x for xs in resources.dump() for x in xs], "mount_uuid": mount_uuid},
|
||||
headers={"Authorization": f"Lab {self.auth}"},
|
||||
timeout=100,
|
||||
timeout=10,
|
||||
)
|
||||
|
||||
with open(os.path.join(BasicConfig.working_dir, "res_resource_tree_add.json"), "w", encoding="utf-8") as f:
|
||||
@@ -126,12 +128,16 @@ class HTTPClient:
|
||||
Returns:
|
||||
Dict[str, str]: 旧UUID到新UUID的映射关系 {old_uuid: new_uuid}
|
||||
"""
|
||||
with open(os.path.join(BasicConfig.working_dir, "req_resource_tree_get.json"), "w", encoding="utf-8") as f:
|
||||
f.write(json.dumps({"uuids": uuid_list, "with_children": with_children}, indent=4))
|
||||
response = requests.post(
|
||||
f"{self.remote_addr}/edge/material/query",
|
||||
json={"uuids": uuid_list, "with_children": with_children},
|
||||
headers={"Authorization": f"Lab {self.auth}"},
|
||||
timeout=100,
|
||||
)
|
||||
with open(os.path.join(BasicConfig.working_dir, "res_resource_tree_get.json"), "w", encoding="utf-8") as f:
|
||||
f.write(f"{response.status_code}" + "\n" + response.text)
|
||||
if response.status_code == 200:
|
||||
res = response.json()
|
||||
if "code" in res and res["code"] != 0:
|
||||
@@ -187,12 +193,16 @@ class HTTPClient:
|
||||
Returns:
|
||||
Dict: 返回的资源数据
|
||||
"""
|
||||
with open(os.path.join(BasicConfig.working_dir, "req_resource_get.json"), "w", encoding="utf-8") as f:
|
||||
f.write(json.dumps({"id": id, "with_children": with_children}, indent=4))
|
||||
response = requests.get(
|
||||
f"{self.remote_addr}/lab/material",
|
||||
params={"id": id, "with_children": with_children},
|
||||
headers={"Authorization": f"Lab {self.auth}"},
|
||||
timeout=20,
|
||||
)
|
||||
with open(os.path.join(BasicConfig.working_dir, "res_resource_get.json"), "w", encoding="utf-8") as f:
|
||||
f.write(f"{response.status_code}" + "\n" + response.text)
|
||||
return response.json()
|
||||
|
||||
def resource_del(self, id: str) -> requests.Response:
|
||||
|
||||
@@ -7,7 +7,7 @@ from unilabos.devices.workstation.bioyond_studio.station import BioyondWorkstati
|
||||
|
||||
class BioyondDispensingStation(BioyondWorkstation):
|
||||
def __init__(
|
||||
self,
|
||||
self,
|
||||
config,
|
||||
# 桌子
|
||||
deck,
|
||||
@@ -77,7 +77,7 @@ class BioyondDispensingStation(BioyondWorkstation):
|
||||
- hold_m_name: 库位名称,如"C01",用于查找对应的holdMId
|
||||
|
||||
返回: 任务创建结果
|
||||
|
||||
|
||||
异常:
|
||||
- BioyondException: 各种错误情况下的统一异常
|
||||
"""
|
||||
@@ -85,7 +85,7 @@ class BioyondDispensingStation(BioyondWorkstation):
|
||||
# 1. 参数验证
|
||||
if not hold_m_name:
|
||||
raise BioyondException("hold_m_name 是必填参数")
|
||||
|
||||
|
||||
# 检查90%物料参数的完整性
|
||||
# 90%_1物料:如果有物料名称或目标重量,就必须有全部参数
|
||||
if percent_90_1_assign_material_name or percent_90_1_target_weigh:
|
||||
@@ -93,21 +93,21 @@ class BioyondDispensingStation(BioyondWorkstation):
|
||||
raise BioyondException("90%_1物料:如果提供了目标重量,必须同时提供物料名称")
|
||||
if not percent_90_1_target_weigh:
|
||||
raise BioyondException("90%_1物料:如果提供了物料名称,必须同时提供目标重量")
|
||||
|
||||
|
||||
# 90%_2物料:如果有物料名称或目标重量,就必须有全部参数
|
||||
if percent_90_2_assign_material_name or percent_90_2_target_weigh:
|
||||
if not percent_90_2_assign_material_name:
|
||||
raise BioyondException("90%_2物料:如果提供了目标重量,必须同时提供物料名称")
|
||||
if not percent_90_2_target_weigh:
|
||||
raise BioyondException("90%_2物料:如果提供了物料名称,必须同时提供目标重量")
|
||||
|
||||
|
||||
# 90%_3物料:如果有物料名称或目标重量,就必须有全部参数
|
||||
if percent_90_3_assign_material_name or percent_90_3_target_weigh:
|
||||
if not percent_90_3_assign_material_name:
|
||||
raise BioyondException("90%_3物料:如果提供了目标重量,必须同时提供物料名称")
|
||||
if not percent_90_3_target_weigh:
|
||||
raise BioyondException("90%_3物料:如果提供了物料名称,必须同时提供目标重量")
|
||||
|
||||
|
||||
# 检查10%物料参数的完整性
|
||||
# 10%_1物料:如果有物料名称、目标重量、体积或液体物料名称中的任何一个,就必须有全部参数
|
||||
if any([percent_10_1_assign_material_name, percent_10_1_target_weigh, percent_10_1_volume, percent_10_1_liquid_material_name]):
|
||||
@@ -119,7 +119,7 @@ class BioyondDispensingStation(BioyondWorkstation):
|
||||
raise BioyondException("10%_1物料:如果提供了其他参数,必须同时提供液体体积")
|
||||
if not percent_10_1_liquid_material_name:
|
||||
raise BioyondException("10%_1物料:如果提供了其他参数,必须同时提供液体物料名称")
|
||||
|
||||
|
||||
# 10%_2物料:如果有物料名称、目标重量、体积或液体物料名称中的任何一个,就必须有全部参数
|
||||
if any([percent_10_2_assign_material_name, percent_10_2_target_weigh, percent_10_2_volume, percent_10_2_liquid_material_name]):
|
||||
if not percent_10_2_assign_material_name:
|
||||
@@ -130,7 +130,7 @@ class BioyondDispensingStation(BioyondWorkstation):
|
||||
raise BioyondException("10%_2物料:如果提供了其他参数,必须同时提供液体体积")
|
||||
if not percent_10_2_liquid_material_name:
|
||||
raise BioyondException("10%_2物料:如果提供了其他参数,必须同时提供液体物料名称")
|
||||
|
||||
|
||||
# 10%_3物料:如果有物料名称、目标重量、体积或液体物料名称中的任何一个,就必须有全部参数
|
||||
if any([percent_10_3_assign_material_name, percent_10_3_target_weigh, percent_10_3_volume, percent_10_3_liquid_material_name]):
|
||||
if not percent_10_3_assign_material_name:
|
||||
@@ -141,7 +141,7 @@ class BioyondDispensingStation(BioyondWorkstation):
|
||||
raise BioyondException("10%_3物料:如果提供了其他参数,必须同时提供液体体积")
|
||||
if not percent_10_3_liquid_material_name:
|
||||
raise BioyondException("10%_3物料:如果提供了其他参数,必须同时提供液体物料名称")
|
||||
|
||||
|
||||
# 2. 生成任务编码和设置默认值
|
||||
order_code = "task_vial_" + str(int(datetime.now().timestamp()))
|
||||
if order_name is None:
|
||||
@@ -152,7 +152,7 @@ class BioyondDispensingStation(BioyondWorkstation):
|
||||
temperature = "40"
|
||||
if delay_time is None:
|
||||
delay_time = "600"
|
||||
|
||||
|
||||
# 3. 工作流ID
|
||||
workflow_id = "3a19310d-16b9-9d81-b109-0748e953694b"
|
||||
|
||||
@@ -160,22 +160,22 @@ class BioyondDispensingStation(BioyondWorkstation):
|
||||
material_info = self.hardware_interface.material_id_query(workflow_id)
|
||||
if not material_info:
|
||||
raise BioyondException(f"无法查询工作流 {workflow_id} 的物料信息")
|
||||
|
||||
|
||||
# 获取locations列表
|
||||
locations = material_info.get("locations", []) if isinstance(material_info, dict) else []
|
||||
if not locations:
|
||||
raise BioyondException(f"工作流 {workflow_id} 没有找到库位信息")
|
||||
|
||||
|
||||
# 查找指定名称的库位
|
||||
hold_mid = None
|
||||
for location in locations:
|
||||
if location.get("holdMName") == hold_m_name:
|
||||
hold_mid = location.get("holdMId")
|
||||
break
|
||||
|
||||
|
||||
if not hold_mid:
|
||||
raise BioyondException(f"未找到库位名称为 {hold_m_name} 的库位,请检查名称是否正确")
|
||||
|
||||
|
||||
extend_properties = f"{{\"{ hold_mid }\": {{}}}}"
|
||||
self.hardware_interface._logger.info(f"找到库位 {hold_m_name} 对应的holdMId: {hold_mid}")
|
||||
|
||||
@@ -271,7 +271,7 @@ class BioyondDispensingStation(BioyondWorkstation):
|
||||
result = self.hardware_interface.create_order(json_str)
|
||||
self.hardware_interface._logger.info(f"创建90%10%小瓶投料任务结果: {result}")
|
||||
return json.dumps({"suc": True})
|
||||
|
||||
|
||||
except BioyondException:
|
||||
# 重新抛出BioyondException
|
||||
raise
|
||||
@@ -307,7 +307,7 @@ class BioyondDispensingStation(BioyondWorkstation):
|
||||
- hold_m_name: 库位名称,如"ODA-1",用于查找对应的holdMId
|
||||
|
||||
返回: 任务创建结果
|
||||
|
||||
|
||||
异常:
|
||||
- BioyondException: 各种错误情况下的统一异常
|
||||
"""
|
||||
@@ -321,8 +321,8 @@ class BioyondDispensingStation(BioyondWorkstation):
|
||||
raise BioyondException("volume 是必填参数")
|
||||
if not hold_m_name:
|
||||
raise BioyondException("hold_m_name 是必填参数")
|
||||
|
||||
|
||||
|
||||
|
||||
# 2. 生成任务编码和设置默认值
|
||||
order_code = "task_oda_" + str(int(datetime.now().timestamp()))
|
||||
if order_name is None:
|
||||
@@ -333,30 +333,30 @@ class BioyondDispensingStation(BioyondWorkstation):
|
||||
temperature = "20"
|
||||
if delay_time is None:
|
||||
delay_time = "600"
|
||||
|
||||
|
||||
# 3. 工作流ID - 二胺溶液配置工作流
|
||||
workflow_id = "3a15d4a1-3bbe-76f9-a458-292896a338f5"
|
||||
|
||||
|
||||
# 4. 查询工作流对应的holdMID
|
||||
material_info = self.material_id_query(workflow_id)
|
||||
material_info = self.hardware_interface.material_id_query(workflow_id)
|
||||
if not material_info:
|
||||
raise BioyondException(f"无法查询工作流 {workflow_id} 的物料信息")
|
||||
|
||||
|
||||
# 获取locations列表
|
||||
locations = material_info.get("locations", []) if isinstance(material_info, dict) else []
|
||||
if not locations:
|
||||
raise BioyondException(f"工作流 {workflow_id} 没有找到库位信息")
|
||||
|
||||
|
||||
# 查找指定名称的库位
|
||||
hold_mid = None
|
||||
for location in locations:
|
||||
if location.get("holdMName") == hold_m_name:
|
||||
hold_mid = location.get("holdMId")
|
||||
break
|
||||
|
||||
|
||||
if not hold_mid:
|
||||
raise BioyondException(f"未找到库位名称为 {hold_m_name} 的库位,请检查名称是否正确")
|
||||
|
||||
|
||||
extend_properties = f"{{\"{ hold_mid }\": {{}}}}"
|
||||
self.hardware_interface._logger.info(f"找到库位 {hold_m_name} 对应的holdMId: {hold_mid}")
|
||||
|
||||
@@ -397,9 +397,9 @@ class BioyondDispensingStation(BioyondWorkstation):
|
||||
# 7. 调用create_order方法创建任务
|
||||
result = self.hardware_interface.create_order(json_str)
|
||||
self.hardware_interface._logger.info(f"创建二胺溶液配置任务结果: {result}")
|
||||
|
||||
|
||||
return json.dumps({"suc": True})
|
||||
|
||||
|
||||
except BioyondException:
|
||||
# 重新抛出BioyondException
|
||||
raise
|
||||
@@ -409,17 +409,278 @@ class BioyondDispensingStation(BioyondWorkstation):
|
||||
self.hardware_interface._logger.error(error_msg)
|
||||
raise BioyondException(error_msg)
|
||||
|
||||
# 批量创建二胺溶液配置任务
|
||||
def batch_create_diamine_solution_tasks(self,
|
||||
solutions,
|
||||
liquid_material_name: str = "NMP",
|
||||
speed: str = None,
|
||||
temperature: str = None,
|
||||
delay_time: str = None) -> str:
|
||||
"""
|
||||
批量创建二胺溶液配置任务
|
||||
|
||||
参数说明:
|
||||
- solutions: 溶液列表(数组)或JSON字符串,格式如下:
|
||||
[
|
||||
{
|
||||
"name": "MDA",
|
||||
"order": 0,
|
||||
"solid_mass": 5.0,
|
||||
"solvent_volume": 20,
|
||||
...
|
||||
},
|
||||
...
|
||||
]
|
||||
- liquid_material_name: 液体物料名称,默认为"NMP"
|
||||
- speed: 搅拌速度,如果为None则使用默认值400
|
||||
- temperature: 温度,如果为None则使用默认值20
|
||||
- delay_time: 延迟时间,如果为None则使用默认值600
|
||||
|
||||
返回: JSON字符串格式的任务创建结果
|
||||
|
||||
异常:
|
||||
- BioyondException: 各种错误情况下的统一异常
|
||||
"""
|
||||
try:
|
||||
# 参数类型转换:如果是字符串则解析为列表
|
||||
if isinstance(solutions, str):
|
||||
try:
|
||||
solutions = json.loads(solutions)
|
||||
except json.JSONDecodeError as e:
|
||||
raise BioyondException(f"solutions JSON解析失败: {str(e)}")
|
||||
|
||||
# 参数验证
|
||||
if not isinstance(solutions, list):
|
||||
raise BioyondException("solutions 必须是列表类型或有效的JSON数组字符串")
|
||||
|
||||
if not solutions:
|
||||
raise BioyondException("solutions 列表不能为空")
|
||||
|
||||
# 批量创建任务
|
||||
results = []
|
||||
success_count = 0
|
||||
failed_count = 0
|
||||
|
||||
for idx, solution in enumerate(solutions):
|
||||
try:
|
||||
# 提取参数
|
||||
name = solution.get("name")
|
||||
solid_mass = solution.get("solid_mass")
|
||||
solvent_volume = solution.get("solvent_volume")
|
||||
order = solution.get("order")
|
||||
|
||||
if not all([name, solid_mass is not None, solvent_volume is not None]):
|
||||
self.hardware_interface._logger.warning(
|
||||
f"跳过第 {idx + 1} 个溶液:缺少必要参数"
|
||||
)
|
||||
results.append({
|
||||
"index": idx + 1,
|
||||
"name": name,
|
||||
"success": False,
|
||||
"error": "缺少必要参数"
|
||||
})
|
||||
failed_count += 1
|
||||
continue
|
||||
|
||||
# 生成库位名称(直接使用物料名称)
|
||||
# 如果需要其他命名规则,可以在这里调整
|
||||
hold_m_name = name
|
||||
|
||||
# 调用单个任务创建方法
|
||||
result = self.create_diamine_solution_task(
|
||||
order_name=f"二胺溶液配置-{name}",
|
||||
material_name=name,
|
||||
target_weigh=str(solid_mass),
|
||||
volume=str(solvent_volume),
|
||||
liquid_material_name=liquid_material_name,
|
||||
speed=speed,
|
||||
temperature=temperature,
|
||||
delay_time=delay_time,
|
||||
hold_m_name=hold_m_name
|
||||
)
|
||||
|
||||
results.append({
|
||||
"index": idx + 1,
|
||||
"name": name,
|
||||
"success": True,
|
||||
"hold_m_name": hold_m_name
|
||||
})
|
||||
success_count += 1
|
||||
self.hardware_interface._logger.info(
|
||||
f"成功创建二胺溶液配置任务: {name}"
|
||||
)
|
||||
|
||||
except BioyondException as e:
|
||||
results.append({
|
||||
"index": idx + 1,
|
||||
"name": solution.get("name", "unknown"),
|
||||
"success": False,
|
||||
"error": str(e)
|
||||
})
|
||||
failed_count += 1
|
||||
self.hardware_interface._logger.error(
|
||||
f"创建第 {idx + 1} 个任务失败: {str(e)}"
|
||||
)
|
||||
except Exception as e:
|
||||
results.append({
|
||||
"index": idx + 1,
|
||||
"name": solution.get("name", "unknown"),
|
||||
"success": False,
|
||||
"error": f"未知错误: {str(e)}"
|
||||
})
|
||||
failed_count += 1
|
||||
self.hardware_interface._logger.error(
|
||||
f"创建第 {idx + 1} 个任务时发生未知错误: {str(e)}"
|
||||
)
|
||||
|
||||
# 返回汇总结果
|
||||
summary = {
|
||||
"total": len(solutions),
|
||||
"success": success_count,
|
||||
"failed": failed_count,
|
||||
"details": results
|
||||
}
|
||||
|
||||
self.hardware_interface._logger.info(
|
||||
f"批量创建二胺溶液配置任务完成: 总数={len(solutions)}, "
|
||||
f"成功={success_count}, 失败={failed_count}"
|
||||
)
|
||||
|
||||
# 返回JSON字符串格式
|
||||
return json.dumps(summary, ensure_ascii=False)
|
||||
|
||||
except BioyondException:
|
||||
raise
|
||||
except Exception as e:
|
||||
error_msg = f"批量创建二胺溶液配置任务时发生未预期的错误: {str(e)}"
|
||||
self.hardware_interface._logger.error(error_msg)
|
||||
raise BioyondException(error_msg)
|
||||
|
||||
# 批量创建90%10%小瓶投料任务
|
||||
def batch_create_90_10_vial_feeding_tasks(self,
|
||||
titration,
|
||||
hold_m_name: str = None,
|
||||
speed: str = None,
|
||||
temperature: str = None,
|
||||
delay_time: str = None,
|
||||
liquid_material_name: str = "NMP") -> str:
|
||||
"""
|
||||
批量创建90%10%小瓶投料任务(仅创建1个任务,但包含所有90%和10%物料)
|
||||
|
||||
参数说明:
|
||||
- titration: 滴定信息的字典或JSON字符串,格式如下:
|
||||
{
|
||||
"name": "BTDA",
|
||||
"main_portion": 1.9152351915461294, # 主称固体质量(g) -> 90%物料
|
||||
"titration_portion": 0.05923407808905555, # 滴定固体质量(g) -> 10%物料固体
|
||||
"titration_solvent": 3.050555021586361 # 滴定溶液体积(mL) -> 10%物料液体
|
||||
}
|
||||
- hold_m_name: 库位名称,如"C01"。必填参数
|
||||
- speed: 搅拌速度,如果为None则使用默认值400
|
||||
- temperature: 温度,如果为None则使用默认值40
|
||||
- delay_time: 延迟时间,如果为None则使用默认值600
|
||||
- liquid_material_name: 10%物料的液体物料名称,默认为"NMP"
|
||||
|
||||
返回: JSON字符串格式的任务创建结果
|
||||
|
||||
异常:
|
||||
- BioyondException: 各种错误情况下的统一异常
|
||||
"""
|
||||
try:
|
||||
# 参数类型转换:如果是字符串则解析为字典
|
||||
if isinstance(titration, str):
|
||||
try:
|
||||
titration = json.loads(titration)
|
||||
except json.JSONDecodeError as e:
|
||||
raise BioyondException(f"titration参数JSON解析失败: {str(e)}")
|
||||
|
||||
# 参数验证
|
||||
if not isinstance(titration, dict):
|
||||
raise BioyondException("titration 必须是字典类型或有效的JSON字符串")
|
||||
|
||||
if not hold_m_name:
|
||||
raise BioyondException("hold_m_name 是必填参数")
|
||||
|
||||
if not titration:
|
||||
raise BioyondException("titration 参数不能为空")
|
||||
|
||||
# 提取滴定数据
|
||||
name = titration.get("name")
|
||||
main_portion = titration.get("main_portion") # 主称固体质量
|
||||
titration_portion = titration.get("titration_portion") # 滴定固体质量
|
||||
titration_solvent = titration.get("titration_solvent") # 滴定溶液体积
|
||||
|
||||
if not all([name, main_portion is not None, titration_portion is not None, titration_solvent is not None]):
|
||||
raise BioyondException("titration 数据缺少必要参数")
|
||||
|
||||
# 将main_portion平均分成3份作为90%物料(3个小瓶)
|
||||
portion_90 = main_portion / 3
|
||||
|
||||
# 调用单个任务创建方法
|
||||
result = self.create_90_10_vial_feeding_task(
|
||||
order_name=f"90%10%小瓶投料-{name}",
|
||||
speed=speed,
|
||||
temperature=temperature,
|
||||
delay_time=delay_time,
|
||||
# 90%物料 - 主称固体平均分成3份
|
||||
percent_90_1_assign_material_name=name,
|
||||
percent_90_1_target_weigh=str(round(portion_90, 6)),
|
||||
percent_90_2_assign_material_name=name,
|
||||
percent_90_2_target_weigh=str(round(portion_90, 6)),
|
||||
percent_90_3_assign_material_name=name,
|
||||
percent_90_3_target_weigh=str(round(portion_90, 6)),
|
||||
# 10%物料 - 滴定固体 + 滴定溶剂(只使用第1个10%小瓶)
|
||||
percent_10_1_assign_material_name=name,
|
||||
percent_10_1_target_weigh=str(round(titration_portion, 6)),
|
||||
percent_10_1_volume=str(round(titration_solvent, 6)),
|
||||
percent_10_1_liquid_material_name=liquid_material_name,
|
||||
hold_m_name=hold_m_name
|
||||
)
|
||||
|
||||
summary = {
|
||||
"success": True,
|
||||
"hold_m_name": hold_m_name,
|
||||
"material_name": name,
|
||||
"90_vials": {
|
||||
"count": 3,
|
||||
"weight_per_vial": round(portion_90, 6),
|
||||
"total_weight": round(main_portion, 6)
|
||||
},
|
||||
"10_vials": {
|
||||
"count": 1,
|
||||
"solid_weight": round(titration_portion, 6),
|
||||
"liquid_volume": round(titration_solvent, 6)
|
||||
}
|
||||
}
|
||||
|
||||
self.hardware_interface._logger.info(
|
||||
f"成功创建90%10%小瓶投料任务: {hold_m_name}, "
|
||||
f"90%物料={portion_90:.6f}g×3, 10%物料={titration_portion:.6f}g+{titration_solvent:.6f}mL"
|
||||
)
|
||||
|
||||
# 返回JSON字符串格式
|
||||
return json.dumps(summary, ensure_ascii=False)
|
||||
|
||||
except BioyondException:
|
||||
raise
|
||||
except Exception as e:
|
||||
error_msg = f"批量创建90%10%小瓶投料任务时发生未预期的错误: {str(e)}"
|
||||
self.hardware_interface._logger.error(error_msg)
|
||||
raise BioyondException(error_msg)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
bioyond = BioyondDispensingStation(config={
|
||||
"api_key": "DE9BDDA0",
|
||||
"api_host": "http://192.168.1.200:44388"
|
||||
})
|
||||
|
||||
|
||||
# ============ 原有示例代码 ============
|
||||
|
||||
# 示例1:使用material_id_query查询工作流对应的holdMID
|
||||
workflow_id_1 = "3a15d4a1-3bbe-76f9-a458-292896a338f5" # 二胺溶液配置工作流ID
|
||||
workflow_id_2 = "3a19310d-16b9-9d81-b109-0748e953694b" # 90%10%小瓶投料工作流ID
|
||||
|
||||
|
||||
#示例2:创建二胺溶液配置任务 - ODA,指定库位名称
|
||||
# bioyond.create_diamine_solution_task(
|
||||
# order_code="task_oda_" + str(int(datetime.now().timestamp())),
|
||||
@@ -433,7 +694,7 @@ if __name__ == "__main__":
|
||||
# delay_time="600",
|
||||
# hold_m_name="烧杯ODA"
|
||||
# )
|
||||
|
||||
|
||||
# bioyond.create_diamine_solution_task(
|
||||
# order_code="task_pda_" + str(int(datetime.now().timestamp())),
|
||||
# order_name="二胺溶液配置-PDA",
|
||||
@@ -446,7 +707,7 @@ if __name__ == "__main__":
|
||||
# delay_time="600",
|
||||
# hold_m_name="烧杯PDA-2"
|
||||
# )
|
||||
|
||||
|
||||
# bioyond.create_diamine_solution_task(
|
||||
# order_code="task_mpda_" + str(int(datetime.now().timestamp())),
|
||||
# order_name="二胺溶液配置-MPDA",
|
||||
@@ -462,8 +723,8 @@ if __name__ == "__main__":
|
||||
|
||||
bioyond.material_id_query("3a19310d-16b9-9d81-b109-0748e953694b")
|
||||
bioyond.material_id_query("3a15d4a1-3bbe-76f9-a458-292896a338f5")
|
||||
|
||||
|
||||
|
||||
|
||||
#示例4:创建90%10%小瓶投料任务
|
||||
# vial_result = bioyond.create_90_10_vial_feeding_task(
|
||||
# order_code="task_vial_" + str(int(datetime.now().timestamp())),
|
||||
@@ -487,7 +748,7 @@ if __name__ == "__main__":
|
||||
# delay_time="1200",
|
||||
# hold_m_name="8.4分装板-1"
|
||||
# )
|
||||
|
||||
|
||||
# vial_result = bioyond.create_90_10_vial_feeding_task(
|
||||
# order_code="task_vial_" + str(int(datetime.now().timestamp())),
|
||||
# order_name="90%10%小瓶投料-2",
|
||||
@@ -510,7 +771,7 @@ if __name__ == "__main__":
|
||||
# delay_time="1200",
|
||||
# hold_m_name="8.4分装板-2"
|
||||
# )
|
||||
|
||||
|
||||
#启动调度器
|
||||
#bioyond.scheduler_start()
|
||||
|
||||
@@ -529,7 +790,7 @@ if __name__ == "__main__":
|
||||
material_data_yp = {
|
||||
"typeId": "3a14196e-b7a0-a5da-1931-35f3000281e9",
|
||||
#"code": "物料编码001",
|
||||
#"barCode": "物料条码001",
|
||||
#"barCode": "物料条码001",
|
||||
"name": "8.4样品板",
|
||||
"unit": "个",
|
||||
"quantity": 1,
|
||||
@@ -540,7 +801,7 @@ if __name__ == "__main__":
|
||||
"name": "BTDA-1",
|
||||
"quantity": 20,
|
||||
"x": 1,
|
||||
"y": 1,
|
||||
"y": 1,
|
||||
#"unit": "单位"
|
||||
"molecular": 1,
|
||||
"Parameters":"{\"molecular\": 1}"
|
||||
@@ -585,7 +846,7 @@ if __name__ == "__main__":
|
||||
material_data_yp = {
|
||||
"typeId": "3a14196e-b7a0-a5da-1931-35f3000281e9",
|
||||
#"code": "物料编码001",
|
||||
#"barCode": "物料条码001",
|
||||
#"barCode": "物料条码001",
|
||||
"name": "8.7样品板",
|
||||
"unit": "个",
|
||||
"quantity": 1,
|
||||
@@ -596,7 +857,7 @@ if __name__ == "__main__":
|
||||
"name": "mianfen",
|
||||
"quantity": 13,
|
||||
"x": 1,
|
||||
"y": 1,
|
||||
"y": 1,
|
||||
#"unit": "单位"
|
||||
"molecular": 1,
|
||||
"Parameters":"{\"molecular\": 1}"
|
||||
@@ -620,7 +881,7 @@ if __name__ == "__main__":
|
||||
material_data_fzb_1 = {
|
||||
"typeId": "3a14196e-5dfe-6e21-0c79-fe2036d052c4",
|
||||
#"code": "物料编码001",
|
||||
#"barCode": "物料条码001",
|
||||
#"barCode": "物料条码001",
|
||||
"name": "8.7分装板",
|
||||
"unit": "个",
|
||||
"quantity": 1,
|
||||
@@ -631,7 +892,7 @@ if __name__ == "__main__":
|
||||
"name": "10%小瓶1",
|
||||
"quantity": 1,
|
||||
"x": 1,
|
||||
"y": 1,
|
||||
"y": 1,
|
||||
#"unit": "单位"
|
||||
"molecular": 1,
|
||||
"Parameters":"{\"molecular\": 1}"
|
||||
@@ -642,7 +903,7 @@ if __name__ == "__main__":
|
||||
"name": "10%小瓶2",
|
||||
"quantity": 1,
|
||||
"x": 1,
|
||||
"y": 2,
|
||||
"y": 2,
|
||||
#"unit": "单位"
|
||||
"molecular": 1,
|
||||
"Parameters":"{\"molecular\": 1}"
|
||||
@@ -653,7 +914,7 @@ if __name__ == "__main__":
|
||||
"name": "10%小瓶3",
|
||||
"quantity": 1,
|
||||
"x": 1,
|
||||
"y": 3,
|
||||
"y": 3,
|
||||
#"unit": "单位"
|
||||
"molecular": 1,
|
||||
"Parameters":"{\"molecular\": 1}"
|
||||
@@ -697,7 +958,7 @@ if __name__ == "__main__":
|
||||
material_data_fzb_2 = {
|
||||
"typeId": "3a14196e-5dfe-6e21-0c79-fe2036d052c4",
|
||||
#"code": "物料编码001",
|
||||
#"barCode": "物料条码001",
|
||||
#"barCode": "物料条码001",
|
||||
"name": "8.4分装板-2",
|
||||
"unit": "个",
|
||||
"quantity": 1,
|
||||
@@ -708,7 +969,7 @@ if __name__ == "__main__":
|
||||
"name": "10%小瓶1",
|
||||
"quantity": 1,
|
||||
"x": 1,
|
||||
"y": 1,
|
||||
"y": 1,
|
||||
#"unit": "单位"
|
||||
"molecular": 1,
|
||||
"Parameters":"{\"molecular\": 1}"
|
||||
@@ -719,7 +980,7 @@ if __name__ == "__main__":
|
||||
"name": "10%小瓶2",
|
||||
"quantity": 1,
|
||||
"x": 1,
|
||||
"y": 2,
|
||||
"y": 2,
|
||||
#"unit": "单位"
|
||||
"molecular": 1,
|
||||
"Parameters":"{\"molecular\": 1}"
|
||||
@@ -730,7 +991,7 @@ if __name__ == "__main__":
|
||||
"name": "10%小瓶3",
|
||||
"quantity": 1,
|
||||
"x": 1,
|
||||
"y": 3,
|
||||
"y": 3,
|
||||
#"unit": "单位"
|
||||
"molecular": 1,
|
||||
"Parameters":"{\"molecular\": 1}"
|
||||
@@ -775,7 +1036,7 @@ if __name__ == "__main__":
|
||||
material_data_sb_oda = {
|
||||
"typeId": "3a14196b-24f2-ca49-9081-0cab8021bf1a",
|
||||
#"code": "物料编码001",
|
||||
#"barCode": "物料条码001",
|
||||
#"barCode": "物料条码001",
|
||||
"name": "mianfen1",
|
||||
"unit": "个",
|
||||
"quantity": 1,
|
||||
@@ -785,7 +1046,7 @@ if __name__ == "__main__":
|
||||
material_data_sb_pda_2 = {
|
||||
"typeId": "3a14196b-24f2-ca49-9081-0cab8021bf1a",
|
||||
#"code": "物料编码001",
|
||||
#"barCode": "物料条码001",
|
||||
#"barCode": "物料条码001",
|
||||
"name": "mianfen2",
|
||||
"unit": "个",
|
||||
"quantity": 1,
|
||||
@@ -795,7 +1056,7 @@ if __name__ == "__main__":
|
||||
# material_data_sb_mpda = {
|
||||
# "typeId": "3a14196b-24f2-ca49-9081-0cab8021bf1a",
|
||||
# #"code": "物料编码001",
|
||||
# #"barCode": "物料条码001",
|
||||
# #"barCode": "物料条码001",
|
||||
# "name": "烧杯MPDA",
|
||||
# "unit": "个",
|
||||
# "quantity": 1,
|
||||
|
||||
@@ -58,8 +58,8 @@ class BioyondReactionStation(BioyondWorkstation):
|
||||
|
||||
Args:
|
||||
assign_material_name: 物料名称(不能为空)
|
||||
cutoff: 截止值/通量配置(需为有效数字字符串,默认 "900000")
|
||||
temperature: 温度上限(°C,范围:-50.00 至 100.00)
|
||||
cutoff: 粘度上限(需为有效数字字符串,默认 "900000")
|
||||
temperature: 温度设定(°C,范围:-50.00 至 100.00)
|
||||
|
||||
Returns:
|
||||
str: JSON 字符串,格式为 {"suc": True}
|
||||
@@ -113,11 +113,11 @@ class BioyondReactionStation(BioyondWorkstation):
|
||||
"""固体进料小瓶
|
||||
|
||||
Args:
|
||||
material_id: 粉末类型ID
|
||||
material_id: 粉末类型ID,1=盐(21分钟),2=面粉(27分钟),3=BTDA(38分钟)
|
||||
time: 观察时间(分钟)
|
||||
torque_variation: 是否观察扭矩变化(int类型, 1=否, 2=是)
|
||||
torque_variation: 是否观察(int类型, 1=否, 2=是)
|
||||
assign_material_name: 物料名称(用于获取试剂瓶位ID)
|
||||
temperature: 温度上限(°C)
|
||||
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
|
||||
@@ -165,9 +165,9 @@ class BioyondReactionStation(BioyondWorkstation):
|
||||
Args:
|
||||
volume_formula: 分液公式(μL)
|
||||
assign_material_name: 物料名称
|
||||
titration_type: 是否滴定(1=滴定, 其他=非滴定)
|
||||
titration_type: 是否滴定(1=否, 2=是)
|
||||
time: 观察时间(分钟)
|
||||
torque_variation: 是否观察扭矩变化(int类型, 1=否, 2=是)
|
||||
torque_variation: 是否观察(int类型, 1=否, 2=是)
|
||||
temperature: 温度(°C)
|
||||
"""
|
||||
self.append_to_workflow_sequence('{"web_workflow_name": "Liquid_feeding_vials(non-titration)"}')
|
||||
@@ -208,7 +208,8 @@ class BioyondReactionStation(BioyondWorkstation):
|
||||
def liquid_feeding_solvents(
|
||||
self,
|
||||
assign_material_name: str,
|
||||
volume: str,
|
||||
volume: str = None,
|
||||
solvents = None,
|
||||
titration_type: str = "1",
|
||||
time: str = "360",
|
||||
torque_variation: int = 2,
|
||||
@@ -218,12 +219,41 @@ class BioyondReactionStation(BioyondWorkstation):
|
||||
|
||||
Args:
|
||||
assign_material_name: 物料名称
|
||||
volume: 分液量(μL)
|
||||
titration_type: 是否滴定
|
||||
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)
|
||||
torque_variation: 是否观察(int类型, 1=否, 2=是)
|
||||
temperature: 温度设定(°C)
|
||||
"""
|
||||
# 处理 volume 参数:优先使用直接传入的 volume,否则从 solvents 中提取
|
||||
if volume is None 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:
|
||||
@@ -273,9 +303,9 @@ class BioyondReactionStation(BioyondWorkstation):
|
||||
Args:
|
||||
volume_formula: 分液公式(μL)
|
||||
assign_material_name: 物料名称
|
||||
titration_type: 是否滴定
|
||||
titration_type: 是否滴定(1=否, 2=是)
|
||||
time: 观察时间(分钟)
|
||||
torque_variation: 是否观察扭矩变化(int类型, 1=否, 2=是)
|
||||
torque_variation: 是否观察(int类型, 1=否, 2=是)
|
||||
temperature: 温度(°C)
|
||||
"""
|
||||
self.append_to_workflow_sequence('{"web_workflow_name": "Liquid_feeding(titration)"}')
|
||||
@@ -328,9 +358,9 @@ class BioyondReactionStation(BioyondWorkstation):
|
||||
volume: 分液量(μL)
|
||||
assign_material_name: 物料名称(试剂瓶位)
|
||||
time: 观察时间(分钟)
|
||||
torque_variation: 是否观察扭矩变化(int类型, 1=否, 2=是)
|
||||
titration_type: 是否滴定
|
||||
temperature: 温度上限(°C)
|
||||
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)
|
||||
@@ -381,9 +411,9 @@ class BioyondReactionStation(BioyondWorkstation):
|
||||
Args:
|
||||
assign_material_name: 物料名称(液体种类)
|
||||
volume: 分液量(μL)
|
||||
titration_type: 是否滴定
|
||||
titration_type: 是否滴定(1=否, 2=是)
|
||||
time: 观察时间(分钟)
|
||||
torque_variation: 是否观察扭矩变化(int类型, 1=否, 2=是)
|
||||
torque_variation: 是否观察(int类型, 1=否, 2=是)
|
||||
temperature: 温度(°C)
|
||||
"""
|
||||
self.append_to_workflow_sequence('{"web_workflow_name": "drip_back"}')
|
||||
@@ -605,7 +635,8 @@ class BioyondReactionStation(BioyondWorkstation):
|
||||
total_params += 1
|
||||
step_parameters[step_id][action_name].append({
|
||||
"Key": param_key,
|
||||
"DisplayValue": param_value
|
||||
"DisplayValue": param_value,
|
||||
"Value": param_value
|
||||
})
|
||||
successful_params += 1
|
||||
# print(f" ✓ {param_key} = {param_value}")
|
||||
|
||||
@@ -4,6 +4,7 @@ Bioyond Workstation Implementation
|
||||
|
||||
集成Bioyond物料管理的工作站示例
|
||||
"""
|
||||
import time
|
||||
import traceback
|
||||
from datetime import datetime
|
||||
from typing import Dict, Any, List, Optional, Union
|
||||
|
||||
404
unilabos/registry/devices/bioyond_dispensing_station.yaml
Normal file
404
unilabos/registry/devices/bioyond_dispensing_station.yaml
Normal file
@@ -0,0 +1,404 @@
|
||||
bioyond_dispensing_station:
|
||||
category:
|
||||
- workstation
|
||||
- bioyond
|
||||
- bioyond_dispensing_station
|
||||
class:
|
||||
action_value_mappings:
|
||||
batch_create_90_10_vial_feeding_tasks:
|
||||
feedback: {}
|
||||
goal:
|
||||
delay_time: delay_time
|
||||
hold_m_name: hold_m_name
|
||||
liquid_material_name: liquid_material_name
|
||||
speed: speed
|
||||
temperature: temperature
|
||||
titration: titration
|
||||
goal_default:
|
||||
delay_time: '600'
|
||||
hold_m_name: ''
|
||||
liquid_material_name: NMP
|
||||
speed: '400'
|
||||
temperature: '40'
|
||||
titration: ''
|
||||
handles:
|
||||
input:
|
||||
- data_key: titration
|
||||
data_source: handle
|
||||
data_type: object
|
||||
handler_key: titration
|
||||
io_type: source
|
||||
label: Titration Data From Calculation Node
|
||||
result:
|
||||
return_info: return_info
|
||||
schema:
|
||||
description: 批量创建90%10%小瓶投料任务。从计算节点接收titration数据,包含物料名称、主称固体质量、滴定固体质量和滴定溶剂体积。
|
||||
properties:
|
||||
feedback:
|
||||
properties: {}
|
||||
required: []
|
||||
title: BatchCreate9010VialFeedingTasks_Feedback
|
||||
type: object
|
||||
goal:
|
||||
properties:
|
||||
delay_time:
|
||||
default: '600'
|
||||
description: 延迟时间(秒),默认600
|
||||
type: string
|
||||
hold_m_name:
|
||||
description: 库位名称,如"C01",必填参数
|
||||
type: string
|
||||
liquid_material_name:
|
||||
default: NMP
|
||||
description: 10%物料的液体物料名称,默认为"NMP"
|
||||
type: string
|
||||
speed:
|
||||
default: '400'
|
||||
description: 搅拌速度,默认400
|
||||
type: string
|
||||
temperature:
|
||||
default: '40'
|
||||
description: 温度(℃),默认40
|
||||
type: string
|
||||
titration:
|
||||
description: '滴定信息对象,包含: name(物料名称), main_portion(主称固体质量g), titration_portion(滴定固体质量g),
|
||||
titration_solvent(滴定溶液体积mL)'
|
||||
type: string
|
||||
required:
|
||||
- titration
|
||||
- hold_m_name
|
||||
title: BatchCreate9010VialFeedingTasks_Goal
|
||||
type: object
|
||||
result:
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
required:
|
||||
- return_info
|
||||
title: BatchCreate9010VialFeedingTasks_Result
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
title: BatchCreate9010VialFeedingTasks
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
batch_create_diamine_solution_tasks:
|
||||
feedback: {}
|
||||
goal:
|
||||
delay_time: delay_time
|
||||
liquid_material_name: liquid_material_name
|
||||
solutions: solutions
|
||||
speed: speed
|
||||
temperature: temperature
|
||||
goal_default:
|
||||
delay_time: '600'
|
||||
liquid_material_name: NMP
|
||||
solutions: ''
|
||||
speed: '400'
|
||||
temperature: '20'
|
||||
handles:
|
||||
input:
|
||||
- data_key: solutions
|
||||
data_source: handle
|
||||
data_type: array
|
||||
handler_key: solutions
|
||||
io_type: source
|
||||
label: Solution Data From Python
|
||||
result:
|
||||
return_info: return_info
|
||||
schema:
|
||||
description: 批量创建二胺溶液配置任务。自动为多个二胺样品创建溶液配置任务,每个任务包含固体物料称量、溶剂添加、搅拌混合等步骤。
|
||||
properties:
|
||||
feedback:
|
||||
properties: {}
|
||||
required: []
|
||||
title: BatchCreateDiamineSolutionTasks_Feedback
|
||||
type: object
|
||||
goal:
|
||||
properties:
|
||||
delay_time:
|
||||
default: '600'
|
||||
description: 溶液配置完成后的延迟时间(秒),用于充分混合和溶解,默认600秒
|
||||
type: string
|
||||
liquid_material_name:
|
||||
default: NMP
|
||||
description: 液体溶剂名称,用于溶解固体物料,默认为NMP(N-甲基吡咯烷酮)
|
||||
type: string
|
||||
solutions:
|
||||
description: '溶液列表,JSON数组格式,每个元素包含: name(物料名称), order(序号), solid_mass(固体质量g),
|
||||
solvent_volume(溶剂体积mL)。示例: [{"name": "MDA", "order": 0, "solid_mass":
|
||||
5.0, "solvent_volume": 20}, {"name": "MPDA", "order": 1, "solid_mass":
|
||||
4.5, "solvent_volume": 18}]'
|
||||
type: string
|
||||
speed:
|
||||
default: '400'
|
||||
description: 搅拌速度(rpm),用于混合溶液,默认400转/分钟
|
||||
type: string
|
||||
temperature:
|
||||
default: '20'
|
||||
description: 配置温度(℃),溶液配置过程的目标温度,默认20℃(室温)
|
||||
type: string
|
||||
required:
|
||||
- solutions
|
||||
title: BatchCreateDiamineSolutionTasks_Goal
|
||||
type: object
|
||||
result:
|
||||
properties:
|
||||
return_info:
|
||||
description: 批量任务创建结果汇总,JSON格式包含总数、成功数、失败数及每个任务的详细信息
|
||||
type: string
|
||||
required:
|
||||
- return_info
|
||||
title: BatchCreateDiamineSolutionTasks_Result
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
title: BatchCreateDiamineSolutionTasks
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
create_90_10_vial_feeding_task:
|
||||
feedback: {}
|
||||
goal:
|
||||
delay_time: delay_time
|
||||
hold_m_name: hold_m_name
|
||||
order_name: order_name
|
||||
percent_10_1_assign_material_name: percent_10_1_assign_material_name
|
||||
percent_10_1_liquid_material_name: percent_10_1_liquid_material_name
|
||||
percent_10_1_target_weigh: percent_10_1_target_weigh
|
||||
percent_10_1_volume: percent_10_1_volume
|
||||
percent_10_2_assign_material_name: percent_10_2_assign_material_name
|
||||
percent_10_2_liquid_material_name: percent_10_2_liquid_material_name
|
||||
percent_10_2_target_weigh: percent_10_2_target_weigh
|
||||
percent_10_2_volume: percent_10_2_volume
|
||||
percent_10_3_assign_material_name: percent_10_3_assign_material_name
|
||||
percent_10_3_liquid_material_name: percent_10_3_liquid_material_name
|
||||
percent_10_3_target_weigh: percent_10_3_target_weigh
|
||||
percent_10_3_volume: percent_10_3_volume
|
||||
percent_90_1_assign_material_name: percent_90_1_assign_material_name
|
||||
percent_90_1_target_weigh: percent_90_1_target_weigh
|
||||
percent_90_2_assign_material_name: percent_90_2_assign_material_name
|
||||
percent_90_2_target_weigh: percent_90_2_target_weigh
|
||||
percent_90_3_assign_material_name: percent_90_3_assign_material_name
|
||||
percent_90_3_target_weigh: percent_90_3_target_weigh
|
||||
speed: speed
|
||||
temperature: temperature
|
||||
goal_default:
|
||||
delay_time: ''
|
||||
hold_m_name: ''
|
||||
order_name: ''
|
||||
percent_10_1_assign_material_name: ''
|
||||
percent_10_1_liquid_material_name: ''
|
||||
percent_10_1_target_weigh: ''
|
||||
percent_10_1_volume: ''
|
||||
percent_10_2_assign_material_name: ''
|
||||
percent_10_2_liquid_material_name: ''
|
||||
percent_10_2_target_weigh: ''
|
||||
percent_10_2_volume: ''
|
||||
percent_10_3_assign_material_name: ''
|
||||
percent_10_3_liquid_material_name: ''
|
||||
percent_10_3_target_weigh: ''
|
||||
percent_10_3_volume: ''
|
||||
percent_90_1_assign_material_name: ''
|
||||
percent_90_1_target_weigh: ''
|
||||
percent_90_2_assign_material_name: ''
|
||||
percent_90_2_target_weigh: ''
|
||||
percent_90_3_assign_material_name: ''
|
||||
percent_90_3_target_weigh: ''
|
||||
speed: ''
|
||||
temperature: ''
|
||||
handles: {}
|
||||
result:
|
||||
return_info: return_info
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
properties: {}
|
||||
required: []
|
||||
title: DispenStationVialFeed_Feedback
|
||||
type: object
|
||||
goal:
|
||||
properties:
|
||||
delay_time:
|
||||
type: string
|
||||
hold_m_name:
|
||||
type: string
|
||||
order_name:
|
||||
type: string
|
||||
percent_10_1_assign_material_name:
|
||||
type: string
|
||||
percent_10_1_liquid_material_name:
|
||||
type: string
|
||||
percent_10_1_target_weigh:
|
||||
type: string
|
||||
percent_10_1_volume:
|
||||
type: string
|
||||
percent_10_2_assign_material_name:
|
||||
type: string
|
||||
percent_10_2_liquid_material_name:
|
||||
type: string
|
||||
percent_10_2_target_weigh:
|
||||
type: string
|
||||
percent_10_2_volume:
|
||||
type: string
|
||||
percent_10_3_assign_material_name:
|
||||
type: string
|
||||
percent_10_3_liquid_material_name:
|
||||
type: string
|
||||
percent_10_3_target_weigh:
|
||||
type: string
|
||||
percent_10_3_volume:
|
||||
type: string
|
||||
percent_90_1_assign_material_name:
|
||||
type: string
|
||||
percent_90_1_target_weigh:
|
||||
type: string
|
||||
percent_90_2_assign_material_name:
|
||||
type: string
|
||||
percent_90_2_target_weigh:
|
||||
type: string
|
||||
percent_90_3_assign_material_name:
|
||||
type: string
|
||||
percent_90_3_target_weigh:
|
||||
type: string
|
||||
speed:
|
||||
type: string
|
||||
temperature:
|
||||
type: string
|
||||
required:
|
||||
- order_name
|
||||
- percent_90_1_assign_material_name
|
||||
- percent_90_1_target_weigh
|
||||
- percent_90_2_assign_material_name
|
||||
- percent_90_2_target_weigh
|
||||
- percent_90_3_assign_material_name
|
||||
- percent_90_3_target_weigh
|
||||
- percent_10_1_assign_material_name
|
||||
- percent_10_1_target_weigh
|
||||
- percent_10_1_volume
|
||||
- percent_10_1_liquid_material_name
|
||||
- percent_10_2_assign_material_name
|
||||
- percent_10_2_target_weigh
|
||||
- percent_10_2_volume
|
||||
- percent_10_2_liquid_material_name
|
||||
- percent_10_3_assign_material_name
|
||||
- percent_10_3_target_weigh
|
||||
- percent_10_3_volume
|
||||
- percent_10_3_liquid_material_name
|
||||
- speed
|
||||
- temperature
|
||||
- delay_time
|
||||
- hold_m_name
|
||||
title: DispenStationVialFeed_Goal
|
||||
type: object
|
||||
result:
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
required:
|
||||
- return_info
|
||||
title: DispenStationVialFeed_Result
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
title: DispenStationVialFeed
|
||||
type: object
|
||||
type: DispenStationVialFeed
|
||||
create_diamine_solution_task:
|
||||
feedback: {}
|
||||
goal:
|
||||
delay_time: delay_time
|
||||
hold_m_name: hold_m_name
|
||||
liquid_material_name: liquid_material_name
|
||||
material_name: material_name
|
||||
order_name: order_name
|
||||
speed: speed
|
||||
target_weigh: target_weigh
|
||||
temperature: temperature
|
||||
volume: volume
|
||||
goal_default:
|
||||
delay_time: ''
|
||||
hold_m_name: ''
|
||||
liquid_material_name: ''
|
||||
material_name: ''
|
||||
order_name: ''
|
||||
speed: ''
|
||||
target_weigh: ''
|
||||
temperature: ''
|
||||
volume: ''
|
||||
handles: {}
|
||||
result:
|
||||
return_info: return_info
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
properties: {}
|
||||
required: []
|
||||
title: DispenStationSolnPrep_Feedback
|
||||
type: object
|
||||
goal:
|
||||
properties:
|
||||
delay_time:
|
||||
type: string
|
||||
hold_m_name:
|
||||
type: string
|
||||
liquid_material_name:
|
||||
type: string
|
||||
material_name:
|
||||
type: string
|
||||
order_name:
|
||||
type: string
|
||||
speed:
|
||||
type: string
|
||||
target_weigh:
|
||||
type: string
|
||||
temperature:
|
||||
type: string
|
||||
volume:
|
||||
type: string
|
||||
required:
|
||||
- order_name
|
||||
- material_name
|
||||
- target_weigh
|
||||
- volume
|
||||
- liquid_material_name
|
||||
- speed
|
||||
- temperature
|
||||
- delay_time
|
||||
- hold_m_name
|
||||
title: DispenStationSolnPrep_Goal
|
||||
type: object
|
||||
result:
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
required:
|
||||
- return_info
|
||||
title: DispenStationSolnPrep_Result
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
title: DispenStationSolnPrep
|
||||
type: object
|
||||
type: DispenStationSolnPrep
|
||||
module: unilabos.devices.workstation.bioyond_studio.dispensing_station:BioyondDispensingStation
|
||||
status_types: {}
|
||||
type: python
|
||||
config_info: []
|
||||
description: ''
|
||||
handles: []
|
||||
icon: preparation_station.webp
|
||||
init_param_schema:
|
||||
config:
|
||||
properties:
|
||||
config:
|
||||
type: string
|
||||
required:
|
||||
- config
|
||||
type: object
|
||||
data:
|
||||
properties: {}
|
||||
required: []
|
||||
type: object
|
||||
version: 1.0.0
|
||||
@@ -4,67 +4,38 @@ reaction_station.bioyond:
|
||||
- reaction_station_bioyond
|
||||
class:
|
||||
action_value_mappings:
|
||||
auto-append_to_workflow_sequence:
|
||||
drip_back:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal:
|
||||
assign_material_name: assign_material_name
|
||||
temperature: temperature
|
||||
time: time
|
||||
titration_type: titration_type
|
||||
torque_variation: torque_variation
|
||||
volume: volume
|
||||
goal_default:
|
||||
web_workflow_name: null
|
||||
assign_material_name: ''
|
||||
temperature: ''
|
||||
time: ''
|
||||
titration_type: ''
|
||||
torque_variation: ''
|
||||
volume: ''
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
description: 滴回去
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
web_workflow_name:
|
||||
assign_material_name:
|
||||
description: 物料名称(不能为空)
|
||||
type: string
|
||||
required:
|
||||
- web_workflow_name
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: append_to_workflow_sequence参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-clear_workflows:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default: {}
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties: {}
|
||||
required: []
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: clear_workflows参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-load_bioyond_data_from_file:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
file_path: null
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
file_path:
|
||||
temperature:
|
||||
description: 温度设定(°C)
|
||||
type: string
|
||||
time:
|
||||
description: 观察时间(分钟)
|
||||
type: string
|
||||
required:
|
||||
- file_path
|
||||
@@ -116,397 +87,35 @@ reaction_station.bioyond:
|
||||
properties:
|
||||
json_str:
|
||||
type: string
|
||||
required:
|
||||
- json_str
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: process_web_workflows参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-reset_workstation:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default: {}
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties: {}
|
||||
required: []
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: reset_workstation参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-resource_tree_add:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
resources: null
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
resources:
|
||||
items:
|
||||
type: object
|
||||
type: array
|
||||
required:
|
||||
- resources
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: resource_tree_add参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-set_workflow_sequence:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
json_str: null
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
json_str:
|
||||
type: string
|
||||
required:
|
||||
- json_str
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: set_workflow_sequence参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-transfer_resource_to_another:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
mount_device_id: null
|
||||
mount_resource: null
|
||||
resource: null
|
||||
sites: null
|
||||
handles: {}
|
||||
placeholder_keys:
|
||||
mount_device_id: unilabos_devices
|
||||
mount_resource: unilabos_resources
|
||||
resource: unilabos_resources
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
mount_device_id:
|
||||
type: object
|
||||
mount_resource:
|
||||
items:
|
||||
properties:
|
||||
category:
|
||||
type: string
|
||||
children:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
config:
|
||||
type: string
|
||||
data:
|
||||
type: string
|
||||
id:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
parent:
|
||||
type: string
|
||||
pose:
|
||||
properties:
|
||||
orientation:
|
||||
properties:
|
||||
w:
|
||||
type: number
|
||||
x:
|
||||
type: number
|
||||
y:
|
||||
type: number
|
||||
z:
|
||||
type: number
|
||||
required:
|
||||
- x
|
||||
- y
|
||||
- z
|
||||
- w
|
||||
title: orientation
|
||||
type: object
|
||||
position:
|
||||
properties:
|
||||
x:
|
||||
type: number
|
||||
y:
|
||||
type: number
|
||||
z:
|
||||
type: number
|
||||
required:
|
||||
- x
|
||||
- y
|
||||
- z
|
||||
title: position
|
||||
type: object
|
||||
required:
|
||||
- position
|
||||
- orientation
|
||||
title: pose
|
||||
type: object
|
||||
sample_id:
|
||||
type: string
|
||||
type:
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
- name
|
||||
- sample_id
|
||||
- children
|
||||
- parent
|
||||
- type
|
||||
- category
|
||||
- pose
|
||||
- config
|
||||
- data
|
||||
title: mount_resource
|
||||
type: object
|
||||
type: array
|
||||
resource:
|
||||
items:
|
||||
properties:
|
||||
category:
|
||||
type: string
|
||||
children:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
config:
|
||||
type: string
|
||||
data:
|
||||
type: string
|
||||
id:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
parent:
|
||||
type: string
|
||||
pose:
|
||||
properties:
|
||||
orientation:
|
||||
properties:
|
||||
w:
|
||||
type: number
|
||||
x:
|
||||
type: number
|
||||
y:
|
||||
type: number
|
||||
z:
|
||||
type: number
|
||||
required:
|
||||
- x
|
||||
- y
|
||||
- z
|
||||
- w
|
||||
title: orientation
|
||||
type: object
|
||||
position:
|
||||
properties:
|
||||
x:
|
||||
type: number
|
||||
y:
|
||||
type: number
|
||||
z:
|
||||
type: number
|
||||
required:
|
||||
- x
|
||||
- y
|
||||
- z
|
||||
title: position
|
||||
type: object
|
||||
required:
|
||||
- position
|
||||
- orientation
|
||||
title: pose
|
||||
type: object
|
||||
sample_id:
|
||||
type: string
|
||||
type:
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
- name
|
||||
- sample_id
|
||||
- children
|
||||
- parent
|
||||
- type
|
||||
- category
|
||||
- pose
|
||||
- config
|
||||
- data
|
||||
title: resource
|
||||
type: object
|
||||
type: array
|
||||
sites:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
required:
|
||||
- resource
|
||||
- mount_resource
|
||||
- sites
|
||||
- mount_device_id
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: transfer_resource_to_another参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
bioyond_sync:
|
||||
feedback: {}
|
||||
goal:
|
||||
force_sync: force_sync
|
||||
sync_type: sync_type
|
||||
goal_default:
|
||||
force_sync: false
|
||||
sync_type: full
|
||||
handles: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: 从Bioyond系统同步物料
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
force_sync:
|
||||
description: 是否强制同步
|
||||
type: boolean
|
||||
sync_type:
|
||||
description: 同步类型
|
||||
enum:
|
||||
- full
|
||||
- incremental
|
||||
type: string
|
||||
required:
|
||||
- sync_type
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: bioyond_sync参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
bioyond_update:
|
||||
feedback: {}
|
||||
goal:
|
||||
material_ids: material_ids
|
||||
sync_all: sync_all
|
||||
goal_default:
|
||||
material_ids: []
|
||||
sync_all: true
|
||||
handles: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: 将本地物料变更同步到Bioyond
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
material_ids:
|
||||
description: 要同步的物料ID列表
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
sync_all:
|
||||
description: 是否同步所有物料
|
||||
type: boolean
|
||||
required:
|
||||
- sync_all
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: bioyond_update参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
reaction_station_drip_back:
|
||||
feedback: {}
|
||||
goal:
|
||||
assign_material_name: assign_material_name
|
||||
time: time
|
||||
torque_variation: torque_variation
|
||||
volume: volume
|
||||
goal_default:
|
||||
assign_material_name: ''
|
||||
time: ''
|
||||
torque_variation: ''
|
||||
volume: ''
|
||||
handles: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: 反应站滴回操作
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
assign_material_name:
|
||||
description: 溶剂名称
|
||||
type: string
|
||||
time:
|
||||
description: 观察时间(单位min)
|
||||
type: string
|
||||
torque_variation:
|
||||
description: 是否观察1否2是
|
||||
type: string
|
||||
volume:
|
||||
description: 投料体积
|
||||
description: 分液公式(μL)
|
||||
type: string
|
||||
required:
|
||||
- volume
|
||||
- assign_material_name
|
||||
- time
|
||||
- torque_variation
|
||||
- titration_type
|
||||
- temperature
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: reaction_station_drip_back参数
|
||||
title: drip_back参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
reaction_station_liquid_feed:
|
||||
liquid_feeding_beaker:
|
||||
feedback: {}
|
||||
goal:
|
||||
assign_material_name: assign_material_name
|
||||
temperature: temperature
|
||||
time: time
|
||||
titration_type: titration_type
|
||||
torque_variation: torque_variation
|
||||
volume: volume
|
||||
goal_default:
|
||||
assign_material_name: ''
|
||||
temperature: ''
|
||||
time: ''
|
||||
titration_type: ''
|
||||
torque_variation: ''
|
||||
@@ -514,40 +123,223 @@ reaction_station.bioyond:
|
||||
handles: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: 反应站液体进料操作
|
||||
description: 液体进料烧杯
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
assign_material_name:
|
||||
description: 溶剂名称
|
||||
description: 物料名称
|
||||
type: string
|
||||
temperature:
|
||||
description: 温度设定(°C)
|
||||
type: string
|
||||
time:
|
||||
description: 观察时间(单位min)
|
||||
description: 观察时间(分钟)
|
||||
type: string
|
||||
titration_type:
|
||||
description: 滴定类型1否2是
|
||||
description: 是否滴定(1=否, 2=是)
|
||||
type: string
|
||||
torque_variation:
|
||||
description: 是否观察1否2是
|
||||
description: 是否观察 (1=否, 2=是)
|
||||
type: string
|
||||
volume:
|
||||
description: 投料体积
|
||||
description: 分液公式(μL)
|
||||
type: string
|
||||
required:
|
||||
- titration_type
|
||||
- volume
|
||||
- assign_material_name
|
||||
- time
|
||||
- torque_variation
|
||||
- titration_type
|
||||
- temperature
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: reaction_station_liquid_feed参数
|
||||
title: liquid_feeding_beaker参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
reaction_station_process_execute:
|
||||
liquid_feeding_solvents:
|
||||
feedback: {}
|
||||
goal:
|
||||
assign_material_name: assign_material_name
|
||||
solvents: solvents
|
||||
temperature: temperature
|
||||
time: time
|
||||
titration_type: titration_type
|
||||
torque_variation: torque_variation
|
||||
volume: volume
|
||||
goal_default:
|
||||
assign_material_name: ''
|
||||
solvents: ''
|
||||
temperature: '25.00'
|
||||
time: '360'
|
||||
titration_type: '1'
|
||||
torque_variation: '2'
|
||||
volume: ''
|
||||
handles:
|
||||
input:
|
||||
- data_key: solvents
|
||||
data_source: handle
|
||||
data_type: object
|
||||
handler_key: solvents
|
||||
io_type: source
|
||||
label: Solvents Data From Calculation Node
|
||||
result: {}
|
||||
schema:
|
||||
description: 液体投料-溶剂。可以直接提供volume(μL),或通过solvents对象自动从additional_solvent(mL)计算volume。
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
assign_material_name:
|
||||
description: 物料名称
|
||||
type: string
|
||||
solvents:
|
||||
description: '溶剂信息对象(可选),包含: additional_solvent(溶剂体积mL), total_liquid_volume(总液体体积mL)。如果提供,将自动计算volume'
|
||||
type: string
|
||||
temperature:
|
||||
default: '25.00'
|
||||
description: 温度设定(°C),默认25.00
|
||||
type: string
|
||||
time:
|
||||
default: '360'
|
||||
description: 观察时间(分钟),默认360
|
||||
type: string
|
||||
titration_type:
|
||||
default: '1'
|
||||
description: 是否滴定(1=否, 2=是),默认1
|
||||
type: string
|
||||
torque_variation:
|
||||
default: '2'
|
||||
description: 是否观察 (1=否, 2=是),默认2
|
||||
type: string
|
||||
volume:
|
||||
description: 分液量(μL)。可直接提供,或通过solvents参数自动计算
|
||||
type: string
|
||||
required:
|
||||
- assign_material_name
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: liquid_feeding_solvents参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
liquid_feeding_titration:
|
||||
feedback: {}
|
||||
goal:
|
||||
assign_material_name: assign_material_name
|
||||
temperature: temperature
|
||||
time: time
|
||||
titration_type: titration_type
|
||||
torque_variation: torque_variation
|
||||
volume_formula: volume_formula
|
||||
goal_default:
|
||||
assign_material_name: ''
|
||||
temperature: ''
|
||||
time: ''
|
||||
titration_type: ''
|
||||
torque_variation: ''
|
||||
volume_formula: ''
|
||||
handles: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: 液体进料(滴定)
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
assign_material_name:
|
||||
description: 物料名称
|
||||
type: string
|
||||
temperature:
|
||||
description: 温度设定(°C)
|
||||
type: string
|
||||
time:
|
||||
description: 观察时间(分钟)
|
||||
type: string
|
||||
titration_type:
|
||||
description: 是否滴定(1=否, 2=是)
|
||||
type: string
|
||||
torque_variation:
|
||||
description: 是否观察 (1=否, 2=是)
|
||||
type: string
|
||||
volume_formula:
|
||||
description: 分液公式(μL)
|
||||
type: string
|
||||
required:
|
||||
- volume_formula
|
||||
- assign_material_name
|
||||
- time
|
||||
- torque_variation
|
||||
- titration_type
|
||||
- temperature
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: liquid_feeding_titration参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
liquid_feeding_vials_non_titration:
|
||||
feedback: {}
|
||||
goal:
|
||||
assign_material_name: assign_material_name
|
||||
temperature: temperature
|
||||
time: time
|
||||
titration_type: titration_type
|
||||
torque_variation: torque_variation
|
||||
volume_formula: volume_formula
|
||||
goal_default:
|
||||
assign_material_name: ''
|
||||
temperature: ''
|
||||
time: ''
|
||||
titration_type: ''
|
||||
torque_variation: ''
|
||||
volume_formula: ''
|
||||
handles: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: 液体进料小瓶(非滴定)
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
assign_material_name:
|
||||
description: 物料名称
|
||||
type: string
|
||||
temperature:
|
||||
description: 温度设定(°C)
|
||||
type: string
|
||||
time:
|
||||
description: 观察时间(分钟)
|
||||
type: string
|
||||
titration_type:
|
||||
description: 是否滴定(1=否, 2=是)
|
||||
type: string
|
||||
torque_variation:
|
||||
description: 是否观察 (1=否, 2=是)
|
||||
type: string
|
||||
volume_formula:
|
||||
description: 分液公式(μL)
|
||||
type: string
|
||||
required:
|
||||
- volume_formula
|
||||
- assign_material_name
|
||||
- time
|
||||
- torque_variation
|
||||
- titration_type
|
||||
- temperature
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: liquid_feeding_vials_non_titration参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
process_and_execute_workflow:
|
||||
feedback: {}
|
||||
goal:
|
||||
task_name: task_name
|
||||
@@ -558,7 +350,7 @@ reaction_station.bioyond:
|
||||
handles: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: 反应站流程执行
|
||||
description: 处理并执行工作流
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
@@ -576,92 +368,10 @@ reaction_station.bioyond:
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: reaction_station_process_execute参数
|
||||
title: process_and_execute_workflow参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
reaction_station_reactor_taken_out:
|
||||
feedback: {}
|
||||
goal:
|
||||
order_id: order_id
|
||||
preintake_id: preintake_id
|
||||
goal_default:
|
||||
order_id: ''
|
||||
preintake_id: ''
|
||||
handles: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: 反应站反应器取出操作 - 通过订单ID和预取样ID进行精确控制
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
order_id:
|
||||
description: 订单ID,用于标识要取出的订单
|
||||
type: string
|
||||
preintake_id:
|
||||
description: 预取样ID,用于标识具体的取样任务
|
||||
type: string
|
||||
required: []
|
||||
type: object
|
||||
result:
|
||||
properties:
|
||||
code:
|
||||
description: 操作结果代码(1表示成功,0表示失败)
|
||||
type: integer
|
||||
return_info:
|
||||
description: 操作结果详细信息
|
||||
type: string
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
title: reaction_station_reactor_taken_out参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
reaction_station_solid_feed_vial:
|
||||
feedback: {}
|
||||
goal:
|
||||
assign_material_name: assign_material_name
|
||||
material_id: material_id
|
||||
time: time
|
||||
torque_variation: torque_variation
|
||||
goal_default:
|
||||
assign_material_name: ''
|
||||
material_id: ''
|
||||
time: ''
|
||||
torque_variation: ''
|
||||
handles: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: 反应站固体进料操作
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
assign_material_name:
|
||||
description: 固体名称_粉末加样模块-投料
|
||||
type: string
|
||||
material_id:
|
||||
description: 固体投料类型_粉末加样模块-投料
|
||||
type: string
|
||||
time:
|
||||
description: 观察时间_反应模块-观察搅拌结果
|
||||
type: string
|
||||
torque_variation:
|
||||
description: 是否观察1否2是_反应模块-观察搅拌结果
|
||||
type: string
|
||||
required:
|
||||
- assign_material_name
|
||||
- material_id
|
||||
- time
|
||||
- torque_variation
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: reaction_station_solid_feed_vial参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
reaction_station_take_in:
|
||||
reactor_taken_in:
|
||||
feedback: {}
|
||||
goal:
|
||||
assign_material_name: assign_material_name
|
||||
@@ -674,7 +384,7 @@ reaction_station.bioyond:
|
||||
handles: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: 反应站取入操作
|
||||
description: 反应器放入 - 将反应器放入工作站,配置物料名称、粘度上限和温度参数
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
@@ -683,10 +393,10 @@ reaction_station.bioyond:
|
||||
description: 物料名称
|
||||
type: string
|
||||
cutoff:
|
||||
description: 截止参数
|
||||
description: 粘度上限
|
||||
type: string
|
||||
temperature:
|
||||
description: 温度
|
||||
description: 温度设定(°C)
|
||||
type: string
|
||||
required:
|
||||
- cutoff
|
||||
@@ -696,10 +406,88 @@ reaction_station.bioyond:
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: reaction_station_take_in参数
|
||||
title: reactor_taken_in参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
module: unilabos.devices.workstation.bioyond_studio.station:BioyondWorkstation
|
||||
reactor_taken_out:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default: {}
|
||||
handles: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: 反应器取出 - 从工作站中取出反应器,无需参数的简单操作
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties: {}
|
||||
required: []
|
||||
type: object
|
||||
result:
|
||||
properties:
|
||||
code:
|
||||
description: 操作结果代码(1表示成功,0表示失败)
|
||||
type: integer
|
||||
return_info:
|
||||
description: 操作结果详细信息
|
||||
type: string
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
title: reactor_taken_out参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
solid_feeding_vials:
|
||||
feedback: {}
|
||||
goal:
|
||||
assign_material_name: assign_material_name
|
||||
material_id: material_id
|
||||
temperature: temperature
|
||||
time: time
|
||||
torque_variation: torque_variation
|
||||
goal_default:
|
||||
assign_material_name: ''
|
||||
material_id: ''
|
||||
temperature: ''
|
||||
time: ''
|
||||
torque_variation: ''
|
||||
handles: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: 固体进料小瓶 - 通过小瓶向反应器中添加固体物料,支持多种粉末类型(盐、面粉、BTDA)
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
assign_material_name:
|
||||
description: 物料名称(用于获取试剂瓶位ID)
|
||||
type: string
|
||||
material_id:
|
||||
description: 粉末类型ID,1=盐(21分钟),2=面粉(27分钟),3=BTDA(38分钟)
|
||||
type: string
|
||||
temperature:
|
||||
description: 温度设定(°C)
|
||||
type: string
|
||||
time:
|
||||
description: 观察时间(分钟)
|
||||
type: string
|
||||
torque_variation:
|
||||
description: 是否观察 (1=否, 2=是)
|
||||
type: string
|
||||
required:
|
||||
- assign_material_name
|
||||
- material_id
|
||||
- time
|
||||
- torque_variation
|
||||
- temperature
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: solid_feeding_vials参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
module: unilabos.devices.workstation.bioyond_studio.reaction_station:BioyondReactionStation
|
||||
protocol_type: []
|
||||
status_types:
|
||||
all_workflows: dict
|
||||
@@ -708,14 +496,14 @@ reaction_station.bioyond:
|
||||
workstation_status: dict
|
||||
type: python
|
||||
config_info: []
|
||||
description: Bioyond反应站 - 专门用于化学反应操作的工作站
|
||||
description: Bioyond反应站
|
||||
handles: []
|
||||
icon: 反应站.webp
|
||||
icon: reaction_station.webp
|
||||
init_param_schema:
|
||||
config:
|
||||
properties:
|
||||
bioyond_config:
|
||||
type: string
|
||||
config:
|
||||
type: object
|
||||
deck:
|
||||
type: string
|
||||
required: []
|
||||
|
||||
@@ -18,6 +18,7 @@ def bioyond_warehouse_1x4x4(name: str) -> WareHouse:
|
||||
)
|
||||
|
||||
|
||||
|
||||
def bioyond_warehouse_1x4x2(name: str) -> WareHouse:
|
||||
"""创建BioYond 4x1x2仓库"""
|
||||
return warehouse_factory(
|
||||
|
||||
@@ -535,6 +535,7 @@ def resource_ulab_to_plr(resource: dict, plr_model=False) -> "ResourcePLR":
|
||||
|
||||
def resource_ulab_to_plr_inner(resource: dict):
|
||||
all_states[resource["name"]] = resource["data"]
|
||||
extra = resource.pop("extra", {})
|
||||
d = {
|
||||
"name": resource["name"],
|
||||
"type": resource["type"],
|
||||
@@ -575,16 +576,16 @@ def resource_plr_to_ulab(resource_plr: "ResourcePLR", parent_name: str = None, w
|
||||
replace_info = {
|
||||
"plate": "plate",
|
||||
"well": "well",
|
||||
"tip_spot": "container",
|
||||
"trash": "container",
|
||||
"tip_spot": "tip_spot",
|
||||
"trash": "trash",
|
||||
"deck": "deck",
|
||||
"tip_rack": "container",
|
||||
"tip_rack": "tip_rack",
|
||||
}
|
||||
if source in replace_info:
|
||||
return replace_info[source]
|
||||
else:
|
||||
print("转换pylabrobot的时候,出现未知类型", source)
|
||||
return "container"
|
||||
return source
|
||||
|
||||
def resource_plr_to_ulab_inner(d: dict, all_states: dict, child=True) -> dict:
|
||||
r = {
|
||||
|
||||
@@ -78,6 +78,7 @@ class ItemizedCarrier(ResourcePLR):
|
||||
sites: Optional[Dict[Union[int, str], Optional[ResourcePLR]]] = None,
|
||||
category: Optional[str] = "carrier",
|
||||
model: Optional[str] = None,
|
||||
invisible_slots: Optional[str] = None,
|
||||
):
|
||||
super().__init__(
|
||||
name=name,
|
||||
@@ -89,6 +90,7 @@ class ItemizedCarrier(ResourcePLR):
|
||||
)
|
||||
self.num_items = len(sites)
|
||||
self.num_items_x, self.num_items_y, self.num_items_z = num_items_x, num_items_y, num_items_z
|
||||
self.invisible_slots = [] if invisible_slots is None else invisible_slots
|
||||
self.layout = "z-y" if self.num_items_z > 1 and self.num_items_x == 1 else "x-z" if self.num_items_z > 1 and self.num_items_y == 1 else "x-y"
|
||||
|
||||
if isinstance(sites, dict):
|
||||
@@ -410,7 +412,7 @@ class ItemizedCarrier(ResourcePLR):
|
||||
"layout": self.layout,
|
||||
"sites": [{
|
||||
"label": str(identifier),
|
||||
"visible": True if self[identifier] is not None else False,
|
||||
"visible": False if identifier in self.invisible_slots else True,
|
||||
"occupied_by": self[identifier].name
|
||||
if isinstance(self[identifier], ResourcePLR) and not isinstance(self[identifier], ResourceHolder) else
|
||||
self[identifier] if isinstance(self[identifier], str) else None,
|
||||
@@ -433,6 +435,7 @@ class BottleCarrier(ItemizedCarrier):
|
||||
sites: Optional[Dict[Union[int, str], ResourceHolder]] = None,
|
||||
category: str = "bottle_carrier",
|
||||
model: Optional[str] = None,
|
||||
invisible_slots: List[str] = None,
|
||||
**kwargs,
|
||||
):
|
||||
super().__init__(
|
||||
@@ -443,4 +446,5 @@ class BottleCarrier(ItemizedCarrier):
|
||||
sites=sites,
|
||||
category=category,
|
||||
model=model,
|
||||
invisible_slots=invisible_slots,
|
||||
)
|
||||
|
||||
@@ -10,7 +10,7 @@ from unilabos.ros.nodes.presets.resource_mesh_manager import ResourceMeshManager
|
||||
from unilabos.ros.nodes.resource_tracker import DeviceNodeResourceTracker, ResourceTreeSet
|
||||
from unilabos.devices.ros_dev.liquid_handler_joint_publisher import LiquidHandlerJointPublisher
|
||||
from unilabos_msgs.srv import SerialCommand # type: ignore
|
||||
from rclpy.executors import MultiThreadedExecutor
|
||||
from rclpy.executors import MultiThreadedExecutor, SingleThreadedExecutor
|
||||
from rclpy.node import Node
|
||||
from rclpy.timer import Timer
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ from unilabos_msgs.msg import Resource # type: ignore
|
||||
|
||||
from unilabos.ros.nodes.resource_tracker import (
|
||||
DeviceNodeResourceTracker,
|
||||
ResourceTreeSet,
|
||||
ResourceTreeSet, ResourceTreeInstance,
|
||||
)
|
||||
from unilabos.ros.x.rclpyx import get_event_loop
|
||||
from unilabos.ros.utils.driver_creator import WorkstationNodeCreator, PyLabRobotCreator, DeviceClassCreator
|
||||
@@ -338,12 +338,12 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
||||
|
||||
# 创建资源管理客户端
|
||||
self._resource_clients: Dict[str, Client] = {
|
||||
"resource_add": self.create_client(ResourceAdd, "/resources/add"),
|
||||
"resource_get": self.create_client(SerialCommand, "/resources/get"),
|
||||
"resource_delete": self.create_client(ResourceDelete, "/resources/delete"),
|
||||
"resource_update": self.create_client(ResourceUpdate, "/resources/update"),
|
||||
"resource_list": self.create_client(ResourceList, "/resources/list"),
|
||||
"c2s_update_resource_tree": self.create_client(SerialCommand, "/c2s_update_resource_tree"),
|
||||
"resource_add": self.create_client(ResourceAdd, "/resources/add", callback_group=self.callback_group),
|
||||
"resource_get": self.create_client(SerialCommand, "/resources/get", callback_group=self.callback_group),
|
||||
"resource_delete": self.create_client(ResourceDelete, "/resources/delete", callback_group=self.callback_group),
|
||||
"resource_update": self.create_client(ResourceUpdate, "/resources/update", callback_group=self.callback_group),
|
||||
"resource_list": self.create_client(ResourceList, "/resources/list", callback_group=self.callback_group),
|
||||
"c2s_update_resource_tree": self.create_client(SerialCommand, "/c2s_update_resource_tree", callback_group=self.callback_group),
|
||||
}
|
||||
|
||||
def re_register_device(req, res):
|
||||
@@ -573,6 +573,52 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
||||
self.lab_logger().error(traceback.format_exc())
|
||||
self.lab_logger().debug(f"资源更新结果: {response}")
|
||||
|
||||
def transfer_to_new_resource(self, plr_resource: "ResourcePLR", tree: ResourceTreeInstance, additional_add_params: Dict[str, Any]):
|
||||
parent_uuid = tree.root_node.res_content.parent_uuid
|
||||
if parent_uuid:
|
||||
parent_resource: ResourcePLR = self.resource_tracker.uuid_to_resources.get(parent_uuid)
|
||||
if parent_resource is None:
|
||||
self.lab_logger().warning(
|
||||
f"物料{plr_resource}请求挂载{tree.root_node.res_content.name}的父节点{parent_uuid}不存在"
|
||||
)
|
||||
else:
|
||||
try:
|
||||
# 特殊兼容所有plr的物料的assign方法,和create_resource append_resource后期同步
|
||||
additional_params = {}
|
||||
extra = getattr(plr_resource, "unilabos_extra", {})
|
||||
if len(extra):
|
||||
self.lab_logger().info(f"发现物料{plr_resource}额外参数: " + str(extra))
|
||||
if "update_resource_site" in extra:
|
||||
additional_add_params["site"] = extra["update_resource_site"]
|
||||
site = additional_add_params.get("site", None)
|
||||
spec = inspect.signature(parent_resource.assign_child_resource)
|
||||
if "spot" in spec.parameters:
|
||||
ordering_dict: Dict[str, Any] = getattr(parent_resource, "_ordering")
|
||||
if ordering_dict:
|
||||
site = list(ordering_dict.keys()).index(site)
|
||||
additional_params["spot"] = site
|
||||
old_parent = plr_resource.parent
|
||||
if old_parent is not None:
|
||||
# plr并不支持同一个deck的加载和卸载
|
||||
self.lab_logger().warning(
|
||||
f"物料{plr_resource}请求从{old_parent}卸载"
|
||||
)
|
||||
old_parent.unassign_child_resource(plr_resource)
|
||||
self.lab_logger().warning(
|
||||
f"物料{plr_resource}请求挂载到{parent_resource},额外参数:{additional_params}"
|
||||
)
|
||||
parent_resource.assign_child_resource(
|
||||
plr_resource, location=None, **additional_params
|
||||
)
|
||||
func = getattr(self.driver_instance, "resource_tree_transfer", None)
|
||||
if callable(func):
|
||||
# 分别是 物料的原来父节点,当前物料的状态,物料的新父节点(此时物料已经重新assign了)
|
||||
func(old_parent, plr_resource, parent_resource)
|
||||
except Exception as e:
|
||||
self.lab_logger().warning(
|
||||
f"物料{plr_resource}请求挂载{tree.root_node.res_content.name}的父节点{parent_resource}[{parent_uuid}]失败!\n{traceback.format_exc()}"
|
||||
)
|
||||
|
||||
async def s2c_resource_tree(self, req: SerialCommand_Request, res: SerialCommand_Response):
|
||||
"""
|
||||
处理资源树更新请求
|
||||
@@ -613,28 +659,7 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
||||
plr_resources = tree_set.to_plr_resources()
|
||||
for plr_resource, tree in zip(plr_resources, tree_set.trees):
|
||||
self.resource_tracker.add_resource(plr_resource)
|
||||
parent_uuid = tree.root_node.res_content.parent_uuid
|
||||
if parent_uuid:
|
||||
parent_resource: ResourcePLR = self.resource_tracker.uuid_to_resources.get(parent_uuid)
|
||||
if parent_resource is None:
|
||||
self.lab_logger().warning(
|
||||
f"物料{plr_resource}请求挂载{tree.root_node.res_content.name}的父节点{parent_uuid}不存在"
|
||||
)
|
||||
else:
|
||||
try:
|
||||
# 特殊兼容所有plr的物料的assign方法,和create_resource append_resource后期同步
|
||||
additional_params = {}
|
||||
site = additional_add_params.get("site", None)
|
||||
spec = inspect.signature(parent_resource.assign_child_resource)
|
||||
if "spot" in spec.parameters:
|
||||
additional_params["spot"] = site
|
||||
parent_resource.assign_child_resource(
|
||||
plr_resource, location=None, **additional_params
|
||||
)
|
||||
except Exception as e:
|
||||
self.lab_logger().warning(
|
||||
f"物料{plr_resource}请求挂载{tree.root_node.res_content.name}的父节点{parent_resource}[{parent_uuid}]失败!\n{traceback.format_exc()}"
|
||||
)
|
||||
self.transfer_to_new_resource(plr_resource, tree, additional_add_params)
|
||||
func = getattr(self.driver_instance, "resource_tree_add", None)
|
||||
if callable(func):
|
||||
func(plr_resources)
|
||||
@@ -647,6 +672,17 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
||||
original_instance: ResourcePLR = self.resource_tracker.figure_resource(
|
||||
{"uuid": tree.root_node.res_content.uuid}, try_mode=False
|
||||
)
|
||||
original_parent_resource = original_instance.parent
|
||||
original_parent_resource_uuid = getattr(original_parent_resource, "unilabos_uuid", None)
|
||||
target_parent_resource_uuid = tree.root_node.res_content.uuid_parent
|
||||
self.lab_logger().info(
|
||||
f"物料{original_instance} 原始父节点{original_parent_resource_uuid} 目标父节点{target_parent_resource_uuid} 更新"
|
||||
)
|
||||
# todo: 对extra进行update
|
||||
if getattr(plr_resource, "unilabos_extra", None) is not None:
|
||||
original_instance.unilabos_extra = getattr(plr_resource, "unilabos_extra")
|
||||
if original_parent_resource_uuid != target_parent_resource_uuid and original_parent_resource is not None:
|
||||
self.transfer_to_new_resource(original_instance, tree, additional_add_params)
|
||||
original_instance.load_all_state(states)
|
||||
self.lab_logger().info(
|
||||
f"更新了资源属性 {plr_resource}[{tree.root_node.res_content.uuid}] 及其子节点 {len(original_instance.get_all_children())} 个"
|
||||
@@ -879,7 +915,7 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
||||
action_type,
|
||||
action_name,
|
||||
execute_callback=self._create_execute_callback(action_name, action_value_mapping),
|
||||
callback_group=ReentrantCallbackGroup(),
|
||||
callback_group=self.callback_group,
|
||||
)
|
||||
|
||||
self.lab_logger().trace(f"发布动作: {action_name}, 类型: {str_action_type}")
|
||||
@@ -1500,7 +1536,7 @@ class ROS2DeviceNode:
|
||||
asyncio.set_event_loop(loop)
|
||||
loop.run_forever()
|
||||
|
||||
ROS2DeviceNode._loop_thread = threading.Thread(target=run_event_loop, daemon=True, name="ROS2DeviceNode")
|
||||
ROS2DeviceNode._loop_thread = threading.Thread(target=run_event_loop, daemon=True, name="ROS2DeviceNodeLoop")
|
||||
ROS2DeviceNode._loop_thread.start()
|
||||
logger.info(f"循环线程已启动")
|
||||
|
||||
|
||||
@@ -285,7 +285,7 @@ class HostNode(BaseROS2DeviceNode):
|
||||
|
||||
# 创建定时器,定期发现设备
|
||||
self._discovery_timer = self.create_timer(
|
||||
discovery_interval, self._discovery_devices_callback, callback_group=ReentrantCallbackGroup()
|
||||
discovery_interval, self._discovery_devices_callback, callback_group=self.callback_group
|
||||
)
|
||||
|
||||
# 添加ping-pong相关属性
|
||||
@@ -494,7 +494,7 @@ class HostNode(BaseROS2DeviceNode):
|
||||
if len(init_new_res) > 1: # 一个物料,多个子节点
|
||||
init_new_res = [init_new_res]
|
||||
resources: List[Resource] | List[List[Resource]] = init_new_res # initialize_resource已经返回list[dict]
|
||||
device_ids = [device_id]
|
||||
device_ids = [device_id.split("/")[-1]]
|
||||
bind_parent_id = [res_creation_input["parent"]]
|
||||
bind_location = [bind_locations]
|
||||
other_calling_param = [
|
||||
@@ -618,7 +618,7 @@ class HostNode(BaseROS2DeviceNode):
|
||||
topic,
|
||||
lambda msg, d=device_id, p=property_name: self.property_callback(msg, d, p),
|
||||
1,
|
||||
callback_group=ReentrantCallbackGroup(),
|
||||
callback_group=self.callback_group,
|
||||
)
|
||||
# 标记为已订阅
|
||||
self._subscribed_topics.add(topic)
|
||||
@@ -829,37 +829,37 @@ class HostNode(BaseROS2DeviceNode):
|
||||
def _init_host_service(self):
|
||||
self._resource_services: Dict[str, Service] = {
|
||||
"resource_add": self.create_service(
|
||||
ResourceAdd, "/resources/add", self._resource_add_callback, callback_group=ReentrantCallbackGroup()
|
||||
ResourceAdd, "/resources/add", self._resource_add_callback, callback_group=self.callback_group
|
||||
),
|
||||
"resource_get": self.create_service(
|
||||
SerialCommand, "/resources/get", self._resource_get_callback, callback_group=ReentrantCallbackGroup()
|
||||
SerialCommand, "/resources/get", self._resource_get_callback, callback_group=self.callback_group
|
||||
),
|
||||
"resource_delete": self.create_service(
|
||||
ResourceDelete,
|
||||
"/resources/delete",
|
||||
self._resource_delete_callback,
|
||||
callback_group=ReentrantCallbackGroup(),
|
||||
callback_group=self.callback_group,
|
||||
),
|
||||
"resource_update": self.create_service(
|
||||
ResourceUpdate,
|
||||
"/resources/update",
|
||||
self._resource_update_callback,
|
||||
callback_group=ReentrantCallbackGroup(),
|
||||
callback_group=self.callback_group,
|
||||
),
|
||||
"resource_list": self.create_service(
|
||||
ResourceList, "/resources/list", self._resource_list_callback, callback_group=ReentrantCallbackGroup()
|
||||
ResourceList, "/resources/list", self._resource_list_callback, callback_group=self.callback_group
|
||||
),
|
||||
"node_info_update": self.create_service(
|
||||
SerialCommand,
|
||||
"/node_info_update",
|
||||
self._node_info_update_callback,
|
||||
callback_group=ReentrantCallbackGroup(),
|
||||
callback_group=self.callback_group,
|
||||
),
|
||||
"c2s_update_resource_tree": self.create_service(
|
||||
SerialCommand,
|
||||
"/c2s_update_resource_tree",
|
||||
self._resource_tree_update_callback,
|
||||
callback_group=ReentrantCallbackGroup(),
|
||||
callback_group=self.callback_group,
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
@@ -194,7 +194,7 @@ class ROS2WorkstationNode(BaseROS2DeviceNode):
|
||||
action_type,
|
||||
action_name,
|
||||
execute_callback=self._create_protocol_execute_callback(action_name, protocol_steps_generator),
|
||||
callback_group=ReentrantCallbackGroup(),
|
||||
callback_group=self.callback_group,
|
||||
)
|
||||
self.lab_logger().trace(f"发布动作: {action_name}, 类型: {str_action_type}")
|
||||
return
|
||||
|
||||
@@ -42,7 +42,9 @@ class ResourceDictPosition(BaseModel):
|
||||
rotation: ResourceDictPositionObject = Field(
|
||||
description="Resource rotation", default_factory=ResourceDictPositionObject
|
||||
)
|
||||
cross_section_type: Literal["rectangle", "circle", "rounded_rectangle"] = Field(description="Cross section type", default="rectangle")
|
||||
cross_section_type: Literal["rectangle", "circle", "rounded_rectangle"] = Field(
|
||||
description="Cross section type", default="rectangle"
|
||||
)
|
||||
|
||||
|
||||
# 统一的资源字典模型,parent 自动序列化为 parent_uuid,children 不序列化
|
||||
@@ -51,7 +53,9 @@ class ResourceDict(BaseModel):
|
||||
uuid: str = Field(description="Resource UUID")
|
||||
name: str = Field(description="Resource name")
|
||||
description: str = Field(description="Resource description", default="")
|
||||
resource_schema: Dict[str, Any] = Field(description="Resource schema", default_factory=dict, serialization_alias="schema", validation_alias="schema")
|
||||
resource_schema: Dict[str, Any] = Field(
|
||||
description="Resource schema", default_factory=dict, serialization_alias="schema", validation_alias="schema"
|
||||
)
|
||||
model: Dict[str, Any] = Field(description="Resource model", default_factory=dict)
|
||||
icon: str = Field(description="Resource icon", default="")
|
||||
parent_uuid: Optional["str"] = Field(description="Parent resource uuid", default=None) # 先设定parent_uuid
|
||||
@@ -62,6 +66,7 @@ class ResourceDict(BaseModel):
|
||||
pose: ResourceDictPosition = Field(description="Resource position", default_factory=ResourceDictPosition)
|
||||
config: Dict[str, Any] = Field(description="Resource configuration")
|
||||
data: Dict[str, Any] = Field(description="Resource data")
|
||||
extra: Dict[str, Any] = Field(description="Extra data")
|
||||
|
||||
@field_serializer("parent_uuid")
|
||||
def _serialize_parent(self, parent_uuid: Optional["ResourceDict"]):
|
||||
@@ -138,6 +143,8 @@ class ResourceDictInstance(object):
|
||||
content["config"] = {}
|
||||
if not content.get("data"):
|
||||
content["data"] = {}
|
||||
if not content.get("extra"): # MagicCode
|
||||
content["extra"] = {}
|
||||
if "pose" not in content:
|
||||
content["pose"] = content.get("position", {})
|
||||
return ResourceDictInstance(ResourceDict.model_validate(content))
|
||||
@@ -311,28 +318,36 @@ class ResourceTreeSet(object):
|
||||
"plate": "plate",
|
||||
"well": "well",
|
||||
"deck": "deck",
|
||||
"tip_rack": "tip_rack",
|
||||
"tip_spot": "tip_spot",
|
||||
"tube": "tube",
|
||||
"bottle_carrier": "bottle_carrier",
|
||||
}
|
||||
if source in replace_info:
|
||||
return replace_info[source]
|
||||
else:
|
||||
print("转换pylabrobot的时候,出现未知类型", source)
|
||||
return "container"
|
||||
return source
|
||||
|
||||
def build_uuid_mapping(res: "PLRResource", uuid_list: list):
|
||||
"""递归构建uuid映射字典"""
|
||||
def build_uuid_mapping(res: "PLRResource", uuid_list: list, parent_uuid: Optional[str] = None):
|
||||
"""递归构建uuid和extra映射字典,返回(current_uuid, parent_uuid, extra)元组列表"""
|
||||
uid = getattr(res, "unilabos_uuid", "")
|
||||
if not uid:
|
||||
uid = str(uuid.uuid4())
|
||||
res.unilabos_uuid = uid
|
||||
logger.warning(f"{res}没有uuid,请设置后再传入,默认填充{uid}!\n{traceback.format_exc()}")
|
||||
uuid_list.append(uid)
|
||||
|
||||
# 获取unilabos_extra,默认为空字典
|
||||
extra = getattr(res, "unilabos_extra", {})
|
||||
|
||||
uuid_list.append((uid, parent_uuid, extra))
|
||||
for child in res.children:
|
||||
build_uuid_mapping(child, uuid_list)
|
||||
build_uuid_mapping(child, uuid_list, uid)
|
||||
|
||||
def resource_plr_inner(
|
||||
d: dict, parent_resource: Optional[ResourceDict], states: dict, uuids: list
|
||||
) -> ResourceDictInstance:
|
||||
current_uuid = uuids.pop(0)
|
||||
current_uuid, parent_uuid, extra = uuids.pop(0)
|
||||
|
||||
raw_pos = (
|
||||
{"x": d["location"]["x"], "y": d["location"]["y"], "z": d["location"]["z"]}
|
||||
@@ -355,13 +370,30 @@ class ResourceTreeSet(object):
|
||||
"uuid": current_uuid,
|
||||
"name": d["name"],
|
||||
"parent": parent_resource, # 直接传入 ResourceDict 对象
|
||||
"parent_uuid": parent_uuid, # 使用 parent_uuid 而不是 parent 对象
|
||||
"type": replace_plr_type(d.get("category", "")),
|
||||
"class": d.get("class", ""),
|
||||
"position": pos,
|
||||
"pose": pos,
|
||||
"config": {k: v for k, v in d.items() if k not in
|
||||
["name", "children", "parent_name", "location", "rotation", "size_x", "size_y", "size_z", "cross_section_type", "bottom_type"]},
|
||||
"config": {
|
||||
k: v
|
||||
for k, v in d.items()
|
||||
if k
|
||||
not in [
|
||||
"name",
|
||||
"children",
|
||||
"parent_name",
|
||||
"location",
|
||||
"rotation",
|
||||
"size_x",
|
||||
"size_y",
|
||||
"size_z",
|
||||
"cross_section_type",
|
||||
"bottom_type",
|
||||
]
|
||||
},
|
||||
"data": states[d["name"]],
|
||||
"extra": extra,
|
||||
}
|
||||
|
||||
# 先转换为 ResourceDictInstance,获取其中的 ResourceDict
|
||||
@@ -379,7 +411,7 @@ class ResourceTreeSet(object):
|
||||
for resource in resources:
|
||||
# 构建uuid列表
|
||||
uuid_list = []
|
||||
build_uuid_mapping(resource, uuid_list)
|
||||
build_uuid_mapping(resource, uuid_list, getattr(resource.parent, "unilabos_uuid", None))
|
||||
|
||||
serialized_data = resource.serialize()
|
||||
all_states = resource.serialize_all_state()
|
||||
@@ -402,14 +434,15 @@ class ResourceTreeSet(object):
|
||||
import inspect
|
||||
|
||||
# 类型映射
|
||||
TYPE_MAP = {"plate": "Plate", "well": "Well", "deck": "Deck"}
|
||||
TYPE_MAP = {"plate": "Plate", "well": "Well", "deck": "Deck", "container": "RegularContainer"}
|
||||
|
||||
def collect_node_data(node: ResourceDictInstance, name_to_uuid: dict, all_states: dict):
|
||||
"""一次遍历收集 name_to_uuid 和 all_states"""
|
||||
def collect_node_data(node: ResourceDictInstance, name_to_uuid: dict, all_states: dict, name_to_extra: dict):
|
||||
"""一次遍历收集 name_to_uuid, all_states 和 name_to_extra"""
|
||||
name_to_uuid[node.res_content.name] = node.res_content.uuid
|
||||
all_states[node.res_content.name] = node.res_content.data
|
||||
name_to_extra[node.res_content.name] = node.res_content.extra
|
||||
for child in node.children:
|
||||
collect_node_data(child, name_to_uuid, all_states)
|
||||
collect_node_data(child, name_to_uuid, all_states, name_to_extra)
|
||||
|
||||
def node_to_plr_dict(node: ResourceDictInstance, has_model: bool):
|
||||
"""转换节点为 PLR 字典格式"""
|
||||
@@ -419,6 +452,7 @@ class ResourceTreeSet(object):
|
||||
logger.warning(f"未知类型 {res.type}")
|
||||
|
||||
d = {
|
||||
**res.config,
|
||||
"name": res.name,
|
||||
"type": res.config.get("type", plr_type),
|
||||
"size_x": res.config.get("size_x", 0),
|
||||
@@ -434,33 +468,35 @@ class ResourceTreeSet(object):
|
||||
"category": res.config.get("category", plr_type),
|
||||
"children": [node_to_plr_dict(child, has_model) for child in node.children],
|
||||
"parent_name": res.parent_instance_name,
|
||||
**res.config,
|
||||
}
|
||||
if has_model:
|
||||
d["model"] = res.config.get("model", None)
|
||||
return d
|
||||
|
||||
plr_resources = []
|
||||
trees = []
|
||||
tracker = DeviceNodeResourceTracker()
|
||||
|
||||
for tree in self.trees:
|
||||
name_to_uuid: Dict[str, str] = {}
|
||||
all_states: Dict[str, Any] = {}
|
||||
collect_node_data(tree.root_node, name_to_uuid, all_states)
|
||||
name_to_extra: Dict[str, dict] = {}
|
||||
collect_node_data(tree.root_node, name_to_uuid, all_states, name_to_extra)
|
||||
has_model = tree.root_node.res_content.type != "deck"
|
||||
plr_dict = node_to_plr_dict(tree.root_node, has_model)
|
||||
try:
|
||||
sub_cls = find_subclass(plr_dict["type"], PLRResource)
|
||||
if sub_cls is None:
|
||||
raise ValueError(f"无法找到类型 {plr_dict['type']} 对应的 PLR 资源类。原始信息:{tree.root_node.res_content}")
|
||||
raise ValueError(
|
||||
f"无法找到类型 {plr_dict['type']} 对应的 PLR 资源类。原始信息:{tree.root_node.res_content}"
|
||||
)
|
||||
spec = inspect.signature(sub_cls)
|
||||
if "category" not in spec.parameters:
|
||||
plr_dict.pop("category", None)
|
||||
plr_resource = sub_cls.deserialize(plr_dict, allow_marshal=True)
|
||||
plr_resource.load_all_state(all_states)
|
||||
# 使用 DeviceNodeResourceTracker 设置 UUID
|
||||
# 使用 DeviceNodeResourceTracker 设置 UUID 和 Extra
|
||||
tracker.loop_set_uuid(plr_resource, name_to_uuid)
|
||||
tracker.loop_set_extra(plr_resource, name_to_extra)
|
||||
plr_resources.append(plr_resource)
|
||||
|
||||
except Exception as e:
|
||||
@@ -802,6 +838,20 @@ class DeviceNodeResourceTracker(object):
|
||||
else:
|
||||
setattr(resource, "unilabos_uuid", new_uuid)
|
||||
|
||||
@staticmethod
|
||||
def set_resource_extra(resource, extra: dict):
|
||||
"""
|
||||
设置资源的 extra,统一处理 dict 和 instance 两种类型
|
||||
|
||||
Args:
|
||||
resource: 资源对象(dict或实例)
|
||||
extra: extra字典值
|
||||
"""
|
||||
if isinstance(resource, dict):
|
||||
resource["extra"] = extra
|
||||
else:
|
||||
setattr(resource, "unilabos_extra", extra)
|
||||
|
||||
def _traverse_and_process(self, resource, process_func) -> int:
|
||||
"""
|
||||
递归遍历资源树,对每个节点执行处理函数
|
||||
@@ -850,6 +900,29 @@ class DeviceNodeResourceTracker(object):
|
||||
|
||||
return self._traverse_and_process(resource, process)
|
||||
|
||||
def loop_set_extra(self, resource, name_to_extra_map: Dict[str, dict]) -> int:
|
||||
"""
|
||||
递归遍历资源树,根据 name 设置所有节点的 extra
|
||||
|
||||
Args:
|
||||
resource: 资源对象(可以是dict或实例)
|
||||
name_to_extra_map: name到extra的映射字典,{name: extra}
|
||||
|
||||
Returns:
|
||||
更新的资源数量
|
||||
"""
|
||||
|
||||
def process(res):
|
||||
resource_name = self._get_resource_attr(res, "name")
|
||||
if resource_name and resource_name in name_to_extra_map:
|
||||
extra = name_to_extra_map[resource_name]
|
||||
self.set_resource_extra(res, extra)
|
||||
logger.debug(f"设置资源Extra: {resource_name} -> {extra}")
|
||||
return 1
|
||||
return 0
|
||||
|
||||
return self._traverse_and_process(resource, process)
|
||||
|
||||
def loop_update_uuid(self, resource, uuid_map: Dict[str, str]) -> int:
|
||||
"""
|
||||
递归遍历资源树,更新所有节点的uuid
|
||||
@@ -892,7 +965,9 @@ class DeviceNodeResourceTracker(object):
|
||||
if current_uuid:
|
||||
old = self.uuid_to_resources.get(current_uuid)
|
||||
self.uuid_to_resources[current_uuid] = res
|
||||
logger.debug(f"收集资源UUID映射: {current_uuid} -> {res} {'' if old is None else f'(覆盖旧值: {old})'}")
|
||||
logger.debug(
|
||||
f"收集资源UUID映射: {current_uuid} -> {res} {'' if old is None else f'(覆盖旧值: {old})'}"
|
||||
)
|
||||
return 0
|
||||
|
||||
self._traverse_and_process(resource, process)
|
||||
|
||||
Reference in New Issue
Block a user