优化了全protocol的运行时间,除了pumptransfer相关的还没

This commit is contained in:
KCFeng425
2025-07-15 10:31:19 +08:00
parent 23eb1139a9
commit ac294194e6
17 changed files with 1800 additions and 2165 deletions

View File

@@ -3,7 +3,7 @@ import logging
from typing import Dict, Any, Optional
class VirtualColumn:
"""Virtual column device for RunColumn protocol"""
"""Virtual column device for RunColumn protocol 🏛️"""
def __init__(self, device_id: str = None, config: Dict[str, Any] = None, **kwargs):
# 处理可能的不同调用方式
@@ -25,45 +25,77 @@ class VirtualColumn:
self._column_length = self.config.get('column_length') or kwargs.get('column_length', 25.0)
self._column_diameter = self.config.get('column_diameter') or kwargs.get('column_diameter', 2.0)
print(f"=== VirtualColumn {self.device_id} created with max_flow_rate={self._max_flow_rate}, length={self._column_length}cm ===")
print(f"🏛️ === 虚拟色谱柱 {self.device_id} 已创建 === ✨")
print(f"📏 柱参数: 流速={self._max_flow_rate}mL/min | 长度={self._column_length}cm | 直径={self._column_diameter}cm 🔬")
async def initialize(self) -> bool:
"""Initialize virtual column"""
self.logger.info(f"Initializing virtual column {self.device_id}")
"""Initialize virtual column 🚀"""
self.logger.info(f"🔧 初始化虚拟色谱柱 {self.device_id}")
self.data.update({
"status": "Idle",
"column_state": "Ready",
"column_state": "Ready",
"current_flow_rate": 0.0,
"max_flow_rate": self._max_flow_rate,
"column_length": self._column_length,
"column_diameter": self._column_diameter,
"processed_volume": 0.0,
"progress": 0.0,
"current_status": "Ready"
"current_status": "Ready for separation"
})
self.logger.info(f"✅ 色谱柱 {self.device_id} 初始化完成 🏛️")
self.logger.info(f"📊 设备规格: 最大流速 {self._max_flow_rate}mL/min | 柱长 {self._column_length}cm 📏")
return True
async def cleanup(self) -> bool:
"""Cleanup virtual column"""
self.logger.info(f"Cleaning up virtual column {self.device_id}")
"""Cleanup virtual column 🧹"""
self.logger.info(f"🧹 清理虚拟色谱柱 {self.device_id} 🔚")
self.data.update({
"status": "Offline",
"column_state": "Offline",
"current_status": "System offline"
})
self.logger.info(f"✅ 色谱柱 {self.device_id} 清理完成 💤")
return True
async def run_column(self, from_vessel: str, to_vessel: str, column: str) -> bool:
"""Execute column chromatography run - matches RunColumn action"""
self.logger.info(f"Running column separation: {from_vessel} -> {to_vessel} using {column}")
async def run_column(self, from_vessel: str, to_vessel: str, column: str, **kwargs) -> bool:
"""Execute column chromatography run - matches RunColumn action 🏛️"""
# 提取额外参数
rf = kwargs.get('rf', '0.3')
solvent1 = kwargs.get('solvent1', 'ethyl_acetate')
solvent2 = kwargs.get('solvent2', 'hexane')
ratio = kwargs.get('ratio', '30:70')
self.logger.info(f"🏛️ 开始柱层析分离: {from_vessel}{to_vessel} 🚰")
self.logger.info(f" 🧪 使用色谱柱: {column}")
self.logger.info(f" 🎯 Rf值: {rf}")
self.logger.info(f" 🧪 洗脱溶剂: {solvent1}:{solvent2} ({ratio}) 💧")
# 更新设备状态
self.data.update({
"status": "Running",
"column_state": "Separating",
"current_status": "Column separation in progress",
"current_status": "🏛️ Column separation in progress",
"progress": 0.0,
"processed_volume": 0.0
"processed_volume": 0.0,
"current_from_vessel": from_vessel,
"current_to_vessel": to_vessel,
"current_column": column,
"current_rf": rf,
"current_solvents": f"{solvent1}:{solvent2} ({ratio})"
})
# 模拟柱层析分离过程
# 假设处理时间基于流速和柱子长度
separation_time = (self._column_length * 2) / self._max_flow_rate # 简化计算
base_time = (self._column_length * 2) / self._max_flow_rate # 简化计算
separation_time = max(base_time, 20.0) # 最少20秒
self.logger.info(f"⏱️ 预计分离时间: {separation_time:.1f}秒 ⌛")
self.logger.info(f"📏 柱参数: 长度 {self._column_length}cm | 流速 {self._max_flow_rate}mL/min 🌊")
steps = 20 # 分20个步骤模拟分离过程
step_time = separation_time / steps
@@ -74,34 +106,65 @@ class VirtualColumn:
progress = (i + 1) / steps * 100
volume_processed = (i + 1) * 5.0 # 假设每步处理5mL
# 不同阶段的状态描述
if progress <= 25:
phase = "🌊 样品上柱阶段"
phase_emoji = "📥"
elif progress <= 50:
phase = "🧪 洗脱开始"
phase_emoji = "💧"
elif progress <= 75:
phase = "⚗️ 成分分离中"
phase_emoji = "🔄"
else:
phase = "🎯 收集产物"
phase_emoji = "📤"
# 更新状态
status_msg = f"{phase_emoji} {phase}: {progress:.1f}% | 💧 已处理: {volume_processed:.1f}mL"
self.data.update({
"progress": progress,
"processed_volume": volume_processed,
"current_status": f"Column separation: {progress:.1f}% - Processing {volume_processed:.1f}mL"
"current_status": status_msg,
"current_phase": phase
})
self.logger.info(f"Column separation progress: {progress:.1f}%")
# 进度日志每25%打印一次)
if progress >= 25 and (i + 1) % 5 == 0: # 每5步25%)打印一次
self.logger.info(f"📊 分离进度: {progress:.0f}% | {phase} | 💧 {volume_processed:.1f}mL 完成 ✨")
# 分离完成
final_status = f"✅ 柱层析分离完成: {from_vessel}{to_vessel} | 💧 共处理 {volume_processed:.1f}mL"
self.data.update({
"status": "Idle",
"column_state": "Ready",
"current_status": "Column separation completed",
"progress": 100.0
"current_status": final_status,
"progress": 100.0,
"final_volume": volume_processed
})
self.logger.info(f"Column separation completed: {from_vessel} -> {to_vessel}")
self.logger.info(f"🎉 柱层析分离完成! ✨")
self.logger.info(f"📊 分离结果:")
self.logger.info(f" 🥽 源容器: {from_vessel}")
self.logger.info(f" 🥽 目标容器: {to_vessel}")
self.logger.info(f" 🏛️ 使用色谱柱: {column}")
self.logger.info(f" 💧 处理体积: {volume_processed:.1f}mL")
self.logger.info(f" 🧪 洗脱条件: {solvent1}:{solvent2} ({ratio})")
self.logger.info(f" 🎯 Rf值: {rf}")
self.logger.info(f" ⏱️ 总耗时: {separation_time:.1f}秒 🏁")
return True
# 状态属性
@property
def status(self) -> str:
return self.data.get("status", "Unknown")
return self.data.get("status", "Unknown")
@property
def column_state(self) -> str:
return self.data.get("column_state", "Unknown")
return self.data.get("column_state", "Unknown")
@property
def current_flow_rate(self) -> float:
@@ -129,4 +192,12 @@ class VirtualColumn:
@property
def current_status(self) -> str:
return self.data.get("current_status", "Ready")
return self.data.get("current_status", "📋 Ready")
@property
def current_phase(self) -> str:
return self.data.get("current_phase", "🏠 待机中")
@property
def final_volume(self) -> float:
return self.data.get("final_volume", 0.0)

View File

