mirror of
https://github.com/dptech-corp/Uni-Lab-OS.git
synced 2026-02-04 21:35:09 +00:00
Compare commits
13 Commits
45eaf7019d
...
c2dfe689aa
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c2dfe689aa | ||
|
|
4cd40865b4 | ||
|
|
fd3dbcf1ff | ||
|
|
ebe9e1b1f8 | ||
|
|
862f250e49 | ||
|
|
73f33c82db | ||
|
|
58bf6496b6 | ||
|
|
2b7da0e396 | ||
|
|
dd89d00588 | ||
|
|
9327d59915 | ||
|
|
736f55765b | ||
|
|
9eb1f9823e | ||
|
|
c61c4aae59 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -241,4 +241,4 @@ unilabos/device_mesh/view_robot.rviz
|
||||
# Certs
|
||||
**/.certs
|
||||
local_test2.py
|
||||
ros-humble-unilabos-msgs-0.9.12-h6403a04_5.tar.bz2
|
||||
ros-humble-unilabos-msgs-0.9.13-h6403a04_5.tar.bz2
|
||||
|
||||
@@ -49,7 +49,7 @@ conda env update --file unilabos-[YOUR_OS].yml -n environment_name
|
||||
|
||||
# Currently, you need to install the `unilabos_msgs` package
|
||||
# You can download the system-specific package from the Release page
|
||||
conda install ros-humble-unilabos-msgs-0.9.12-xxxxx.tar.bz2
|
||||
conda install ros-humble-unilabos-msgs-0.9.13-xxxxx.tar.bz2
|
||||
|
||||
# Install PyLabRobot and other prerequisites
|
||||
git clone https://github.com/PyLabRobot/pylabrobot plr_repo
|
||||
|
||||
@@ -49,7 +49,7 @@ conda env update --file unilabos-[YOUR_OS].yml -n 环境名
|
||||
|
||||
# 现阶段,需要安装 `unilabos_msgs` 包
|
||||
# 可以前往 Release 页面下载系统对应的包进行安装
|
||||
conda install ros-humble-unilabos-msgs-0.9.12-xxxxx.tar.bz2
|
||||
conda install ros-humble-unilabos-msgs-0.9.13-xxxxx.tar.bz2
|
||||
|
||||
# 安装PyLabRobot等前置
|
||||
git clone https://github.com/PyLabRobot/pylabrobot plr_repo
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package:
|
||||
name: ros-humble-unilabos-msgs
|
||||
version: 0.9.12
|
||||
version: 0.9.13
|
||||
source:
|
||||
path: ../../unilabos_msgs
|
||||
folder: ros-humble-unilabos-msgs/src/work
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package:
|
||||
name: unilabos
|
||||
version: "0.9.12"
|
||||
version: "0.9.13"
|
||||
|
||||
source:
|
||||
path: ../..
|
||||
|
||||
2
setup.py
2
setup.py
@@ -4,7 +4,7 @@ package_name = 'unilabos'
|
||||
|
||||
setup(
|
||||
name=package_name,
|
||||
version='0.9.12',
|
||||
version='0.9.13',
|
||||
packages=find_packages(),
|
||||
include_package_data=True,
|
||||
install_requires=['setuptools'],
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import networkx as nx
|
||||
import logging
|
||||
from typing import List, Dict, Any
|
||||
from typing import List, Dict, Any, Union
|
||||
from .pump_protocol import generate_pump_protocol_with_rinsing
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -216,7 +216,7 @@ def calculate_reagent_volume(target_ph_value: float, reagent: str, vessel_volume
|
||||
|
||||
def generate_adjust_ph_protocol(
|
||||
G: nx.DiGraph,
|
||||
vessel: dict, # 🔧 修改:从字符串改为字典类型
|
||||
vessel:Union[dict,str], # 🔧 修改:从字符串改为字典类型
|
||||
ph_value: float,
|
||||
reagent: str,
|
||||
**kwargs
|
||||
@@ -235,8 +235,17 @@ def generate_adjust_ph_protocol(
|
||||
List[Dict[str, Any]]: 动作序列
|
||||
"""
|
||||
|
||||
# 🔧 核心修改:从字典中提取容器ID
|
||||
vessel_id = vessel["id"]
|
||||
# 统一处理vessel参数
|
||||
if isinstance(vessel, dict):
|
||||
vessel_id = list(vessel.values())[0].get("id", "")
|
||||
vessel_data = vessel.get("data", {})
|
||||
else:
|
||||
vessel_id = str(vessel)
|
||||
vessel_data = G.nodes[vessel_id].get("data", {}) if vessel_id in G.nodes() else {}
|
||||
|
||||
if not vessel_id:
|
||||
debug_print(f"❌ vessel 参数无效,必须包含id字段或直接提供容器ID. vessel: {vessel}")
|
||||
raise ValueError("vessel 参数无效,必须包含id字段或直接提供容器ID")
|
||||
|
||||
debug_print("=" * 60)
|
||||
debug_print("🧪 开始生成pH调节协议")
|
||||
|
||||
@@ -22,7 +22,7 @@ def parse_time_input(time_input: Union[str, float]) -> float:
|
||||
"""
|
||||
if isinstance(time_input, (int, float)):
|
||||
debug_print(f"⏱️ 时间输入为数字: {time_input}s ✨")
|
||||
return float(time_input)
|
||||
return float(time_input) # 🔧 确保返回float
|
||||
|
||||
if not time_input or not str(time_input).strip():
|
||||
debug_print(f"⚠️ 时间输入为空,使用默认值: 180s (3分钟) 🕐")
|
||||
@@ -48,7 +48,7 @@ def parse_time_input(time_input: Union[str, float]) -> float:
|
||||
try:
|
||||
value = float(time_str)
|
||||
debug_print(f"✅ 时间解析成功: {time_str} → {value}s(无单位,默认秒)⏰")
|
||||
return value
|
||||
return float(value) # 🔧 确保返回float
|
||||
except ValueError:
|
||||
debug_print(f"❌ 无法解析时间: '{time_str}',使用默认值180s (3分钟) 😅")
|
||||
return 180.0
|
||||
@@ -70,7 +70,7 @@ def parse_time_input(time_input: Union[str, float]) -> float:
|
||||
time_sec = value # 已经是s
|
||||
debug_print(f"🕐 时间转换: {value}s → {time_sec}s (已是秒) ⏰")
|
||||
|
||||
return time_sec
|
||||
return float(time_sec) # 🔧 确保返回float
|
||||
|
||||
def find_rotavap_device(G: nx.DiGraph, vessel: str = None) -> Optional[str]:
|
||||
"""
|
||||
@@ -389,12 +389,12 @@ def generate_evaporate_protocol(
|
||||
"device_id": rotavap_device,
|
||||
"action_name": "evaporate",
|
||||
"action_kwargs": {
|
||||
"vessel": target_vessel, # 使用 target_vessel
|
||||
"pressure": pressure,
|
||||
"temp": temp,
|
||||
"time": final_time,
|
||||
"stir_speed": stir_speed,
|
||||
"solvent": solvent
|
||||
"vessel": target_vessel,
|
||||
"pressure": float(pressure),
|
||||
"temp": float(temp),
|
||||
"time": float(final_time), # 🔧 强制转换为float类型
|
||||
"stir_speed": float(stir_speed),
|
||||
"solvent": str(solvent)
|
||||
}
|
||||
}
|
||||
action_sequence.append(evaporate_action)
|
||||
|
||||
@@ -170,33 +170,94 @@ def find_solvent_vessel(G: nx.DiGraph, solvent: str) -> str:
|
||||
debug_print(f" 🎉 通过名称匹配找到容器: {vessel_name} ✨")
|
||||
return vessel_name
|
||||
|
||||
# 第二步:通过模糊匹配
|
||||
# 第二步:通过模糊匹配(节点ID和名称)
|
||||
debug_print(" 🔍 步骤2: 模糊名称匹配...")
|
||||
for node_id in G.nodes():
|
||||
if G.nodes[node_id].get('type') == 'container':
|
||||
node_name = G.nodes[node_id].get('name', '').lower()
|
||||
|
||||
if solvent.lower() in node_id.lower() or solvent.lower() in node_name:
|
||||
debug_print(f" 🎉 通过模糊匹配找到容器: {node_id} ✨")
|
||||
debug_print(f" 🎉 通过模糊匹配找到容器: {node_id} (名称: {node_name}) ✨")
|
||||
return node_id
|
||||
|
||||
# 第三步:通过液体类型匹配
|
||||
debug_print(" 🧪 步骤3: 液体类型匹配...")
|
||||
# 第三步:通过配置中的试剂信息匹配
|
||||
debug_print(" 🧪 步骤3: 配置试剂信息匹配...")
|
||||
for node_id in G.nodes():
|
||||
if G.nodes[node_id].get('type') == 'container':
|
||||
# 检查 config 中的 reagent 字段
|
||||
node_config = G.nodes[node_id].get('config', {})
|
||||
config_reagent = node_config.get('reagent', '').lower()
|
||||
|
||||
if config_reagent and solvent.lower() == config_reagent:
|
||||
debug_print(f" 🎉 通过config.reagent匹配找到容器: {node_id} (试剂: {config_reagent}) ✨")
|
||||
return node_id
|
||||
|
||||
# 第四步:通过数据中的试剂信息匹配
|
||||
debug_print(" 🧪 步骤4: 数据试剂信息匹配...")
|
||||
for node_id in G.nodes():
|
||||
if G.nodes[node_id].get('type') == 'container':
|
||||
vessel_data = G.nodes[node_id].get('data', {})
|
||||
liquids = vessel_data.get('liquid', [])
|
||||
|
||||
# 检查 data 中的 reagent_name 字段
|
||||
reagent_name = vessel_data.get('reagent_name', '').lower()
|
||||
if reagent_name and solvent.lower() == reagent_name:
|
||||
debug_print(f" 🎉 通过data.reagent_name匹配找到容器: {node_id} (试剂: {reagent_name}) ✨")
|
||||
return node_id
|
||||
|
||||
# 检查 data 中的液体信息
|
||||
liquids = vessel_data.get('liquid', [])
|
||||
for liquid in liquids:
|
||||
if isinstance(liquid, dict):
|
||||
liquid_type = (liquid.get('liquid_type') or liquid.get('name', '')).lower()
|
||||
reagent_name = vessel_data.get('reagent_name', '').lower()
|
||||
|
||||
if solvent.lower() in liquid_type or solvent.lower() in reagent_name:
|
||||
debug_print(f" 🎉 通过液体类型匹配找到容器: {node_id} ✨")
|
||||
if solvent.lower() in liquid_type:
|
||||
debug_print(f" 🎉 通过液体类型匹配找到容器: {node_id} (液体类型: {liquid_type}) ✨")
|
||||
return node_id
|
||||
|
||||
# 第五步:部分匹配(如果前面都没找到)
|
||||
debug_print(" 🔍 步骤5: 部分匹配...")
|
||||
for node_id in G.nodes():
|
||||
if G.nodes[node_id].get('type') == 'container':
|
||||
node_config = G.nodes[node_id].get('config', {})
|
||||
node_data = G.nodes[node_id].get('data', {})
|
||||
node_name = G.nodes[node_id].get('name', '').lower()
|
||||
|
||||
config_reagent = node_config.get('reagent', '').lower()
|
||||
data_reagent = node_data.get('reagent_name', '').lower()
|
||||
|
||||
# 检查是否包含溶剂名称
|
||||
if (solvent.lower() in config_reagent or
|
||||
solvent.lower() in data_reagent or
|
||||
solvent.lower() in node_name or
|
||||
solvent.lower() in node_id.lower()):
|
||||
debug_print(f" 🎉 通过部分匹配找到容器: {node_id} ✨")
|
||||
debug_print(f" - 节点名称: {node_name}")
|
||||
debug_print(f" - 配置试剂: {config_reagent}")
|
||||
debug_print(f" - 数据试剂: {data_reagent}")
|
||||
return node_id
|
||||
|
||||
# 调试信息:列出所有容器
|
||||
debug_print(" 🔎 调试信息:列出所有容器...")
|
||||
container_list = []
|
||||
for node_id in G.nodes():
|
||||
if G.nodes[node_id].get('type') == 'container':
|
||||
node_config = G.nodes[node_id].get('config', {})
|
||||
node_data = G.nodes[node_id].get('data', {})
|
||||
node_name = G.nodes[node_id].get('name', '')
|
||||
|
||||
container_info = {
|
||||
'id': node_id,
|
||||
'name': node_name,
|
||||
'config_reagent': node_config.get('reagent', ''),
|
||||
'data_reagent': node_data.get('reagent_name', '')
|
||||
}
|
||||
container_list.append(container_info)
|
||||
debug_print(f" - 容器: {node_id}, 名称: {node_name}, config试剂: {node_config.get('reagent', '')}, data试剂: {node_data.get('reagent_name', '')}")
|
||||
|
||||
debug_print(f"❌ 找不到溶剂 '{solvent}' 对应的容器 😭")
|
||||
debug_print(f"🔍 查找的溶剂: '{solvent}' (小写: '{solvent.lower()}')")
|
||||
debug_print(f"📊 总共发现 {len(container_list)} 个容器")
|
||||
|
||||
raise ValueError(f"找不到溶剂 '{solvent}' 对应的容器")
|
||||
|
||||
|
||||
|
||||
@@ -149,7 +149,7 @@ def extract_vessel_id(vessel: Union[str, dict]) -> str:
|
||||
str: vessel_id
|
||||
"""
|
||||
if isinstance(vessel, dict):
|
||||
vessel_id = vessel.get("id", "")
|
||||
vessel_id = list(vessel.values())[0].get("id", "")
|
||||
debug_print(f"🔧 从vessel字典提取ID: {vessel_id}")
|
||||
return vessel_id
|
||||
elif isinstance(vessel, str):
|
||||
|
||||
@@ -165,7 +165,7 @@ def extract_vessel_id(vessel: Union[str, dict]) -> str:
|
||||
str: vessel_id
|
||||
"""
|
||||
if isinstance(vessel, dict):
|
||||
vessel_id = vessel.get("id", "")
|
||||
vessel_id = list(vessel.values())[0].get("id", "")
|
||||
debug_print(f"🔧 从vessel字典提取ID: {vessel_id}")
|
||||
return vessel_id
|
||||
elif isinstance(vessel, str):
|
||||
|
||||
@@ -590,14 +590,14 @@ class LiquidHandlerAbstract(LiquidHandlerMiddleware):
|
||||
else:
|
||||
# 首先应该对任务分组,然后每次1个/8个进行操作处理
|
||||
if len(use_channels) == 1 and self.backend.num_channels == 1:
|
||||
tip = []
|
||||
for _ in range(len(use_channels)):
|
||||
tip.extend(next(self.current_tip))
|
||||
await self.pick_up_tips(tip)
|
||||
|
||||
for _ in range(len(waste_liquid)):
|
||||
for _ in range(len(sources)):
|
||||
tip = []
|
||||
for __ in range(len(use_channels)):
|
||||
tip.extend(next(self.current_tip))
|
||||
await self.pick_up_tips(tip)
|
||||
await self.aspirate(
|
||||
resources=sources,
|
||||
resources=[sources[_]],
|
||||
vols=[vols[_]],
|
||||
use_channels=use_channels,
|
||||
flow_rates=[flow_rates[0]] if flow_rates else None,
|
||||
@@ -608,8 +608,9 @@ class LiquidHandlerAbstract(LiquidHandlerMiddleware):
|
||||
)
|
||||
if delays is not None:
|
||||
await self.custom_delay(seconds=delays[0])
|
||||
|
||||
await self.dispense(
|
||||
resources=[waste_liquid[_]],
|
||||
resources=[waste_liquid],
|
||||
vols=[vols[_]],
|
||||
use_channels=use_channels,
|
||||
flow_rates=[flow_rates[1]] if flow_rates else None,
|
||||
@@ -618,7 +619,7 @@ class LiquidHandlerAbstract(LiquidHandlerMiddleware):
|
||||
liquid_height=[liquid_height[1]] if liquid_height else None,
|
||||
spread=spread,
|
||||
)
|
||||
await self.discard_tips()
|
||||
await self.discard_tips()
|
||||
elif len(use_channels) == 8 and self.backend.num_channels == 8:
|
||||
tip = []
|
||||
for _ in range(len(use_channels)):
|
||||
@@ -706,9 +707,9 @@ class LiquidHandlerAbstract(LiquidHandlerMiddleware):
|
||||
mix_liquid_height: Optional[float] = None,
|
||||
none_keys: List[str] = [],
|
||||
):
|
||||
"""A complete *add* (aspirate reagent → dispense into targets) operation."""
|
||||
# """A complete *add* (aspirate reagent → dispense into targets) operation."""
|
||||
|
||||
try:
|
||||
# # try:
|
||||
if is_96_well:
|
||||
pass # This mode is not verified.
|
||||
else:
|
||||
@@ -720,9 +721,9 @@ class LiquidHandlerAbstract(LiquidHandlerMiddleware):
|
||||
tip = []
|
||||
for _ in range(len(use_channels)):
|
||||
tip.extend(next(self.current_tip))
|
||||
|
||||
await self.pick_up_tips(tip)
|
||||
await self.pick_up_tips(tip)
|
||||
for _ in range(len(targets)):
|
||||
print(use_channels, reagent_sources)
|
||||
await self.aspirate(
|
||||
resources=reagent_sources,
|
||||
vols=[asp_vols[_]],
|
||||
@@ -733,6 +734,7 @@ class LiquidHandlerAbstract(LiquidHandlerMiddleware):
|
||||
blow_out_air_volume=[blow_out_air_volume[0]] if blow_out_air_volume else None,
|
||||
spread=spread,
|
||||
)
|
||||
|
||||
if delays is not None:
|
||||
await self.custom_delay(seconds=delays[0])
|
||||
await self.dispense(
|
||||
@@ -749,7 +751,7 @@ class LiquidHandlerAbstract(LiquidHandlerMiddleware):
|
||||
if delays is not None:
|
||||
await self.custom_delay(seconds=delays[1])
|
||||
await self.mix(
|
||||
targets=targets[_],
|
||||
targets=[targets[_]],
|
||||
mix_time=mix_time,
|
||||
mix_vol=mix_vol,
|
||||
offsets=offsets if offsets else None,
|
||||
@@ -759,7 +761,7 @@ class LiquidHandlerAbstract(LiquidHandlerMiddleware):
|
||||
if delays is not None:
|
||||
await self.custom_delay(seconds=delays[1])
|
||||
await self.touch_tip(targets[_])
|
||||
|
||||
await self.discard_tips()
|
||||
elif len(use_channels) == 8:
|
||||
|
||||
# 对于8个的情况,需要判断此时任务是不是能被8通道移液站来成功处理
|
||||
@@ -825,9 +827,9 @@ class LiquidHandlerAbstract(LiquidHandlerMiddleware):
|
||||
await self.discard_tips()
|
||||
|
||||
|
||||
except Exception as e:
|
||||
traceback.print_exc()
|
||||
raise RuntimeError(f"Liquid addition failed: {e}") from e
|
||||
# except Exception as e:
|
||||
# traceback.print_exc()
|
||||
# raise RuntimeError(f"Liquid addition failed: {e}") from e
|
||||
|
||||
# ---------------------------------------------------------------
|
||||
# TRANSFER LIQUID ------------------------------------------------
|
||||
@@ -872,126 +874,126 @@ class LiquidHandlerAbstract(LiquidHandlerMiddleware):
|
||||
Set *True* to use the 96‑channel head.
|
||||
"""
|
||||
|
||||
try:
|
||||
if is_96_well:
|
||||
pass # This mode is not verified.
|
||||
else:
|
||||
if len(asp_vols) != len(targets):
|
||||
raise ValueError(f"Length of `asp_vols` {len(asp_vols)} must match `targets` {len(targets)}.")
|
||||
|
||||
# 首先应该对任务分组,然后每次1个/8个进行操作处理
|
||||
if len(use_channels) == 1:
|
||||
if is_96_well:
|
||||
pass # This mode is not verified.
|
||||
else:
|
||||
if len(asp_vols) != len(targets):
|
||||
raise ValueError(f"Length of `asp_vols` {len(asp_vols)} must match `targets` {len(targets)}.")
|
||||
|
||||
# 首先应该对任务分组,然后每次1个/8个进行操作处理
|
||||
if len(use_channels) == 1:
|
||||
for _ in range(len(targets)):
|
||||
tip = []
|
||||
for ___ in range(len(use_channels)):
|
||||
tip.extend(next(self.current_tip))
|
||||
await self.pick_up_tips(tip)
|
||||
|
||||
await self.aspirate(
|
||||
resources=[sources[_]],
|
||||
vols=[asp_vols[_]],
|
||||
use_channels=use_channels,
|
||||
flow_rates=[asp_flow_rates[0]] if asp_flow_rates else None,
|
||||
offsets=[offsets[0]] if offsets else None,
|
||||
liquid_height=[liquid_height[0]] if liquid_height else None,
|
||||
blow_out_air_volume=[blow_out_air_volume[0]] if blow_out_air_volume else None,
|
||||
spread=spread,
|
||||
)
|
||||
if delays is not None:
|
||||
await self.custom_delay(seconds=delays[0])
|
||||
await self.dispense(
|
||||
resources=[targets[_]],
|
||||
vols=[dis_vols[_]],
|
||||
use_channels=use_channels,
|
||||
flow_rates=[dis_flow_rates[1]] if dis_flow_rates else None,
|
||||
offsets=[offsets[1]] if offsets else None,
|
||||
blow_out_air_volume=[blow_out_air_volume[1]] if blow_out_air_volume else None,
|
||||
liquid_height=[liquid_height[1]] if liquid_height else None,
|
||||
spread=spread,
|
||||
)
|
||||
if delays is not None:
|
||||
await self.custom_delay(seconds=delays[1])
|
||||
await self.mix(
|
||||
targets=[targets[_]],
|
||||
mix_time=mix_times,
|
||||
mix_vol=mix_vol,
|
||||
offsets=offsets if offsets else None,
|
||||
height_to_bottom=mix_liquid_height if mix_liquid_height else None,
|
||||
mix_rate=mix_rate if mix_rate else None,
|
||||
)
|
||||
if delays is not None:
|
||||
await self.custom_delay(seconds=delays[1])
|
||||
await self.touch_tip(targets[_])
|
||||
await self.discard_tips()
|
||||
|
||||
elif len(use_channels) == 8:
|
||||
# 对于8个的情况,需要判断此时任务是不是能被8通道移液站来成功处理
|
||||
if len(targets) % 8 != 0:
|
||||
raise ValueError(f"Length of `targets` {len(targets)} must be a multiple of 8 for 8-channel mode.")
|
||||
|
||||
# 8个8个来取任务序列
|
||||
|
||||
for i in range(0, len(targets), 8):
|
||||
# 取出8个任务
|
||||
tip = []
|
||||
for _ in range(len(use_channels)):
|
||||
tip.extend(next(self.current_tip))
|
||||
await self.pick_up_tips(tip)
|
||||
current_targets = targets[i:i + 8]
|
||||
current_reagent_sources = sources[i:i + 8]
|
||||
current_asp_vols = asp_vols[i:i + 8]
|
||||
current_dis_vols = dis_vols[i:i + 8]
|
||||
current_asp_flow_rates = asp_flow_rates[i:i + 8]
|
||||
current_asp_offset = offsets[i:i + 8] if offsets else [None] * 8
|
||||
current_dis_offset = offsets[-i*8-8:len(offsets)-i*8] if offsets else [None] * 8
|
||||
current_asp_liquid_height = liquid_height[i:i + 8] if liquid_height else [None] * 8
|
||||
current_dis_liquid_height = liquid_height[-i*8-8:len(liquid_height)-i*8] if liquid_height else [None] * 8
|
||||
current_asp_blow_out_air_volume = blow_out_air_volume[i:i + 8] if blow_out_air_volume else [None] * 8
|
||||
current_dis_blow_out_air_volume = blow_out_air_volume[-i*8-8:len(blow_out_air_volume)-i*8] if blow_out_air_volume else [None] * 8
|
||||
current_dis_flow_rates = dis_flow_rates[i:i + 8] if dis_flow_rates else [None] * 8
|
||||
|
||||
for _ in range(len(targets)):
|
||||
await self.aspirate(
|
||||
resources=sources,
|
||||
vols=[asp_vols[_]],
|
||||
use_channels=use_channels,
|
||||
flow_rates=[asp_flow_rates[0]] if asp_flow_rates else None,
|
||||
offsets=[offsets[0]] if offsets else None,
|
||||
liquid_height=[liquid_height[0]] if liquid_height else None,
|
||||
blow_out_air_volume=[blow_out_air_volume[0]] if blow_out_air_volume else None,
|
||||
spread=spread,
|
||||
)
|
||||
if delays is not None:
|
||||
await self.custom_delay(seconds=delays[0])
|
||||
await self.dispense(
|
||||
resources=[targets[_]],
|
||||
vols=[dis_vols[_]],
|
||||
use_channels=use_channels,
|
||||
flow_rates=[dis_flow_rates[1]] if dis_flow_rates else None,
|
||||
offsets=[offsets[1]] if offsets else None,
|
||||
blow_out_air_volume=[blow_out_air_volume[1]] if blow_out_air_volume else None,
|
||||
liquid_height=[liquid_height[1]] if liquid_height else None,
|
||||
spread=spread,
|
||||
)
|
||||
if delays is not None:
|
||||
await self.custom_delay(seconds=delays[1])
|
||||
await self.mix(
|
||||
targets=targets[_],
|
||||
mix_time=mix_times,
|
||||
mix_vol=mix_vol,
|
||||
offsets=offsets if offsets else None,
|
||||
height_to_bottom=mix_liquid_height if mix_liquid_height else None,
|
||||
mix_rate=mix_rate if mix_rate else None,
|
||||
)
|
||||
if delays is not None:
|
||||
await self.custom_delay(seconds=delays[1])
|
||||
await self.touch_tip(targets[_])
|
||||
await self.aspirate(
|
||||
resources=current_reagent_sources,
|
||||
vols=current_asp_vols,
|
||||
use_channels=use_channels,
|
||||
flow_rates=current_asp_flow_rates,
|
||||
offsets=current_asp_offset,
|
||||
blow_out_air_volume=current_asp_blow_out_air_volume,
|
||||
liquid_height=current_asp_liquid_height,
|
||||
spread=spread,
|
||||
)
|
||||
|
||||
if delays is not None:
|
||||
await self.custom_delay(seconds=delays[0])
|
||||
await self.dispense(
|
||||
resources=current_targets,
|
||||
vols=current_dis_vols,
|
||||
use_channels=use_channels,
|
||||
flow_rates=current_dis_flow_rates,
|
||||
offsets=current_dis_offset,
|
||||
blow_out_air_volume=current_dis_blow_out_air_volume,
|
||||
liquid_height=current_dis_liquid_height,
|
||||
spread=spread,
|
||||
)
|
||||
if delays is not None:
|
||||
await self.custom_delay(seconds=delays[1])
|
||||
|
||||
await self.mix(
|
||||
targets=current_targets,
|
||||
mix_time=mix_times,
|
||||
mix_vol=mix_vol,
|
||||
offsets=offsets if offsets else None,
|
||||
height_to_bottom=mix_liquid_height if mix_liquid_height else None,
|
||||
mix_rate=mix_rate if mix_rate else None,
|
||||
)
|
||||
if delays is not None:
|
||||
await self.custom_delay(seconds=delays[1])
|
||||
await self.touch_tip(current_targets)
|
||||
await self.discard_tips()
|
||||
|
||||
elif len(use_channels) == 8:
|
||||
# 对于8个的情况,需要判断此时任务是不是能被8通道移液站来成功处理
|
||||
if len(targets) % 8 != 0:
|
||||
raise ValueError(f"Length of `targets` {len(targets)} must be a multiple of 8 for 8-channel mode.")
|
||||
|
||||
# 8个8个来取任务序列
|
||||
|
||||
for i in range(0, len(targets), 8):
|
||||
# 取出8个任务
|
||||
tip = []
|
||||
for _ in range(len(use_channels)):
|
||||
tip.extend(next(self.current_tip))
|
||||
await self.pick_up_tips(tip)
|
||||
current_targets = targets[i:i + 8]
|
||||
current_reagent_sources = sources[i:i + 8]
|
||||
current_asp_vols = asp_vols[i:i + 8]
|
||||
current_dis_vols = dis_vols[i:i + 8]
|
||||
current_asp_flow_rates = asp_flow_rates[i:i + 8]
|
||||
current_asp_offset = offsets[i:i + 8] if offsets else [None] * 8
|
||||
current_dis_offset = offsets[-i*8-8:len(offsets)-i*8] if offsets else [None] * 8
|
||||
current_asp_liquid_height = liquid_height[i:i + 8] if liquid_height else [None] * 8
|
||||
current_dis_liquid_height = liquid_height[-i*8-8:len(liquid_height)-i*8] if liquid_height else [None] * 8
|
||||
current_asp_blow_out_air_volume = blow_out_air_volume[i:i + 8] if blow_out_air_volume else [None] * 8
|
||||
current_dis_blow_out_air_volume = blow_out_air_volume[-i*8-8:len(blow_out_air_volume)-i*8] if blow_out_air_volume else [None] * 8
|
||||
current_dis_flow_rates = dis_flow_rates[i:i + 8] if dis_flow_rates else [None] * 8
|
||||
|
||||
await self.aspirate(
|
||||
resources=current_reagent_sources,
|
||||
vols=current_asp_vols,
|
||||
use_channels=use_channels,
|
||||
flow_rates=current_asp_flow_rates,
|
||||
offsets=current_asp_offset,
|
||||
blow_out_air_volume=current_asp_blow_out_air_volume,
|
||||
liquid_height=current_asp_liquid_height,
|
||||
spread=spread,
|
||||
)
|
||||
|
||||
if delays is not None:
|
||||
await self.custom_delay(seconds=delays[0])
|
||||
await self.dispense(
|
||||
resources=current_targets,
|
||||
vols=current_dis_vols,
|
||||
use_channels=use_channels,
|
||||
flow_rates=current_dis_flow_rates,
|
||||
offsets=current_dis_offset,
|
||||
blow_out_air_volume=current_dis_blow_out_air_volume,
|
||||
liquid_height=current_dis_liquid_height,
|
||||
spread=spread,
|
||||
)
|
||||
if delays is not None:
|
||||
await self.custom_delay(seconds=delays[1])
|
||||
|
||||
await self.mix(
|
||||
targets=current_targets,
|
||||
mix_time=mix_times,
|
||||
mix_vol=mix_vol,
|
||||
offsets=offsets if offsets else None,
|
||||
height_to_bottom=mix_liquid_height if mix_liquid_height else None,
|
||||
mix_rate=mix_rate if mix_rate else None,
|
||||
)
|
||||
if delays is not None:
|
||||
await self.custom_delay(seconds=delays[1])
|
||||
await self.touch_tip(current_targets)
|
||||
await self.discard_tips()
|
||||
|
||||
except Exception as e:
|
||||
traceback.print_exc()
|
||||
raise RuntimeError(f"Liquid addition failed: {e}") from e
|
||||
|
||||
# except Exception as e:
|
||||
# traceback.print_exc()
|
||||
# raise RuntimeError(f"Liquid addition failed: {e}") from e
|
||||
|
||||
|
||||
# ---------------------------------------------------------------
|
||||
|
||||
@@ -433,10 +433,14 @@ class PRCXI9300Backend(LiquidHandlerBackend):
|
||||
|
||||
plate_indexes = []
|
||||
for op in ops:
|
||||
plate = op.resource.parent.parent
|
||||
plate = op.resource.parent
|
||||
deck = plate.parent
|
||||
plate_index = deck.children.index(plate)
|
||||
print(f"Plate index: {plate_index}, Plate name: {plate.name}")
|
||||
print(f"Number of children in deck: {len(deck.children)}")
|
||||
|
||||
plate_indexes.append(plate_index)
|
||||
|
||||
if len(set(plate_indexes)) != 1:
|
||||
raise ValueError("All pickups must be from the same plate. Found different plates: " + str(plate_indexes))
|
||||
|
||||
@@ -447,7 +451,8 @@ class PRCXI9300Backend(LiquidHandlerBackend):
|
||||
tip_columns.append(tipspot_index // 8)
|
||||
if len(set(tip_columns)) != 1:
|
||||
raise ValueError("All pickups must be from the same tip column. Found different columns: " + str(tip_columns))
|
||||
|
||||
# print('111'*99)
|
||||
# print(plate_indexes[0])
|
||||
PlateNo = plate_indexes[0] + 1
|
||||
hole_col = tip_columns[0] + 1
|
||||
|
||||
@@ -485,7 +490,7 @@ class PRCXI9300Backend(LiquidHandlerBackend):
|
||||
|
||||
plate_indexes = []
|
||||
for op in ops:
|
||||
plate = op.resource.parent.parent
|
||||
plate = op.resource.parent
|
||||
deck = plate.parent
|
||||
plate_index = deck.children.index(plate)
|
||||
plate_indexes.append(plate_index)
|
||||
@@ -532,11 +537,10 @@ class PRCXI9300Backend(LiquidHandlerBackend):
|
||||
|
||||
"""Mix liquid in the specified resources."""
|
||||
|
||||
|
||||
plate_indexes = []
|
||||
for op in targets:
|
||||
deck = op.parent.parent.parent
|
||||
plate = op.parent.parent
|
||||
deck = op.parent.parent
|
||||
plate = op.parent
|
||||
plate_index = deck.children.index(plate)
|
||||
plate_indexes.append(plate_index)
|
||||
|
||||
@@ -577,7 +581,7 @@ class PRCXI9300Backend(LiquidHandlerBackend):
|
||||
|
||||
plate_indexes = []
|
||||
for op in ops:
|
||||
plate = op.resource.parent.parent
|
||||
plate = op.resource.parent
|
||||
deck = plate.parent
|
||||
plate_index = deck.children.index(plate)
|
||||
plate_indexes.append(plate_index)
|
||||
@@ -617,7 +621,7 @@ class PRCXI9300Backend(LiquidHandlerBackend):
|
||||
|
||||
plate_indexes = []
|
||||
for op in ops:
|
||||
plate = op.resource.parent.parent
|
||||
plate = op.resource.parent
|
||||
deck = plate.parent
|
||||
plate_index = deck.children.index(plate)
|
||||
plate_indexes.append(plate_index)
|
||||
@@ -1244,7 +1248,7 @@ if __name__ == "__main__":
|
||||
handler = PRCXI9300Handler(deck=deck, host="10.181.102.13", port=9999,
|
||||
timeout=10.0, setup=False, debug=False,
|
||||
matrix_id="fd383e6d-2d0e-40b5-9c01-1b2870b1f1b1",
|
||||
channel_num=1)
|
||||
channel_num=1, axis="Right") # Initialize the handler with the deck and host settings
|
||||
|
||||
handler.set_tiprack([plate8]) # Set the tip rack for the handler
|
||||
asyncio.run(handler.setup()) # Initialize the handler and setup the connection
|
||||
@@ -1262,32 +1266,78 @@ if __name__ == "__main__":
|
||||
|
||||
print(plate11.get_well(0).tracker.get_used_volume())
|
||||
asyncio.run(handler.create_protocol(protocol_name="Test Protocol")) # Initialize the backend and setup the connection
|
||||
print(plate8.children[3])
|
||||
asyncio.run(handler.pick_up_tips([plate8.children[3]],[0]))
|
||||
asyncio.run(handler.aspirate([plate11.children[0]],[10], [0]))
|
||||
asyncio.run(handler.dispense([plate1.children[3]],[10],[0]))
|
||||
asyncio.run(handler.mix([plate1.children[3]], mix_time=3, mix_vol=5, height_to_bottom=0.5, offsets=Coordinate(0, 0, 0), mix_rate=100))
|
||||
asyncio.run(handler.discard_tips())
|
||||
|
||||
# asyncio.run(handler.pick_up_tips([plate8.children[8]],[0]))
|
||||
# print(plate8.children[8])
|
||||
# # asyncio.run(handler.run_protocol())
|
||||
# asyncio.run(handler.aspirate([plate11.children[0]],[10], [0]))
|
||||
# print(plate11.children[0])
|
||||
# # asyncio.run(handler.run_protocol())
|
||||
# asyncio.run(handler.dispense([plate1.children[0]],[10],[0]))
|
||||
# print(plate1.children[0])
|
||||
# # asyncio.run(handler.run_protocol())
|
||||
# asyncio.run(handler.mix([plate1.children[0]], mix_time=3, mix_vol=5, height_to_bottom=0.5, offsets=Coordinate(0, 0, 0), mix_rate=100))
|
||||
# print(plate1.children[0])
|
||||
# asyncio.run(handler.discard_tips())
|
||||
|
||||
# asyncio.run(handler.add_liquid(
|
||||
# asp_vols=[10]*2,
|
||||
# dis_vols=[10]*2,
|
||||
# reagent_sources=[plate11.children[0]],
|
||||
# targets=plate11.children[-2:],
|
||||
# use_channels=[0],
|
||||
# flow_rates=[None] * 4,
|
||||
# offsets=[Coordinate(0, 0, 0)] * 4,
|
||||
# liquid_height=[None] * 2,
|
||||
# blow_out_air_volume=[None] * 2,
|
||||
# delays=None,
|
||||
# mix_time=3,
|
||||
# mix_vol=5,
|
||||
# spread="wide",
|
||||
# ))
|
||||
|
||||
# asyncio.run(handler.transfer_liquid(
|
||||
# asp_vols=[10]*2,
|
||||
# dis_vols=[10]*2,
|
||||
# sources=plate11.children[:2],
|
||||
# targets=plate11.children[-2:],
|
||||
# use_channels=[0],
|
||||
# offsets=[Coordinate(0, 0, 0)] * 4,
|
||||
# liquid_height=[None] * 2,
|
||||
# blow_out_air_volume=[None] * 2,
|
||||
# delays=None,
|
||||
# mix_times=3,
|
||||
# mix_vol=5,
|
||||
# spread="wide",
|
||||
# tip_racks=[plate8]
|
||||
# ))
|
||||
|
||||
asyncio.run(handler.remove_liquid(
|
||||
vols=[10]*2,
|
||||
sources=plate11.children[:2],
|
||||
waste_liquid=plate11.children[43],
|
||||
use_channels=[0],
|
||||
offsets=[Coordinate(0, 0, 0)] * 4,
|
||||
liquid_height=[None] * 2,
|
||||
blow_out_air_volume=[None] * 2,
|
||||
delays=None,
|
||||
spread="wide"
|
||||
))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
asyncio.run(handler.run_protocol())
|
||||
|
||||
# asyncio.run(handler.discard_tips())
|
||||
# asyncio.run(handler.mix(well_containers.children[:8
|
||||
# ], mix_time=3, mix_vol=50, height_to_bottom=0.5, offsets=Coordinate(0, 0, 0), mix_rate=100))
|
||||
#print(json.dumps(handler._unilabos_backend.steps_todo_list, indent=2)) # Print matrix info
|
||||
# asyncio.run(handler.add_liquid(
|
||||
# asp_vols=[100]*16,
|
||||
# dis_vols=[100]*16,
|
||||
# reagent_sources=final_plate_2.children[-16:],
|
||||
# targets=final_plate_2.children[:16],
|
||||
# use_channels=[0, 1, 2, 3, 4, 5, 6, 7],
|
||||
# flow_rates=[None] * 32,
|
||||
# offsets=[Coordinate(0, 0, 0)] * 32,
|
||||
# liquid_height=[None] * 16,
|
||||
# blow_out_air_volume=[None] * 16,
|
||||
# delays=None,
|
||||
# mix_time=3,
|
||||
# mix_vol=50,
|
||||
# spread="wide",
|
||||
# ))
|
||||
|
||||
|
||||
# asyncio.run(handler.remove_liquid(
|
||||
# vols=[100]*16,
|
||||
|
||||
@@ -70,11 +70,32 @@ class VirtualMultiwayValve:
|
||||
command: 目标位置 (0-8) 或位置字符串
|
||||
0: transfer pump位置
|
||||
1-8: 其他设备位置
|
||||
'default': 默认位置(0号位)
|
||||
"""
|
||||
try:
|
||||
# 如果是字符串形式的位置,先转换为数字
|
||||
# 🔧 处理特殊字符串命令
|
||||
if isinstance(command, str):
|
||||
pos = int(command)
|
||||
command_lower = command.lower().strip()
|
||||
|
||||
# 处理特殊命令
|
||||
if command_lower in ['default', 'pump', 'transfer_pump', 'home']:
|
||||
pos = 0 # 默认位置为0号位(transfer pump)
|
||||
self.logger.info(f"🔧 特殊命令 '{command}' 映射到位置 {pos}")
|
||||
elif command_lower in ['open']:
|
||||
pos = 0 # open命令也映射到0号位
|
||||
self.logger.info(f"🔧 OPEN命令映射到位置 {pos}")
|
||||
elif command_lower in ['close', 'closed']:
|
||||
# 关闭命令保持当前位置
|
||||
pos = self._current_position
|
||||
self.logger.info(f"🔧 CLOSE命令保持当前位置 {pos}")
|
||||
else:
|
||||
# 尝试转换为数字
|
||||
try:
|
||||
pos = int(command)
|
||||
except ValueError:
|
||||
error_msg = f"无法识别的命令: '{command}'"
|
||||
self.logger.error(f"❌ {error_msg}")
|
||||
raise ValueError(error_msg)
|
||||
else:
|
||||
pos = int(command)
|
||||
|
||||
|
||||
@@ -88,6 +88,20 @@ class VirtualRotavap:
|
||||
) -> bool:
|
||||
"""Execute evaporate action - 简化版 🌪️"""
|
||||
|
||||
# 🔧 新增:确保time参数是数值类型
|
||||
if isinstance(time, str):
|
||||
try:
|
||||
time = float(time)
|
||||
except ValueError:
|
||||
self.logger.error(f"❌ 无法转换时间参数 '{time}' 为数值,使用默认值180.0秒")
|
||||
time = 180.0
|
||||
elif not isinstance(time, (int, float)):
|
||||
self.logger.error(f"❌ 时间参数类型无效: {type(time)},使用默认值180.0秒")
|
||||
time = 180.0
|
||||
|
||||
# 确保time是float类型
|
||||
time = float(time)
|
||||
|
||||
# 🔧 简化处理:如果vessel就是设备自己,直接操作
|
||||
if vessel == self.device_id:
|
||||
debug_print(f"🎯 在设备 {self.device_id} 上直接执行蒸发操作")
|
||||
@@ -158,7 +172,7 @@ class VirtualRotavap:
|
||||
})
|
||||
return False
|
||||
|
||||
# 开始蒸发
|
||||
# 开始蒸发 - 🔧 现在time已经确保是float类型
|
||||
self.logger.info(f"🚀 启动蒸发程序! 预计用时 {time/60:.1f}分钟 ⏱️")
|
||||
|
||||
self.data.update({
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
serial:
|
||||
category:
|
||||
- serial
|
||||
class:
|
||||
action_value_mappings:
|
||||
auto-handle_serial_request:
|
||||
@@ -76,6 +78,7 @@ serial:
|
||||
module: unilabos.ros.nodes.presets.serial_node:ROS2SerialNode
|
||||
status_types: {}
|
||||
type: ros2
|
||||
config_info: []
|
||||
description: Serial communication interface, used when sharing same serial port
|
||||
for multiple devices
|
||||
handles: []
|
||||
@@ -100,4 +103,4 @@ serial:
|
||||
properties: {}
|
||||
required: []
|
||||
type: object
|
||||
version: 0.0.1
|
||||
version: 1.0.0
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
camera:
|
||||
category:
|
||||
- camera
|
||||
class:
|
||||
action_value_mappings:
|
||||
auto-destroy_node:
|
||||
@@ -44,6 +46,7 @@ camera:
|
||||
module: unilabos.ros.nodes.presets.camera:VideoPublisher
|
||||
status_types: {}
|
||||
type: ros2
|
||||
config_info: []
|
||||
description: VideoPublisher摄像头设备节点,用于实时视频采集和流媒体发布。该设备通过OpenCV连接本地摄像头(如USB摄像头、内置摄像头等),定时采集视频帧并将其转换为ROS2的sensor_msgs/Image消息格式发布到视频话题。主要用于实验室自动化系统中的视觉监控、图像分析、实时观察等应用场景。支持可配置的摄像头索引、发布频率等参数。
|
||||
handles: []
|
||||
icon: ''
|
||||
@@ -67,4 +70,4 @@ camera:
|
||||
properties: {}
|
||||
required: []
|
||||
type: object
|
||||
version: 0.0.1
|
||||
version: 1.0.0
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
hplc.agilent:
|
||||
category:
|
||||
- characterization_optic
|
||||
class:
|
||||
action_value_mappings:
|
||||
auto-check_status:
|
||||
@@ -176,6 +178,7 @@ hplc.agilent:
|
||||
status_text: str
|
||||
success: bool
|
||||
type: python
|
||||
config_info: []
|
||||
description: 安捷伦高效液相色谱(HPLC)分析设备,用于复杂化合物的分离、检测和定量分析。该设备通过UI自动化技术控制安捷伦ChemStation软件,实现全自动的样品分析流程。具备序列启动、设备状态监控、数据文件提取、结果处理等功能。支持多样品批量处理和实时状态反馈,适用于药物分析、环境检测、食品安全、化学研究等需要高精度色谱分析的实验室应用。
|
||||
handles: []
|
||||
icon: ''
|
||||
@@ -215,8 +218,10 @@ hplc.agilent:
|
||||
- finish_status
|
||||
- data_file
|
||||
type: object
|
||||
version: 0.0.1
|
||||
version: 1.0.0
|
||||
raman_home_made:
|
||||
category:
|
||||
- characterization_optic
|
||||
class:
|
||||
action_value_mappings:
|
||||
auto-ccd_time:
|
||||
@@ -378,6 +383,7 @@ raman_home_made:
|
||||
module: unilabos.devices.raman_uv.home_made_raman:RamanObj
|
||||
status_types: {}
|
||||
type: python
|
||||
config_info: []
|
||||
description: 拉曼光谱分析设备,用于物质的分子结构和化学成分表征。该设备集成激光器和CCD检测器,通过串口通信控制激光功率和光谱采集。具备背景扣除、多次平均、自动数据处理等功能,支持高精度的拉曼光谱测量。适用于材料表征、化学分析、质量控制、研究开发等需要分子指纹识别和结构分析的实验应用。
|
||||
handles: []
|
||||
icon: ''
|
||||
@@ -402,4 +408,4 @@ raman_home_made:
|
||||
properties: {}
|
||||
required: []
|
||||
type: object
|
||||
version: 0.0.1
|
||||
version: 1.0.0
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
hotel.thermo_orbitor_rs2_hotel:
|
||||
category:
|
||||
- hotel
|
||||
class:
|
||||
action_value_mappings: {}
|
||||
module: unilabos.devices.resource_container.container:HotelContainer
|
||||
status_types:
|
||||
rotation: String
|
||||
type: python
|
||||
config_info: []
|
||||
description: Thermo Orbitor RS2 Hotel容器设备,用于实验室样品的存储和管理。该设备通过HotelContainer类实现容器的旋转控制和状态监控,主要用于存储实验样品、试剂瓶或其他实验器具,支持旋转功能以便于样品的自动化存取。适用于需要有序存储和快速访问大量样品的实验室自动化场景。
|
||||
handles: []
|
||||
icon: ''
|
||||
@@ -29,4 +32,4 @@ hotel.thermo_orbitor_rs2_hotel:
|
||||
model:
|
||||
mesh: thermo_orbitor_rs2_hotel
|
||||
type: device
|
||||
version: 0.0.1
|
||||
version: 1.0.0
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
laiyu_add_solid:
|
||||
category:
|
||||
- laiyu_add_solid
|
||||
class:
|
||||
action_value_mappings:
|
||||
add_powder_tube:
|
||||
@@ -355,6 +357,7 @@ laiyu_add_solid:
|
||||
status_types:
|
||||
status: str
|
||||
type: python
|
||||
config_info: []
|
||||
description: 来渝固体粉末自动分装设备,用于实验室化学试剂的精确称量和分装。该设备通过Modbus RTU协议与控制系统通信,集成了精密天平、三轴运动平台、粉筒管理系统等组件。支持多种粉末试剂的自动拿取、精确称量、定点分装和归位操作。具备高精度称量、位置控制和批量处理能力,适用于化学合成、药物研发、材料制备等需要精确固体试剂配制的实验室应用场景。
|
||||
handles: []
|
||||
icon: ''
|
||||
@@ -379,4 +382,4 @@ laiyu_add_solid:
|
||||
required:
|
||||
- status
|
||||
type: object
|
||||
version: 0.0.1
|
||||
version: 1.0.0
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,6 @@
|
||||
mock_chiller:
|
||||
category:
|
||||
- mock_devices
|
||||
class:
|
||||
action_value_mappings:
|
||||
emergency_stop:
|
||||
@@ -325,6 +327,7 @@ mock_chiller:
|
||||
target_temperature: float
|
||||
vessel: str
|
||||
type: python
|
||||
config_info: []
|
||||
description: Mock Chiller Device
|
||||
handles: []
|
||||
icon: ''
|
||||
@@ -364,8 +367,10 @@ mock_chiller:
|
||||
- purpose
|
||||
- status_info
|
||||
type: object
|
||||
version: 0.0.1
|
||||
version: 1.0.0
|
||||
mock_filter:
|
||||
category:
|
||||
- mock_devices
|
||||
class:
|
||||
action_value_mappings:
|
||||
auto-emergency_stop:
|
||||
@@ -743,6 +748,7 @@ mock_filter:
|
||||
temperature: float
|
||||
vessel: str
|
||||
type: python
|
||||
config_info: []
|
||||
description: Mock Filter Device
|
||||
handles: []
|
||||
icon: ''
|
||||
@@ -803,8 +809,10 @@ mock_filter:
|
||||
- target_volume
|
||||
- status_info
|
||||
type: object
|
||||
version: 0.0.1
|
||||
version: 1.0.0
|
||||
mock_heater:
|
||||
category:
|
||||
- mock_devices
|
||||
class:
|
||||
action_value_mappings:
|
||||
auto-set_heating_power:
|
||||
@@ -1361,6 +1369,7 @@ mock_heater:
|
||||
target_temperature: float
|
||||
vessel: str
|
||||
type: python
|
||||
config_info: []
|
||||
description: Mock Heater Device
|
||||
handles: []
|
||||
icon: ''
|
||||
@@ -1409,8 +1418,10 @@ mock_heater:
|
||||
- stir_speed
|
||||
- status_info
|
||||
type: object
|
||||
version: 0.0.1
|
||||
version: 1.0.0
|
||||
mock_pump:
|
||||
category:
|
||||
- mock_devices
|
||||
class:
|
||||
action_value_mappings:
|
||||
auto-emergency_stop:
|
||||
@@ -1887,6 +1898,7 @@ mock_pump:
|
||||
transfer_time: float
|
||||
transfer_volume: float
|
||||
type: python
|
||||
config_info: []
|
||||
description: Mock Pump Device
|
||||
handles: []
|
||||
icon: ''
|
||||
@@ -1968,8 +1980,10 @@ mock_pump:
|
||||
- time_remaining
|
||||
- status_info
|
||||
type: object
|
||||
version: 0.0.1
|
||||
version: 1.0.0
|
||||
mock_rotavap:
|
||||
category:
|
||||
- mock_devices
|
||||
class:
|
||||
action_value_mappings:
|
||||
auto-emergency_stop:
|
||||
@@ -2296,6 +2310,7 @@ mock_rotavap:
|
||||
temperature: float
|
||||
vacuum_level: float
|
||||
type: python
|
||||
config_info: []
|
||||
description: Mock Rotavap Device
|
||||
handles: []
|
||||
icon: ''
|
||||
@@ -2341,8 +2356,10 @@ mock_rotavap:
|
||||
- target_temperature
|
||||
- status_info
|
||||
type: object
|
||||
version: 0.0.1
|
||||
version: 1.0.0
|
||||
mock_separator:
|
||||
category:
|
||||
- mock_devices
|
||||
class:
|
||||
action_value_mappings:
|
||||
separate:
|
||||
@@ -3247,6 +3264,7 @@ mock_separator:
|
||||
valve_state: str
|
||||
waste_phase_to_vessel: str
|
||||
type: python
|
||||
config_info: []
|
||||
description: Simplified Mock Separator Device
|
||||
handles: []
|
||||
icon: ''
|
||||
@@ -3325,8 +3343,10 @@ mock_separator:
|
||||
- time_remaining
|
||||
- status_info
|
||||
type: object
|
||||
version: 0.0.1
|
||||
version: 1.0.0
|
||||
mock_solenoid_valve:
|
||||
category:
|
||||
- mock_devices
|
||||
class:
|
||||
action_value_mappings:
|
||||
auto-is_closed:
|
||||
@@ -3481,6 +3501,7 @@ mock_solenoid_valve:
|
||||
status: str
|
||||
valve_status: str
|
||||
type: python
|
||||
config_info: []
|
||||
description: Mock Solenoid Valve Device
|
||||
handles: []
|
||||
icon: ''
|
||||
@@ -3502,8 +3523,10 @@ mock_solenoid_valve:
|
||||
- status
|
||||
- valve_status
|
||||
type: object
|
||||
version: 0.0.1
|
||||
version: 1.0.0
|
||||
mock_stirrer:
|
||||
category:
|
||||
- mock_devices
|
||||
class:
|
||||
action_value_mappings:
|
||||
auto-emergency_stop:
|
||||
@@ -3749,6 +3772,7 @@ mock_stirrer:
|
||||
target_temperature: float
|
||||
temperature: float
|
||||
type: python
|
||||
config_info: []
|
||||
description: Mock Stirrer Device
|
||||
handles: []
|
||||
icon: ''
|
||||
@@ -3797,8 +3821,10 @@ mock_stirrer:
|
||||
- max_temperature
|
||||
- status_info
|
||||
type: object
|
||||
version: 0.0.1
|
||||
version: 1.0.0
|
||||
mock_stirrer_new:
|
||||
category:
|
||||
- mock_devices
|
||||
class:
|
||||
action_value_mappings:
|
||||
start_stir:
|
||||
@@ -4279,6 +4305,7 @@ mock_stirrer_new:
|
||||
target_stir_speed: float
|
||||
vessel: str
|
||||
type: python
|
||||
config_info: []
|
||||
description: Mock Stirrer Device (Copy Version)
|
||||
handles: []
|
||||
icon: ''
|
||||
@@ -4327,8 +4354,10 @@ mock_stirrer_new:
|
||||
- progress
|
||||
- status_info
|
||||
type: object
|
||||
version: 0.0.1
|
||||
version: 1.0.0
|
||||
mock_vacuum:
|
||||
category:
|
||||
- mock_devices
|
||||
class:
|
||||
action_value_mappings:
|
||||
auto-emergency_stop:
|
||||
@@ -4610,6 +4639,7 @@ mock_vacuum:
|
||||
target_vacuum: float
|
||||
vacuum_level: float
|
||||
type: python
|
||||
config_info: []
|
||||
description: Mock Vacuum Pump Device
|
||||
handles: []
|
||||
icon: ''
|
||||
@@ -4652,4 +4682,4 @@ mock_vacuum:
|
||||
- max_pump_speed
|
||||
- status_info
|
||||
type: object
|
||||
version: 0.0.1
|
||||
version: 1.0.0
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
moveit.arm_slider:
|
||||
category:
|
||||
- moveit_config
|
||||
class:
|
||||
action_value_mappings:
|
||||
auto-check_tf_update_actions:
|
||||
@@ -321,6 +323,7 @@ moveit.arm_slider:
|
||||
module: unilabos.devices.ros_dev.moveit_interface:MoveitInterface
|
||||
status_types: {}
|
||||
type: python
|
||||
config_info: []
|
||||
description: 机械臂与滑块运动系统,基于MoveIt2运动规划框架的多自由度机械臂控制设备。该系统集成机械臂和线性滑块,通过ROS2和MoveIt2实现精确的轨迹规划和协调运动控制。支持笛卡尔空间和关节空间的运动规划、碰撞检测、逆运动学求解等功能。适用于复杂的pick-and-place操作、精密装配、多工位协作等需要高精度多轴协调运动的实验室自动化应用。
|
||||
handles: []
|
||||
icon: ''
|
||||
@@ -346,8 +349,10 @@ moveit.arm_slider:
|
||||
model:
|
||||
mesh: arm_slider
|
||||
type: device
|
||||
version: 0.0.1
|
||||
version: 1.0.0
|
||||
moveit.toyo_xyz:
|
||||
category:
|
||||
- moveit_config
|
||||
class:
|
||||
action_value_mappings:
|
||||
auto-check_tf_update_actions:
|
||||
@@ -670,6 +675,7 @@ moveit.toyo_xyz:
|
||||
module: unilabos.devices.ros_dev.moveit_interface:MoveitInterface
|
||||
status_types: {}
|
||||
type: python
|
||||
config_info: []
|
||||
description: 东洋XYZ三轴运动平台,基于MoveIt2运动规划框架的精密定位设备。该设备通过ROS2和MoveIt2实现三维空间的精确运动控制,支持复杂轨迹规划、多点定位、速度控制等功能。具备高精度定位、平稳运动、实时轨迹监控等特性。适用于精密加工、样品定位、检测扫描、自动化装配等需要高精度三维运动控制的实验室和工业应用场景。
|
||||
handles: []
|
||||
icon: ''
|
||||
@@ -695,4 +701,4 @@ moveit.toyo_xyz:
|
||||
model:
|
||||
mesh: toyo_xyz
|
||||
type: device
|
||||
version: 0.0.1
|
||||
version: 1.0.0
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
rotavap.one:
|
||||
category:
|
||||
- organic_miscellaneous
|
||||
class:
|
||||
action_value_mappings:
|
||||
auto-cmd_write:
|
||||
@@ -140,6 +142,7 @@ rotavap.one:
|
||||
module: unilabos.devices.rotavap.rotavap_one:RotavapOne
|
||||
status_types: {}
|
||||
type: python
|
||||
config_info: []
|
||||
description: 旋转蒸发仪设备,用于有机化学实验中的溶剂回收和浓缩操作。该设备通过串口通信控制,集成旋转和真空泵功能,支持定时控制和自动化操作。具备旋转速度调节、真空度控制、温度管理等功能,实现高效的溶剂蒸发和回收。适用于有机合成、天然产物提取、药物制备等需要溶剂去除和浓缩的实验室应用场景。
|
||||
handles: []
|
||||
icon: ''
|
||||
@@ -158,8 +161,10 @@ rotavap.one:
|
||||
properties: {}
|
||||
required: []
|
||||
type: object
|
||||
version: 0.0.1
|
||||
version: 1.0.0
|
||||
separator.homemade:
|
||||
category:
|
||||
- organic_miscellaneous
|
||||
class:
|
||||
action_value_mappings:
|
||||
auto-read_sensor_loop:
|
||||
@@ -446,6 +451,7 @@ separator.homemade:
|
||||
module: unilabos.devices.separator.homemade_grbl_conductivity:SeparatorController
|
||||
status_types: {}
|
||||
type: python
|
||||
config_info: []
|
||||
description: 液-液分离器设备,基于自制Grbl控制器的自动化分离系统。该设备集成搅拌、沉降、阀门控制和电导率传感器,通过串口通信实现精确的分离操作控制。支持自动搅拌、分层沉降、基于传感器反馈的智能分液等功能。适用于有机化学中的萃取分离、相分离、液-液提取等需要精确分离控制的实验应用。
|
||||
handles: []
|
||||
icon: ''
|
||||
@@ -470,4 +476,4 @@ separator.homemade:
|
||||
properties: {}
|
||||
required: []
|
||||
type: object
|
||||
version: 0.0.1
|
||||
version: 1.0.0
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
solenoid_valve:
|
||||
category:
|
||||
- pump_and_valve
|
||||
class:
|
||||
action_value_mappings:
|
||||
auto-close:
|
||||
@@ -170,6 +172,7 @@ solenoid_valve:
|
||||
status: str
|
||||
valve_position: str
|
||||
type: python
|
||||
config_info: []
|
||||
description: 电磁阀控制设备,用于精确的流体路径控制和开关操作。该设备通过串口通信控制电磁阀的开关状态,支持远程操作和状态监测。具备快速响应、可靠密封、状态反馈等特性,广泛应用于流体输送、样品进样、路径切换等需要精确流体控制的实验室自动化应用。
|
||||
handles: []
|
||||
icon: ''
|
||||
@@ -191,8 +194,10 @@ solenoid_valve:
|
||||
- status
|
||||
- valve_position
|
||||
type: object
|
||||
version: 0.0.1
|
||||
version: 1.0.0
|
||||
solenoid_valve.mock:
|
||||
category:
|
||||
- pump_and_valve
|
||||
class:
|
||||
action_value_mappings:
|
||||
auto-is_closed:
|
||||
@@ -328,6 +333,7 @@ solenoid_valve.mock:
|
||||
status: str
|
||||
valve_position: str
|
||||
type: python
|
||||
config_info: []
|
||||
description: 模拟电磁阀设备,用于系统测试和开发调试。该设备模拟真实电磁阀的开关操作和状态变化,提供与实际设备相同的控制接口和反馈机制。支持流体路径的虚拟控制,便于在没有实际硬件的情况下进行流体系统的集成测试和算法验证。适用于系统开发、流程调试和培训演示等场景。
|
||||
handles:
|
||||
- data_type: fluid
|
||||
@@ -359,8 +365,10 @@ solenoid_valve.mock:
|
||||
- status
|
||||
- valve_position
|
||||
type: object
|
||||
version: 0.0.1
|
||||
version: 1.0.0
|
||||
syringe_pump_with_valve.runze:
|
||||
category:
|
||||
- pump_and_valve
|
||||
class:
|
||||
action_value_mappings:
|
||||
auto-close:
|
||||
@@ -754,6 +762,7 @@ syringe_pump_with_valve.runze:
|
||||
velocity_grade: String
|
||||
velocity_init: String
|
||||
type: python
|
||||
config_info: []
|
||||
description: 润泽精密注射泵设备,集成阀门控制的高精度流体输送系统。该设备通过串口通信控制,支持多种运行模式和精确的体积控制。具备可变速度控制、精密定位、阀门切换、实时状态监控等功能。适用于微量液体输送、精密进样、流速控制、化学反应进料等需要高精度流体操作的实验室自动化应用。
|
||||
handles: []
|
||||
icon: ''
|
||||
@@ -804,4 +813,4 @@ syringe_pump_with_valve.runze:
|
||||
- position
|
||||
- plunger_position
|
||||
type: object
|
||||
version: 0.0.1
|
||||
version: 1.0.0
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
agv.SEER:
|
||||
category:
|
||||
- robot_agv
|
||||
class:
|
||||
action_value_mappings:
|
||||
auto-send:
|
||||
@@ -82,6 +84,7 @@ agv.SEER:
|
||||
pose: list
|
||||
status: str
|
||||
type: python
|
||||
config_info: []
|
||||
description: SEER AGV自动导引车设备,用于实验室内物料和设备的自主移动运输。该AGV通过TCP socket与导航系统通信,具备精确的定位和路径规划能力。支持实时位置监控、状态查询和导航任务执行,可在预设的实验室环境中自主移动至指定位置。适用于样品运输、设备转移、多工位协作等实验室自动化物流场景。
|
||||
handles: []
|
||||
icon: ''
|
||||
@@ -103,4 +106,4 @@ agv.SEER:
|
||||
- pose
|
||||
- status
|
||||
type: object
|
||||
version: 0.0.1
|
||||
version: 1.0.0
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
robotic_arm.UR:
|
||||
category:
|
||||
- robot_arm
|
||||
class:
|
||||
action_value_mappings:
|
||||
auto-arm_init:
|
||||
@@ -140,6 +142,7 @@ robotic_arm.UR:
|
||||
gripper_pose: float
|
||||
gripper_status: str
|
||||
type: python
|
||||
config_info: []
|
||||
description: Universal Robots机械臂设备,用于实验室精密操作和自动化作业。该设备集成了UR机械臂本体、Robotiq夹爪和RTDE通信接口,支持六自由度精确运动控制和力觉反馈。具备实时位置监控、状态反馈、轨迹规划等功能,可执行复杂的多点位运动任务。适用于样品抓取、精密装配、实验器具操作等需要高精度和高重复性的实验室自动化场景。
|
||||
handles: []
|
||||
icon: ''
|
||||
@@ -170,4 +173,4 @@ robotic_arm.UR:
|
||||
- arm_status
|
||||
- gripper_status
|
||||
type: object
|
||||
version: 0.0.1
|
||||
version: 1.0.0
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
gripper.misumi_rz:
|
||||
category:
|
||||
- robot_gripper
|
||||
class:
|
||||
action_value_mappings:
|
||||
auto-data_loop:
|
||||
@@ -417,6 +419,7 @@ gripper.misumi_rz:
|
||||
status_types:
|
||||
status: str
|
||||
type: python
|
||||
config_info: []
|
||||
description: Misumi RZ系列电子夹爪设备,集成旋转和抓取双重功能的精密夹爪系统。该设备通过Modbus RTU协议与控制系统通信,支持位置、速度、力矩的精确控制。具备高精度的位置反馈、实时状态监控和故障检测功能。适用于需要精密抓取和旋转操作的实验室自动化场景,如样品管理、精密装配、器件操作等应用。
|
||||
handles: []
|
||||
icon: ''
|
||||
@@ -444,8 +447,10 @@ gripper.misumi_rz:
|
||||
required:
|
||||
- status
|
||||
type: object
|
||||
version: 0.0.1
|
||||
version: 1.0.0
|
||||
gripper.mock:
|
||||
category:
|
||||
- robot_gripper
|
||||
class:
|
||||
action_value_mappings:
|
||||
auto-edit_id:
|
||||
@@ -563,6 +568,7 @@ gripper.mock:
|
||||
torque: float
|
||||
velocity: float
|
||||
type: python
|
||||
config_info: []
|
||||
description: 模拟夹爪设备,用于系统测试和开发调试。该设备模拟真实夹爪的位置、速度、力矩等物理特性,支持虚拟的抓取和移动操作。提供与真实夹爪相同的接口和状态反馈,便于在没有实际硬件的情况下进行系统集成测试和算法验证。适用于软件开发、系统调试和培训演示等场景。
|
||||
handles: []
|
||||
icon: ''
|
||||
@@ -587,4 +593,4 @@ gripper.mock:
|
||||
- torque
|
||||
- status
|
||||
type: object
|
||||
version: 0.0.1
|
||||
version: 1.0.0
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
linear_motion.grbl:
|
||||
category:
|
||||
- robot_linear_motion
|
||||
class:
|
||||
action_value_mappings:
|
||||
auto-initialize:
|
||||
@@ -430,6 +432,7 @@ linear_motion.grbl:
|
||||
spindle_speed: float
|
||||
status: str
|
||||
type: python
|
||||
config_info: []
|
||||
description: Grbl数控机床(CNC)设备,用于实验室精密加工和三轴定位操作。该设备基于Grbl固件,通过串口通信控制步进电机实现X、Y、Z三轴的精确运动。支持绝对定位、轨迹规划、主轴控制和实时状态监控。具备安全限位保护和运动平滑控制功能。适用于精密钻孔、铣削、雕刻、样品制备等需要高精度定位和加工的实验室应用场景。
|
||||
handles: []
|
||||
icon: ''
|
||||
@@ -466,8 +469,10 @@ linear_motion.grbl:
|
||||
- position
|
||||
- spindle_speed
|
||||
type: object
|
||||
version: 0.0.1
|
||||
version: 1.0.0
|
||||
motor.iCL42:
|
||||
category:
|
||||
- robot_linear_motion
|
||||
class:
|
||||
action_value_mappings:
|
||||
auto-execute_run_motor:
|
||||
@@ -604,6 +609,7 @@ motor.iCL42:
|
||||
motor_position: int
|
||||
success: bool
|
||||
type: python
|
||||
config_info: []
|
||||
description: iCL42步进电机驱动器,用于实验室设备的精密线性运动控制。该设备通过串口通信控制iCL42型步进电机驱动器,支持多种运动模式和精确的位置、速度控制。具备位置反馈、运行状态监控和故障检测功能。适用于自动进样器、样品传送、精密定位平台等需要准确线性运动控制的实验室自动化设备。
|
||||
handles: []
|
||||
icon: ''
|
||||
@@ -631,4 +637,4 @@ motor.iCL42:
|
||||
- is_executing_run
|
||||
- success
|
||||
type: object
|
||||
version: 0.0.1
|
||||
version: 1.0.0
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
lh_joint_publisher:
|
||||
category:
|
||||
- sim_nodes
|
||||
class:
|
||||
action_value_mappings:
|
||||
auto-check_tf_update_actions:
|
||||
@@ -285,6 +287,7 @@ lh_joint_publisher:
|
||||
module: unilabos.devices.ros_dev.liquid_handler_joint_publisher:LiquidHandlerJointPublisher
|
||||
status_types: {}
|
||||
type: ros2
|
||||
config_info: []
|
||||
description: 液体处理器关节发布器,用于ROS2仿真系统中的液体处理设备运动控制。该节点通过发布关节状态驱动仿真模型中的机械臂运动,支持三维坐标到关节空间的逆运动学转换、多关节协调控制、资源跟踪和TF变换。具备精确的位置控制、速度调节、pick-and-place操作等功能。适用于液体处理系统的虚拟仿真、运动规划验证、系统集成测试等应用场景。
|
||||
handles: []
|
||||
icon: ''
|
||||
@@ -309,4 +312,4 @@ lh_joint_publisher:
|
||||
properties: {}
|
||||
required: []
|
||||
type: object
|
||||
version: 0.0.1
|
||||
version: 1.0.0
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
chiller:
|
||||
category:
|
||||
- temperature
|
||||
class:
|
||||
action_value_mappings:
|
||||
auto-build_modbus_frame:
|
||||
@@ -156,6 +158,7 @@ chiller:
|
||||
module: unilabos.devices.temperature.chiller:Chiller
|
||||
status_types: {}
|
||||
type: python
|
||||
config_info: []
|
||||
description: 实验室制冷设备,用于精确的温度控制和冷却操作。该设备通过Modbus RTU协议与控制系统通信,支持精确的温度设定和监控。具备快速降温、恒温控制和温度保持功能,广泛应用于需要低温环境的化学反应、样品保存、结晶操作等实验场景。提供稳定可靠的冷却性能,确保实验过程的温度精度。
|
||||
handles: []
|
||||
icon: ''
|
||||
@@ -174,8 +177,10 @@ chiller:
|
||||
properties: {}
|
||||
required: []
|
||||
type: object
|
||||
version: 0.0.1
|
||||
version: 1.0.0
|
||||
heaterstirrer.dalong:
|
||||
category:
|
||||
- temperature
|
||||
class:
|
||||
action_value_mappings:
|
||||
auto-close:
|
||||
@@ -523,6 +528,7 @@ heaterstirrer.dalong:
|
||||
temp_target: float
|
||||
temp_warning: float
|
||||
type: python
|
||||
config_info: []
|
||||
description: 大龙加热搅拌器,集成加热和搅拌双重功能的实验室设备。该设备通过串口通信控制,支持精确的温度调节、搅拌速度控制和安全保护功能。具备实时温度监测、目标温度设定、安全温度报警等特性。适用于化学合成、样品制备、反应控制等需要同时进行加热和搅拌的实验操作,提供稳定均匀的反应环境。
|
||||
handles: []
|
||||
icon: ''
|
||||
@@ -559,8 +565,10 @@ heaterstirrer.dalong:
|
||||
- temp_warning
|
||||
- temp_target
|
||||
type: object
|
||||
version: 0.0.1
|
||||
version: 1.0.0
|
||||
tempsensor:
|
||||
category:
|
||||
- temperature
|
||||
class:
|
||||
action_value_mappings:
|
||||
auto-build_modbus_request:
|
||||
@@ -719,6 +727,7 @@ tempsensor:
|
||||
status_types:
|
||||
value: float
|
||||
type: python
|
||||
config_info: []
|
||||
description: 高精度温度传感器设备,用于实验室环境和设备的温度监测。该传感器通过Modbus RTU协议与控制系统通信,提供实时准确的温度数据。具备高精度测量、报警温度设定、数据稳定性好等特点。适用于反应器监控、环境温度监测、设备保护等需要精确温度测量的实验场景,为实验安全和数据可靠性提供保障。
|
||||
handles: []
|
||||
icon: ''
|
||||
@@ -746,4 +755,4 @@ tempsensor:
|
||||
required:
|
||||
- value
|
||||
type: object
|
||||
version: 0.0.1
|
||||
version: 1.0.0
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
gas_source.mock:
|
||||
category:
|
||||
- vacuum_and_purge
|
||||
class:
|
||||
action_value_mappings:
|
||||
auto-is_closed:
|
||||
@@ -149,6 +151,7 @@ gas_source.mock:
|
||||
status_types:
|
||||
status: str
|
||||
type: python
|
||||
config_info: []
|
||||
description: 模拟气体源设备,用于系统测试和开发调试。该设备模拟真实气体源的开关控制和状态监测功能,支持气体供应的启停操作。提供与真实气体源相同的接口和状态反馈,便于在没有实际硬件的情况下进行系统集成测试和算法验证。适用于气路系统调试、软件开发和实验流程验证等场景。
|
||||
handles:
|
||||
- data_key: fluid_out
|
||||
@@ -173,8 +176,10 @@ gas_source.mock:
|
||||
required:
|
||||
- status
|
||||
type: object
|
||||
version: 0.0.1
|
||||
version: 1.0.0
|
||||
vacuum_pump.mock:
|
||||
category:
|
||||
- vacuum_and_purge
|
||||
class:
|
||||
action_value_mappings:
|
||||
auto-is_closed:
|
||||
@@ -325,6 +330,7 @@ vacuum_pump.mock:
|
||||
status_types:
|
||||
status: str
|
||||
type: python
|
||||
config_info: []
|
||||
description: 模拟真空泵设备,用于系统测试和开发调试。该设备模拟真实真空泵的抽气功能和状态控制,支持真空系统的启停操作和状态监测。提供与真实真空泵相同的接口和控制逻辑,便于在没有实际硬件的情况下进行真空系统的集成测试。适用于真空工艺调试、软件开发和实验流程验证等场景。
|
||||
handles:
|
||||
- data_key: fluid_in
|
||||
@@ -349,4 +355,4 @@ vacuum_pump.mock:
|
||||
required:
|
||||
- status
|
||||
type: object
|
||||
version: 0.0.1
|
||||
version: 1.0.0
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
virtual_centrifuge:
|
||||
category:
|
||||
- virtual_device
|
||||
class:
|
||||
action_value_mappings:
|
||||
auto-cleanup:
|
||||
@@ -222,6 +224,7 @@ virtual_centrifuge:
|
||||
target_temp: float
|
||||
time_remaining: float
|
||||
type: python
|
||||
config_info: []
|
||||
description: Virtual Centrifuge for CentrifugeProtocol Testing
|
||||
handles:
|
||||
- data_key: vessel
|
||||
@@ -282,8 +285,10 @@ virtual_centrifuge:
|
||||
- progress
|
||||
- message
|
||||
type: object
|
||||
version: 0.0.1
|
||||
version: 1.0.0
|
||||
virtual_column:
|
||||
category:
|
||||
- virtual_device
|
||||
class:
|
||||
action_value_mappings:
|
||||
auto-cleanup:
|
||||
@@ -609,6 +614,7 @@ virtual_column:
|
||||
progress: float
|
||||
status: str
|
||||
type: python
|
||||
config_info: []
|
||||
description: Virtual Column Chromatography Device for RunColumn Protocol Testing
|
||||
handles:
|
||||
- data_key: from_vessel
|
||||
@@ -674,8 +680,10 @@ virtual_column:
|
||||
- current_phase
|
||||
- final_volume
|
||||
type: object
|
||||
version: 0.0.1
|
||||
version: 1.0.0
|
||||
virtual_filter:
|
||||
category:
|
||||
- virtual_device
|
||||
class:
|
||||
action_value_mappings:
|
||||
auto-cleanup:
|
||||
@@ -1002,6 +1010,7 @@ virtual_filter:
|
||||
progress: float
|
||||
status: str
|
||||
type: python
|
||||
config_info: []
|
||||
description: Virtual Filter for FilterProtocol Testing
|
||||
handles:
|
||||
- data_key: vessel_in
|
||||
@@ -1069,8 +1078,10 @@ virtual_filter:
|
||||
- max_stir_speed
|
||||
- max_volume
|
||||
type: object
|
||||
version: 0.0.1
|
||||
version: 1.0.0
|
||||
virtual_gas_source:
|
||||
category:
|
||||
- virtual_device
|
||||
class:
|
||||
action_value_mappings:
|
||||
auto-cleanup:
|
||||
@@ -1261,6 +1272,7 @@ virtual_gas_source:
|
||||
status_types:
|
||||
status: str
|
||||
type: python
|
||||
config_info: []
|
||||
description: Virtual gas source
|
||||
handles:
|
||||
- data_key: fluid_out
|
||||
@@ -1288,8 +1300,10 @@ virtual_gas_source:
|
||||
required:
|
||||
- status
|
||||
type: object
|
||||
version: 0.0.1
|
||||
version: 1.0.0
|
||||
virtual_heatchill:
|
||||
category:
|
||||
- virtual_device
|
||||
class:
|
||||
action_value_mappings:
|
||||
auto-cleanup:
|
||||
@@ -1803,6 +1817,7 @@ virtual_heatchill:
|
||||
status: str
|
||||
stir_speed: float
|
||||
type: python
|
||||
config_info: []
|
||||
description: Virtual HeatChill for HeatChillProtocol Testing
|
||||
handles:
|
||||
- data_key: vessel
|
||||
@@ -1854,8 +1869,10 @@ virtual_heatchill:
|
||||
- min_temp
|
||||
- max_stir_speed
|
||||
type: object
|
||||
version: 0.0.1
|
||||
version: 1.0.0
|
||||
virtual_multiway_valve:
|
||||
category:
|
||||
- virtual_device
|
||||
class:
|
||||
action_value_mappings:
|
||||
auto-close:
|
||||
@@ -2155,6 +2172,7 @@ virtual_multiway_valve:
|
||||
valve_position: int
|
||||
valve_state: str
|
||||
type: python
|
||||
config_info: []
|
||||
description: Virtual 8-Way Valve for flow direction control
|
||||
handles:
|
||||
- data_key: fluid_in
|
||||
@@ -2275,8 +2293,10 @@ virtual_multiway_valve:
|
||||
- flow_path
|
||||
- info
|
||||
type: object
|
||||
version: 0.0.1
|
||||
version: 1.0.0
|
||||
virtual_pump:
|
||||
category:
|
||||
- virtual_device
|
||||
class:
|
||||
action_value_mappings:
|
||||
auto-clean_vessel:
|
||||
@@ -2741,6 +2761,7 @@ virtual_pump:
|
||||
transferred_volume: float
|
||||
valve_position: int
|
||||
type: python
|
||||
config_info: []
|
||||
description: Virtual Pump for PumpTransferProtocol Testing
|
||||
handles:
|
||||
- data_key: fluid_in
|
||||
@@ -2794,8 +2815,10 @@ virtual_pump:
|
||||
- transferred_volume
|
||||
- current_status
|
||||
type: object
|
||||
version: 0.0.1
|
||||
version: 1.0.0
|
||||
virtual_rotavap:
|
||||
category:
|
||||
- virtual_device
|
||||
class:
|
||||
action_value_mappings:
|
||||
auto-cleanup:
|
||||
@@ -3048,6 +3071,7 @@ virtual_rotavap:
|
||||
status: str
|
||||
vacuum_pressure: float
|
||||
type: python
|
||||
config_info: []
|
||||
description: Virtual Rotary Evaporator for EvaporateProtocol Testing
|
||||
handles:
|
||||
- data_key: vessel_in
|
||||
@@ -3121,8 +3145,10 @@ virtual_rotavap:
|
||||
- max_rotation_speed
|
||||
- remaining_time
|
||||
type: object
|
||||
version: 0.0.1
|
||||
version: 1.0.0
|
||||
virtual_separator:
|
||||
category:
|
||||
- virtual_device
|
||||
class:
|
||||
action_value_mappings:
|
||||
auto-cleanup:
|
||||
@@ -3938,6 +3964,7 @@ virtual_separator:
|
||||
stir_speed: float
|
||||
volume: float
|
||||
type: python
|
||||
config_info: []
|
||||
description: Virtual Separator for SeparateProtocol Testing
|
||||
handles:
|
||||
- data_key: from_vessel
|
||||
@@ -4013,8 +4040,10 @@ virtual_separator:
|
||||
- progress
|
||||
- message
|
||||
type: object
|
||||
version: 0.0.1
|
||||
version: 1.0.0
|
||||
virtual_solenoid_valve:
|
||||
category:
|
||||
- virtual_device
|
||||
class:
|
||||
action_value_mappings:
|
||||
auto-cleanup:
|
||||
@@ -4277,6 +4306,7 @@ virtual_solenoid_valve:
|
||||
valve_position: str
|
||||
valve_state: str
|
||||
type: python
|
||||
config_info: []
|
||||
description: Virtual Solenoid Valve for simple on/off flow control
|
||||
handles:
|
||||
- data_key: fluid_port_in
|
||||
@@ -4324,8 +4354,10 @@ virtual_solenoid_valve:
|
||||
- valve_position
|
||||
- state
|
||||
type: object
|
||||
version: 0.0.1
|
||||
version: 1.0.0
|
||||
virtual_solid_dispenser:
|
||||
category:
|
||||
- virtual_device
|
||||
class:
|
||||
action_value_mappings:
|
||||
add_solid:
|
||||
@@ -4548,7 +4580,6 @@ virtual_solid_dispenser:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties: {}
|
||||
required: []
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
@@ -4556,30 +4587,6 @@ virtual_solid_dispenser:
|
||||
title: cleanup参数
|
||||
type: object
|
||||
type: UniLabJsonCommandAsync
|
||||
auto-find_solid_reagent_bottle:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
reagent_name: null
|
||||
handles: []
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
reagent_name:
|
||||
type: string
|
||||
required:
|
||||
- reagent_name
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: find_solid_reagent_bottle参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-initialize:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
@@ -4592,7 +4599,6 @@ virtual_solid_dispenser:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties: {}
|
||||
required: []
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
@@ -4600,62 +4606,14 @@ virtual_solid_dispenser:
|
||||
title: initialize参数
|
||||
type: object
|
||||
type: UniLabJsonCommandAsync
|
||||
auto-parse_mass_string:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
mass_str: null
|
||||
handles: []
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
mass_str:
|
||||
type: string
|
||||
required:
|
||||
- mass_str
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: parse_mass_string参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-parse_mol_string:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
mol_str: null
|
||||
handles: []
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
mol_str:
|
||||
type: string
|
||||
required:
|
||||
- mol_str
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: parse_mol_string参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
module: unilabos.devices.virtual.virtual_solid_dispenser:VirtualSolidDispenser
|
||||
status_types:
|
||||
current_reagent: str
|
||||
device_info: dict
|
||||
dispensed_amount: float
|
||||
status: str
|
||||
total_operations: int
|
||||
type: python
|
||||
config_info: []
|
||||
description: Virtual Solid Dispenser for Add Protocol Testing - supports mass and
|
||||
molar additions
|
||||
handles:
|
||||
@@ -4683,14 +4641,18 @@ virtual_solid_dispenser:
|
||||
type: object
|
||||
device_id:
|
||||
type: string
|
||||
max_capacity:
|
||||
default: 100.0
|
||||
type: number
|
||||
precision:
|
||||
default: 0.001
|
||||
type: number
|
||||
required: []
|
||||
type: object
|
||||
data:
|
||||
properties:
|
||||
current_reagent:
|
||||
type: string
|
||||
device_info:
|
||||
type: object
|
||||
dispensed_amount:
|
||||
type: number
|
||||
status:
|
||||
@@ -4702,10 +4664,11 @@ virtual_solid_dispenser:
|
||||
- current_reagent
|
||||
- dispensed_amount
|
||||
- total_operations
|
||||
- device_info
|
||||
type: object
|
||||
version: 0.0.1
|
||||
version: 1.0.0
|
||||
virtual_stirrer:
|
||||
category:
|
||||
- virtual_device
|
||||
class:
|
||||
action_value_mappings:
|
||||
auto-cleanup:
|
||||
@@ -5219,6 +5182,7 @@ virtual_stirrer:
|
||||
remaining_time: float
|
||||
status: str
|
||||
type: python
|
||||
config_info: []
|
||||
description: Virtual Stirrer for StirProtocol Testing
|
||||
handles:
|
||||
- data_key: vessel
|
||||
@@ -5270,8 +5234,10 @@ virtual_stirrer:
|
||||
- min_speed
|
||||
- device_info
|
||||
type: object
|
||||
version: 0.0.1
|
||||
version: 1.0.0
|
||||
virtual_transfer_pump:
|
||||
category:
|
||||
- virtual_device
|
||||
class:
|
||||
action_value_mappings:
|
||||
auto-aspirate:
|
||||
@@ -5728,6 +5694,7 @@ virtual_transfer_pump:
|
||||
status: str
|
||||
transfer_rate: float
|
||||
type: python
|
||||
config_info: []
|
||||
description: Virtual Transfer Pump for TransferProtocol Testing (Syringe-style)
|
||||
handles:
|
||||
- data_key: fluid_port
|
||||
@@ -5773,8 +5740,10 @@ virtual_transfer_pump:
|
||||
- remaining_capacity
|
||||
- pump_info
|
||||
type: object
|
||||
version: 0.0.1
|
||||
version: 1.0.0
|
||||
virtual_vacuum_pump:
|
||||
category:
|
||||
- virtual_device
|
||||
class:
|
||||
action_value_mappings:
|
||||
auto-cleanup:
|
||||
@@ -5965,6 +5934,7 @@ virtual_vacuum_pump:
|
||||
status_types:
|
||||
status: str
|
||||
type: python
|
||||
config_info: []
|
||||
description: Virtual vacuum pump
|
||||
handles:
|
||||
- data_key: fluid_in
|
||||
@@ -5992,4 +5962,4 @@ virtual_vacuum_pump:
|
||||
required:
|
||||
- status
|
||||
type: object
|
||||
version: 0.0.1
|
||||
version: 1.0.0
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
workstation:
|
||||
category:
|
||||
- work_station
|
||||
class:
|
||||
action_value_mappings:
|
||||
AGVTransferProtocol:
|
||||
@@ -243,8 +245,13 @@ workstation:
|
||||
feedback: {}
|
||||
goal:
|
||||
amount: amount
|
||||
equiv: equiv
|
||||
event: event
|
||||
mass: mass
|
||||
mol: mol
|
||||
purpose: purpose
|
||||
rate_spec: rate_spec
|
||||
ratio: ratio
|
||||
reagent: reagent
|
||||
stir: stir
|
||||
stir_speed: stir_speed
|
||||
@@ -468,6 +475,11 @@ workstation:
|
||||
ph_value: ph_value
|
||||
reagent: reagent
|
||||
vessel: vessel
|
||||
volume: volume
|
||||
stir: stir
|
||||
stir_speed: stir_speed
|
||||
stir_time: stir_time
|
||||
settling_time: settling_time
|
||||
goal_default:
|
||||
ph_value: 0.0
|
||||
reagent: ''
|
||||
@@ -491,6 +503,11 @@ workstation:
|
||||
z: 0.0
|
||||
sample_id: ''
|
||||
type: ''
|
||||
volume: 0.0
|
||||
stir: false
|
||||
stir_speed: 300.0
|
||||
stir_time: 60.0
|
||||
settling_time: 30.0
|
||||
handles:
|
||||
input:
|
||||
- data_key: vessel
|
||||
@@ -509,6 +526,8 @@ workstation:
|
||||
data_type: resource
|
||||
handler_key: VesselOut
|
||||
label: Vessel
|
||||
placeholder_keys:
|
||||
vessel: unilabos_resources
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
@@ -603,6 +622,21 @@ workstation:
|
||||
- data
|
||||
title: Resource
|
||||
type: object
|
||||
volume:
|
||||
type: number
|
||||
description: 'Volume of the solution to adjust pH'
|
||||
stir:
|
||||
type: boolean
|
||||
description: "是否启用搅拌"
|
||||
stir_speed:
|
||||
type: number
|
||||
description: "搅拌速度(RPM)"
|
||||
stir_time:
|
||||
type: number
|
||||
description: "搅拌时间(秒)"
|
||||
settling_time:
|
||||
type: number
|
||||
description: "pH平衡时间(秒)"
|
||||
required:
|
||||
- vessel
|
||||
- ph_value
|
||||
@@ -672,6 +706,8 @@ workstation:
|
||||
data_type: resource
|
||||
handler_key: VesselOut
|
||||
label: Vessel
|
||||
placeholder_keys:
|
||||
vessel: unilabos_resources
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
@@ -851,6 +887,8 @@ workstation:
|
||||
data_type: resource
|
||||
handler_key: VesselOut
|
||||
label: Vessel
|
||||
placeholder_keys:
|
||||
vessel: unilabos_resources
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
@@ -1058,6 +1096,8 @@ workstation:
|
||||
data_type: resource
|
||||
handler_key: VesselOut
|
||||
label: Vessel
|
||||
placeholder_keys:
|
||||
vessel: unilabos_resources
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
@@ -1189,6 +1229,10 @@ workstation:
|
||||
feedback: {}
|
||||
goal:
|
||||
amount: amount
|
||||
event: event
|
||||
mass: mass
|
||||
mol: mol
|
||||
reagent: reagent
|
||||
solvent: solvent
|
||||
stir_speed: stir_speed
|
||||
temp: temp
|
||||
@@ -1244,6 +1288,8 @@ workstation:
|
||||
data_type: resource
|
||||
handler_key: VesselOut
|
||||
label: Vessel
|
||||
placeholder_keys:
|
||||
vessel: unilabos_resources
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
@@ -1427,6 +1473,8 @@ workstation:
|
||||
data_type: resource
|
||||
handler_key: VesselOut
|
||||
label: Vessel
|
||||
placeholder_keys:
|
||||
vessel: unilabos_resources
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
@@ -1583,6 +1631,8 @@ workstation:
|
||||
data_type: resource
|
||||
handler_key: VesselOut
|
||||
label: Vessel
|
||||
placeholder_keys:
|
||||
vessel: unilabos_resources
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
@@ -1776,6 +1826,8 @@ workstation:
|
||||
data_type: resource
|
||||
handler_key: vessel_out
|
||||
label: Evaporation Vessel
|
||||
placeholder_keys:
|
||||
vessel: unilabos_nodes
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
@@ -2012,6 +2064,9 @@ workstation:
|
||||
data_type: resource
|
||||
handler_key: filtrate_out
|
||||
label: Filtrate Vessel
|
||||
placeholder_keys:
|
||||
filtrate_vessel: unilabos_resources
|
||||
vessel: unilabos_nodes
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
@@ -2193,7 +2248,7 @@ workstation:
|
||||
type: number
|
||||
required:
|
||||
- vessel
|
||||
- filtrate_vessel
|
||||
- #filtrate_vessel
|
||||
- stir
|
||||
- stir_speed
|
||||
- temp
|
||||
@@ -2323,6 +2378,9 @@ workstation:
|
||||
data_type: resource
|
||||
handler_key: ToVesselOut
|
||||
label: To Vessel
|
||||
placeholder_keys:
|
||||
from_vessel: unilabos_resources
|
||||
to_vessel: unilabos_resources
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
@@ -2654,6 +2712,8 @@ workstation:
|
||||
data_type: resource
|
||||
handler_key: VesselOut
|
||||
label: Vessel
|
||||
placeholder_keys:
|
||||
vessel: unilabos_resources
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
@@ -2833,6 +2893,8 @@ workstation:
|
||||
data_type: resource
|
||||
handler_key: VesselOut
|
||||
label: Vessel
|
||||
placeholder_keys:
|
||||
vessel: unilabos_resources
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
@@ -2984,6 +3046,8 @@ workstation:
|
||||
data_type: resource
|
||||
handler_key: VesselOut
|
||||
label: Vessel
|
||||
placeholder_keys:
|
||||
vessel: unilabos_resources
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
@@ -3133,6 +3197,8 @@ workstation:
|
||||
data_type: resource
|
||||
handler_key: VesselOut
|
||||
label: Vessel
|
||||
placeholder_keys:
|
||||
vessel: unilabos_resources
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
@@ -3352,6 +3418,9 @@ workstation:
|
||||
data_type: resource
|
||||
handler_key: ToVesselOut
|
||||
label: To Vessel
|
||||
placeholder_keys:
|
||||
from_vessel: unilabos_nodes
|
||||
to_vessel: unilabos_nodes
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
@@ -3665,6 +3734,8 @@ workstation:
|
||||
data_type: resource
|
||||
handler_key: VesselOut
|
||||
label: Vessel
|
||||
placeholder_keys:
|
||||
vessel: unilabos_resources
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
@@ -4017,6 +4088,10 @@ workstation:
|
||||
data_type: resource
|
||||
handler_key: ToVesselOut
|
||||
label: To Vessel
|
||||
placeholder_keys:
|
||||
column: unilabos_devices
|
||||
from_vessel: unilabos_resources
|
||||
to_vessel: unilabos_resources
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
@@ -4420,6 +4495,11 @@ workstation:
|
||||
data_type: resource
|
||||
handler_key: ToVesselOut
|
||||
label: To Vessel
|
||||
placeholder_keys:
|
||||
from_vessel: unilabos_resources
|
||||
to_vessel: unilabos_resources
|
||||
waste_phase_to_vessel: unilabos_resources
|
||||
waste_vessel: unilabos_resources
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
@@ -5051,6 +5131,8 @@ workstation:
|
||||
data_type: resource
|
||||
handler_key: VesselOut
|
||||
label: Vessel
|
||||
placeholder_keys:
|
||||
vessel: unilabos_resources
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
@@ -5223,6 +5305,8 @@ workstation:
|
||||
data_type: resource
|
||||
handler_key: VesselOut
|
||||
label: Vessel
|
||||
placeholder_keys:
|
||||
vessel: unilabos_resources
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
@@ -5389,6 +5473,8 @@ workstation:
|
||||
data_type: resource
|
||||
handler_key: VesselOut
|
||||
label: Vessel
|
||||
placeholder_keys:
|
||||
vessel: unilabos_resources
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
@@ -5554,6 +5640,9 @@ workstation:
|
||||
data_type: resource
|
||||
handler_key: ToVesselOut
|
||||
label: To Vessel
|
||||
placeholder_keys:
|
||||
from_vessel: unilabos_nodes
|
||||
to_vessel: unilabos_nodes
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
@@ -5720,6 +5809,9 @@ workstation:
|
||||
data_type: resource
|
||||
handler_key: filtrate_vessel_out
|
||||
label: Filtrate Vessel
|
||||
placeholder_keys:
|
||||
filtrate_vessel: unilabos_resources
|
||||
vessel: unilabos_resources
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
@@ -6033,6 +6125,7 @@ workstation:
|
||||
module: unilabos.ros.nodes.presets.protocol_node:ROS2ProtocolNode
|
||||
status_types: {}
|
||||
type: ros2
|
||||
config_info: []
|
||||
description: Workstation
|
||||
handles: []
|
||||
icon: ''
|
||||
@@ -6057,4 +6150,4 @@ workstation:
|
||||
properties: {}
|
||||
required: []
|
||||
type: object
|
||||
version: 0.0.1
|
||||
version: 1.0.0
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
zhida_hplc:
|
||||
category:
|
||||
- zhida_hplc
|
||||
class:
|
||||
action_value_mappings:
|
||||
abort:
|
||||
@@ -150,6 +152,7 @@ zhida_hplc:
|
||||
methods: dict
|
||||
status: dict
|
||||
type: python
|
||||
config_info: []
|
||||
description: 智达高效液相色谱(HPLC)分析设备,用于实验室样品的分离、检测和定量分析。该设备通过TCP socket与HPLC控制系统通信,支持远程控制和状态监控。具备自动进样、梯度洗脱、多检测器数据采集等功能,可执行复杂的色谱分析方法。适用于化学分析、药物检测、环境监测、生物样品分析等需要高精度分离分析的实验室应用场景。
|
||||
handles: []
|
||||
icon: ''
|
||||
@@ -177,4 +180,4 @@ zhida_hplc:
|
||||
- status
|
||||
- methods
|
||||
type: object
|
||||
version: 0.0.1
|
||||
version: 1.0.0
|
||||
|
||||
@@ -9,10 +9,11 @@ from typing import Any, Dict, List
|
||||
|
||||
import yaml
|
||||
|
||||
from unilabos.resources.graphio import resource_plr_to_ulab, tree_to_list
|
||||
from unilabos.ros.msgs.message_converter import msg_converter_manager, ros_action_to_json_schema, String
|
||||
from unilabos.utils import logger
|
||||
from unilabos.utils.decorator import singleton
|
||||
from unilabos.utils.import_manager import get_enhanced_class_info
|
||||
from unilabos.utils.import_manager import get_enhanced_class_info, get_class
|
||||
from unilabos.utils.type_check import NoAliasDumper
|
||||
|
||||
DEFAULT_PATHS = [Path(__file__).absolute().parent]
|
||||
@@ -65,7 +66,9 @@ class Registry:
|
||||
},
|
||||
"feedback": {},
|
||||
"result": {"success": "success"},
|
||||
"schema": ros_action_to_json_schema(self.ResourceCreateFromOuter, '用于创建或更新物料资源,每次传入多个物料信息。'),
|
||||
"schema": ros_action_to_json_schema(
|
||||
self.ResourceCreateFromOuter, "用于创建或更新物料资源,每次传入多个物料信息。"
|
||||
),
|
||||
"goal_default": yaml.safe_load(
|
||||
io.StringIO(get_yaml_from_goal_type(self.ResourceCreateFromOuter.Goal))
|
||||
),
|
||||
@@ -86,7 +89,9 @@ class Registry:
|
||||
},
|
||||
"feedback": {},
|
||||
"result": {"success": "success"},
|
||||
"schema": ros_action_to_json_schema(self.ResourceCreateFromOuterEasy, '用于创建或更新物料资源,每次传入一个物料信息。'),
|
||||
"schema": ros_action_to_json_schema(
|
||||
self.ResourceCreateFromOuterEasy, "用于创建或更新物料资源,每次传入一个物料信息。"
|
||||
),
|
||||
"goal_default": yaml.safe_load(
|
||||
io.StringIO(get_yaml_from_goal_type(self.ResourceCreateFromOuterEasy.Goal))
|
||||
),
|
||||
@@ -113,13 +118,15 @@ class Registry:
|
||||
"goal": {},
|
||||
"feedback": {},
|
||||
"result": {"latency_ms": "latency_ms", "time_diff_ms": "time_diff_ms"},
|
||||
"schema": ros_action_to_json_schema(self.EmptyIn, '用于测试延迟的动作,返回延迟时间和时间差。'),
|
||||
"schema": ros_action_to_json_schema(
|
||||
self.EmptyIn, "用于测试延迟的动作,返回延迟时间和时间差。"
|
||||
),
|
||||
"goal_default": {},
|
||||
"handles": {},
|
||||
},
|
||||
},
|
||||
},
|
||||
"version": "0.0.1",
|
||||
"version": "1.0.0",
|
||||
"icon": "icon_device.webp",
|
||||
"registry_type": "device",
|
||||
"handles": [],
|
||||
@@ -147,20 +154,47 @@ class Registry:
|
||||
logger.debug(f"[UniLab Registry] resources: {resource_path.exists()}, total: {len(files)}")
|
||||
current_resource_number = len(self.resource_type_registry) + 1
|
||||
for i, file in enumerate(files):
|
||||
data = yaml.safe_load(open(file, encoding="utf-8"))
|
||||
with open(file, encoding="utf-8", mode="r") as f:
|
||||
data = yaml.safe_load(io.StringIO(f.read()))
|
||||
complete_data = {}
|
||||
if data:
|
||||
# 为每个资源添加文件路径信息
|
||||
for resource_id, resource_info in data.items():
|
||||
resource_info["file_path"] = str(file.absolute()).replace("\\", "/")
|
||||
if "description" not in resource_info:
|
||||
resource_info["description"] = ""
|
||||
if "version" not in resource_info:
|
||||
resource_info["version"] = "1.0.0"
|
||||
if "category" not in resource_info:
|
||||
resource_info["category"] = [file.stem]
|
||||
elif file.stem not in resource_info["category"]:
|
||||
resource_info["category"].append(file.stem)
|
||||
if "config_info" not in resource_info:
|
||||
resource_info["config_info"] = []
|
||||
if "icon" not in resource_info:
|
||||
resource_info["icon"] = ""
|
||||
if "handles" not in resource_info:
|
||||
resource_info["handles"] = []
|
||||
if "init_param_schema" not in resource_info:
|
||||
resource_info["init_param_schema"] = {}
|
||||
if complete_registry:
|
||||
class_info = resource_info.get("class", {})
|
||||
if len(class_info) and "module" in class_info:
|
||||
if class_info.get("type") == "pylabrobot":
|
||||
res_class = get_class(class_info["module"])
|
||||
if callable(res_class) and not isinstance(
|
||||
res_class, type
|
||||
): # 有的是类,有的是函数,这里暂时只登记函数类的
|
||||
res_instance = res_class(res_class.__name__)
|
||||
res_ulr = tree_to_list([resource_plr_to_ulab(res_instance)])
|
||||
resource_info["config_info"] = res_ulr
|
||||
resource_info["registry_type"] = "resource"
|
||||
complete_data[resource_id] = copy.deepcopy(dict(sorted(resource_info.items()))) # 稍后dump到文件
|
||||
|
||||
complete_data = dict(sorted(complete_data.items()))
|
||||
complete_data = copy.deepcopy(complete_data)
|
||||
if complete_registry:
|
||||
with open(file, "w", encoding="utf-8") as f:
|
||||
yaml.dump(complete_data, f, allow_unicode=True, default_flow_style=False, Dumper=NoAliasDumper)
|
||||
|
||||
self.resource_type_registry.update(data)
|
||||
logger.debug(
|
||||
f"[UniLab Registry] Resource-{current_resource_number} File-{i+1}/{len(files)} "
|
||||
@@ -344,6 +378,7 @@ class Registry:
|
||||
}
|
||||
|
||||
def load_device_types(self, path: os.PathLike, complete_registry: bool):
|
||||
# return
|
||||
abs_path = Path(path).absolute()
|
||||
devices_path = abs_path / "devices"
|
||||
device_comms_path = abs_path / "device_comms"
|
||||
@@ -369,7 +404,13 @@ class Registry:
|
||||
for device_id, device_config in data.items():
|
||||
# 添加文件路径信息 - 使用规范化的完整文件路径
|
||||
if "version" not in device_config:
|
||||
device_config["version"] = "0.0.1"
|
||||
device_config["version"] = "1.0.0"
|
||||
if "category" not in device_config:
|
||||
device_config["category"] = [file.stem]
|
||||
elif file.stem not in device_config["category"]:
|
||||
device_config["category"].append(file.stem)
|
||||
if "config_info" not in device_config:
|
||||
device_config["config_info"] = []
|
||||
if "description" not in device_config:
|
||||
device_config["description"] = ""
|
||||
if "icon" not in device_config:
|
||||
@@ -439,7 +480,9 @@ class Registry:
|
||||
# 恢复原有的description信息(auto开头的不修改)
|
||||
for action_name, description in old_descriptions.items():
|
||||
if action_name in device_config["class"]["action_value_mappings"]: # 有一些会被删除
|
||||
device_config["class"]["action_value_mappings"][action_name]["schema"]["description"] = description
|
||||
device_config["class"]["action_value_mappings"][action_name]["schema"][
|
||||
"description"
|
||||
] = description
|
||||
device_config["init_param_schema"] = {}
|
||||
device_config["init_param_schema"]["config"] = self._generate_unilab_json_command_schema(
|
||||
enhanced_info["init_params"], "__init__"
|
||||
|
||||
@@ -1,8 +1,17 @@
|
||||
OTDeck:
|
||||
description: Opentrons deck
|
||||
class:
|
||||
category:
|
||||
- deck
|
||||
class:
|
||||
module: pylabrobot.resources.opentrons.deck:OTDeck
|
||||
type: pylabrobot
|
||||
config_info: []
|
||||
description: Opentrons deck
|
||||
file_path: C:/Users/10230/PycharmProjects/Uni-Lab-OS/unilabos/registry/resources/opentrons/deck.yaml
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
model:
|
||||
mesh: opentrons_liquid_handler
|
||||
type: device
|
||||
mesh: opentrons_liquid_handler
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
|
||||
@@ -1,5 +1,39 @@
|
||||
Opentrons_96_adapter_Vb:
|
||||
description: Opentrons 96 adapter Vb
|
||||
class:
|
||||
category:
|
||||
- plate_adapters
|
||||
class:
|
||||
module: pylabrobot.resources.opentrons.plate_adapters:Opentrons_96_adapter_Vb
|
||||
type: pylabrobot
|
||||
type: pylabrobot
|
||||
config_info:
|
||||
- children: []
|
||||
class: ''
|
||||
config:
|
||||
barcode: null
|
||||
category: plate_adapter
|
||||
model: Opentrons_96_adapter_Vb
|
||||
rotation:
|
||||
type: Rotation
|
||||
x: 0
|
||||
y: 0
|
||||
z: 0
|
||||
size_x: 127.76
|
||||
size_y: 85.48
|
||||
size_z: 18.55
|
||||
type: PlateAdapter
|
||||
data: {}
|
||||
id: Opentrons_96_adapter_Vb
|
||||
name: Opentrons_96_adapter_Vb
|
||||
parent: null
|
||||
position:
|
||||
x: 0
|
||||
y: 0
|
||||
z: 0
|
||||
sample_id: null
|
||||
type: container
|
||||
description: Opentrons 96 adapter Vb
|
||||
file_path: C:/Users/10230/PycharmProjects/Uni-Lab-OS/unilabos/registry/resources/opentrons/plate_adapters.yaml
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,28 +1,35 @@
|
||||
container:
|
||||
description: regular organic container
|
||||
icon: Flask.webp
|
||||
class:
|
||||
category:
|
||||
- container
|
||||
class:
|
||||
module: unilabos.resources.container:RegularContainer
|
||||
type: unilabos
|
||||
config_info: []
|
||||
description: regular organic container
|
||||
file_path: C:/Users/10230/PycharmProjects/Uni-Lab-OS/unilabos/registry/resources/organic/container.yaml
|
||||
handles:
|
||||
- handler_key: top
|
||||
label: top
|
||||
io_type: target
|
||||
data_type: fluid
|
||||
side: NORTH
|
||||
data_source: handle
|
||||
data_key: fluid_in
|
||||
- handler_key: bottom
|
||||
label: bottom
|
||||
io_type: source
|
||||
data_type: fluid
|
||||
side: SOUTH
|
||||
data_source: handle
|
||||
data_key: fluid_out
|
||||
- handler_key: bind
|
||||
label: bind
|
||||
io_type: target
|
||||
data_type: mechanical
|
||||
side: SOUTH
|
||||
data_source: handle
|
||||
data_key: mechanical_port
|
||||
- data_key: fluid_in
|
||||
data_source: handle
|
||||
data_type: fluid
|
||||
handler_key: top
|
||||
io_type: target
|
||||
label: top
|
||||
side: NORTH
|
||||
- data_key: fluid_out
|
||||
data_source: handle
|
||||
data_type: fluid
|
||||
handler_key: bottom
|
||||
io_type: source
|
||||
label: bottom
|
||||
side: SOUTH
|
||||
- data_key: mechanical_port
|
||||
data_source: handle
|
||||
data_type: mechanical
|
||||
handler_key: bind
|
||||
io_type: target
|
||||
label: bind
|
||||
side: SOUTH
|
||||
icon: Flask.webp
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
|
||||
@@ -248,7 +248,7 @@ def dict_to_nested_dict(nodes: dict, devices_only: bool = False) -> dict:
|
||||
root_nodes = {
|
||||
node["id"]: node
|
||||
for node in nodes_list
|
||||
if node.get("parent", node.get("parent_name")) in [None, "", "None", np.nan]
|
||||
if node.get("parent", node.get("parent_name")) in [None, "", "None", np.nan] or len(nodes_list) == 1
|
||||
}
|
||||
|
||||
# 如果存在多个根节点,返回所有根节点
|
||||
|
||||
@@ -656,6 +656,8 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
||||
if v in ["unilabos_msgs/Resource", "sequence<unilabos_msgs/Resource>"]:
|
||||
self.lab_logger().info(f"查询资源状态: Key: {k} Type: {v}")
|
||||
current_resources = []
|
||||
# TODO: resource后面需要分组
|
||||
only_one_resource = False
|
||||
try:
|
||||
if len(action_kwargs[k]) > 1:
|
||||
for i in action_kwargs[k]:
|
||||
@@ -665,6 +667,7 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
||||
response = await self._resource_clients["resource_get"].call_async(r)
|
||||
current_resources.extend(response.resources)
|
||||
else:
|
||||
only_one_resource = True
|
||||
r = ResourceGet.Request()
|
||||
r.id = (
|
||||
action_kwargs[k]["id"]
|
||||
@@ -682,7 +685,10 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
||||
type_hint = action_paramtypes[k]
|
||||
final_type = get_type_class(type_hint)
|
||||
# 判断 ACTION 是否需要特殊的物料类型如 pylabrobot.resources.Resource,并做转换
|
||||
final_resource = [convert_resources_to_type([i], final_type)[0] for i in resources_list]
|
||||
if only_one_resource:
|
||||
final_resource = convert_resources_to_type(resources_list, final_type)
|
||||
else:
|
||||
final_resource = [convert_resources_to_type([i], final_type)[0] for i in resources_list]
|
||||
action_kwargs[k] = self.resource_tracker.figure_resource(final_resource)
|
||||
|
||||
##### self.lab_logger().info(f"准备执行: {action_kwargs}, 函数: {ACTION.__name__}")
|
||||
|
||||
@@ -189,45 +189,26 @@ class ROS2ProtocolNode(BaseROS2DeviceNode):
|
||||
|
||||
# # 🔧 完全禁用Host查询,直接使用转换后的数据
|
||||
# print(f"🔧 跳过Host查询,直接使用转换后的数据")
|
||||
|
||||
# 🔧 额外验证:确保vessel数据完整
|
||||
if 'vessel' in protocol_kwargs:
|
||||
vessel_data = protocol_kwargs['vessel']
|
||||
#print(f"🔍 验证vessel数据: {vessel_data}")
|
||||
|
||||
# 如果vessel是空字典,尝试重新构建
|
||||
if not vessel_data or (isinstance(vessel_data, dict) and not vessel_data):
|
||||
# print(f"⚠️ vessel数据为空,尝试从原始goal重新提取...")
|
||||
|
||||
# 直接从原始goal提取vessel
|
||||
if hasattr(goal, 'vessel') and goal.vessel:
|
||||
# print(f"🔍 原始goal.vessel: {goal.vessel}")
|
||||
# 手动转换vessel
|
||||
vessel_data = {
|
||||
'id': goal.vessel.id,
|
||||
'name': goal.vessel.name,
|
||||
'type': goal.vessel.type,
|
||||
'category': goal.vessel.category,
|
||||
'config': goal.vessel.config,
|
||||
'data': goal.vessel.data
|
||||
}
|
||||
protocol_kwargs['vessel'] = vessel_data
|
||||
# print(f"✅ 手动重建vessel数据: {vessel_data}")
|
||||
else:
|
||||
# print(f"❌ 无法从原始goal提取vessel数据")
|
||||
# 创建一个基本的vessel
|
||||
vessel_data = {'id': 'default_vessel'}
|
||||
protocol_kwargs['vessel'] = vessel_data
|
||||
# print(f"🔧 创建默认vessel: {vessel_data}")
|
||||
# 向Host查询物料当前状态
|
||||
for k, v in goal.get_fields_and_field_types().items():
|
||||
if v in ["unilabos_msgs/Resource", "sequence<unilabos_msgs/Resource>"]:
|
||||
r = ResourceGet.Request()
|
||||
resource_id = (
|
||||
protocol_kwargs[k]["id"] if v == "unilabos_msgs/Resource" else protocol_kwargs[k][0]["id"]
|
||||
)
|
||||
r.id = resource_id
|
||||
r.with_children = True
|
||||
response = await self._resource_clients["resource_get"].call_async(r)
|
||||
protocol_kwargs[k] = list_to_nested_dict(
|
||||
[convert_from_ros_msg(rs) for rs in response.resources]
|
||||
)
|
||||
|
||||
#print(f"🔍 最终传递给协议的 protocol_kwargs: {protocol_kwargs}")
|
||||
#print(f"🔍 最终的 vessel: {protocol_kwargs.get('vessel', 'NOT_FOUND')}")
|
||||
self.lab_logger().info(f"🔍 最终传递给协议的 protocol_kwargs: {protocol_kwargs}")
|
||||
self.lab_logger().info(f"🔍 最终的 vessel: {protocol_kwargs.get('vessel', 'NOT_FOUND')}")
|
||||
|
||||
from unilabos.resources.graphio import physical_setup_graph
|
||||
|
||||
self.lab_logger().info(f"Working on physical setup: {physical_setup_graph}")
|
||||
self.lab_logger().info(f"Protocol kwargs: {goal}")
|
||||
self.lab_logger().info(f"Protocol kwargs: {action_value_mapping}")
|
||||
protocol_steps = protocol_steps_generator(G=physical_setup_graph, **protocol_kwargs)
|
||||
|
||||
self.lab_logger().info(f"Goal received: {protocol_kwargs}, running steps: \n{protocol_steps}")
|
||||
@@ -263,14 +244,14 @@ class ROS2ProtocolNode(BaseROS2DeviceNode):
|
||||
}
|
||||
)
|
||||
|
||||
# # 向Host更新物料当前状态
|
||||
# for k, v in goal.get_fields_and_field_types().items():
|
||||
# if v in ["unilabos_msgs/Resource", "sequence<unilabos_msgs/Resource>"]:
|
||||
# r = ResourceUpdate.Request()
|
||||
# r.resources = [
|
||||
# convert_to_ros_msg(Resource, rs) for rs in nested_dict_to_list(protocol_kwargs[k])
|
||||
# ]
|
||||
# response = await self._resource_clients["resource_update"].call_async(r)
|
||||
# 向Host更新物料当前状态
|
||||
for k, v in goal.get_fields_and_field_types().items():
|
||||
if v in ["unilabos_msgs/Resource", "sequence<unilabos_msgs/Resource>"]:
|
||||
r = ResourceUpdate.Request()
|
||||
r.resources = [
|
||||
convert_to_ros_msg(Resource, rs) for rs in nested_dict_to_list(protocol_kwargs[k])
|
||||
]
|
||||
response = await self._resource_clients["resource_update"].call_async(r)
|
||||
|
||||
# 设置成功状态和返回值
|
||||
execution_success = True
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import collections.abc
|
||||
import json
|
||||
from collections import OrderedDict
|
||||
from typing import get_origin, get_args
|
||||
|
||||
import yaml
|
||||
@@ -29,6 +30,14 @@ class NoAliasDumper(yaml.SafeDumper):
|
||||
return True
|
||||
|
||||
|
||||
# 为NoAliasDumper添加OrderedDict的representation方法
|
||||
def represent_ordereddict(dumper, data):
|
||||
return dumper.represent_mapping("tag:yaml.org,2002:map", data.items())
|
||||
|
||||
|
||||
# 注册OrderedDict的representer
|
||||
NoAliasDumper.add_representer(OrderedDict, represent_ordereddict)
|
||||
|
||||
|
||||
class ResultInfoEncoder(json.JSONEncoder):
|
||||
"""专门用于处理任务执行结果信息的JSON编码器"""
|
||||
|
||||
@@ -65,6 +65,7 @@ set(action_files
|
||||
"action/LiquidHandlerReturnTips.action"
|
||||
"action/LiquidHandlerReturnTips96.action"
|
||||
"action/LiquidHandlerSetLiquid.action"
|
||||
"action/LiquidHandlerSetTipRack.action"
|
||||
"action/LiquidHandlerStamp.action"
|
||||
"action/LiquidHandlerTransfer.action"
|
||||
|
||||
|
||||
5
unilabos_msgs/action/LiquidHandlerSetTipRack.action
Normal file
5
unilabos_msgs/action/LiquidHandlerSetTipRack.action
Normal file
@@ -0,0 +1,5 @@
|
||||
Resource[] tip_racks
|
||||
---
|
||||
string return_info
|
||||
bool success
|
||||
---
|
||||
Reference in New Issue
Block a user