diff --git a/unilabos/app/web/client.py b/unilabos/app/web/client.py index 98d3b3f..937a3ba 100644 --- a/unilabos/app/web/client.py +++ b/unilabos/app/web/client.py @@ -46,6 +46,8 @@ class HTTPClient: headers={"Authorization": f"lab {self.auth}"}, timeout=5, ) + if response.status_code != 200: + logger.error(f"添加物料关系失败: {response.text}") return response def resource_add(self, resources: List[Dict[str, Any]], database_process_later: bool) -> requests.Response: @@ -64,6 +66,8 @@ class HTTPClient: headers={"Authorization": f"lab {self.auth}"}, timeout=5, ) + if response.status_code != 200: + logger.error(f"添加物料失败: {response.text}") return response def resource_get(self, id: str, with_children: bool = False) -> Dict[str, Any]: diff --git a/unilabos/devices/virtual/virtual_rotavap.py b/unilabos/devices/virtual/virtual_rotavap.py index e69de29..01f1ae0 100644 --- a/unilabos/devices/virtual/virtual_rotavap.py +++ b/unilabos/devices/virtual/virtual_rotavap.py @@ -0,0 +1,172 @@ +import asyncio +import logging +from typing import Dict, Any, Optional + + +class VirtualRotavap: + """Virtual rotary evaporator device for EvaporateProtocol testing""" + + def __init__(self, device_id: Optional[str] = None, config: Optional[Dict[str, Any]] = None, **kwargs): + # 处理可能的不同调用方式 + if device_id is None and "id" in kwargs: + device_id = kwargs.pop("id") + if config is None and "config" in kwargs: + config = kwargs.pop("config") + + # 设置默认值 + self.device_id = device_id or "unknown_rotavap" + self.config = config or {} + + self.logger = logging.getLogger(f"VirtualRotavap.{self.device_id}") + self.data = {} + + # 添加调试信息 + print(f"=== VirtualRotavap {self.device_id} is being created! ===") + print(f"=== Config: {self.config} ===") + print(f"=== Kwargs: {kwargs} ===") + + # 从config或kwargs中获取配置参数 + self.port = self.config.get("port") or kwargs.get("port", "VIRTUAL") + self._max_temp = self.config.get("max_temp") or kwargs.get("max_temp", 180.0) + self._max_rotation_speed = self.config.get("max_rotation_speed") or kwargs.get("max_rotation_speed", 280.0) + + # 处理其他kwargs参数,但跳过已知的配置参数 + skip_keys = {"port", "max_temp", "max_rotation_speed"} + for key, value in kwargs.items(): + if key not in skip_keys and not hasattr(self, key): + setattr(self, key, value) + + async def initialize(self) -> bool: + """Initialize virtual rotary evaporator""" + print(f"=== VirtualRotavap {self.device_id} initialize() called! ===") + self.logger.info(f"Initializing virtual rotary evaporator {self.device_id}") + self.data.update( + { + "status": "Idle", + "rotavap_state": "Ready", + "current_temp": 25.0, + "target_temp": 25.0, + "max_temp": self._max_temp, + "rotation_speed": 0.0, + "max_rotation_speed": self._max_rotation_speed, + "vacuum_pressure": 1.0, # atmospheric pressure + "evaporated_volume": 0.0, + "progress": 0.0, + "message": "", + } + ) + return True + + async def cleanup(self) -> bool: + """Cleanup virtual rotary evaporator""" + self.logger.info(f"Cleaning up virtual rotary evaporator {self.device_id}") + return True + + async def evaporate( + self, vessel: str, pressure: float = 0.5, temp: float = 60.0, time: float = 300.0, stir_speed: float = 100.0 + ) -> bool: + """Execute evaporate action - matches Evaporate action""" + self.logger.info(f"Evaporate: vessel={vessel}, pressure={pressure}, temp={temp}, time={time}") + + # 验证参数 + if temp > self._max_temp: + self.logger.error(f"Temperature {temp} exceeds maximum {self._max_temp}") + self.data["message"] = f"温度 {temp} 超过最大值 {self._max_temp}" + return False + + if stir_speed > self._max_rotation_speed: + self.logger.error(f"Rotation speed {stir_speed} exceeds maximum {self._max_rotation_speed}") + self.data["message"] = f"旋转速度 {stir_speed} 超过最大值 {self._max_rotation_speed}" + return False + + if pressure < 0.01 or pressure > 1.0: + self.logger.error(f"Pressure {pressure} bar is out of valid range (0.01-1.0)") + self.data["message"] = f"真空度 {pressure} bar 超出有效范围 (0.01-1.0)" + return False + + # 开始蒸发 + self.data.update( + { + "status": "Running", + "rotavap_state": "Evaporating", + "target_temp": temp, + "current_temp": temp, + "rotation_speed": stir_speed, + "vacuum_pressure": pressure, + "vessel": vessel, + "target_time": time, + "progress": 0.0, + "message": f"正在蒸发: {vessel}", + } + ) + + # 模拟蒸发过程 + simulation_time = min(time / 60.0, 10.0) # 最多模拟10秒 + for progress in range(0, 101, 10): + await asyncio.sleep(simulation_time / 10) + self.data["progress"] = progress + self.data["evaporated_volume"] = progress * 0.5 # 假设最多蒸发50mL + + # 蒸发完成 + evaporated_vol = 50.0 # 假设蒸发了50mL + self.data.update( + { + "status": "Idle", + "rotavap_state": "Ready", + "current_temp": 25.0, + "target_temp": 25.0, + "rotation_speed": 0.0, + "vacuum_pressure": 1.0, + "evaporated_volume": evaporated_vol, + "progress": 100.0, + "message": f"蒸发完成: {evaporated_vol}mL", + } + ) + + self.logger.info(f"Evaporation completed: {evaporated_vol}mL from {vessel}") + return True + + # 状态属性 + @property + def status(self) -> str: + return self.data.get("status", "Unknown") + + @property + def rotavap_state(self) -> str: + return self.data.get("rotavap_state", "Unknown") + + @property + def current_temp(self) -> float: + return self.data.get("current_temp", 25.0) + + @property + def target_temp(self) -> float: + return self.data.get("target_temp", 25.0) + + @property + def max_temp(self) -> float: + return self.data.get("max_temp", self._max_temp) + + @property + def rotation_speed(self) -> float: + return self.data.get("rotation_speed", 0.0) + + @property + def max_rotation_speed(self) -> float: + return self.data.get("max_rotation_speed", self._max_rotation_speed) + + @property + def vacuum_pressure(self) -> float: + return self.data.get("vacuum_pressure", 1.0) + + @property + def evaporated_volume(self) -> float: + return self.data.get("evaporated_volume", 0.0) + + @property + def progress(self) -> float: + return self.data.get("progress", 0.0) + + @property + def message(self) -> str: + return self.data.get("message", "") diff --git a/unilabos/devices/virtual/virtual_separator.py b/unilabos/devices/virtual/virtual_separator.py new file mode 100644 index 0000000..e1c4612 --- /dev/null +++ b/unilabos/devices/virtual/virtual_separator.py @@ -0,0 +1,184 @@ +import asyncio +import logging +from typing import Dict, Any, Optional + + +class VirtualSeparator: + """Virtual separator device for SeparateProtocol testing""" + + def __init__(self, device_id: Optional[str] = None, config: Optional[Dict[str, Any]] = None, **kwargs): + # 处理可能的不同调用方式 + if device_id is None and "id" in kwargs: + device_id = kwargs.pop("id") + if config is None and "config" in kwargs: + config = kwargs.pop("config") + + # 设置默认值 + self.device_id = device_id or "unknown_separator" + self.config = config or {} + + self.logger = logging.getLogger(f"VirtualSeparator.{self.device_id}") + self.data = {} + + # 添加调试信息 + print(f"=== VirtualSeparator {self.device_id} is being created! ===") + print(f"=== Config: {self.config} ===") + print(f"=== Kwargs: {kwargs} ===") + + # 从config或kwargs中获取配置参数 + self.port = self.config.get("port") or kwargs.get("port", "VIRTUAL") + self._volume = self.config.get("volume") or kwargs.get("volume", 250.0) + self._has_phases = self.config.get("has_phases") or kwargs.get("has_phases", True) + + # 处理其他kwargs参数,但跳过已知的配置参数 + skip_keys = {"port", "volume", "has_phases"} + for key, value in kwargs.items(): + if key not in skip_keys and not hasattr(self, key): + setattr(self, key, value) + + async def initialize(self) -> bool: + """Initialize virtual separator""" + print(f"=== VirtualSeparator {self.device_id} initialize() called! ===") + self.logger.info(f"Initializing virtual separator {self.device_id}") + self.data.update( + { + "status": "Ready", + "separator_state": "Ready", + "volume": self._volume, + "has_phases": self._has_phases, + "phase_separation": False, + "stir_speed": 0.0, + "settling_time": 0.0, + "progress": 0.0, + "message": "", + } + ) + return True + + async def cleanup(self) -> bool: + """Cleanup virtual separator""" + self.logger.info(f"Cleaning up virtual separator {self.device_id}") + return True + + async def separate( + self, + purpose: str, + product_phase: str, + from_vessel: str, + separation_vessel: str, + to_vessel: str, + waste_phase_to_vessel: str = "", + solvent: str = "", + solvent_volume: float = 50.0, + through: str = "", + repeats: int = 1, + stir_time: float = 30.0, + stir_speed: float = 300.0, + settling_time: float = 300.0, + ) -> bool: + """Execute separate action - matches Separate action""" + self.logger.info(f"Separate: purpose={purpose}, product_phase={product_phase}, from_vessel={from_vessel}") + + # 验证参数 + if product_phase not in ["top", "bottom"]: + self.logger.error(f"Invalid product_phase {product_phase}, must be 'top' or 'bottom'") + self.data["message"] = f"产物相位 {product_phase} 无效,必须是 'top' 或 'bottom'" + return False + + if purpose not in ["wash", "extract"]: + self.logger.error(f"Invalid purpose {purpose}, must be 'wash' or 'extract'") + self.data["message"] = f"分离目的 {purpose} 无效,必须是 'wash' 或 'extract'" + return False + + # 开始分离 + self.data.update( + { + "status": "Running", + "separator_state": "Separating", + "purpose": purpose, + "product_phase": product_phase, + "from_vessel": from_vessel, + "separation_vessel": separation_vessel, + "to_vessel": to_vessel, + "waste_phase_to_vessel": waste_phase_to_vessel, + "solvent": solvent, + "solvent_volume": solvent_volume, + "repeats": repeats, + "stir_speed": stir_speed, + "settling_time": settling_time, + "phase_separation": True, + "progress": 0.0, + "message": f"正在分离: {from_vessel} -> {to_vessel}", + } + ) + + # 模拟分离过程 + total_time = (stir_time + settling_time) * repeats + simulation_time = min(total_time / 60.0, 15.0) # 最多模拟15秒 + + for repeat in range(repeats): + # 搅拌阶段 + for progress in range(0, 51, 10): + await asyncio.sleep(simulation_time / (repeats * 10)) + overall_progress = ((repeat * 100) + (progress * 0.5)) / repeats + self.data["progress"] = overall_progress + self.data["message"] = f"第{repeat+1}次分离 - 搅拌中 ({progress}%)" + + # 静置分相阶段 + for progress in range(50, 101, 10): + await asyncio.sleep(simulation_time / (repeats * 10)) + overall_progress = ((repeat * 100) + (progress * 0.5)) / repeats + self.data["progress"] = overall_progress + self.data["message"] = f"第{repeat+1}次分离 - 静置分相中 ({progress}%)" + + # 分离完成 + self.data.update( + { + "status": "Ready", + "separator_state": "Ready", + "phase_separation": False, + "stir_speed": 0.0, + "progress": 100.0, + "message": f"分离完成: {repeats}次分离操作", + } + ) + + self.logger.info(f"Separation completed: {repeats} cycles from {from_vessel} to {to_vessel}") + return True + + # 状态属性 + @property + def status(self) -> str: + return self.data.get("status", "Unknown") + + @property + def separator_state(self) -> str: + return self.data.get("separator_state", "Unknown") + + @property + def volume(self) -> float: + return self.data.get("volume", self._volume) + + @property + def has_phases(self) -> bool: + return self.data.get("has_phases", self._has_phases) + + @property + def phase_separation(self) -> bool: + return self.data.get("phase_separation", False) + + @property + def stir_speed(self) -> float: + return self.data.get("stir_speed", 0.0) + + @property + def settling_time(self) -> float: + return self.data.get("settling_time", 0.0) + + @property + def progress(self) -> float: + return self.data.get("progress", 0.0) + + @property + def message(self) -> str: + return self.data.get("message", "") diff --git a/unilabos/devices/virtual/virtual_transferpump.py b/unilabos/devices/virtual/virtual_transferpump.py index 7e6f930..a680833 100644 --- a/unilabos/devices/virtual/virtual_transferpump.py +++ b/unilabos/devices/virtual/virtual_transferpump.py @@ -14,9 +14,10 @@ class VirtualPumpMode(Enum): class VirtualPump: """虚拟泵类 - 模拟泵的基本功能,无需实际硬件""" - def __init__(self, device_id: str = None, max_volume: float = 25.0, mode: VirtualPumpMode = VirtualPumpMode.Normal): + def __init__(self, device_id: str = None, max_volume: float = 25.0, mode: VirtualPumpMode = VirtualPumpMode.Normal, transfer_rate=0): self.device_id = device_id or "virtual_pump" self.max_volume = max_volume + self._transfer_rate = transfer_rate self.mode = mode # 状态变量 @@ -24,7 +25,7 @@ class VirtualPump: self._position = 0.0 # 当前柱塞位置 (ml) self._max_velocity = 5.0 # 默认最大速度 (ml/s) self._current_volume = 0.0 # 当前注射器中的体积 - + self.logger = logging.getLogger(f"VirtualPump.{self.device_id}") async def initialize(self) -> bool: @@ -60,6 +61,10 @@ class VirtualPump: def max_velocity(self) -> float: return self._max_velocity + @property + def transfer_rate(self) -> float: + return self._transfer_rate + def set_max_velocity(self, velocity: float): """设置最大速度 (ml/s)""" self._max_velocity = max(0.1, min(50.0, velocity)) # 限制在合理范围内 diff --git a/unilabos/registry/devices/virtual_device.yaml b/unilabos/registry/devices/virtual_device.yaml index 8960072..47b7b1b 100644 --- a/unilabos/registry/devices/virtual_device.yaml +++ b/unilabos/registry/devices/virtual_device.yaml @@ -44,6 +44,17 @@ # 描述:分离纯化设备,用于样品纯化 # 连接特性:1个输入口 + 1个输出口 # 数据类型:resource(资源/样品连接) + +# 9. virtual_rotavap - 虚拟旋转蒸发仪 +# 描述:旋转蒸发仪用于溶剂蒸发和浓缩,具有加热、旋转和真空功能 +# 连接特性:1个输入口(样品),1个输出口(浓缩物),1个冷凝器出口(回收溶剂) +# 数据类型:resource(资源/样品连接) + +# 10. virtual_separator - 虚拟分液器 +# 描述:分液器用于两相液体的分离,可进行萃取和洗涤操作 +# 连接特性:1个输入口(混合液),2个输出口(上相和下相) +# 数据类型:fluid(流体连接) + virtual_pump: description: Virtual Pump for PumpTransferProtocol Testing class: @@ -52,7 +63,7 @@ virtual_pump: status_types: status: String position: Float64 - valve_position: Int32 # 修复:使用 Int32 而不是 String + valve_position: Int32 # 修复:使用 Int32 而不是 String max_volume: Float64 current_volume: Float64 action_value_mappings: @@ -83,13 +94,13 @@ virtual_pump: success: success # 虚拟泵节点配置 - 具有多通道阀门特性,根据valve_position可连接多个容器 handles: - - handler_key: pump-inlet - label: Pump Inlet - data_type: fluid - io_type: target - data_source: handle - data_key: fluid_in - description: "泵的进液口,连接源容器" + - handler_key: pump-inlet + label: Pump Inlet + data_type: fluid + io_type: target + data_source: handle + data_key: fluid_in + description: "泵的进液口,连接源容器" schema: type: object properties: @@ -139,14 +150,14 @@ virtual_stirrer: success: success # 虚拟搅拌器节点配置 - 机械连接设备,单一双向连接点 handles: - - handler_key: stirrer-vessel - label: Vessel Connection - data_type: mechanical - side: SOUTH - io_type: undirected - data_source: handle - data_key: vessel - description: "搅拌器的机械连接口,直接与反应容器连接提供搅拌功能" + - handler_key: stirrer-vessel + label: Vessel Connection + data_type: mechanical + side: SOUTH + io_type: undirected + data_source: handle + data_key: vessel + description: "搅拌器的机械连接口,直接与反应容器连接提供搅拌功能" schema: type: object properties: @@ -182,77 +193,77 @@ virtual_multiway_valve: success: success # 八通阀门节点配置 - 1个输入口,8个输出口,可切换流向 handles: - - handler_key: multiway-valve-inlet - label: Valve Inlet - data_type: fluid - io_type: target - data_source: handle - data_key: fluid_in - description: "八通阀门进液口,接收来源流体" - - handler_key: multiway-valve-port-1 - label: 1 - data_type: fluid - side: NORTH - io_type: source - data_source: executor - data_key: fluid_port_1 - description: "八通阀门端口1,position=1时流体从此口流出" - - handler_key: multiway-valve-port-2 - label: 2 - data_type: fluid - side: EAST - io_type: source - data_source: executor - data_key: fluid_port_2 - description: "八通阀门端口2,position=2时流体从此口流出" - - handler_key: multiway-valve-port-3 - label: 3 - data_type: fluid - side: EAST - io_type: source - data_source: executor - data_key: fluid_port_3 - description: "八通阀门端口3,position=3时流体从此口流出" - - handler_key: multiway-valve-port-4 - label: 4 - data_type: fluid - side: SOUTH - io_type: source - data_source: executor - data_key: fluid_port_4 - description: "八通阀门端口4,position=4时流体从此口流出" - - handler_key: multiway-valve-port-5 - label: 5 - data_type: fluid - side: SOUTH - io_type: source - data_source: executor - data_key: fluid_port_5 - description: "八通阀门端口5,position=5时流体从此口流出" - - handler_key: multiway-valve-port-7 - label: 7 - data_type: fluid - side: WEST - io_type: source - data_source: executor - data_key: fluid_port_7 - description: "八通阀门端口7,position=7时流体从此口流出" - - handler_key: multiway-valve-port-6 - label: 6 - data_type: fluid - side: WEST - io_type: source - data_source: executor - data_key: fluid_port_6 - description: "八通阀门端口6,position=6时流体从此口流出" - - handler_key: multiway-valve-port-8 - label: 8 - data_type: fluid - side: NORTH - io_type: source - data_source: executor - data_key: fluid_port_8 - description: "八通阀门端口8,position=8时流体从此口流出" + - handler_key: multiway-valve-inlet + label: Valve Inlet + data_type: fluid + io_type: target + data_source: handle + data_key: fluid_in + description: "八通阀门进液口,接收来源流体" + - handler_key: multiway-valve-port-1 + label: 1 + data_type: fluid + side: NORTH + io_type: source + data_source: executor + data_key: fluid_port_1 + description: "八通阀门端口1,position=1时流体从此口流出" + - handler_key: multiway-valve-port-2 + label: 2 + data_type: fluid + side: EAST + io_type: source + data_source: executor + data_key: fluid_port_2 + description: "八通阀门端口2,position=2时流体从此口流出" + - handler_key: multiway-valve-port-3 + label: 3 + data_type: fluid + side: EAST + io_type: source + data_source: executor + data_key: fluid_port_3 + description: "八通阀门端口3,position=3时流体从此口流出" + - handler_key: multiway-valve-port-4 + label: 4 + data_type: fluid + side: SOUTH + io_type: source + data_source: executor + data_key: fluid_port_4 + description: "八通阀门端口4,position=4时流体从此口流出" + - handler_key: multiway-valve-port-5 + label: 5 + data_type: fluid + side: SOUTH + io_type: source + data_source: executor + data_key: fluid_port_5 + description: "八通阀门端口5,position=5时流体从此口流出" + - handler_key: multiway-valve-port-7 + label: 7 + data_type: fluid + side: WEST + io_type: source + data_source: executor + data_key: fluid_port_7 + description: "八通阀门端口7,position=7时流体从此口流出" + - handler_key: multiway-valve-port-6 + label: 6 + data_type: fluid + side: WEST + io_type: source + data_source: executor + data_key: fluid_port_6 + description: "八通阀门端口6,position=6时流体从此口流出" + - handler_key: multiway-valve-port-8 + label: 8 + data_type: fluid + side: NORTH + io_type: source + data_source: executor + data_key: fluid_port_8 + description: "八通阀门端口8,position=8时流体从此口流出" schema: type: object properties: @@ -270,19 +281,19 @@ virtual_solenoid_valve: type: python status_types: status: String - valve_state: String # "open" or "closed" + valve_state: String # "open" or "closed" is_open: Bool action_value_mappings: open: type: SendCmd - goal: + goal: command: "open" feedback: {} result: success: success close: type: SendCmd - goal: + goal: command: "close" feedback: {} result: @@ -290,26 +301,26 @@ virtual_solenoid_valve: set_state: type: SendCmd goal: - command: command + command: command feedback: {} result: success: success # 电磁阀门节点配置 - 双向流通的开关型阀门,流动方向由泵决定 handles: - - handler_key: solenoid-valve-port-in - label: in - data_type: fluid - io_type: undirected - data_source: handle - data_key: fluid_port - description: "电磁阀的双向流体口,开启时允许流体双向通过,关闭时完全阻断" - - handler_key: solenoid-valve-port-out - label: out - data_type: fluid - io_type: undirected - data_source: handle - data_key: fluid_port - description: "电磁阀的双向流体口,开启时允许流体双向通过,关闭时完全阻断" + - handler_key: solenoid-valve-port-in + label: in + data_type: fluid + io_type: undirected + data_source: handle + data_key: fluid_port + description: "电磁阀的双向流体口,开启时允许流体双向通过,关闭时完全阻断" + - handler_key: solenoid-valve-port-out + label: out + data_type: fluid + io_type: undirected + data_source: handle + data_key: fluid_port + description: "电磁阀的双向流体口,开启时允许流体双向通过,关闭时完全阻断" schema: type: object properties: @@ -357,13 +368,13 @@ virtual_centrifuge: message: message # 虚拟离心机节点配置 - 单个样品处理设备,输入输出都是同一个样品容器 handles: - - handler_key: centrifuge-sample - label: Sample Input/Output - data_type: transport - io_type: undirected - data_source: handle - data_key: vessel - description: "需要离心的样品容器" + - handler_key: centrifuge-sample + label: Sample Input/Output + data_type: transport + io_type: undirected + data_source: handle + data_key: vessel + description: "需要离心的样品容器" schema: type: object properties: @@ -418,30 +429,30 @@ virtual_filter: message: message # 虚拟过滤器节点配置 - 分离设备,1个输入(原始样品),2个输出(滤液和滤渣) handles: - - handler_key: filter-in - label: Input - data_type: fluid - side: NORTH - io_type: target - data_source: handle - data_key: vessel - description: "需要过滤的原始样品容器" - - handler_key: filter-filtrate-out - label: Output - data_type: fluid - side: SOUTH - io_type: source - data_source: executor - data_key: filtrate_vessel - description: "过滤后的滤液容器" - - handler_key: filter-residue-out - label: Residue - data_type: resource - side: WEST - io_type: source - data_source: executor - data_key: residue_vessel - description: "过滤后的滤渣(固体残留物)" + - handler_key: filter-in + label: Input + data_type: fluid + side: NORTH + io_type: target + data_source: handle + data_key: vessel + description: "需要过滤的原始样品容器" + - handler_key: filter-filtrate-out + label: Output + data_type: fluid + side: SOUTH + io_type: source + data_source: executor + data_key: filtrate_vessel + description: "过滤后的滤液容器" + - handler_key: filter-residue-out + label: Residue + data_type: resource + side: WEST + io_type: source + data_source: executor + data_key: residue_vessel + description: "过滤后的滤渣(固体残留物)" schema: type: object properties: @@ -497,14 +508,14 @@ virtual_heatchill: success: success # 虚拟加热/冷却器节点配置 - 温控设备,单一双向连接点用于放置容器 handles: - - handler_key: heatchill-vessel - label: Connection - data_type: mechanical - side: NORTH - io_type: undirected - data_source: handle - data_key: vessel - description: "加热/冷却器的物理连接口,容器直接放置在设备上进行温度控制" + - handler_key: heatchill-vessel + label: Connection + data_type: mechanical + side: NORTH + io_type: undirected + data_source: handle + data_key: vessel + description: "加热/冷却器的物理连接口,容器直接放置在设备上进行温度控制" schema: type: object properties: @@ -516,7 +527,7 @@ virtual_heatchill: default: 200.0 min_temp: type: number - default: -80.0 + default: -80 max_stir_speed: type: number default: 1000.0 @@ -533,10 +544,6 @@ virtual_transfer_pump: max_volume: Float64 transfer_rate: Float64 from_vessel: String - to_vessel: String - progress: Float64 - transferred_volume: Float64 - current_status: String action_value_mappings: transfer: type: Transfer @@ -612,22 +619,22 @@ virtual_column: message: message # 虚拟色谱柱节点配置 - 分离纯化设备,1个样品输入口,1个纯化产物输出口 handles: - - handler_key: column-sample-inlet - label: Sample Input - data_type: fluid - side: NORTH - io_type: target - data_source: handle - data_key: from_vessel - description: "需要纯化的样品输入口" - - handler_key: column-product-outlet - label: Purified Product - data_type: fluid - side: SOUTH - io_type: source - data_source: executor - data_key: to_vessel - description: "经过色谱柱纯化的产物输出口" + - handler_key: column-sample-inlet + label: Sample Input + data_type: fluid + side: NORTH + io_type: target + data_source: handle + data_key: from_vessel + description: "需要纯化的样品输入口" + - handler_key: column-product-outlet + label: Purified Product + data_type: fluid + side: SOUTH + io_type: source + data_source: executor + data_key: to_vessel + description: "经过色谱柱纯化的产物输出口" schema: type: object properties: @@ -643,4 +650,156 @@ virtual_column: column_diameter: type: number default: 2.0 - additionalProperties: false \ No newline at end of file + additionalProperties: false + +virtual_rotavap: + description: Virtual Rotary Evaporator for EvaporateProtocol Testing + class: + module: unilabos.devices.virtual.virtual_rotavap:VirtualRotavap + type: python + status_types: + status: String + rotavap_state: String + current_temp: Float64 + target_temp: Float64 + max_temp: Float64 + rotation_speed: Float64 + max_rotation_speed: Float64 + vacuum_pressure: Float64 + evaporated_volume: Float64 + progress: Float64 + message: String + action_value_mappings: + evaporate: + type: Evaporate + goal: + vessel: vessel + pressure: pressure + temp: temp + time: time + stir_speed: stir_speed + feedback: + progress: progress + current_temp: current_temp + evaporated_volume: evaporated_volume + current_status: status + result: + success: success + message: message + # 虚拟旋转蒸发仪节点配置 - 蒸发浓缩设备,1个输入口(样品),2个输出口(浓缩物和冷凝液) + handles: + - handler_key: rotavap-sample-inlet + label: Sample Input + data_type: fluid + side: NORTH + io_type: target + data_source: handle + data_key: vessel + description: "需要蒸发的样品输入口" + - handler_key: rotavap-concentrate-outlet + label: Concentrate + data_type: fluid + side: SOUTH + io_type: source + data_source: executor + data_key: concentrate_vessel + description: "蒸发浓缩后的产物输出口" + - handler_key: rotavap-distillate-outlet + label: Distillate + data_type: fluid + side: WEST + io_type: source + data_source: executor + data_key: distillate_vessel + description: "冷凝回收的溶剂输出口" + schema: + type: object + properties: + port: + type: string + default: "VIRTUAL" + max_temp: + type: number + default: 180.0 + max_rotation_speed: + type: number + default: 280.0 + additionalProperties: false + +virtual_separator: + description: Virtual Separator for SeparateProtocol Testing + class: + module: unilabos.devices.virtual.virtual_separator:VirtualSeparator + type: python + status_types: + status: String + separator_state: String + volume: Float64 + has_phases: Bool + phase_separation: Bool + stir_speed: Float64 + settling_time: Float64 + progress: Float64 + message: String + action_value_mappings: + separate: + type: Separate + goal: + purpose: purpose + product_phase: product_phase + from_vessel: from_vessel + separation_vessel: separation_vessel + to_vessel: to_vessel + waste_phase_to_vessel: waste_phase_to_vessel + solvent: solvent + solvent_volume: solvent_volume + through: through + repeats: repeats + stir_time: stir_time + stir_speed: stir_speed + settling_time: settling_time + feedback: + progress: progress + current_status: status + result: + success: success + message: message + # 虚拟分液器节点配置 - 分离设备,1个输入口(混合液),2个输出口(上相和下相) + handles: + - handler_key: separator-inlet + label: Mixed Input + data_type: fluid + side: NORTH + io_type: target + data_source: handle + data_key: from_vessel + description: "需要分离的混合液体输入口" + - handler_key: separator-top-outlet + label: Top Phase + data_type: fluid + side: EAST + io_type: source + data_source: executor + data_key: top_outlet + description: "上相(轻相)液体输出口" + - handler_key: separator-bottom-outlet + label: Bottom Phase + data_type: fluid + side: SOUTH + io_type: source + data_source: executor + data_key: bottom_outlet + description: "下相(重相)液体输出口" + schema: + type: object + properties: + port: + type: string + default: "VIRTUAL" + volume: + type: number + default: 250.0 + has_phases: + type: boolean + default: true + additionalProperties: false