@@ -5,7 +5,7 @@ from typing import Dict, Any, Optional
class VirtualFilter:
"""Virtual filter device - 完全按照 Filter.action 规范"""
"""Virtual filter device - 完全按照 Filter.action 规范 🌊"""
def __init__(self, device_id: Optional[str] = None, config: Optional[Dict[str, Any]] = None, **kwargs):
if device_id is None and 'id' in kwargs:
@@ -31,8 +31,8 @@ class VirtualFilter:
setattr(self, key, value)
async def initialize(self) -> bool:
"""Initialize virtual filter"""
self.logger.info(f"Initializing virtual filter {self.device_id}")
"""Initialize virtual filter 🚀"""
self.logger.info(f"🔧 初始化虚拟过滤器 {self.device_id}")
# 按照 Filter.action 的 feedback 字段初始化
self.data.update({
@@ -43,17 +43,21 @@ class VirtualFilter:
"current_status": "Ready for filtration", # 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"Cleaning up virtual filter {self.device_id}")
"""Cleanup virtual filter 🧹"""
self.logger.info(f"🧹 清理虚拟过滤器 {self.device_id} 🔚")
self.data.update({
"status": "Offline",
"current_status": "System offline",
"message": "System offline"
})
self.logger.info(f"✅ 过滤器 {self.device_id} 清理完成 💤")
return True
async def filter(
@@ -66,74 +70,82 @@ class VirtualFilter:
continue_heatchill: bool = False,
volume: float = 0.0
) -> bool:
"""Execute filter action - 完全按照 Filter.action 参数"""
"""Execute filter action - 完全按照 Filter.action 参数 🌊"""
# 🔧 新增:温度自动调整
original_temp = temp
if temp == 0.0:
temp = 25.0 # 0度自动设置为室温
self.logger.info(f"温度自动调整: {original_temp}°C → {temp}°C (室温)")
self.logger.info(f"🌡️ 温度自动调整: {original_temp}°C → {temp}°C (室温) 🏠")
elif temp < 4.0:
temp = 4.0 # 小于4度自动设置为4度
self.logger.info(f"温度自动调整: {original_temp}°C → {temp}°C (最低温度)")
self.logger.info(f"🌡️ 温度自动调整: {original_temp}°C → {temp}°C (最低温度) ❄️")
self.logger.info(f"Filter: vessel={vessel}, filtrate_vessel={filtrate_vessel}")
self.logger.info(f" stir={stir}, stir_speed={stir_speed}, temp={temp}")
self.logger.info(f" continue_heatchill={continue_heatchill}, volume={volume}")
self.logger.info(f"🌊 开始过滤操作: {vessel}{filtrate_vessel} 🚰")
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(error_msg)
error_msg = f"🌡️ 温度 {temp}°C 超出范围 (4-{self._max_temp}°C) ⚠️"
self.logger.error(f"{error_msg}")
self.data.update({
"status": f"Error: {error_msg}",
"current_status": f"Error: {error_msg}",
"status": f"Error: 温度超出范围 ⚠️",
"current_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(error_msg)
error_msg = f"🌪️ 搅拌速度 {stir_speed} RPM 超出范围 (0-{self._max_stir_speed} RPM) ⚠️"
self.logger.error(f"{error_msg}")
self.data.update({
"status": f"Error: {error_msg}",
"current_status": f"Error: {error_msg}",
"status": f"Error: 搅拌速度超出范围 ⚠️",
"current_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(error_msg)
error_msg = f"💧 过滤体积 {volume} mL 超出范围 (0-{self._max_volume} mL) ⚠️"
self.logger.error(f"{error_msg}")
self.data.update({
"status": f"Error: {error_msg}",
"current_status": f"Error: {error_msg}",
"status": f"Error: 体积超出范围 ⚠️",
"current_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"过滤中: {vessel}",
"status": f"🌊 过滤中: {vessel}",
"current_temp": temp,
"filtered_volume": 0.0,
"progress": 0.0,
"current_status": f"Filtering {vessel}{filtrate_vessel}",
"message": f"Starting filtration: {vessel}{filtrate_vessel}"
"current_status": f"🌊 Filtering {vessel}{filtrate_vessel}",
"message": f"🚀 Starting filtration: {vessel}{filtrate_vessel}"
})
try:
# 过滤过程 - 实时更新进度
start_time = time_module.time()
# 根据体积和搅拌估算过滤时间
base_time = filter_volume / 5.0 # 5mL/s 基础速度
if stir:
base_time *= 0.8 # 搅拌加速过滤
self.logger.info(f"🌪️ 搅拌加速过滤预计时间减少20% ⚡")
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()
@@ -143,20 +155,24 @@ class VirtualFilter:
current_filtered = (progress / 100.0) * filter_volume
# 更新状态 - 按照 Filter.action feedback 字段
status_msg = f"过滤中: {vessel}"
status_msg = f"🌊 过滤中: {vessel}"
if stir:
status_msg += f" | 搅拌: {stir_speed} RPM"
status_msg += f" | {temp}°C | {progress:.1f}% | 已过滤: {current_filtered:.1f}mL"
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
"current_status": f"Filtering: {progress:.1f}% complete", # Filter.action feedback
"current_status": f"🌊 Filtering: {progress:.1f}% complete", # Filter.action feedback
"status": status_msg,
"message": f"Filtering: {progress:.1f}% complete, {current_filtered:.1f}mL filtered"
"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
@@ -164,54 +180,57 @@ class VirtualFilter:
# 过滤完成
final_temp = temp if continue_heatchill else 25.0
final_status = f"过滤完成: {vessel} | {filter_volume}mL → {filtrate_vessel}"
final_status = f"过滤完成: {vessel} | 💧 {filter_volume}mL → {filtrate_vessel}"
if continue_heatchill:
final_status += " | 继续加热搅拌"
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
"current_status": f"Filtration completed: {filter_volume}mL", # Filter.action feedback
"message": f"Filtration completed: {filter_volume}mL filtered from {vessel}"
"current_status": f"Filtration completed: {filter_volume}mL", # Filter.action feedback
"message": f"Filtration completed: {filter_volume}mL filtered from {vessel}"
})
self.logger.info(f"Filtration completed: {filter_volume}mL from {vessel} to {filtrate_vessel}")
self.logger.info(f"🎉 过滤完成! 💧 {filter_volume}mL {vessel} 过滤到 {filtrate_vessel}")
self.logger.info(f"📊 最终状态: 温度 {final_temp}°C | 进度 100% | 体积 {filter_volume}mL 🏁")
return True
except Exception as e:
self.logger.error(f"Error during filtration: {str(e)}")
error_msg = f"过滤过程中发生错误: {str(e)} 💥"
self.logger.error(f"{error_msg}")
self.data.update({
"status": f"过滤错误: {str(e)}",
"current_status": f"Filtration failed: {str(e)}",
"message": f"Filtration failed: {str(e)}"
"status": f"过滤错误: {str(e)}",
"current_status": f"Filtration failed: {str(e)}",
"message": f"Filtration failed: {str(e)}"
})
return False
# === 核心状态属性 - 按照 Filter.action feedback 字段 ===
@property
def status(self) -> str:
return self.data.get("status", "Unknown")
return self.data.get("status", "Unknown")
@property
def progress(self) -> float:
"""Filter.action feedback 字段"""
"""Filter.action feedback 字段 📊"""
return self.data.get("progress", 0.0)
@property
def current_temp(self) -> float:
"""Filter.action feedback 字段"""
"""Filter.action feedback 字段 🌡️"""
return self.data.get("current_temp", 25.0)
@property
def filtered_volume(self) -> float:
"""Filter.action feedback 字段"""
"""Filter.action feedback 字段 💧"""
return self.data.get("filtered_volume", 0.0)
@property
def current_status(self) -> str:
"""Filter.action feedback 字段"""
"""Filter.action feedback 字段 📋"""
return self.data.get("current_status", "")
@property

View File

@@ -4,7 +4,7 @@ import time as time_module # 重命名time模块避免与参数冲突
from typing import Dict, Any
class VirtualHeatChill:
"""Virtual heat chill device for HeatChillProtocol testing"""
"""Virtual heat chill device for HeatChillProtocol testing 🌡️"""
def __init__(self, device_id: str = None, config: Dict[str, Any] = None, **kwargs):
# 处理可能的不同调用方式
@@ -31,31 +31,40 @@ class VirtualHeatChill:
for key, value in kwargs.items():
if key not in skip_keys and not hasattr(self, key):
setattr(self, key, value)
print(f"🌡️ === 虚拟温控设备 {self.device_id} 已创建 === ✨")
print(f"🔥 温度范围: {self._min_temp}°C ~ {self._max_temp}°C | 🌪️ 最大搅拌: {self._max_stir_speed} RPM")
async def initialize(self) -> bool:
"""Initialize virtual heat chill"""
self.logger.info(f"Initializing virtual heat chill {self.device_id}")
"""Initialize virtual heat chill 🚀"""
self.logger.info(f"🔧 初始化虚拟温控设备 {self.device_id}")
# 初始化状态信息
self.data.update({
"status": "Idle",
"status": "🏠 待机中",
"operation_mode": "Idle",
"is_stirring": False,
"stir_speed": 0.0,
"remaining_time": 0.0,
})
self.logger.info(f"✅ 温控设备 {self.device_id} 初始化完成 🌡️")
self.logger.info(f"📊 设备规格: 温度范围 {self._min_temp}°C ~ {self._max_temp}°C | 搅拌范围 0 ~ {self._max_stir_speed} RPM")
return True
async def cleanup(self) -> bool:
"""Cleanup virtual heat chill"""
self.logger.info(f"Cleaning up virtual heat chill {self.device_id}")
"""Cleanup virtual heat chill 🧹"""
self.logger.info(f"🧹 清理虚拟温控设备 {self.device_id} 🔚")
self.data.update({
"status": "Offline",
"status": "💤 离线",
"operation_mode": "Offline",
"is_stirring": False,
"stir_speed": 0.0,
"remaining_time": 0.0
})
self.logger.info(f"✅ 温控设备 {self.device_id} 清理完成 💤")
return True
async def heat_chill(self, vessel: str, temp: float, time, stir: bool,
@@ -72,62 +81,73 @@ class VirtualHeatChill:
purpose = str(purpose)
except (ValueError, TypeError) as e:
error_msg = f"参数类型转换错误: temp={temp}({type(temp)}), time={time}({type(time)}), error={str(e)}"
self.logger.error(error_msg)
self.logger.error(f"{error_msg}")
self.data.update({
"status": f"Error: {error_msg}",
"status": f"❌ 错误: {error_msg}",
"operation_mode": "Error"
})
return False
self.logger.info(f"HeatChill: vessel={vessel}, temp={temp}°C, time={time_value}s, stir={stir}, stir_speed={stir_speed}")
# 确定温度操作emoji
if temp > 25.0:
temp_emoji = "🔥"
operation_mode = "Heating"
status_action = "加热"
elif temp < 25.0:
temp_emoji = "❄️"
operation_mode = "Cooling"
status_action = "冷却"
else:
temp_emoji = "🌡️"
operation_mode = "Maintaining"
status_action = "保温"
self.logger.info(f"🌡️ 开始温控操作: {vessel}{temp}°C {temp_emoji}")
self.logger.info(f" 🥽 容器: {vessel}")
self.logger.info(f" 🎯 目标温度: {temp}°C {temp_emoji}")
self.logger.info(f" ⏰ 持续时间: {time_value}s")
self.logger.info(f" 🌪️ 搅拌: {stir} ({stir_speed} RPM)")
self.logger.info(f" 📝 目的: {purpose}")
# 验证参数范围
if temp > self._max_temp or temp < self._min_temp:
error_msg = f"温度 {temp}°C 超出范围 ({self._min_temp}°C - {self._max_temp}°C)"
self.logger.error(error_msg)
error_msg = f"🌡️ 温度 {temp}°C 超出范围 ({self._min_temp}°C - {self._max_temp}°C) ⚠️"
self.logger.error(f"{error_msg}")
self.data.update({
"status": f"Error: {error_msg}",
"status": f"❌ 错误: 温度超出范围 ⚠️",
"operation_mode": "Error"
})
return False
if stir and stir_speed > self._max_stir_speed:
error_msg = f"搅拌速度 {stir_speed} RPM 超出最大值 {self._max_stir_speed} RPM"
self.logger.error(error_msg)
error_msg = f"🌪️ 搅拌速度 {stir_speed} RPM 超出最大值 {self._max_stir_speed} RPM ⚠️"
self.logger.error(f"{error_msg}")
self.data.update({
"status": f"Error: {error_msg}",
"status": f"❌ 错误: 搅拌速度超出范围 ⚠️",
"operation_mode": "Error"
})
return False
if time_value <= 0:
error_msg = f"时间 {time_value}s 必须大于0"
self.logger.error(error_msg)
error_msg = f"时间 {time_value}s 必须大于0 ⚠️"
self.logger.error(f"{error_msg}")
self.data.update({
"status": f"Error: {error_msg}",
"status": f"❌ 错误: 时间参数无效 ⚠️",
"operation_mode": "Error"
})
return False
# 确定操作模式
if temp > 25.0:
operation_mode = "Heating"
status_action = "加热"
elif temp < 25.0:
operation_mode = "Cooling"
status_action = "冷却"
else:
operation_mode = "Maintaining"
status_action = "保温"
# 🔧 修复:使用转换后的时间值
start_time = time_module.time()
total_time = time_value # 使用转换后的浮点数
self.logger.info(f"🚀 开始{status_action}程序! 预计用时 {total_time:.1f}秒 ⏱️")
# 开始操作
stir_info = f" | 搅拌: {stir_speed} RPM" if stir else ""
stir_info = f" | 🌪️ 搅拌: {stir_speed} RPM" if stir else ""
self.data.update({
"status": f"运行中: {status_action} {vessel}{temp}°C | 剩余: {total_time:.0f}s{stir_info}",
"status": f"{temp_emoji} 运行中: {status_action} {vessel}{temp}°C | 剩余: {total_time:.0f}s{stir_info}",
"operation_mode": operation_mode,
"is_stirring": stir,
"stir_speed": stir_speed if stir else 0.0,
@@ -135,17 +155,25 @@ class VirtualHeatChill:
})
# 在等待过程中每秒更新剩余时间
last_logged_time = 0
while True:
current_time = time_module.time()
elapsed = current_time - start_time
remaining = max(0, total_time - elapsed)
progress = (elapsed / total_time) * 100 if total_time > 0 else 100
# 更新剩余时间和状态
self.data.update({
"remaining_time": remaining,
"status": f"运行中: {status_action} {vessel}{temp}°C | 剩余: {remaining:.0f}s{stir_info}"
"status": f"{temp_emoji} 运行中: {status_action} {vessel}{temp}°C | 剩余: {remaining:.0f}s{stir_info}",
"progress": progress
})
# 进度日志每25%打印一次)
if progress >= 25 and int(progress) % 25 == 0 and int(progress) != last_logged_time:
self.logger.info(f"📊 {status_action}进度: {progress:.0f}% | ⏰ 剩余: {remaining:.0f}s | {temp_emoji} 目标: {temp}°C ✨")
last_logged_time = int(progress)
# 如果时间到了,退出循环
if remaining <= 0:
break
@@ -154,20 +182,30 @@ class VirtualHeatChill:
await asyncio.sleep(1.0)
# 操作完成
final_stir_info = f" | 搅拌: {stir_speed} RPM" if stir else ""
final_stir_info = f" | 🌪️ 搅拌: {stir_speed} RPM" if stir else ""
self.data.update({
"status": f"完成: {vessel} 已达到 {temp}°C | 用时: {total_time:.0f}s{final_stir_info}",
"status": f"完成: {vessel} 已达到 {temp}°C {temp_emoji} | ⏱️ 用时: {total_time:.0f}s{final_stir_info}",
"operation_mode": "Completed",
"remaining_time": 0.0,
"is_stirring": False,
"stir_speed": 0.0
"stir_speed": 0.0,
"progress": 100.0
})
self.logger.info(f"HeatChill completed for vessel {vessel} at {temp}°C after {total_time}s")
self.logger.info(f"🎉 温控操作完成! ✨")
self.logger.info(f"📊 操作结果:")
self.logger.info(f" 🥽 容器: {vessel}")
self.logger.info(f" 🌡️ 达到温度: {temp}°C {temp_emoji}")
self.logger.info(f" ⏱️ 总用时: {total_time:.0f}s")
if stir:
self.logger.info(f" 🌪️ 搅拌速度: {stir_speed} RPM")
self.logger.info(f" 📝 操作目的: {purpose} 🏁")
return True
async def heat_chill_start(self, vessel: str, temp: float, purpose: str) -> bool:
"""Start continuous heat chill"""
"""Start continuous heat chill 🔄"""
# 🔧 添加类型转换
try:
@@ -176,73 +214,82 @@ class VirtualHeatChill:
purpose = str(purpose)
except (ValueError, TypeError) as e:
error_msg = f"参数类型转换错误: {str(e)}"
self.logger.error(error_msg)
self.logger.error(f"{error_msg}")
self.data.update({
"status": f"Error: {error_msg}",
"status": f"❌ 错误: {error_msg}",
"operation_mode": "Error"
})
return False
self.logger.info(f"HeatChillStart: vessel={vessel}, temp={temp}°C")
# 验证参数
if temp > self._max_temp or temp < self._min_temp:
error_msg = f"温度 {temp}°C 超出范围 ({self._min_temp}°C - {self._max_temp}°C)"
self.logger.error(error_msg)
self.data.update({
"status": f"Error: {error_msg}",
"operation_mode": "Error"
})
return False
# 确定操作模式
# 确定温度操作emoji
if temp > 25.0:
temp_emoji = "🔥"
operation_mode = "Heating"
status_action = "持续加热"
elif temp < 25.0:
temp_emoji = "❄️"
operation_mode = "Cooling"
status_action = "持续冷却"
else:
temp_emoji = "🌡️"
operation_mode = "Maintaining"
status_action = "恒温保持"
self.logger.info(f"🔄 启动持续温控: {vessel}{temp}°C {temp_emoji}")
self.logger.info(f" 🥽 容器: {vessel}")
self.logger.info(f" 🎯 目标温度: {temp}°C {temp_emoji}")
self.logger.info(f" 🔄 模式: {status_action}")
self.logger.info(f" 📝 目的: {purpose}")
# 验证参数
if temp > self._max_temp or temp < self._min_temp:
error_msg = f"🌡️ 温度 {temp}°C 超出范围 ({self._min_temp}°C - {self._max_temp}°C) ⚠️"
self.logger.error(f"{error_msg}")
self.data.update({
"status": f"❌ 错误: 温度超出范围 ⚠️",
"operation_mode": "Error"
})
return False
self.data.update({
"status": f"启动: {status_action} {vessel}{temp}°C | 持续运行",
"status": f"🔄 启动: {status_action} {vessel}{temp}°C {temp_emoji} | ♾️ 持续运行",
"operation_mode": operation_mode,
"is_stirring": False,
"stir_speed": 0.0,
"remaining_time": -1.0, # -1 表示持续运行
})
self.logger.info(f"✅ 持续温控已启动! {temp_emoji} {status_action}模式 🚀")
return True
async def heat_chill_stop(self, vessel: str) -> bool:
"""Stop heat chill"""
"""Stop heat chill 🛑"""
# 🔧 添加类型转换
try:
vessel = str(vessel)
except (ValueError, TypeError) as e:
error_msg = f"参数类型转换错误: {str(e)}"
self.logger.error(error_msg)
self.logger.error(f"{error_msg}")
return False
self.logger.info(f"HeatChillStop: vessel={vessel}")
self.logger.info(f"🛑 停止温控: {vessel}")
self.data.update({
"status": f"已停止: {vessel} 温控停止",
"status": f"🛑 已停止: {vessel} 温控停止",
"operation_mode": "Stopped",
"is_stirring": False,
"stir_speed": 0.0,
"remaining_time": 0.0,
})
self.logger.info(f"✅ 温控设备已停止 {vessel} 的温度控制 🏁")
return True
# 状态属性
@property
def status(self) -> str:
return self.data.get("status", "Idle")
return self.data.get("status", "🏠 待机中")
@property
def operation_mode(self) -> str:
@@ -258,4 +305,20 @@ class VirtualHeatChill:
@property
def remaining_time(self) -> float:
return self.data.get("remaining_time", 0.0)
return self.data.get("remaining_time", 0.0)
@property
def progress(self) -> float:
return self.data.get("progress", 0.0)
@property
def max_temp(self) -> float:
return self._max_temp
@property
def min_temp(self) -> float:
return self._min_temp
@property
def max_stir_speed(self) -> float:
return self._max_stir_speed

View File

@@ -1,16 +1,20 @@
import time
import logging
from typing import Union, Dict, Optional
class VirtualMultiwayValve:
"""
虚拟九通阀门 - 0号位连接transfer pump1-8号位连接其他设备
虚拟九通阀门 - 0号位连接transfer pump1-8号位连接其他设备 🔄
"""
def __init__(self, port: str = "VIRTUAL", positions: int = 8):
self.port = port
self.max_positions = positions # 1-8号位
self.total_positions = positions + 1 # 0-8号位共9个位置
# 添加日志记录器
self.logger = logging.getLogger(f"VirtualMultiwayValve.{port}")
# 状态属性
self._status = "Idle"
self._valve_state = "Ready"
@@ -29,6 +33,10 @@ class VirtualMultiwayValve:
7: "port_7", # 7号位
8: "port_8" # 8号位
}
print(f"🔄 === 虚拟多通阀门已创建 === ✨")
print(f"🎯 端口: {port} | 📊 位置范围: 0-{self.max_positions} | 🏠 初始位置: 0 (transfer_pump)")
self.logger.info(f"🔧 多通阀门初始化: 端口={port}, 最大位置={self.max_positions}")
@property
def status(self) -> str:
@@ -47,16 +55,16 @@ class VirtualMultiwayValve:
return self._target_position
def get_current_position(self) -> int:
"""获取当前阀门位置"""
"""获取当前阀门位置 📍"""
return self._current_position
def get_current_port(self) -> str:
"""获取当前连接的端口名称"""
"""获取当前连接的端口名称 🔌"""
return self.position_map.get(self._current_position, "unknown")
def set_position(self, command: Union[int, str]):
"""
设置阀门位置 - 支持0-8位置
设置阀门位置 - 支持0-8位置 🎯
Args:
command: 目标位置 (0-8) 或位置字符串
@@ -71,112 +79,162 @@ class VirtualMultiwayValve:
pos = int(command)
if pos < 0 or pos > self.max_positions:
raise ValueError(f"Position must be between 0 and {self.max_positions}")
error_msg = f"位置必须在 0-{self.max_positions} 范围内"
self.logger.error(f"{error_msg}: 请求位置={pos}")
raise ValueError(error_msg)
# 获取位置描述emoji
if pos == 0:
pos_emoji = "🚰"
pos_desc = "泵位置"
else:
pos_emoji = "🔌"
pos_desc = f"端口{pos}"
old_position = self._current_position
old_port = self.get_current_port()
self.logger.info(f"🔄 阀门切换: {old_position}({old_port}) → {pos}({self.position_map.get(pos, 'unknown')}) {pos_emoji}")
self._status = "Busy"
self._valve_state = "Moving"
self._target_position = pos
# 模拟阀门切换时间
switch_time = abs(self._current_position - pos) * 0.5 # 每个位置0.5
time.sleep(switch_time)
switch_time = abs(self._current_position - pos) * 0.5 # 每个位置0.1
if switch_time > 0:
self.logger.info(f"⏱️ 阀门移动中... 预计用时: {switch_time:.1f}秒 🔄")
time.sleep(switch_time)
self._current_position = pos
self._status = "Idle"
self._valve_state = "Ready"
current_port = self.get_current_port()
return f"Position set to {pos} ({current_port})"
success_msg = f"✅ 阀门已切换到位置 {pos} ({current_port}) {pos_emoji}"
self.logger.info(success_msg)
return success_msg
except ValueError as e:
error_msg = f"❌ 阀门切换失败: {str(e)}"
self._status = "Error"
self._valve_state = "Error"
return f"Error: {str(e)}"
self.logger.error(error_msg)
return error_msg
def set_to_pump_position(self):
"""切换到transfer pump位置0号位"""
"""切换到transfer pump位置0号位🚰"""
self.logger.info(f"🚰 切换到泵位置...")
return self.set_position(0)
def set_to_port(self, port_number: int):
"""
切换到指定端口位置
切换到指定端口位置 🔌
Args:
port_number: 端口号 (1-8)
"""
if port_number < 1 or port_number > self.max_positions:
raise ValueError(f"Port number must be between 1 and {self.max_positions}")
error_msg = f"端口号必须在 1-{self.max_positions} 范围内"
self.logger.error(f"{error_msg}: 请求端口={port_number}")
raise ValueError(error_msg)
self.logger.info(f"🔌 切换到端口 {port_number}...")
return self.set_position(port_number)
def open(self):
"""打开阀门 - 设置到transfer pump位置0号位"""
"""打开阀门 - 设置到transfer pump位置0号位🔓"""
self.logger.info(f"🔓 打开阀门,设置到泵位置...")
return self.set_to_pump_position()
def close(self):
"""关闭阀门 - 对于多通阀门,设置到一个"关闭"状态"""
"""关闭阀门 - 对于多通阀门,设置到一个"关闭"状态 🔒"""
self.logger.info(f"🔒 关闭阀门...")
self._status = "Busy"
self._valve_state = "Closing"
time.sleep(0.5)
# 可以选择保持当前位置或设置特殊关闭状态
self._status = "Idle"
self._valve_state = "Closed"
return f"Valve closed at position {self._current_position}"
close_msg = f"🔒 阀门已关闭,保持在位置 {self._current_position} ({self.get_current_port()})"
self.logger.info(close_msg)
return close_msg
def get_valve_position(self) -> int:
"""获取阀门位置 - 兼容性方法"""
"""获取阀门位置 - 兼容性方法 📍"""
return self._current_position
def is_at_position(self, position: int) -> bool:
"""检查是否在指定位置"""
return self._current_position == position
"""检查是否在指定位置 🎯"""
result = self._current_position == position
self.logger.debug(f"🎯 位置检查: 当前={self._current_position}, 目标={position}, 匹配={result}")
return result
def is_at_pump_position(self) -> bool:
"""检查是否在transfer pump位置"""
return self._current_position == 0
"""检查是否在transfer pump位置 🚰"""
result = self._current_position == 0
pump_status = "" if result else ""
self.logger.debug(f"🚰 泵位置检查: {pump_status} (当前位置: {self._current_position})")
return result
def is_at_port(self, port_number: int) -> bool:
"""检查是否在指定端口位置"""
return self._current_position == port_number
"""检查是否在指定端口位置 🔌"""
result = self._current_position == port_number
port_status = "" if result else ""
self.logger.debug(f"🔌 端口{port_number}检查: {port_status} (当前位置: {self._current_position})")
return result
def get_available_positions(self) -> list:
"""获取可用位置列表"""
return list(range(0, self.max_positions + 1))
"""获取可用位置列表 📋"""
positions = list(range(0, self.max_positions + 1))
self.logger.debug(f"📋 可用位置: {positions}")
return positions
def get_available_ports(self) -> Dict[int, str]:
"""获取可用端口映射"""
"""获取可用端口映射 🗺️"""
self.logger.debug(f"🗺️ 端口映射: {self.position_map}")
return self.position_map.copy()
def reset(self):
"""重置阀门到transfer pump位置0号位"""
"""重置阀门到transfer pump位置0号位🔄"""
self.logger.info(f"🔄 重置阀门到泵位置...")
return self.set_position(0)
def switch_between_pump_and_port(self, port_number: int):
"""
在transfer pump位置和指定端口之间切换
在transfer pump位置和指定端口之间切换 🔄
Args:
port_number: 目标端口号 (1-8)
"""
if self._current_position == 0:
# 当前在pump位置切换到指定端口
self.logger.info(f"🔄 从泵位置切换到端口 {port_number}...")
return self.set_to_port(port_number)
else:
# 当前在某个端口切换到pump位置
self.logger.info(f"🔄 从端口 {self._current_position} 切换到泵位置...")
return self.set_to_pump_position()
def get_flow_path(self) -> str:
"""获取当前流路路径描述"""
"""获取当前流路路径描述 🌊"""
current_port = self.get_current_port()
if self._current_position == 0:
return f"Transfer pump connected (position {self._current_position})"
flow_path = f"🚰 转移泵已连接 (位置 {self._current_position})"
else:
return f"Port {self._current_position} connected ({current_port})"
flow_path = f"🔌 端口 {self._current_position} 已连接 ({current_port})"
self.logger.debug(f"🌊 当前流路: {flow_path}")
return flow_path
def get_info(self) -> dict:
"""获取阀门详细信息"""
return {
"""获取阀门详细信息 📊"""
info = {
"port": self.port,
"max_positions": self.max_positions,
"total_positions": self.total_positions,
@@ -188,18 +246,25 @@ class VirtualMultiwayValve:
"flow_path": self.get_flow_path(),
"position_map": self.position_map
}
self.logger.debug(f"📊 阀门信息: 位置={self._current_position}, 状态={self._status}, 端口={self.get_current_port()}")
return info
def __str__(self):
return f"VirtualMultiwayValve(Position: {self._current_position}/{self.max_positions}, Port: {self.get_current_port()}, Status: {self._status})"
current_port = self.get_current_port()
status_emoji = "" if self._status == "Idle" else "🔄" if self._status == "Busy" else ""
return f"🔄 VirtualMultiwayValve({status_emoji} 位置: {self._current_position}/{self.max_positions}, 端口: {current_port}, 状态: {self._status})"
def set_valve_position(self, command: Union[int, str]):
"""
设置阀门位置 - 兼容pump_protocol调用
设置阀门位置 - 兼容pump_protocol调用 🎯
这是set_position的别名方法用于兼容pump_protocol.py
Args:
command: 目标位置 (0-8) 或位置字符串
"""
self.logger.debug(f"🎯 兼容性调用: set_valve_position({command})")
return self.set_position(command)
@@ -207,25 +272,35 @@ class VirtualMultiwayValve:
if __name__ == "__main__":
valve = VirtualMultiwayValve()
print("=== 虚拟九通阀门测试 ===")
print(f"初始状态: {valve}")
print(f"当前流路: {valve.get_flow_path()}")
print("🔄 === 虚拟九通阀门测试 ===")
print(f"🏠 初始状态: {valve}")
print(f"🌊 当前流路: {valve.get_flow_path()}")
# 切换到试剂瓶11号位
print(f"\n切换到1号位: {valve.set_position(1)}")
print(f"当前状态: {valve}")
print(f"\n🔌 切换到1号位: {valve.set_position(1)}")
print(f"📍 当前状态: {valve}")
# 切换到transfer pump位置0号位
print(f"\n切换到pump位置: {valve.set_to_pump_position()}")
print(f"当前状态: {valve}")
print(f"\n🚰 切换到pump位置: {valve.set_to_pump_position()}")
print(f"📍 当前状态: {valve}")
# 切换到试剂瓶22号位
print(f"\n切换到2号位: {valve.set_to_port(2)}")
print(f"当前状态: {valve}")
print(f"\n🔌 切换到2号位: {valve.set_to_port(2)}")
print(f"📍 当前状态: {valve}")
# 显示所有可用位置
print(f"\n可用位置: {valve.get_available_positions()}")
print(f"端口映射: {valve.get_available_ports()}")
print(f"\n📋 可用位置: {valve.get_available_positions()}")
print(f"🗺️ 端口映射: {valve.get_available_ports()}")
# 获取详细信息
print(f"\n详细信息: {valve.get_info()}")
print(f"\n📊 详细信息: {valve.get_info()}")
# 测试切换功能
print(f"\n🔄 智能切换测试:")
print(f"当前位置: {valve._current_position}")
print(f"切换结果: {valve.switch_between_pump_and_port(3)}")
print(f"新位置: {valve._current_position}")
# 重置测试
print(f"\n🔄 重置测试: {valve.reset()}")
print(f"📍 重置后状态: {valve}")

View File

@@ -4,11 +4,11 @@ import time as time_module
from typing import Dict, Any, Optional
def debug_print(message):
"""调试输出"""
print(f"[ROTAVAP] {message}", flush=True)
"""调试输出 🔍"""
print(f"🌪️ [ROTAVAP] {message}", flush=True)
class VirtualRotavap:
"""Virtual rotary evaporator device - 简化版,只保留核心功能"""
"""Virtual rotary evaporator device - 简化版,只保留核心功能 🌪️"""
def __init__(self, device_id: Optional[str] = None, config: Optional[Dict[str, Any]] = None, **kwargs):
# 处理可能的不同调用方式
@@ -35,13 +35,16 @@ class VirtualRotavap:
if key not in skip_keys and not hasattr(self, key):
setattr(self, key, value)
print(f"🌪️ === 虚拟旋转蒸发仪 {self.device_id} 已创建 === ✨")
print(f"🔥 温度范围: 10°C ~ {self._max_temp}°C | 🌀 转速范围: 10 ~ {self._max_rotation_speed} RPM")
async def initialize(self) -> bool:
"""Initialize virtual rotary evaporator"""
self.logger.info(f"Initializing virtual rotary evaporator {self.device_id}")
"""Initialize virtual rotary evaporator 🚀"""
self.logger.info(f"🔧 初始化虚拟旋转蒸发仪 {self.device_id}")
# 只保留核心状态
self.data.update({
"status": "Idle",
"status": "🏠 待机中",
"rotavap_state": "Ready", # Ready, Evaporating, Completed, Error
"current_temp": 25.0,
"target_temp": 25.0,
@@ -50,22 +53,27 @@ class VirtualRotavap:
"evaporated_volume": 0.0,
"progress": 0.0,
"remaining_time": 0.0,
"message": "Ready for evaporation"
"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")
return True
async def cleanup(self) -> bool:
"""Cleanup virtual rotary evaporator"""
self.logger.info(f"Cleaning up virtual rotary evaporator {self.device_id}")
"""Cleanup virtual rotary evaporator 🧹"""
self.logger.info(f"🧹 清理虚拟旋转蒸发仪 {self.device_id} 🔚")
self.data.update({
"status": "Offline",
"status": "💤 离线",
"rotavap_state": "Offline",
"current_temp": 25.0,
"rotation_speed": 0.0,
"vacuum_pressure": 1.0,
"message": "System offline"
"message": "💤 System offline"
})
self.logger.info(f"✅ 旋转蒸发仪 {self.device_id} 清理完成 💤")
return True
async def evaporate(
@@ -78,37 +86,44 @@ class VirtualRotavap:
solvent: str = "",
**kwargs
) -> bool:
"""Execute evaporate action - 简化版"""
"""Execute evaporate action - 简化版 🌪️"""
# 🔧 简化处理如果vessel就是设备自己直接操作
if vessel == self.device_id:
debug_print(f"在设备 {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}")
self.logger.info(f"🧪 识别到溶剂: {solvent}")
# 根据溶剂调整参数
solvent_lower = solvent.lower()
if any(s in solvent_lower for s in ['water', 'aqueous']):
temp = max(temp, 80.0)
pressure = max(pressure, 0.2)
self.logger.info("水系溶剂:调整参数")
self.logger.info(f"💧 水系溶剂:调整参数 → 温度 {temp}°C, 压力 {pressure} bar")
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("易挥发溶剂:调整参数")
self.logger.info(f"易挥发溶剂:调整参数 → 温度 {temp}°C, 压力 {pressure} bar")
self.logger.info(f"Evaporate: vessel={actual_vessel}, pressure={pressure} bar, temp={temp}°C, time={time}s, rotation={stir_speed} RPM, solvent={solvent}")
self.logger.info(f"🌪️ 开始蒸发操作: {actual_vessel}")
self.logger.info(f" 🥽 容器: {actual_vessel}")
self.logger.info(f" 🌡️ 温度: {temp}°C")
self.logger.info(f" 💨 真空度: {pressure} bar")
self.logger.info(f" ⏰ 时间: {time}s")
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(error_msg)
error_msg = f"🌡️ 温度 {temp}°C 超出范围 (10-{self._max_temp}°C) ⚠️"
self.logger.error(f"{error_msg}")
self.data.update({
"status": f"Error: {error_msg}",
"status": f"❌ 错误: 温度超出范围",
"rotavap_state": "Error",
"current_temp": 25.0,
"progress": 0.0,
@@ -118,10 +133,10 @@ class VirtualRotavap:
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(error_msg)
error_msg = f"🌀 旋转速度 {stir_speed} RPM 超出范围 (10-{self._max_rotation_speed} RPM) ⚠️"
self.logger.error(f"{error_msg}")
self.data.update({
"status": f"Error: {error_msg}",
"status": f"❌ 错误: 转速超出范围",
"rotavap_state": "Error",
"current_temp": 25.0,
"progress": 0.0,
@@ -131,10 +146,10 @@ class VirtualRotavap:
return False
if pressure < 0.01 or pressure > 1.0:
error_msg = f"真空度 {pressure} bar 超出范围 (0.01-1.0 bar)"
self.logger.error(error_msg)
error_msg = f"💨 真空度 {pressure} bar 超出范围 (0.01-1.0 bar) ⚠️"
self.logger.error(f"{error_msg}")
self.data.update({
"status": f"Error: {error_msg}",
"status": f"❌ 错误: 压力超出范围",
"rotavap_state": "Error",
"current_temp": 25.0,
"progress": 0.0,
@@ -144,8 +159,10 @@ class VirtualRotavap:
return False
# 开始蒸发
self.logger.info(f"🚀 启动蒸发程序! 预计用时 {time/60:.1f}分钟 ⏱️")
self.data.update({
"status": f"蒸发中: {actual_vessel}",
"status": f"🌪️ 蒸发中: {actual_vessel}",
"rotavap_state": "Evaporating",
"current_temp": temp,
"target_temp": temp,
@@ -154,13 +171,14 @@ class VirtualRotavap:
"remaining_time": time,
"progress": 0.0,
"evaporated_volume": 0.0,
"message": f"Evaporating {actual_vessel} at {temp}°C, {pressure} bar, {stir_speed} RPM"
"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()
@@ -168,19 +186,31 @@ class VirtualRotavap:
remaining = max(0, total_time - elapsed)
progress = min(100.0, (elapsed / total_time) * 100)
# 模拟蒸发体积
evaporated_vol = progress * 0.8 # 假设最多蒸发80mL
# 模拟蒸发体积 - 根据溶剂类型调整
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']):
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": f"蒸发中: {actual_vessel} | {temp}°C | {pressure} bar | {progress:.1f}% | 剩余: {remaining:.0f}s",
"message": f"Evaporating: {progress:.1f}% complete, {remaining:.0f}s 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 ✨")
last_logged_progress = int(progress)
# 时间到了,退出循环
if remaining <= 0:
break
@@ -189,42 +219,59 @@ class VirtualRotavap:
await asyncio.sleep(1.0)
# 蒸发完成
final_evaporated = 80.0
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']):
final_evaporated = 100.0 # 易挥发溶剂
else:
final_evaporated = 80.0 # 默认
self.data.update({
"status": f"蒸发完成: {actual_vessel} | 蒸发量: {final_evaporated:.1f}mL",
"status": f"蒸发完成: {actual_vessel} | 💧 蒸发量: {final_evaporated:.1f}mL",
"rotavap_state": "Completed",
"evaporated_volume": final_evaporated,
"progress": 100.0,
"current_temp": temp, # 保持温度信息
"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}"
"rotation_speed": 0.0,
"vacuum_pressure": 1.0,
"message": f"Evaporation completed: {final_evaporated}mL evaporated from {actual_vessel}"
})
self.logger.info(f"Evaporation completed: {final_evaporated}mL evaporated from {actual_vessel}")
self.logger.info(f"🎉 蒸发操作完成! ✨")
self.logger.info(f"📊 蒸发结果:")
self.logger.info(f" 🥽 容器: {actual_vessel}")
self.logger.info(f" 💧 蒸发量: {final_evaporated:.1f}mL")
self.logger.info(f" 🌡️ 蒸发温度: {temp}°C")
self.logger.info(f" 💨 真空度: {pressure} bar")
self.logger.info(f" 🌀 旋转速度: {stir_speed} RPM")
self.logger.info(f" ⏱️ 总用时: {total_time:.0f}s")
if solvent:
self.logger.info(f" 🧪 处理溶剂: {solvent} 🏁")
return True
except Exception as e:
# 出错处理
self.logger.error(f"Error during evaporation: {str(e)}")
error_msg = f"蒸发过程中发生错误: {str(e)} 💥"
self.logger.error(f"{error_msg}")
self.data.update({
"status": f"蒸发错误: {str(e)}",
"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)}"
"message": f"Evaporation failed: {str(e)}"
})
return False
# === 核心状态属性 ===
@property
def status(self) -> str:
return self.data.get("status", "Unknown")
return self.data.get("status", "Unknown")
@property
def rotavap_state(self) -> str:

View File

@@ -5,12 +5,12 @@ from typing import Dict, Any, Optional
class VirtualSolidDispenser:
"""
虚拟固体粉末加样器 - 用于处理 Add Protocol 中的固体试剂添加
虚拟固体粉末加样器 - 用于处理 Add Protocol 中的固体试剂添加 ⚗️
特点:
- 高兼容性:缺少参数不报错
- 智能识别:自动查找固体试剂瓶
- 简单反馈:成功/失败 + 消息
- 高兼容性:缺少参数不报错
- 智能识别:自动查找固体试剂瓶 🔍
- 简单反馈:成功/失败 + 消息 📊
"""
def __init__(self, device_id: str = None, config: Dict[str, Any] = None, **kwargs):
@@ -29,26 +29,30 @@ class VirtualSolidDispenser:
self.logger = logging.getLogger(f"VirtualSolidDispenser.{self.device_id}")
print(f"=== VirtualSolidDispenser {self.device_id} 创建成功! ===")
print(f"=== 最大容量: {self.max_capacity}g, 精度: {self.precision}g ===")
print(f"⚗️ === 虚拟固体分配器 {self.device_id} 创建成功! ===")
print(f"📊 设备规格: 最大容量 {self.max_capacity}g | 精度 {self.precision}g 🎯")
async def initialize(self) -> bool:
"""初始化固体加样器"""
self.logger.info(f"初始化固体加样{self.device_id}")
"""初始化固体加样器 🚀"""
self.logger.info(f"🔧 初始化固体分配{self.device_id}")
self._status = "Ready"
self._current_reagent = ""
self._dispensed_amount = 0.0
self.logger.info(f"✅ 固体分配器 {self.device_id} 初始化完成 ⚗️")
return True
async def cleanup(self) -> bool:
"""清理固体加样器"""
self.logger.info(f"清理固体加样{self.device_id}")
"""清理固体加样器 🧹"""
self.logger.info(f"🧹 清理固体分配{self.device_id} 🔚")
self._status = "Idle"
self.logger.info(f"✅ 固体分配器 {self.device_id} 清理完成 💤")
return True
def parse_mass_string(self, mass_str: str) -> float:
"""
解析质量字符串为数值 (g)
解析质量字符串为数值 (g) ⚖️
支持格式: "2.9 g", "19.3g", "4.5 mg", "1.2 kg"
"""
@@ -63,6 +67,7 @@ class VirtualSolidDispenser:
match = re.search(pattern, mass_clean)
if not match:
self.logger.debug(f"🔍 无法解析质量字符串: {mass_str}")
return 0.0
try:
@@ -87,15 +92,18 @@ class VirtualSolidDispenser:
}
multiplier = unit_multipliers.get(unit, 1.0)
return value * multiplier
result = value * multiplier
self.logger.debug(f"⚖️ 质量解析: {mass_str}{result:.6f}g (原值: {value} {unit})")
return result
except (ValueError, TypeError):
self.logger.warning(f"无法解析质量字符串: {mass_str}")
self.logger.warning(f"⚠️ 无法解析质量字符串: {mass_str}")
return 0.0
def parse_mol_string(self, mol_str: str) -> float:
"""
解析摩尔数字符串为数值 (mol)
解析摩尔数字符串为数值 (mol) 🧮
支持格式: "0.12 mol", "16.2 mmol", "25.2mmol"
"""
@@ -110,6 +118,7 @@ class VirtualSolidDispenser:
match = re.search(pattern, mol_clean)
if not match:
self.logger.debug(f"🔍 无法解析摩尔数字符串: {mol_str}")
return 0.0
try:
@@ -118,21 +127,25 @@ class VirtualSolidDispenser:
# 单位转换为 mol
if unit == 'mmol':
return value * 0.001
result = value * 0.001
else: # mol
return value
result = value
self.logger.debug(f"🧮 摩尔数解析: {mol_str}{result:.6f}mol (原值: {value} {unit})")
return result
except (ValueError, TypeError):
self.logger.warning(f"无法解析摩尔数字符串: {mol_str}")
self.logger.warning(f"⚠️ 无法解析摩尔数字符串: {mol_str}")
return 0.0
def find_solid_reagent_bottle(self, reagent_name: str) -> str:
"""
查找固体试剂瓶
查找固体试剂瓶 🔍
这是一个简化版本,实际使用时应该连接到系统的设备图
"""
if not reagent_name:
self.logger.debug(f"🔍 未指定试剂名称,使用默认瓶")
return "unknown_solid_bottle"
# 可能的固体试剂瓶命名模式
@@ -146,7 +159,9 @@ class VirtualSolidDispenser:
]
# 这里简化处理,实际应该查询设备图
return possible_names[0]
selected_bottle = possible_names[0]
self.logger.debug(f"🔍 为试剂 {reagent_name} 选择试剂瓶: {selected_bottle}")
return selected_bottle
async def add_solid(
self,
@@ -158,7 +173,7 @@ class VirtualSolidDispenser:
**kwargs # 兼容额外参数
) -> Dict[str, Any]:
"""
添加固体试剂的主要方法
添加固体试剂的主要方法 ⚗️
Args:
vessel: 目标容器
@@ -172,22 +187,24 @@ class VirtualSolidDispenser:
Dict: 操作结果
"""
try:
self.logger.info(f"=== 开始固体加样操作 ===")
self.logger.info(f"目标容器: {vessel}")
self.logger.info(f"试剂: {reagent}")
self.logger.info(f"质量: {mass}")
self.logger.info(f"摩尔数: {mol}")
self.logger.info(f"目的: {purpose}")
self.logger.info(f"⚗️ === 开始固体加样操作 ===")
self.logger.info(f" 🥽 目标容器: {vessel}")
self.logger.info(f" 🧪 试剂: {reagent}")
self.logger.info(f" ⚖️ 质量: {mass}")
self.logger.info(f" 🧮 摩尔数: {mol}")
self.logger.info(f" 📝 目的: {purpose}")
# 参数验证 - 宽松处理
if not vessel:
vessel = "main_reactor" # 默认容器
self.logger.warning(f"未指定容器,使用默认容器: {vessel}")
self.logger.warning(f"⚠️ 未指定容器,使用默认容器: {vessel} 🏠")
if not reagent:
error_msg = "❌ 错误: 必须指定试剂名称"
self.logger.error(error_msg)
return {
"success": False,
"message": "错误: 必须指定试剂名称",
"message": error_msg,
"return_info": "missing_reagent"
}
@@ -195,36 +212,41 @@ class VirtualSolidDispenser:
mass_value = self.parse_mass_string(mass)
mol_value = self.parse_mol_string(mol)
self.logger.info(f"解析后 - 质量: {mass_value}g, 摩尔数: {mol_value}mol")
self.logger.info(f"📊 解析结果 - 质量: {mass_value:.6f}g | 摩尔数: {mol_value:.6f}mol")
# 确定实际加样量
if mass_value > 0:
actual_amount = mass_value
amount_unit = "g"
self.logger.info(f"按质量加样: {actual_amount} {amount_unit}")
amount_emoji = "⚖️"
self.logger.info(f"⚖️ 按质量加样: {actual_amount:.6f} {amount_unit}")
elif mol_value > 0:
# 简化处理假设分子量为100 g/mol
assumed_mw = 100.0
actual_amount = mol_value * assumed_mw
amount_unit = "g (from mol)"
self.logger.info(f"按摩尔数加样: {mol_value} mol → {actual_amount} g (假设分子量 {assumed_mw})")
amount_emoji = "🧮"
self.logger.info(f"🧮 按摩尔数加样: {mol_value:.6f} mol → {actual_amount:.6f} g (假设分子量 {assumed_mw})")
else:
# 没有指定量,使用默认值
actual_amount = 1.0
amount_unit = "g (default)"
self.logger.warning(f"未指定质量或摩尔数,使用默认值: {actual_amount} {amount_unit}")
amount_emoji = "🎯"
self.logger.warning(f"⚠️ 未指定质量或摩尔数,使用默认值: {actual_amount} {amount_unit} 🎯")
# 检查容量限制
if actual_amount > self.max_capacity:
error_msg = f"❌ 错误: 请求量 {actual_amount:.3f}g 超过最大容量 {self.max_capacity}g"
self.logger.error(error_msg)
return {
"success": False,
"message": f"错误: 请求量 {actual_amount}g 超过最大容量 {self.max_capacity}g",
"message": error_msg,
"return_info": "exceeds_capacity"
}
# 查找试剂瓶
reagent_bottle = self.find_solid_reagent_bottle(reagent)
self.logger.info(f"使用试剂瓶: {reagent_bottle}")
self.logger.info(f"🔍 使用试剂瓶: {reagent_bottle}")
# 模拟加样过程
self._status = "Dispensing"
@@ -233,8 +255,17 @@ class VirtualSolidDispenser:
# 计算操作时间 (基于质量)
operation_time = max(0.5, actual_amount * 0.1) # 每克0.1秒最少0.5秒
self.logger.info(f"开始加样,预计时间: {operation_time:.1f}")
await asyncio.sleep(operation_time)
self.logger.info(f"🚀 开始加样,预计时间: {operation_time:.1f} ⏱️")
# 显示进度的模拟
steps = max(3, int(operation_time))
step_time = operation_time / steps
for i in range(steps):
progress = (i + 1) / steps * 100
await asyncio.sleep(step_time)
if i % 2 == 0: # 每隔一步显示进度
self.logger.debug(f"📊 加样进度: {progress:.0f}% | {amount_emoji} 正在分配 {reagent}...")
# 更新状态
self._dispensed_amount = actual_amount
@@ -242,19 +273,26 @@ class VirtualSolidDispenser:
self._status = "Ready"
# 成功结果
success_message = f"成功添加 {reagent} {actual_amount:.3f} {amount_unit}{vessel}"
success_message = f"成功添加 {reagent} {actual_amount:.6f} {amount_unit}{vessel}"
self.logger.info(f"=== 固体加样完成 ===")
self.logger.info(success_message)
self.logger.info(f"🎉 === 固体加样完成 ===")
self.logger.info(f"📊 操作结果:")
self.logger.info(f"{success_message}")
self.logger.info(f" 🧪 试剂瓶: {reagent_bottle}")
self.logger.info(f" ⏱️ 用时: {operation_time:.1f}")
self.logger.info(f" 🎯 总操作次数: {self._total_operations} 🏁")
return {
"success": True,
"message": success_message,
"return_info": f"dispensed_{actual_amount:.3f}g"
"return_info": f"dispensed_{actual_amount:.6f}g",
"dispensed_amount": actual_amount,
"reagent": reagent,
"vessel": vessel
}
except Exception as e:
error_message = f"固体加样失败: {str(e)}"
error_message = f"固体加样失败: {str(e)} 💥"
self.logger.error(error_message)
self._status = "Error"
@@ -282,8 +320,8 @@ class VirtualSolidDispenser:
return self._total_operations
def get_device_info(self) -> Dict[str, Any]:
"""获取设备状态信息"""
return {
"""获取设备状态信息 📊"""
info = {
"device_id": self.device_id,
"status": self._status,
"current_reagent": self._current_reagent,
@@ -292,43 +330,59 @@ class VirtualSolidDispenser:
"max_capacity": self.max_capacity,
"precision": self.precision
}
self.logger.debug(f"📊 设备信息: 状态={self._status}, 试剂={self._current_reagent}, 加样量={self._dispensed_amount:.6f}g")
return info
def __str__(self):
return f"VirtualSolidDispenser({self.device_id}: {self._status}, 最后加样 {self._dispensed_amount:.3f}g)"
status_emoji = "" if self._status == "Ready" else "🔄" if self._status == "Dispensing" else "" if self._status == "Error" else "🏠"
return f"⚗️ VirtualSolidDispenser({status_emoji} {self.device_id}: {self._status}, 最后加样 {self._dispensed_amount:.3f}g)"
# 测试函数
async def test_solid_dispenser():
"""测试固体加样器"""
print("=== 固体加样器测试 ===")
"""测试固体加样器 🧪"""
print("⚗️ === 固体加样器测试开始 === 🧪")
dispenser = VirtualSolidDispenser("test_dispenser")
await dispenser.initialize()
# 测试1: 按质量加样
print(f"\n🧪 测试1: 按质量加样...")
result1 = await dispenser.add_solid(
vessel="main_reactor",
reagent="magnesium",
mass="2.9 g"
)
print(f"测试1结果: {result1}")
print(f"📊 测试1结果: {result1}")
# 测试2: 按摩尔数加样
print(f"\n🧮 测试2: 按摩尔数加样...")
result2 = await dispenser.add_solid(
vessel="main_reactor",
reagent="sodium_nitrite",
mol="0.28 mol"
)
print(f"测试2结果: {result2}")
print(f"📊 测试2结果: {result2}")
# 测试3: 缺少参数
print(f"\n⚠️ 测试3: 缺少参数测试...")
result3 = await dispenser.add_solid(
reagent="test_compound"
)
print(f"测试3结果: {result3}")
print(f"📊 测试3结果: {result3}")
print(f"设备信息: {dispenser.get_device_info()}")
print("=== 测试完成 ===")
# 测试4: 超容量测试
print(f"\n❌ 测试4: 超容量测试...")
result4 = await dispenser.add_solid(
vessel="main_reactor",
reagent="heavy_compound",
mass="150 g" # 超过100g限制
)
print(f"📊 测试4结果: {result4}")
print(f"\n📊 最终设备信息: {dispenser.get_device_info()}")
print(f"✅ === 测试完成 === 🎉")
if __name__ == "__main__":

View File

@@ -4,7 +4,7 @@ import time as time_module
from typing import Dict, Any
class VirtualStirrer:
"""Virtual stirrer device for StirProtocol testing - 功能完整版"""
"""Virtual stirrer device for StirProtocol testing - 功能完整版 🌪️"""
def __init__(self, device_id: str = None, config: Dict[str, Any] = None, **kwargs):
# 处理可能的不同调用方式
@@ -30,45 +30,69 @@ class VirtualStirrer:
for key, value in kwargs.items():
if key not in skip_keys and not hasattr(self, key):
setattr(self, key, value)
print(f"🌪️ === 虚拟搅拌器 {self.device_id} 已创建 === ✨")
print(f"🔧 速度范围: {self._min_speed} ~ {self._max_speed} RPM | 📱 端口: {self.port}")
async def initialize(self) -> bool:
"""Initialize virtual stirrer"""
self.logger.info(f"Initializing virtual stirrer {self.device_id}")
"""Initialize virtual stirrer 🚀"""
self.logger.info(f"🔧 初始化虚拟搅拌器 {self.device_id}")
# 初始化状态信息
self.data.update({
"status": "Idle",
"status": "🏠 待机中",
"operation_mode": "Idle", # 操作模式: Idle, Stirring, Settling, Completed, Error
"current_vessel": "", # 当前搅拌的容器
"current_speed": 0.0, # 当前搅拌速度
"is_stirring": False, # 是否正在搅拌
"remaining_time": 0.0, # 剩余时间
})
self.logger.info(f"✅ 搅拌器 {self.device_id} 初始化完成 🌪️")
self.logger.info(f"📊 设备规格: 速度范围 {self._min_speed} ~ {self._max_speed} RPM")
return True
async def cleanup(self) -> bool:
"""Cleanup virtual stirrer"""
self.logger.info(f"Cleaning up virtual stirrer {self.device_id}")
"""Cleanup virtual stirrer 🧹"""
self.logger.info(f"🧹 清理虚拟搅拌器 {self.device_id} 🔚")
self.data.update({
"status": "Offline",
"status": "💤 离线",
"operation_mode": "Offline",
"current_vessel": "",
"current_speed": 0.0,
"is_stirring": False,
"remaining_time": 0.0,
})
self.logger.info(f"✅ 搅拌器 {self.device_id} 清理完成 💤")
return True
async def stir(self, stir_time: float, stir_speed: float, settling_time: float) -> bool:
"""Execute stir action - 定时搅拌 + 沉降"""
self.logger.info(f"Stir: speed={stir_speed} RPM, time={stir_time}s, settling={settling_time}s")
async def stir(self, stir_time: float, stir_speed: float, settling_time: float, **kwargs) -> bool:
"""Execute stir action - 定时搅拌 + 沉降 🌪️"""
# 🔧 类型转换 - 确保所有参数都是数字类型
try:
stir_time = float(stir_time)
stir_speed = float(stir_speed)
settling_time = float(settling_time)
except (ValueError, TypeError) as e:
error_msg = f"参数类型转换失败: stir_time={stir_time}, stir_speed={stir_speed}, settling_time={settling_time}, error={e}"
self.logger.error(f"{error_msg}")
self.data.update({
"status": f"❌ 错误: {error_msg}",
"operation_mode": "Error"
})
return False
self.logger.info(f"🌪️ 开始搅拌操作: 速度 {stir_speed} RPM | 时间 {stir_time}s | 沉降 {settling_time}s")
# 验证参数
if stir_speed > self._max_speed or stir_speed < self._min_speed:
error_msg = f"搅拌速度 {stir_speed} RPM 超出范围 ({self._min_speed} - {self._max_speed} RPM)"
self.logger.error(error_msg)
error_msg = f"🌪️ 搅拌速度 {stir_speed} RPM 超出范围 ({self._min_speed} - {self._max_speed} RPM) ⚠️"
self.logger.error(f"{error_msg}")
self.data.update({
"status": f"Error: {error_msg}",
"status": f"❌ 错误: 速度超出范围",
"operation_mode": "Error"
})
return False
@@ -77,8 +101,10 @@ class VirtualStirrer:
start_time = time_module.time()
total_stir_time = stir_time
self.logger.info(f"🚀 开始搅拌阶段: {stir_speed} RPM × {total_stir_time}s ⏱️")
self.data.update({
"status": f"搅拌中: {stir_speed} RPM | 剩余: {total_stir_time:.0f}s",
"status": f"🌪️ 搅拌中: {stir_speed} RPM | 剩余: {total_stir_time:.0f}s",
"operation_mode": "Stirring",
"current_speed": stir_speed,
"is_stirring": True,
@@ -86,30 +112,41 @@ class VirtualStirrer:
})
# 搅拌过程 - 实时更新剩余时间
last_logged_time = 0
while True:
current_time = time_module.time()
elapsed = current_time - start_time
remaining = max(0, total_stir_time - elapsed)
progress = (elapsed / total_stir_time) * 100 if total_stir_time > 0 else 100
# 更新状态
self.data.update({
"remaining_time": remaining,
"status": f"搅拌中: {stir_speed} RPM | 剩余: {remaining:.0f}s"
"status": f"🌪️ 搅拌中: {stir_speed} RPM | 剩余: {remaining:.0f}s"
})
# 进度日志每25%打印一次)
if progress >= 25 and int(progress) % 25 == 0 and int(progress) != last_logged_time:
self.logger.info(f"📊 搅拌进度: {progress:.0f}% | 🌪️ {stir_speed} RPM | ⏰ 剩余: {remaining:.0f}s ✨")
last_logged_time = int(progress)
# 搅拌时间到了
if remaining <= 0:
break
await asyncio.sleep(1.0)
self.logger.info(f"✅ 搅拌阶段完成! 🌪️ {stir_speed} RPM × {stir_time}s")
# === 第二阶段:沉降(如果需要)===
if settling_time > 0:
start_settling_time = time_module.time()
total_settling_time = settling_time
self.logger.info(f"🛑 开始沉降阶段: 停止搅拌 × {total_settling_time}s ⏱️")
self.data.update({
"status": f"沉降中: 停止搅拌 | 剩余: {total_settling_time:.0f}s",
"status": f"🛑 沉降中: 停止搅拌 | 剩余: {total_settling_time:.0f}s",
"operation_mode": "Settling",
"current_speed": 0.0,
"is_stirring": False,
@@ -117,52 +154,87 @@ class VirtualStirrer:
})
# 沉降过程 - 实时更新剩余时间
last_logged_settling = 0
while True:
current_time = time_module.time()
elapsed = current_time - start_settling_time
remaining = max(0, total_settling_time - elapsed)
progress = (elapsed / total_settling_time) * 100 if total_settling_time > 0 else 100
# 更新状态
self.data.update({
"remaining_time": remaining,
"status": f"沉降中: 停止搅拌 | 剩余: {remaining:.0f}s"
"status": f"🛑 沉降中: 停止搅拌 | 剩余: {remaining:.0f}s"
})
# 进度日志每25%打印一次)
if progress >= 25 and int(progress) % 25 == 0 and int(progress) != last_logged_settling:
self.logger.info(f"📊 沉降进度: {progress:.0f}% | 🛑 静置中 | ⏰ 剩余: {remaining:.0f}s ✨")
last_logged_settling = int(progress)
# 沉降时间到了
if remaining <= 0:
break
await asyncio.sleep(1.0)
self.logger.info(f"✅ 沉降阶段完成! 🛑 静置 {settling_time}s")
# === 操作完成 ===
settling_info = f" | 沉降: {settling_time:.0f}s" if settling_time > 0 else ""
settling_info = f" | 🛑 沉降: {settling_time:.0f}s" if settling_time > 0 else ""
self.data.update({
"status": f"完成: 搅拌 {stir_speed} RPM, {stir_time:.0f}s{settling_info}",
"status": f"完成: 🌪️ 搅拌 {stir_speed} RPM × {stir_time:.0f}s{settling_info}",
"operation_mode": "Completed",
"current_speed": 0.0,
"is_stirring": False,
"remaining_time": 0.0,
})
self.logger.info(f"Stir completed: {stir_speed} RPM for {stir_time}s + settling {settling_time}s")
self.logger.info(f"🎉 搅拌操作完成! ✨")
self.logger.info(f"📊 操作总结:")
self.logger.info(f" 🌪️ 搅拌: {stir_speed} RPM × {stir_time}s")
if settling_time > 0:
self.logger.info(f" 🛑 沉降: {settling_time}s")
self.logger.info(f" ⏱️ 总用时: {(stir_time + settling_time):.0f}s 🏁")
return True
async def start_stir(self, vessel: str, stir_speed: float, purpose: str) -> bool:
"""Start stir action - 开始持续搅拌"""
self.logger.info(f"StartStir: vessel={vessel}, speed={stir_speed} RPM, purpose={purpose}")
async def start_stir(self, vessel: str, stir_speed: float, purpose: str = "") -> bool:
"""Start stir action - 开始持续搅拌 🔄"""
# 验证参数
if stir_speed > self._max_speed or stir_speed < self._min_speed:
error_msg = f"搅拌速度 {stir_speed} RPM 超出范围 ({self._min_speed} - {self._max_speed} RPM)"
self.logger.error(error_msg)
# 🔧 类型转换
try:
stir_speed = float(stir_speed)
vessel = str(vessel)
purpose = str(purpose)
except (ValueError, TypeError) as e:
error_msg = f"参数类型转换错误: {str(e)}"
self.logger.error(f"{error_msg}")
self.data.update({
"status": f"Error: {error_msg}",
"status": f"❌ 错误: {error_msg}",
"operation_mode": "Error"
})
return False
self.logger.info(f"🔄 启动持续搅拌: {vessel} | 🌪️ {stir_speed} RPM")
if purpose:
self.logger.info(f"📝 搅拌目的: {purpose}")
# 验证参数
if stir_speed > self._max_speed or stir_speed < self._min_speed:
error_msg = f"🌪️ 搅拌速度 {stir_speed} RPM 超出范围 ({self._min_speed} - {self._max_speed} RPM) ⚠️"
self.logger.error(f"{error_msg}")
self.data.update({
"status": f"❌ 错误: 速度超出范围",
"operation_mode": "Error"
})
return False
purpose_info = f" | 📝 {purpose}" if purpose else ""
self.data.update({
"status": f"启动: 持续搅拌 {vessel} at {stir_speed} RPM | {purpose}",
"status": f"🔄 启动: 持续搅拌 {vessel} | 🌪️ {stir_speed} RPM{purpose_info}",
"operation_mode": "Stirring",
"current_vessel": vessel,
"current_speed": stir_speed,
@@ -170,16 +242,28 @@ class VirtualStirrer:
"remaining_time": -1.0, # -1 表示持续运行
})
self.logger.info(f"✅ 持续搅拌已启动! 🌪️ {stir_speed} RPM × ♾️ 🚀")
return True
async def stop_stir(self, vessel: str) -> bool:
"""Stop stir action - 停止搅拌"""
self.logger.info(f"StopStir: vessel={vessel}")
"""Stop stir action - 停止搅拌 🛑"""
# 🔧 类型转换
try:
vessel = str(vessel)
except (ValueError, TypeError) as e:
error_msg = f"参数类型转换错误: {str(e)}"
self.logger.error(f"{error_msg}")
return False
current_speed = self.data.get("current_speed", 0.0)
self.logger.info(f"🛑 停止搅拌: {vessel}")
if current_speed > 0:
self.logger.info(f"🌪️ 之前搅拌速度: {current_speed} RPM")
self.data.update({
"status": f"已停止: {vessel} 搅拌停止 | 之前速度: {current_speed} RPM",
"status": f"🛑 已停止: {vessel} 搅拌停止 | 之前速度: {current_speed} RPM",
"operation_mode": "Stopped",
"current_vessel": "",
"current_speed": 0.0,
@@ -187,12 +271,13 @@ class VirtualStirrer:
"remaining_time": 0.0,
})
self.logger.info(f"✅ 搅拌器已停止 {vessel} 的搅拌操作 🏁")
return True
# 状态属性
@property
def status(self) -> str:
return self.data.get("status", "Idle")
return self.data.get("status", "🏠 待机中")
@property
def operation_mode(self) -> str:
@@ -212,4 +297,33 @@ class VirtualStirrer:
@property
def remaining_time(self) -> float:
return self.data.get("remaining_time", 0.0)
return self.data.get("remaining_time", 0.0)
@property
def max_speed(self) -> float:
return self._max_speed
@property
def min_speed(self) -> float:
return self._min_speed
def get_device_info(self) -> Dict[str, Any]:
"""获取设备状态信息 📊"""
info = {
"device_id": self.device_id,
"status": self.status,
"operation_mode": self.operation_mode,
"current_vessel": self.current_vessel,
"current_speed": self.current_speed,
"is_stirring": self.is_stirring,
"remaining_time": self.remaining_time,
"max_speed": self._max_speed,
"min_speed": self._min_speed
}
self.logger.debug(f"📊 设备信息: 模式={self.operation_mode}, 速度={self.current_speed} RPM, 搅拌={self.is_stirring}")
return info
def __str__(self):
status_emoji = "" if self.operation_mode == "Idle" else "🌪️" if self.operation_mode == "Stirring" else "🛑" if self.operation_mode == "Settling" else ""
return f"🌪️ VirtualStirrer({status_emoji} {self.device_id}: {self.operation_mode}, {self.current_speed} RPM)"