mirror of
https://github.com/dptech-corp/Uni-Lab-OS.git
synced 2025-12-17 13:01:12 +00:00
modify devices to use correct executor (sleep, create_task)
This commit is contained in:
@@ -3,9 +3,13 @@ import logging
|
||||
import time as time_module
|
||||
from typing import Dict, Any, Optional
|
||||
|
||||
from unilabos.ros.nodes.base_device_node import BaseROS2DeviceNode
|
||||
|
||||
|
||||
class VirtualCentrifuge:
|
||||
"""Virtual centrifuge device - 简化版,只保留核心功能"""
|
||||
|
||||
_ros_node: BaseROS2DeviceNode
|
||||
|
||||
def __init__(self, device_id: Optional[str] = None, config: Optional[Dict[str, Any]] = None, **kwargs):
|
||||
# 处理可能的不同调用方式
|
||||
@@ -32,6 +36,9 @@ class VirtualCentrifuge:
|
||||
for key, value in kwargs.items():
|
||||
if key not in skip_keys and not hasattr(self, key):
|
||||
setattr(self, key, value)
|
||||
|
||||
def post_init(self, ros_node: BaseROS2DeviceNode):
|
||||
self._ros_node = ros_node
|
||||
|
||||
async def initialize(self) -> bool:
|
||||
"""Initialize virtual centrifuge"""
|
||||
@@ -132,7 +139,7 @@ class VirtualCentrifuge:
|
||||
break
|
||||
|
||||
# 每秒更新一次
|
||||
await asyncio.sleep(1.0)
|
||||
await self._ros_node.sleep(1.0)
|
||||
|
||||
# 离心完成
|
||||
self.data.update({
|
||||
|
||||
@@ -2,9 +2,13 @@ import asyncio
|
||||
import logging
|
||||
from typing import Dict, Any, Optional
|
||||
|
||||
from unilabos.ros.nodes.base_device_node import BaseROS2DeviceNode
|
||||
|
||||
class VirtualColumn:
|
||||
"""Virtual column device for RunColumn protocol 🏛️"""
|
||||
|
||||
_ros_node: BaseROS2DeviceNode
|
||||
|
||||
def __init__(self, device_id: str = None, config: Dict[str, Any] = None, **kwargs):
|
||||
# 处理可能的不同调用方式
|
||||
if device_id is None and 'id' in kwargs:
|
||||
@@ -28,6 +32,9 @@ class VirtualColumn:
|
||||
print(f"🏛️ === 虚拟色谱柱 {self.device_id} 已创建 === ✨")
|
||||
print(f"📏 柱参数: 流速={self._max_flow_rate}mL/min | 长度={self._column_length}cm | 直径={self._column_diameter}cm 🔬")
|
||||
|
||||
def post_init(self, ros_node: BaseROS2DeviceNode):
|
||||
self._ros_node = ros_node
|
||||
|
||||
async def initialize(self) -> bool:
|
||||
"""Initialize virtual column 🚀"""
|
||||
self.logger.info(f"🔧 初始化虚拟色谱柱 {self.device_id} ✨")
|
||||
@@ -101,7 +108,7 @@ class VirtualColumn:
|
||||
step_time = separation_time / steps
|
||||
|
||||
for i in range(steps):
|
||||
await asyncio.sleep(step_time)
|
||||
await self._ros_node.sleep(step_time)
|
||||
|
||||
progress = (i + 1) / steps * 100
|
||||
volume_processed = (i + 1) * 5.0 # 假设每步处理5mL
|
||||
|
||||
@@ -4,70 +4,76 @@ import time as time_module
|
||||
from typing import Dict, Any, Optional
|
||||
|
||||
from unilabos.compile.utils.vessel_parser import get_vessel
|
||||
from unilabos.ros.nodes.base_device_node import BaseROS2DeviceNode
|
||||
|
||||
|
||||
class VirtualFilter:
|
||||
"""Virtual filter device - 完全按照 Filter.action 规范 🌊"""
|
||||
|
||||
|
||||
_ros_node: BaseROS2DeviceNode
|
||||
|
||||
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')
|
||||
|
||||
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_filter"
|
||||
self.config = config or {}
|
||||
self.logger = logging.getLogger(f"VirtualFilter.{self.device_id}")
|
||||
self.data = {}
|
||||
|
||||
|
||||
# 从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', 100.0)
|
||||
self._max_stir_speed = self.config.get('max_stir_speed') or kwargs.get('max_stir_speed', 1000.0)
|
||||
self._max_volume = self.config.get('max_volume') or kwargs.get('max_volume', 500.0)
|
||||
|
||||
self.port = self.config.get("port") or kwargs.get("port", "VIRTUAL")
|
||||
self._max_temp = self.config.get("max_temp") or kwargs.get("max_temp", 100.0)
|
||||
self._max_stir_speed = self.config.get("max_stir_speed") or kwargs.get("max_stir_speed", 1000.0)
|
||||
self._max_volume = self.config.get("max_volume") or kwargs.get("max_volume", 500.0)
|
||||
|
||||
# 处理其他kwargs参数
|
||||
skip_keys = {'port', 'max_temp', 'max_stir_speed', 'max_volume'}
|
||||
skip_keys = {"port", "max_temp", "max_stir_speed", "max_volume"}
|
||||
for key, value in kwargs.items():
|
||||
if key not in skip_keys and not hasattr(self, key):
|
||||
setattr(self, key, value)
|
||||
|
||||
|
||||
def post_init(self, ros_node: BaseROS2DeviceNode):
|
||||
self._ros_node = ros_node
|
||||
|
||||
async def initialize(self) -> bool:
|
||||
"""Initialize virtual filter 🚀"""
|
||||
self.logger.info(f"🔧 初始化虚拟过滤器 {self.device_id} ✨")
|
||||
|
||||
|
||||
# 按照 Filter.action 的 feedback 字段初始化
|
||||
self.data.update({
|
||||
"status": "Idle",
|
||||
"progress": 0.0, # Filter.action feedback
|
||||
"current_temp": 25.0, # Filter.action feedback
|
||||
"filtered_volume": 0.0, # Filter.action feedback
|
||||
"message": "Ready for filtration"
|
||||
})
|
||||
|
||||
self.data.update(
|
||||
{
|
||||
"status": "Idle",
|
||||
"progress": 0.0, # Filter.action feedback
|
||||
"current_temp": 25.0, # Filter.action feedback
|
||||
"filtered_volume": 0.0, # Filter.action feedback
|
||||
"message": "Ready for filtration",
|
||||
}
|
||||
)
|
||||
|
||||
self.logger.info(f"✅ 过滤器 {self.device_id} 初始化完成 🌊")
|
||||
return True
|
||||
|
||||
|
||||
async def cleanup(self) -> bool:
|
||||
"""Cleanup virtual filter 🧹"""
|
||||
self.logger.info(f"🧹 清理虚拟过滤器 {self.device_id} 🔚")
|
||||
|
||||
self.data.update({
|
||||
"status": "Offline"
|
||||
})
|
||||
|
||||
|
||||
self.data.update({"status": "Offline"})
|
||||
|
||||
self.logger.info(f"✅ 过滤器 {self.device_id} 清理完成 💤")
|
||||
return True
|
||||
|
||||
|
||||
async def filter(
|
||||
self,
|
||||
self,
|
||||
vessel: dict,
|
||||
filtrate_vessel: dict = {},
|
||||
stir: bool = False,
|
||||
stir_speed: float = 300.0,
|
||||
temp: float = 25.0,
|
||||
continue_heatchill: bool = False,
|
||||
volume: float = 0.0
|
||||
stir: bool = False,
|
||||
stir_speed: float = 300.0,
|
||||
temp: float = 25.0,
|
||||
continue_heatchill: bool = False,
|
||||
volume: float = 0.0,
|
||||
) -> bool:
|
||||
"""Execute filter action - 完全按照 Filter.action 参数 🌊"""
|
||||
vessel_id, _ = get_vessel(vessel)
|
||||
@@ -79,59 +85,52 @@ class VirtualFilter:
|
||||
temp = 25.0 # 0度自动设置为室温
|
||||
self.logger.info(f"🌡️ 温度自动调整: {original_temp}°C → {temp}°C (室温) 🏠")
|
||||
elif temp < 4.0:
|
||||
temp = 4.0 # 小于4度自动设置为4度
|
||||
temp = 4.0 # 小于4度自动设置为4度
|
||||
self.logger.info(f"🌡️ 温度自动调整: {original_temp}°C → {temp}°C (最低温度) ❄️")
|
||||
|
||||
|
||||
self.logger.info(f"🌊 开始过滤操作: {vessel_id} → {filtrate_vessel_id} 🚰")
|
||||
self.logger.info(f" 🌪️ 搅拌: {stir} ({stir_speed} RPM)")
|
||||
self.logger.info(f" 🌡️ 温度: {temp}°C")
|
||||
self.logger.info(f" 💧 体积: {volume}mL")
|
||||
self.logger.info(f" 🔥 保持加热: {continue_heatchill}")
|
||||
|
||||
|
||||
# 验证参数
|
||||
if temp > self._max_temp or temp < 4.0:
|
||||
error_msg = f"🌡️ 温度 {temp}°C 超出范围 (4-{self._max_temp}°C) ⚠️"
|
||||
self.logger.error(f"❌ {error_msg}")
|
||||
self.data.update({
|
||||
"status": f"Error: 温度超出范围 ⚠️",
|
||||
"message": error_msg
|
||||
})
|
||||
self.data.update({"status": f"Error: 温度超出范围 ⚠️", "message": error_msg})
|
||||
return False
|
||||
|
||||
|
||||
if stir and stir_speed > self._max_stir_speed:
|
||||
error_msg = f"🌪️ 搅拌速度 {stir_speed} RPM 超出范围 (0-{self._max_stir_speed} RPM) ⚠️"
|
||||
self.logger.error(f"❌ {error_msg}")
|
||||
self.data.update({
|
||||
"status": f"Error: 搅拌速度超出范围 ⚠️",
|
||||
"message": error_msg
|
||||
})
|
||||
self.data.update({"status": f"Error: 搅拌速度超出范围 ⚠️", "message": error_msg})
|
||||
return False
|
||||
|
||||
|
||||
if volume > self._max_volume:
|
||||
error_msg = f"💧 过滤体积 {volume} mL 超出范围 (0-{self._max_volume} mL) ⚠️"
|
||||
self.logger.error(f"❌ {error_msg}")
|
||||
self.data.update({
|
||||
"status": f"Error",
|
||||
"message": error_msg
|
||||
})
|
||||
self.data.update({"status": f"Error", "message": error_msg})
|
||||
return False
|
||||
|
||||
|
||||
# 开始过滤
|
||||
filter_volume = volume if volume > 0 else 50.0
|
||||
self.logger.info(f"🚀 开始过滤 {filter_volume}mL 液体 💧")
|
||||
|
||||
self.data.update({
|
||||
"status": f"Running",
|
||||
"current_temp": temp,
|
||||
"filtered_volume": 0.0,
|
||||
"progress": 0.0,
|
||||
"message": f"🚀 Starting filtration: {vessel_id} → {filtrate_vessel_id}"
|
||||
})
|
||||
|
||||
|
||||
self.data.update(
|
||||
{
|
||||
"status": f"Running",
|
||||
"current_temp": temp,
|
||||
"filtered_volume": 0.0,
|
||||
"progress": 0.0,
|
||||
"message": f"🚀 Starting filtration: {vessel_id} → {filtrate_vessel_id}",
|
||||
}
|
||||
)
|
||||
|
||||
try:
|
||||
# 过滤过程 - 实时更新进度
|
||||
start_time = time_module.time()
|
||||
|
||||
|
||||
# 根据体积和搅拌估算过滤时间
|
||||
base_time = filter_volume / 5.0 # 5mL/s 基础速度
|
||||
if stir:
|
||||
@@ -140,78 +139,79 @@ class VirtualFilter:
|
||||
if temp > 50.0:
|
||||
base_time *= 0.7 # 高温加速过滤
|
||||
self.logger.info(f"🔥 高温加速过滤,预计时间减少30% ⚡")
|
||||
|
||||
|
||||
filter_time = max(base_time, 10.0) # 最少10秒
|
||||
self.logger.info(f"⏱️ 预计过滤时间: {filter_time:.1f}秒 ⌛")
|
||||
|
||||
|
||||
while True:
|
||||
current_time = time_module.time()
|
||||
elapsed = current_time - start_time
|
||||
remaining = max(0, filter_time - elapsed)
|
||||
progress = min(100.0, (elapsed / filter_time) * 100)
|
||||
current_filtered = (progress / 100.0) * filter_volume
|
||||
|
||||
|
||||
# 更新状态 - 按照 Filter.action feedback 字段
|
||||
status_msg = f"🌊 过滤中: {vessel}"
|
||||
if stir:
|
||||
status_msg += f" | 🌪️ 搅拌: {stir_speed} RPM"
|
||||
status_msg += f" | 🌡️ {temp}°C | 📊 {progress:.1f}% | 💧 已过滤: {current_filtered:.1f}mL"
|
||||
|
||||
self.data.update({
|
||||
"progress": progress, # Filter.action feedback
|
||||
"current_temp": temp, # Filter.action feedback
|
||||
"filtered_volume": current_filtered, # Filter.action feedback
|
||||
"status": "Running",
|
||||
"message": f"🌊 Filtering: {progress:.1f}% complete, {current_filtered:.1f}mL filtered"
|
||||
})
|
||||
|
||||
|
||||
self.data.update(
|
||||
{
|
||||
"progress": progress, # Filter.action feedback
|
||||
"current_temp": temp, # Filter.action feedback
|
||||
"filtered_volume": current_filtered, # Filter.action feedback
|
||||
"status": "Running",
|
||||
"message": f"🌊 Filtering: {progress:.1f}% complete, {current_filtered:.1f}mL filtered",
|
||||
}
|
||||
)
|
||||
|
||||
# 进度日志(每25%打印一次)
|
||||
if progress >= 25 and progress % 25 < 1:
|
||||
self.logger.info(f"📊 过滤进度: {progress:.0f}% | 💧 {current_filtered:.1f}mL 完成 ✨")
|
||||
|
||||
|
||||
if remaining <= 0:
|
||||
break
|
||||
|
||||
await asyncio.sleep(1.0)
|
||||
|
||||
|
||||
await self._ros_node.sleep(1.0)
|
||||
|
||||
# 过滤完成
|
||||
final_temp = temp if continue_heatchill else 25.0
|
||||
final_status = f"✅ 过滤完成: {vessel} | 💧 {filter_volume}mL → {filtrate_vessel}"
|
||||
if continue_heatchill:
|
||||
final_status += " | 🔥 继续加热搅拌"
|
||||
self.logger.info(f"🔥 继续保持加热搅拌状态 🌪️")
|
||||
|
||||
self.data.update({
|
||||
"status": final_status,
|
||||
"progress": 100.0, # Filter.action feedback
|
||||
"current_temp": final_temp, # Filter.action feedback
|
||||
"filtered_volume": filter_volume, # Filter.action feedback
|
||||
"message": f"✅ Filtration completed: {filter_volume}mL filtered from {vessel_id}"
|
||||
})
|
||||
|
||||
|
||||
self.data.update(
|
||||
{
|
||||
"status": final_status,
|
||||
"progress": 100.0, # Filter.action feedback
|
||||
"current_temp": final_temp, # Filter.action feedback
|
||||
"filtered_volume": filter_volume, # Filter.action feedback
|
||||
"message": f"✅ Filtration completed: {filter_volume}mL filtered from {vessel_id}",
|
||||
}
|
||||
)
|
||||
|
||||
self.logger.info(f"🎉 过滤完成! 💧 {filter_volume}mL 从 {vessel_id} 过滤到 {filtrate_vessel_id} ✨")
|
||||
self.logger.info(f"📊 最终状态: 温度 {final_temp}°C | 进度 100% | 体积 {filter_volume}mL 🏁")
|
||||
return True
|
||||
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"过滤过程中发生错误: {str(e)} 💥"
|
||||
self.logger.error(f"❌ {error_msg}")
|
||||
self.data.update({
|
||||
"status": f"Error",
|
||||
"message": f"❌ Filtration failed: {str(e)}"
|
||||
})
|
||||
self.data.update({"status": f"Error", "message": f"❌ Filtration failed: {str(e)}"})
|
||||
return False
|
||||
|
||||
|
||||
# === 核心状态属性 - 按照 Filter.action feedback 字段 ===
|
||||
@property
|
||||
def status(self) -> str:
|
||||
return self.data.get("status", "❓ Unknown")
|
||||
|
||||
|
||||
@property
|
||||
def progress(self) -> float:
|
||||
"""Filter.action feedback 字段 📊"""
|
||||
return self.data.get("progress", 0.0)
|
||||
|
||||
|
||||
@property
|
||||
def current_temp(self) -> float:
|
||||
"""Filter.action feedback 字段 🌡️"""
|
||||
@@ -230,15 +230,15 @@ class VirtualFilter:
|
||||
@property
|
||||
def message(self) -> str:
|
||||
return self.data.get("message", "")
|
||||
|
||||
|
||||
@property
|
||||
def max_temp(self) -> float:
|
||||
return self._max_temp
|
||||
|
||||
|
||||
@property
|
||||
def max_stir_speed(self) -> float:
|
||||
return self._max_stir_speed
|
||||
|
||||
|
||||
@property
|
||||
def max_volume(self) -> float:
|
||||
return self._max_volume
|
||||
return self._max_volume
|
||||
|
||||
@@ -3,9 +3,13 @@ import logging
|
||||
import time as time_module # 重命名time模块,避免与参数冲突
|
||||
from typing import Dict, Any
|
||||
|
||||
from unilabos.ros.nodes.base_device_node import BaseROS2DeviceNode
|
||||
|
||||
class VirtualHeatChill:
|
||||
"""Virtual heat chill device for HeatChillProtocol testing 🌡️"""
|
||||
|
||||
_ros_node: BaseROS2DeviceNode
|
||||
|
||||
def __init__(self, device_id: str = None, config: Dict[str, Any] = None, **kwargs):
|
||||
# 处理可能的不同调用方式
|
||||
if device_id is None and 'id' in kwargs:
|
||||
@@ -35,6 +39,9 @@ class VirtualHeatChill:
|
||||
print(f"🌡️ === 虚拟温控设备 {self.device_id} 已创建 === ✨")
|
||||
print(f"🔥 温度范围: {self._min_temp}°C ~ {self._max_temp}°C | 🌪️ 最大搅拌: {self._max_stir_speed} RPM")
|
||||
|
||||
def post_init(self, ros_node: BaseROS2DeviceNode):
|
||||
self._ros_node = ros_node
|
||||
|
||||
async def initialize(self) -> bool:
|
||||
"""Initialize virtual heat chill 🚀"""
|
||||
self.logger.info(f"🔧 初始化虚拟温控设备 {self.device_id} ✨")
|
||||
@@ -177,7 +184,7 @@ class VirtualHeatChill:
|
||||
break
|
||||
|
||||
# 等待1秒后再次检查
|
||||
await asyncio.sleep(1.0)
|
||||
await self._ros_node.sleep(1.0)
|
||||
|
||||
# 操作完成
|
||||
final_stir_info = f" | 🌪️ 搅拌: {stir_speed} RPM" if stir else ""
|
||||
|
||||
@@ -3,13 +3,19 @@ import logging
|
||||
import time as time_module
|
||||
from typing import Dict, Any, Optional
|
||||
|
||||
from unilabos.ros.nodes.base_device_node import BaseROS2DeviceNode
|
||||
|
||||
|
||||
def debug_print(message):
|
||||
"""调试输出 🔍"""
|
||||
print(f"🌪️ [ROTAVAP] {message}", flush=True)
|
||||
|
||||
|
||||
class VirtualRotavap:
|
||||
"""Virtual rotary evaporator device - 简化版,只保留核心功能 🌪️"""
|
||||
|
||||
_ros_node: BaseROS2DeviceNode
|
||||
|
||||
def __init__(self, device_id: Optional[str] = None, config: Optional[Dict[str, Any]] = None, **kwargs):
|
||||
# 处理可能的不同调用方式
|
||||
if device_id is None and "id" in kwargs:
|
||||
@@ -38,56 +44,65 @@ class VirtualRotavap:
|
||||
print(f"🌪️ === 虚拟旋转蒸发仪 {self.device_id} 已创建 === ✨")
|
||||
print(f"🔥 温度范围: 10°C ~ {self._max_temp}°C | 🌀 转速范围: 10 ~ {self._max_rotation_speed} RPM")
|
||||
|
||||
def post_init(self, ros_node: BaseROS2DeviceNode):
|
||||
self._ros_node = ros_node
|
||||
|
||||
async def initialize(self) -> bool:
|
||||
"""Initialize virtual rotary evaporator 🚀"""
|
||||
self.logger.info(f"🔧 初始化虚拟旋转蒸发仪 {self.device_id} ✨")
|
||||
|
||||
|
||||
# 只保留核心状态
|
||||
self.data.update({
|
||||
"status": "🏠 待机中",
|
||||
"rotavap_state": "Ready", # Ready, Evaporating, Completed, Error
|
||||
"current_temp": 25.0,
|
||||
"target_temp": 25.0,
|
||||
"rotation_speed": 0.0,
|
||||
"vacuum_pressure": 1.0, # 大气压
|
||||
"evaporated_volume": 0.0,
|
||||
"progress": 0.0,
|
||||
"remaining_time": 0.0,
|
||||
"message": "🌪️ Ready for evaporation"
|
||||
})
|
||||
|
||||
self.data.update(
|
||||
{
|
||||
"status": "🏠 待机中",
|
||||
"rotavap_state": "Ready", # Ready, Evaporating, Completed, Error
|
||||
"current_temp": 25.0,
|
||||
"target_temp": 25.0,
|
||||
"rotation_speed": 0.0,
|
||||
"vacuum_pressure": 1.0, # 大气压
|
||||
"evaporated_volume": 0.0,
|
||||
"progress": 0.0,
|
||||
"remaining_time": 0.0,
|
||||
"message": "🌪️ Ready for evaporation",
|
||||
}
|
||||
)
|
||||
|
||||
self.logger.info(f"✅ 旋转蒸发仪 {self.device_id} 初始化完成 🌪️")
|
||||
self.logger.info(f"📊 设备规格: 温度范围 10°C ~ {self._max_temp}°C | 转速范围 10 ~ {self._max_rotation_speed} RPM")
|
||||
self.logger.info(
|
||||
f"📊 设备规格: 温度范围 10°C ~ {self._max_temp}°C | 转速范围 10 ~ {self._max_rotation_speed} RPM"
|
||||
)
|
||||
return True
|
||||
|
||||
async def cleanup(self) -> bool:
|
||||
"""Cleanup virtual rotary evaporator 🧹"""
|
||||
self.logger.info(f"🧹 清理虚拟旋转蒸发仪 {self.device_id} 🔚")
|
||||
|
||||
self.data.update({
|
||||
"status": "💤 离线",
|
||||
"rotavap_state": "Offline",
|
||||
"current_temp": 25.0,
|
||||
"rotation_speed": 0.0,
|
||||
"vacuum_pressure": 1.0,
|
||||
"message": "💤 System offline"
|
||||
})
|
||||
|
||||
|
||||
self.data.update(
|
||||
{
|
||||
"status": "💤 离线",
|
||||
"rotavap_state": "Offline",
|
||||
"current_temp": 25.0,
|
||||
"rotation_speed": 0.0,
|
||||
"vacuum_pressure": 1.0,
|
||||
"message": "💤 System offline",
|
||||
}
|
||||
)
|
||||
|
||||
self.logger.info(f"✅ 旋转蒸发仪 {self.device_id} 清理完成 💤")
|
||||
return True
|
||||
|
||||
async def evaporate(
|
||||
self,
|
||||
vessel: str,
|
||||
pressure: float = 0.1,
|
||||
temp: float = 60.0,
|
||||
self,
|
||||
vessel: str,
|
||||
pressure: float = 0.1,
|
||||
temp: float = 60.0,
|
||||
time: float = 180.0,
|
||||
stir_speed: float = 100.0,
|
||||
solvent: str = "",
|
||||
**kwargs
|
||||
**kwargs,
|
||||
) -> bool:
|
||||
"""Execute evaporate action - 简化版 🌪️"""
|
||||
|
||||
|
||||
# 🔧 新增:确保time参数是数值类型
|
||||
if isinstance(time, str):
|
||||
try:
|
||||
@@ -98,31 +113,31 @@ class VirtualRotavap:
|
||||
elif not isinstance(time, (int, float)):
|
||||
self.logger.error(f"❌ 时间参数类型无效: {type(time)},使用默认值180.0秒")
|
||||
time = 180.0
|
||||
|
||||
|
||||
# 确保time是float类型; 并加速
|
||||
time = float(time) / 10.0
|
||||
|
||||
|
||||
# 🔧 简化处理:如果vessel就是设备自己,直接操作
|
||||
if vessel == self.device_id:
|
||||
debug_print(f"🎯 在设备 {self.device_id} 上直接执行蒸发操作")
|
||||
actual_vessel = self.device_id
|
||||
else:
|
||||
actual_vessel = vessel
|
||||
|
||||
|
||||
# 参数预处理
|
||||
if solvent:
|
||||
self.logger.info(f"🧪 识别到溶剂: {solvent}")
|
||||
# 根据溶剂调整参数
|
||||
solvent_lower = solvent.lower()
|
||||
if any(s in solvent_lower for s in ['water', 'aqueous']):
|
||||
if any(s in solvent_lower for s in ["water", "aqueous"]):
|
||||
temp = max(temp, 80.0)
|
||||
pressure = max(pressure, 0.2)
|
||||
self.logger.info(f"💧 水系溶剂:调整参数 → 温度 {temp}°C, 压力 {pressure} bar")
|
||||
elif any(s in solvent_lower for s in ['ethanol', 'methanol', 'acetone']):
|
||||
elif any(s in solvent_lower for s in ["ethanol", "methanol", "acetone"]):
|
||||
temp = min(temp, 50.0)
|
||||
pressure = min(pressure, 0.05)
|
||||
self.logger.info(f"⚡ 易挥发溶剂:调整参数 → 温度 {temp}°C, 压力 {pressure} bar")
|
||||
|
||||
|
||||
self.logger.info(f"🌪️ 开始蒸发操作: {actual_vessel}")
|
||||
self.logger.info(f" 🥽 容器: {actual_vessel}")
|
||||
self.logger.info(f" 🌡️ 温度: {temp}°C")
|
||||
@@ -131,126 +146,140 @@ class VirtualRotavap:
|
||||
self.logger.info(f" 🌀 转速: {stir_speed} RPM")
|
||||
if solvent:
|
||||
self.logger.info(f" 🧪 溶剂: {solvent}")
|
||||
|
||||
|
||||
# 验证参数
|
||||
if temp > self._max_temp or temp < 10.0:
|
||||
error_msg = f"🌡️ 温度 {temp}°C 超出范围 (10-{self._max_temp}°C) ⚠️"
|
||||
self.logger.error(f"❌ {error_msg}")
|
||||
self.data.update({
|
||||
"status": f"❌ 错误: 温度超出范围",
|
||||
"rotavap_state": "Error",
|
||||
"current_temp": 25.0,
|
||||
"progress": 0.0,
|
||||
"evaporated_volume": 0.0,
|
||||
"message": error_msg
|
||||
})
|
||||
self.data.update(
|
||||
{
|
||||
"status": f"❌ 错误: 温度超出范围",
|
||||
"rotavap_state": "Error",
|
||||
"current_temp": 25.0,
|
||||
"progress": 0.0,
|
||||
"evaporated_volume": 0.0,
|
||||
"message": error_msg,
|
||||
}
|
||||
)
|
||||
return False
|
||||
|
||||
if stir_speed > self._max_rotation_speed or stir_speed < 10.0:
|
||||
error_msg = f"🌀 旋转速度 {stir_speed} RPM 超出范围 (10-{self._max_rotation_speed} RPM) ⚠️"
|
||||
self.logger.error(f"❌ {error_msg}")
|
||||
self.data.update({
|
||||
"status": f"❌ 错误: 转速超出范围",
|
||||
"rotavap_state": "Error",
|
||||
"current_temp": 25.0,
|
||||
"progress": 0.0,
|
||||
"evaporated_volume": 0.0,
|
||||
"message": error_msg
|
||||
})
|
||||
self.data.update(
|
||||
{
|
||||
"status": f"❌ 错误: 转速超出范围",
|
||||
"rotavap_state": "Error",
|
||||
"current_temp": 25.0,
|
||||
"progress": 0.0,
|
||||
"evaporated_volume": 0.0,
|
||||
"message": error_msg,
|
||||
}
|
||||
)
|
||||
return False
|
||||
|
||||
if pressure < 0.01 or pressure > 1.0:
|
||||
error_msg = f"💨 真空度 {pressure} bar 超出范围 (0.01-1.0 bar) ⚠️"
|
||||
self.logger.error(f"❌ {error_msg}")
|
||||
self.data.update({
|
||||
"status": f"❌ 错误: 压力超出范围",
|
||||
"rotavap_state": "Error",
|
||||
"current_temp": 25.0,
|
||||
"progress": 0.0,
|
||||
"evaporated_volume": 0.0,
|
||||
"message": error_msg
|
||||
})
|
||||
self.data.update(
|
||||
{
|
||||
"status": f"❌ 错误: 压力超出范围",
|
||||
"rotavap_state": "Error",
|
||||
"current_temp": 25.0,
|
||||
"progress": 0.0,
|
||||
"evaporated_volume": 0.0,
|
||||
"message": error_msg,
|
||||
}
|
||||
)
|
||||
return False
|
||||
|
||||
# 开始蒸发 - 🔧 现在time已经确保是float类型
|
||||
self.logger.info(f"🚀 启动蒸发程序! 预计用时 {time/60:.1f}分钟 ⏱️")
|
||||
|
||||
self.data.update({
|
||||
"status": f"🌪️ 蒸发中: {actual_vessel}",
|
||||
"rotavap_state": "Evaporating",
|
||||
"current_temp": temp,
|
||||
"target_temp": temp,
|
||||
"rotation_speed": stir_speed,
|
||||
"vacuum_pressure": pressure,
|
||||
"remaining_time": time,
|
||||
"progress": 0.0,
|
||||
"evaporated_volume": 0.0,
|
||||
"message": f"🌪️ Evaporating {actual_vessel} at {temp}°C, {pressure} bar, {stir_speed} RPM"
|
||||
})
|
||||
|
||||
self.data.update(
|
||||
{
|
||||
"status": f"🌪️ 蒸发中: {actual_vessel}",
|
||||
"rotavap_state": "Evaporating",
|
||||
"current_temp": temp,
|
||||
"target_temp": temp,
|
||||
"rotation_speed": stir_speed,
|
||||
"vacuum_pressure": pressure,
|
||||
"remaining_time": time,
|
||||
"progress": 0.0,
|
||||
"evaporated_volume": 0.0,
|
||||
"message": f"🌪️ Evaporating {actual_vessel} at {temp}°C, {pressure} bar, {stir_speed} RPM",
|
||||
}
|
||||
)
|
||||
|
||||
try:
|
||||
# 蒸发过程 - 实时更新进度
|
||||
start_time = time_module.time()
|
||||
total_time = time
|
||||
last_logged_progress = 0
|
||||
|
||||
|
||||
while True:
|
||||
current_time = time_module.time()
|
||||
elapsed = current_time - start_time
|
||||
remaining = max(0, total_time - elapsed)
|
||||
progress = min(100.0, (elapsed / total_time) * 100)
|
||||
|
||||
|
||||
# 模拟蒸发体积 - 根据溶剂类型调整
|
||||
if solvent and any(s in solvent.lower() for s in ['water', 'aqueous']):
|
||||
if solvent and any(s in solvent.lower() for s in ["water", "aqueous"]):
|
||||
evaporated_vol = progress * 0.6 # 水系溶剂蒸发慢
|
||||
elif solvent and any(s in solvent.lower() for s in ['ethanol', 'methanol', 'acetone']):
|
||||
elif solvent and any(s in solvent.lower() for s in ["ethanol", "methanol", "acetone"]):
|
||||
evaporated_vol = progress * 1.0 # 易挥发溶剂蒸发快
|
||||
else:
|
||||
evaporated_vol = progress * 0.8 # 默认蒸发量
|
||||
|
||||
|
||||
# 🔧 更新状态 - 确保包含所有必需字段
|
||||
status_msg = f"🌪️ 蒸发中: {actual_vessel} | 🌡️ {temp}°C | 💨 {pressure} bar | 🌀 {stir_speed} RPM | 📊 {progress:.1f}% | ⏰ 剩余: {remaining:.0f}s"
|
||||
|
||||
self.data.update({
|
||||
"remaining_time": remaining,
|
||||
"progress": progress,
|
||||
"evaporated_volume": evaporated_vol,
|
||||
"current_temp": temp,
|
||||
"status": status_msg,
|
||||
"message": f"🌪️ Evaporating: {progress:.1f}% complete, 💧 {evaporated_vol:.1f}mL evaporated, ⏰ {remaining:.0f}s remaining"
|
||||
})
|
||||
|
||||
|
||||
self.data.update(
|
||||
{
|
||||
"remaining_time": remaining,
|
||||
"progress": progress,
|
||||
"evaporated_volume": evaporated_vol,
|
||||
"current_temp": temp,
|
||||
"status": status_msg,
|
||||
"message": f"🌪️ Evaporating: {progress:.1f}% complete, 💧 {evaporated_vol:.1f}mL evaporated, ⏰ {remaining:.0f}s remaining",
|
||||
}
|
||||
)
|
||||
|
||||
# 进度日志(每25%打印一次)
|
||||
if progress >= 25 and int(progress) % 25 == 0 and int(progress) != last_logged_progress:
|
||||
self.logger.info(f"📊 蒸发进度: {progress:.0f}% | 💧 已蒸发: {evaporated_vol:.1f}mL | ⏰ 剩余: {remaining:.0f}s ✨")
|
||||
self.logger.info(
|
||||
f"📊 蒸发进度: {progress:.0f}% | 💧 已蒸发: {evaporated_vol:.1f}mL | ⏰ 剩余: {remaining:.0f}s ✨"
|
||||
)
|
||||
last_logged_progress = int(progress)
|
||||
|
||||
|
||||
# 时间到了,退出循环
|
||||
if remaining <= 0:
|
||||
break
|
||||
|
||||
|
||||
# 每秒更新一次
|
||||
await asyncio.sleep(1.0)
|
||||
|
||||
await self._ros_node.sleep(1.0)
|
||||
|
||||
# 蒸发完成
|
||||
if solvent and any(s in solvent.lower() for s in ['water', 'aqueous']):
|
||||
if solvent and any(s in solvent.lower() for s in ["water", "aqueous"]):
|
||||
final_evaporated = 60.0 # 水系溶剂
|
||||
elif solvent and any(s in solvent.lower() for s in ['ethanol', 'methanol', 'acetone']):
|
||||
elif solvent and any(s in solvent.lower() for s in ["ethanol", "methanol", "acetone"]):
|
||||
final_evaporated = 100.0 # 易挥发溶剂
|
||||
else:
|
||||
final_evaporated = 80.0 # 默认
|
||||
|
||||
self.data.update({
|
||||
"status": f"✅ 蒸发完成: {actual_vessel} | 💧 蒸发量: {final_evaporated:.1f}mL",
|
||||
"rotavap_state": "Completed",
|
||||
"evaporated_volume": final_evaporated,
|
||||
"progress": 100.0,
|
||||
"current_temp": temp,
|
||||
"remaining_time": 0.0,
|
||||
"rotation_speed": 0.0,
|
||||
"vacuum_pressure": 1.0,
|
||||
"message": f"✅ Evaporation completed: {final_evaporated}mL evaporated from {actual_vessel}"
|
||||
})
|
||||
|
||||
self.data.update(
|
||||
{
|
||||
"status": f"✅ 蒸发完成: {actual_vessel} | 💧 蒸发量: {final_evaporated:.1f}mL",
|
||||
"rotavap_state": "Completed",
|
||||
"evaporated_volume": final_evaporated,
|
||||
"progress": 100.0,
|
||||
"current_temp": temp,
|
||||
"remaining_time": 0.0,
|
||||
"rotation_speed": 0.0,
|
||||
"vacuum_pressure": 1.0,
|
||||
"message": f"✅ Evaporation completed: {final_evaporated}mL evaporated from {actual_vessel}",
|
||||
}
|
||||
)
|
||||
|
||||
self.logger.info(f"🎉 蒸发操作完成! ✨")
|
||||
self.logger.info(f"📊 蒸发结果:")
|
||||
@@ -262,24 +291,26 @@ class VirtualRotavap:
|
||||
self.logger.info(f" ⏱️ 总用时: {total_time:.0f}s")
|
||||
if solvent:
|
||||
self.logger.info(f" 🧪 处理溶剂: {solvent} 🏁")
|
||||
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
# 出错处理
|
||||
error_msg = f"蒸发过程中发生错误: {str(e)} 💥"
|
||||
self.logger.error(f"❌ {error_msg}")
|
||||
|
||||
self.data.update({
|
||||
"status": f"❌ 蒸发错误: {str(e)}",
|
||||
"rotavap_state": "Error",
|
||||
"current_temp": 25.0,
|
||||
"progress": 0.0,
|
||||
"evaporated_volume": 0.0,
|
||||
"rotation_speed": 0.0,
|
||||
"vacuum_pressure": 1.0,
|
||||
"message": f"❌ Evaporation failed: {str(e)}"
|
||||
})
|
||||
|
||||
self.data.update(
|
||||
{
|
||||
"status": f"❌ 蒸发错误: {str(e)}",
|
||||
"rotavap_state": "Error",
|
||||
"current_temp": 25.0,
|
||||
"progress": 0.0,
|
||||
"evaporated_volume": 0.0,
|
||||
"rotation_speed": 0.0,
|
||||
"vacuum_pressure": 1.0,
|
||||
"message": f"❌ Evaporation failed: {str(e)}",
|
||||
}
|
||||
)
|
||||
return False
|
||||
|
||||
# === 核心状态属性 ===
|
||||
|
||||
@@ -2,9 +2,13 @@ import asyncio
|
||||
import logging
|
||||
from typing import Dict, Any, Optional
|
||||
|
||||
from unilabos.ros.nodes.base_device_node import BaseROS2DeviceNode
|
||||
|
||||
|
||||
class VirtualSeparator:
|
||||
"""Virtual separator device for SeparateProtocol testing"""
|
||||
|
||||
_ros_node: BaseROS2DeviceNode
|
||||
|
||||
def __init__(self, device_id: Optional[str] = None, config: Optional[Dict[str, Any]] = None, **kwargs):
|
||||
# 处理可能的不同调用方式
|
||||
@@ -35,6 +39,9 @@ class VirtualSeparator:
|
||||
for key, value in kwargs.items():
|
||||
if key not in skip_keys and not hasattr(self, key):
|
||||
setattr(self, key, value)
|
||||
|
||||
def post_init(self, ros_node: BaseROS2DeviceNode):
|
||||
self._ros_node = ros_node
|
||||
|
||||
async def initialize(self) -> bool:
|
||||
"""Initialize virtual separator"""
|
||||
@@ -119,14 +126,14 @@ class VirtualSeparator:
|
||||
for repeat in range(repeats):
|
||||
# 搅拌阶段
|
||||
for progress in range(0, 51, 10):
|
||||
await asyncio.sleep(simulation_time / (repeats * 10))
|
||||
await self._ros_node.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))
|
||||
await self._ros_node.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}%)"
|
||||
|
||||
@@ -2,11 +2,16 @@ import time
|
||||
import asyncio
|
||||
from typing import Union
|
||||
|
||||
from unilabos.ros.nodes.base_device_node import BaseROS2DeviceNode
|
||||
|
||||
|
||||
class VirtualSolenoidValve:
|
||||
"""
|
||||
虚拟电磁阀门 - 简单的开关型阀门,只有开启和关闭两个状态
|
||||
"""
|
||||
|
||||
_ros_node: BaseROS2DeviceNode
|
||||
|
||||
def __init__(self, device_id: str = None, config: dict = None, **kwargs):
|
||||
# 从配置中获取参数,提供默认值
|
||||
if config is None:
|
||||
@@ -21,6 +26,9 @@ class VirtualSolenoidValve:
|
||||
self._status = "Idle"
|
||||
self._valve_state = "Closed" # "Open" or "Closed"
|
||||
self._is_open = False
|
||||
|
||||
def post_init(self, ros_node: BaseROS2DeviceNode):
|
||||
self._ros_node = ros_node
|
||||
|
||||
async def initialize(self) -> bool:
|
||||
"""初始化设备"""
|
||||
@@ -63,7 +71,7 @@ class VirtualSolenoidValve:
|
||||
self._status = "Busy"
|
||||
|
||||
# 模拟阀门响应时间
|
||||
await asyncio.sleep(self.response_time)
|
||||
await self._ros_node.sleep(self.response_time)
|
||||
|
||||
# 处理不同的命令格式
|
||||
if isinstance(command, str):
|
||||
|
||||
@@ -3,6 +3,8 @@ import logging
|
||||
import re
|
||||
from typing import Dict, Any, Optional
|
||||
|
||||
from unilabos.ros.nodes.base_device_node import BaseROS2DeviceNode
|
||||
|
||||
class VirtualSolidDispenser:
|
||||
"""
|
||||
虚拟固体粉末加样器 - 用于处理 Add Protocol 中的固体试剂添加 ⚗️
|
||||
@@ -13,6 +15,8 @@ class VirtualSolidDispenser:
|
||||
- 简单反馈:成功/失败 + 消息 📊
|
||||
"""
|
||||
|
||||
_ros_node: BaseROS2DeviceNode
|
||||
|
||||
def __init__(self, device_id: str = None, config: Dict[str, Any] = None, **kwargs):
|
||||
self.device_id = device_id or "virtual_solid_dispenser"
|
||||
self.config = config or {}
|
||||
@@ -32,6 +36,9 @@ class VirtualSolidDispenser:
|
||||
print(f"⚗️ === 虚拟固体分配器 {self.device_id} 创建成功! === ✨")
|
||||
print(f"📊 设备规格: 最大容量 {self.max_capacity}g | 精度 {self.precision}g 🎯")
|
||||
|
||||
def post_init(self, ros_node: BaseROS2DeviceNode):
|
||||
self._ros_node = ros_node
|
||||
|
||||
async def initialize(self) -> bool:
|
||||
"""初始化固体加样器 🚀"""
|
||||
self.logger.info(f"🔧 初始化固体分配器 {self.device_id} ✨")
|
||||
@@ -263,7 +270,7 @@ class VirtualSolidDispenser:
|
||||
|
||||
for i in range(steps):
|
||||
progress = (i + 1) / steps * 100
|
||||
await asyncio.sleep(step_time)
|
||||
await self._ros_node.sleep(step_time)
|
||||
if i % 2 == 0: # 每隔一步显示进度
|
||||
self.logger.debug(f"📊 加样进度: {progress:.0f}% | {amount_emoji} 正在分配 {reagent}...")
|
||||
|
||||
|
||||
@@ -3,9 +3,13 @@ import logging
|
||||
import time as time_module
|
||||
from typing import Dict, Any
|
||||
|
||||
from unilabos.ros.nodes.base_device_node import BaseROS2DeviceNode
|
||||
|
||||
class VirtualStirrer:
|
||||
"""Virtual stirrer device for StirProtocol testing - 功能完整版 🌪️"""
|
||||
|
||||
_ros_node: BaseROS2DeviceNode
|
||||
|
||||
def __init__(self, device_id: str = None, config: Dict[str, Any] = None, **kwargs):
|
||||
# 处理可能的不同调用方式
|
||||
if device_id is None and 'id' in kwargs:
|
||||
@@ -34,6 +38,9 @@ class VirtualStirrer:
|
||||
print(f"🌪️ === 虚拟搅拌器 {self.device_id} 已创建 === ✨")
|
||||
print(f"🔧 速度范围: {self._min_speed} ~ {self._max_speed} RPM | 📱 端口: {self.port}")
|
||||
|
||||
def post_init(self, ros_node: BaseROS2DeviceNode):
|
||||
self._ros_node = ros_node
|
||||
|
||||
async def initialize(self) -> bool:
|
||||
"""Initialize virtual stirrer 🚀"""
|
||||
self.logger.info(f"🔧 初始化虚拟搅拌器 {self.device_id} ✨")
|
||||
@@ -134,7 +141,7 @@ class VirtualStirrer:
|
||||
if remaining <= 0:
|
||||
break
|
||||
|
||||
await asyncio.sleep(1.0)
|
||||
await self._ros_node.sleep(1.0)
|
||||
|
||||
self.logger.info(f"✅ 搅拌阶段完成! 🌪️ {stir_speed} RPM × {stir_time}s")
|
||||
|
||||
@@ -176,7 +183,7 @@ class VirtualStirrer:
|
||||
if remaining <= 0:
|
||||
break
|
||||
|
||||
await asyncio.sleep(1.0)
|
||||
await self._ros_node.sleep(1.0)
|
||||
|
||||
self.logger.info(f"✅ 沉降阶段完成! 🛑 静置 {settling_time}s")
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@ from enum import Enum
|
||||
from typing import Union, Optional
|
||||
import logging
|
||||
|
||||
from unilabos.ros.nodes.base_device_node import BaseROS2DeviceNode
|
||||
|
||||
|
||||
class VirtualPumpMode(Enum):
|
||||
Normal = 0
|
||||
@@ -14,6 +16,8 @@ class VirtualPumpMode(Enum):
|
||||
class VirtualTransferPump:
|
||||
"""虚拟转移泵类 - 模拟泵的基本功能,无需实际硬件 🚰"""
|
||||
|
||||
_ros_node: BaseROS2DeviceNode
|
||||
|
||||
def __init__(self, device_id: str = None, config: dict = None, **kwargs):
|
||||
"""
|
||||
初始化虚拟转移泵
|
||||
@@ -53,6 +57,9 @@ class VirtualTransferPump:
|
||||
print(f"💨 快速模式: {'启用' if self._fast_mode else '禁用'} | 移动时间: {self._fast_move_time}s | 喷射时间: {self._fast_dispense_time}s")
|
||||
print(f"📊 最大容量: {self.max_volume}mL | 端口: {self.port}")
|
||||
|
||||
def post_init(self, ros_node: BaseROS2DeviceNode):
|
||||
self._ros_node = ros_node
|
||||
|
||||
async def initialize(self) -> bool:
|
||||
"""初始化虚拟泵 🚀"""
|
||||
self.logger.info(f"🔧 初始化虚拟转移泵 {self.device_id} ✨")
|
||||
@@ -104,7 +111,7 @@ class VirtualTransferPump:
|
||||
async def _simulate_operation(self, duration: float):
|
||||
"""模拟操作延时 ⏱️"""
|
||||
self._status = "Busy"
|
||||
await asyncio.sleep(duration)
|
||||
await self._ros_node.sleep(duration)
|
||||
self._status = "Idle"
|
||||
|
||||
def _calculate_duration(self, volume: float, velocity: float = None) -> float:
|
||||
@@ -223,7 +230,7 @@ class VirtualTransferPump:
|
||||
|
||||
# 等待一小步时间
|
||||
if i < steps and step_duration > 0:
|
||||
await asyncio.sleep(step_duration)
|
||||
await self._ros_node.sleep(step_duration)
|
||||
else:
|
||||
# 移动距离很小,直接完成
|
||||
self._position = target_position
|
||||
@@ -341,7 +348,7 @@ class VirtualTransferPump:
|
||||
|
||||
# 短暂停顿
|
||||
self.logger.debug("⏸️ 短暂停顿...")
|
||||
await asyncio.sleep(0.1)
|
||||
await self._ros_node.sleep(0.1)
|
||||
|
||||
# 排液
|
||||
await self.dispense(volume, dispense_velocity)
|
||||
|
||||
Reference in New Issue
Block a user