Squash merge from dev

Update recipe.yaml

fix: figure_resource

use call_async in all service to avoid deadlock

fix: prcxi import error

临时兼容错误的driver写法

fix protocol node

fix filter protocol

bugfixes on organic protocols

fix and remove redundant info

feat: 新增use_remote_resource参数

fix all protocol_compilers and remove deprecated devices

feat: 优化protocol node节点运行日志

fix pumps and liquid_handler handle

feat: workstation example

add: prcxi res
fix: startup slow

fix: prcxi_res

fix: discard_tips

fix: discard_tips error

fix: drop_tips not using auto resource select

feat: 添加ChinWe设备控制类,支持串口通信和电机控制功能 (#79)

feat: add trace log level

modify default discovery_interval to 15s

fix: working dir error when input config path
feat: report publish topic when error

fix: workstation handlers and vessel_id parsing

Cleanup registry to be easy-understanding (#76)

* delete deprecated mock devices

* rename categories

* combine chromatographic devices

* rename rviz simulation nodes

* organic virtual devices

* parse vessel_id

* run registry completion before merge

---------

Co-authored-by: Xuwznln <18435084+Xuwznln@users.noreply.github.com>
This commit is contained in:
Junhan Chang
2025-08-03 11:21:37 +08:00
committed by Xuwznln
parent a555c59dc2
commit 0bfb52df00
97 changed files with 5033 additions and 164837 deletions

View File

@@ -138,6 +138,8 @@ class LiquidHandlerMiddleware(LiquidHandler):
offsets: Optional[List[Coordinate]] = None,
**backend_kwargs,
):
if not offsets or (isinstance(offsets, list) and len(offsets) != len(use_channels)):
offsets = [Coordinate.zero()] * len(use_channels)
if self._simulator:
return await self._simulate_handler.discard_tips(use_channels, allow_nonzero_volume, offsets, **backend_kwargs)
return await super().discard_tips(use_channels, allow_nonzero_volume, offsets, **backend_kwargs)

View File

@@ -67,7 +67,7 @@ class PRCXI9300Deck(Deck):
class PRCXI9300Container(Plate, TipRack):
"""PRCXI 9300 的专用 Deck 类,继承自 Deck。
"""PRCXI 9300 的专用 Container 类,继承自 Plate和TipRack。
该类定义了 PRCXI 9300 的工作台布局和槽位信息。
"""

View File

@@ -0,0 +1,44 @@
import collections
from pylabrobot.resources import opentrons_96_tiprack_10ul
from pylabrobot.resources.opentrons.plates import corning_96_wellplate_360ul_flat, nest_96_wellplate_2ml_deep
from unilabos.devices.liquid_handling.prcxi.prcxi import PRCXI9300Container, PRCXI9300Trash
def get_well_container(name: str) -> PRCXI9300Container:
well_containers = corning_96_wellplate_360ul_flat(name).serialize()
plate = PRCXI9300Container(name=name, size_x=50, size_y=50, size_z=10, category="plate",
ordering=collections.OrderedDict())
plate_serialized = plate.serialize()
well_containers.update({k: v for k, v in plate_serialized.items() if k not in ["children"]})
new_plate: PRCXI9300Container = PRCXI9300Container.deserialize(well_containers)
return new_plate
def get_tip_rack(name: str) -> PRCXI9300Container:
tip_racks = opentrons_96_tiprack_10ul("name").serialize()
tip_rack = PRCXI9300Container(name=name, size_x=50, size_y=50, size_z=10, category="tip_rack",
ordering=collections.OrderedDict())
tip_rack_serialized = tip_rack.serialize()
tip_racks.update({k: v for k, v in tip_rack_serialized.items() if k not in ["children"]})
new_tip_rack: PRCXI9300Container = PRCXI9300Container.deserialize(tip_racks)
return new_tip_rack
def prcxi_96_wellplate_360ul_flat(name: str):
return get_well_container(name)
def prcxi_opentrons_96_tiprack_10ul(name: str):
return get_tip_rack(name)
def prcxi_trash(name: str = None):
return PRCXI9300Trash(name="trash", size_x=50, size_y=50, size_z=10, category="trash")
if __name__ == "__main__":
# Example usage
test_plate = prcxi_96_wellplate_360ul_flat("test_plate")
test_rack = prcxi_opentrons_96_tiprack_10ul("test_rack")
tash = prcxi_trash("trash")
print(test_plate)
print(test_rack)
print(tash)
# Output will be a dictionary representation of the PRCXI9300Container with well details

View File

@@ -1,177 +0,0 @@
import time
import threading
class MockChiller:
def __init__(self, port: str = "MOCK"):
self.port = port
self._current_temperature: float = 25.0 # 室温开始
self._target_temperature: float = 25.0
self._status: str = "Idle"
self._is_cooling: bool = False
self._is_heating: bool = False
self._vessel = "Unknown"
self._purpose = "Unknown"
# 模拟温度变化的线程
self._temperature_thread = None
self._running = True
self._temperature_thread = threading.Thread(target=self._temperature_control_loop)
self._temperature_thread.daemon = True
self._temperature_thread.start()
@property
def current_temperature(self) -> float:
"""当前温度 - 会被自动识别的设备属性"""
return self._current_temperature
@property
def target_temperature(self) -> float:
"""目标温度"""
return self._target_temperature
@property
def status(self) -> str:
"""设备状态 - 会被自动识别的设备属性"""
return self._status
@property
def is_cooling(self) -> bool:
"""是否正在冷却"""
return self._is_cooling
@property
def is_heating(self) -> bool:
"""是否正在加热"""
return self._is_heating
@property
def vessel(self) -> str:
"""当前操作的容器名称"""
return self._vessel
@property
def purpose(self) -> str:
"""当前操作目的"""
return self._purpose
def heat_chill_start(self, vessel: str, temp: float, purpose: str):
"""设置目标温度并记录容器和目的"""
self._vessel = str(vessel)
self._purpose = str(purpose)
self._target_temperature = float(temp)
diff = self._target_temperature - self._current_temperature
if abs(diff) < 0.1:
self._status = "At Target Temperature"
self._is_cooling = False
self._is_heating = False
elif diff < 0:
self._status = "Cooling"
self._is_cooling = True
self._is_heating = False
else:
self._status = "Heating"
self._is_heating = True
self._is_cooling = False
self._start_temperature_control()
return True
def heat_chill_stop(self, vessel: str):
"""停止加热/制冷"""
if vessel != self._vessel:
return {"success": False, "status": f"Wrong vessel: expected {self._vessel}, got {vessel}"}
# 停止温度控制线程,锁定当前温度
self._stop_temperature_control()
# 更新状态
self._status = "Stopped"
self._is_cooling = False
self._is_heating = False
# 重新启动线程但保持温度
self._running = True
self._temperature_thread = threading.Thread(target=self._temperature_control_loop)
self._temperature_thread.daemon = True
self._temperature_thread.start()
return {"success": True, "status": self._status}
def _start_temperature_control(self):
"""启动温度控制线程"""
self._running = True
if self._temperature_thread is None or not self._temperature_thread.is_alive():
self._temperature_thread = threading.Thread(target=self._temperature_control_loop)
self._temperature_thread.daemon = True
self._temperature_thread.start()
def _stop_temperature_control(self):
"""停止温度控制"""
self._running = False
if self._temperature_thread:
self._temperature_thread.join(timeout=1.0)
def _temperature_control_loop(self):
"""温度控制循环 - 模拟真实冷却器的温度变化"""
while self._running:
# 如果状态是 Stopped不改变温度
if self._status == "Stopped":
time.sleep(1.0)
continue
temp_diff = self._target_temperature - self._current_temperature
if abs(temp_diff) < 0.1:
self._status = "At Target Temperature"
self._is_cooling = False
self._is_heating = False
elif temp_diff < 0:
self._status = "Cooling"
self._is_cooling = True
self._is_heating = False
self._current_temperature -= 0.5
else:
self._status = "Heating"
self._is_heating = True
self._is_cooling = False
self._current_temperature += 0.3
time.sleep(1.0)
def emergency_stop(self):
"""紧急停止"""
self._status = "Emergency Stop"
self._stop_temperature_control()
self._is_cooling = False
self._is_heating = False
def get_status_info(self) -> dict:
"""获取完整状态信息"""
return {
"current_temperature": self._current_temperature,
"target_temperature": self._target_temperature,
"status": self._status,
"is_cooling": self._is_cooling,
"is_heating": self._is_heating,
"vessel": self._vessel,
"purpose": self._purpose,
}
# 用于测试的主函数
if __name__ == "__main__":
chiller = MockChiller()
# 测试基本功能
print("启动冷却器测试...")
print(f"初始状态: {chiller.get_status_info()}")
# 模拟运行10秒
for i in range(10):
time.sleep(1)
print(f"{i+1}秒: 当前温度={chiller.current_temperature:.1f}°C, 状态={chiller.status}")
chiller.emergency_stop()
print("测试完成")

View File

@@ -1,235 +0,0 @@
import time
import threading
class MockFilter:
def __init__(self, port: str = "MOCK"):
# 基本参数初始化
self.port = port
self._status: str = "Idle"
self._is_filtering: bool = False
# 过滤性能参数
self._flow_rate: float = 1.0 # 流速(L/min)
self._pressure_drop: float = 0.0 # 压降(Pa)
self._filter_life: float = 100.0 # 滤芯寿命(%)
# 过滤操作参数
self._vessel: str = "" # 源容器
self._filtrate_vessel: str = "" # 目标容器
self._stir: bool = False # 是否搅拌
self._stir_speed: float = 0.0 # 搅拌速度
self._temperature: float = 25.0 # 温度(℃)
self._continue_heatchill: bool = False # 是否继续加热/制冷
self._target_volume: float = 0.0 # 目标过滤体积(L)
self._filtered_volume: float = 0.0 # 已过滤体积(L)
self._progress: float = 0.0 # 过滤进度(%)
# 线程控制
self._filter_thread = None
self._running = False
@property
def status(self) -> str:
return self._status
@property
def is_filtering(self) -> bool:
return self._is_filtering
@property
def flow_rate(self) -> float:
return self._flow_rate
@property
def pressure_drop(self) -> float:
return self._pressure_drop
@property
def filter_life(self) -> float:
return self._filter_life
# 新增 property
@property
def vessel(self) -> str:
return self._vessel
@property
def filtrate_vessel(self) -> str:
return self._filtrate_vessel
@property
def filtered_volume(self) -> float:
return self._filtered_volume
@property
def progress(self) -> float:
return self._progress
@property
def stir(self) -> bool:
return self._stir
@property
def stir_speed(self) -> float:
return self._stir_speed
@property
def temperature(self) -> float:
return self._temperature
@property
def continue_heatchill(self) -> bool:
return self._continue_heatchill
@property
def target_volume(self) -> float:
return self._target_volume
def filter(self, vessel: str, filtrate_vessel: str, stir: bool = False, stir_speed: float = 0.0, temp: float = 25.0, continue_heatchill: bool = False, volume: float = 0.0) -> dict:
"""新的过滤操作"""
# 停止任何正在进行的过滤
if self._is_filtering:
self.stop_filtering()
# 验证参数
if volume <= 0:
return {"success": False, "message": "Target volume must be greater than 0"}
# 设置新的过滤参数
self._vessel = vessel
self._filtrate_vessel = filtrate_vessel
self._stir = stir
self._stir_speed = stir_speed
self._temperature = temp
self._continue_heatchill = continue_heatchill
self._target_volume = volume
# 重置过滤状态
self._filtered_volume = 0.0
self._progress = 0.0
self._status = "Starting Filter"
# 启动过滤过程
self._flow_rate = 1.0 # 设置默认流速
self._start_filter_process()
return {"success": True, "message": "Filter started"}
def stop_filtering(self):
"""停止过滤"""
self._status = "Stopping Filter"
self._stop_filter_process()
self._flow_rate = 0.0
self._is_filtering = False
self._status = "Stopped"
return True
def replace_filter(self):
"""更换滤芯"""
self._filter_life = 100.0
self._status = "Filter Replaced"
return True
def _start_filter_process(self):
"""启动过滤过程线程"""
if not self._running:
self._running = True
self._is_filtering = True
self._filter_thread = threading.Thread(target=self._filter_loop)
self._filter_thread.daemon = True
self._filter_thread.start()
def _stop_filter_process(self):
"""停止过滤过程"""
self._running = False
if self._filter_thread:
self._filter_thread.join(timeout=1.0)
def _filter_loop(self):
"""过滤进程主循环"""
update_interval = 1.0 # 更新间隔(秒)
while self._running and self._is_filtering:
try:
self._status = "Filtering"
# 计算这一秒过滤的体积 (L/min -> L/s)
volume_increment = (self._flow_rate / 60.0) * update_interval
# 更新已过滤体积
self._filtered_volume += volume_increment
# 更新进度 (避免除零错误)
if self._target_volume > 0:
self._progress = min(100.0, (self._filtered_volume / self._target_volume) * 100.0)
# 更新滤芯寿命 (每过滤1L减少0.5%寿命)
self._filter_life = max(0.0, self._filter_life - (volume_increment * 0.5))
# 更新压降 (根据滤芯寿命和流速动态计算)
life_factor = self._filter_life / 100.0 # 将寿命转换为0-1的因子
flow_factor = self._flow_rate / 2.0 # 将流速标准化(假设2L/min是标准流速)
base_pressure = 100.0 # 基础压降
# 压降随滤芯寿命降低而增加,随流速增加而增加
self._pressure_drop = base_pressure * (2 - life_factor) * flow_factor
# 检查是否完成目标体积
if self._target_volume > 0 and self._filtered_volume >= self._target_volume:
self._status = "Completed"
self._progress = 100.0
self.stop_filtering()
break
# 检查滤芯寿命
if self._filter_life <= 10.0:
self._status = "Filter Needs Replacement"
time.sleep(update_interval)
except Exception as e:
print(f"Error in filter loop: {e}")
self.emergency_stop()
break
def emergency_stop(self):
"""紧急停止"""
self._status = "Emergency Stop"
self._stop_filter_process()
self._is_filtering = False
self._flow_rate = 0.0
def get_status_info(self) -> dict:
"""扩展的状态信息"""
return {
"status": self._status,
"is_filtering": self._is_filtering,
"flow_rate": self._flow_rate,
"pressure_drop": self._pressure_drop,
"filter_life": self._filter_life,
"vessel": self._vessel,
"filtrate_vessel": self._filtrate_vessel,
"filtered_volume": self._filtered_volume,
"target_volume": self._target_volume,
"progress": self._progress,
"temperature": self._temperature,
"stir": self._stir,
"stir_speed": self._stir_speed
}
# 用于测试的主函数
if __name__ == "__main__":
filter_device = MockFilter()
# 测试基本功能
print("启动过滤器测试...")
print(f"初始状态: {filter_device.get_status_info()}")
# 模拟运行10秒
for i in range(10):
time.sleep(1)
print(
f"{i+1}秒: "
f"寿命={filter_device.filter_life:.1f}%, 状态={filter_device.status}"
)
filter_device.emergency_stop()
print("测试完成")

View File

@@ -1,247 +0,0 @@
import time
import threading
class MockHeater:
def __init__(self, port: str = "MOCK"):
self.port = port
self._current_temperature: float = 25.0 # 室温开始
self._target_temperature: float = 25.0
self._status: str = "Idle"
self._is_heating: bool = False
self._heating_power: float = 0.0 # 加热功率百分比 0-100
self._max_temperature: float = 300.0 # 最大加热温度
# 新增加的属性
self._vessel: str = "Unknown"
self._purpose: str = "Unknown"
self._stir: bool = False
self._stir_speed: float = 0.0
# 模拟加热过程的线程
self._heating_thread = None
self._running = True
self._heating_thread = threading.Thread(target=self._heating_control_loop)
self._heating_thread.daemon = True
self._heating_thread.start()
@property
def current_temperature(self) -> float:
"""当前温度 - 会被自动识别的设备属性"""
return self._current_temperature
@property
def target_temperature(self) -> float:
"""目标温度"""
return self._target_temperature
@property
def status(self) -> str:
"""设备状态 - 会被自动识别的设备属性"""
return self._status
@property
def is_heating(self) -> bool:
"""是否正在加热"""
return self._is_heating
@property
def heating_power(self) -> float:
"""加热功率百分比"""
return self._heating_power
@property
def max_temperature(self) -> float:
"""最大加热温度"""
return self._max_temperature
@property
def vessel(self) -> str:
"""当前操作的容器名称"""
return self._vessel
@property
def purpose(self) -> str:
"""操作目的"""
return self._purpose
@property
def stir(self) -> bool:
"""是否搅拌"""
return self._stir
@property
def stir_speed(self) -> float:
"""搅拌速度"""
return self._stir_speed
def heat_chill_start(self, vessel: str, temp: float, purpose: str) -> dict:
"""开始加热/制冷过程"""
self._vessel = str(vessel)
self._purpose = str(purpose)
self._target_temperature = float(temp)
diff = self._target_temperature - self._current_temperature
if abs(diff) < 0.1:
self._status = "At Target Temperature"
self._is_heating = False
elif diff > 0:
self._status = "Heating"
self._is_heating = True
else:
self._status = "Cooling Down"
self._is_heating = False
return {"success": True, "status": self._status}
def heat_chill_stop(self, vessel: str) -> dict:
"""停止加热/制冷"""
if vessel != self._vessel:
return {"success": False, "status": f"Wrong vessel: expected {self._vessel}, got {vessel}"}
self._status = "Stopped"
self._is_heating = False
self._heating_power = 0.0
return {"success": True, "status": self._status}
def heat_chill(self, vessel: str, temp: float, time: float,
stir: bool = False, stir_speed: float = 0.0,
purpose: str = "Unknown") -> dict:
"""完整的加热/制冷控制"""
self._vessel = str(vessel)
self._target_temperature = float(temp)
self._purpose = str(purpose)
self._stir = stir
self._stir_speed = stir_speed
diff = self._target_temperature - self._current_temperature
if abs(diff) < 0.1:
self._status = "At Target Temperature"
self._is_heating = False
elif diff > 0:
self._status = "Heating"
self._is_heating = True
else:
self._status = "Cooling Down"
self._is_heating = False
return {"success": True, "status": self._status}
def set_temperature(self, temperature: float):
"""设置目标温度 - 需要在注册表添加的设备动作"""
try:
temperature = float(temperature)
except ValueError:
self._status = "Error: Invalid temperature value"
return False
if temperature > self._max_temperature:
self._status = f"Error: Temperature exceeds maximum ({self._max_temperature}°C)"
return False
self._target_temperature = temperature
self._status = "Setting Temperature"
# 启动加热控制
self._start_heating_control()
return True
def set_heating_power(self, power: float):
"""设置加热功率"""
try:
power = float(power)
except ValueError:
self._status = "Error: Invalid power value"
return False
self._heating_power = max(0.0, min(100.0, power)) # 限制在0-100%
return True
def _start_heating_control(self):
"""启动加热控制线程"""
if not self._running:
self._running = True
self._heating_thread = threading.Thread(target=self._heating_control_loop)
self._heating_thread.daemon = True
self._heating_thread.start()
def _stop_heating_control(self):
"""停止加热控制"""
self._running = False
if self._heating_thread:
self._heating_thread.join(timeout=1.0)
def _heating_control_loop(self):
"""加热控制循环"""
while self._running:
# 如果状态是 Stopped不改变温度
if self._status == "Stopped":
time.sleep(1.0)
continue
temp_diff = self._target_temperature - self._current_temperature
if abs(temp_diff) < 0.1:
self._status = "At Target Temperature"
self._is_heating = False
self._heating_power = 10.0
elif temp_diff > 0:
self._status = "Heating"
self._is_heating = True
self._heating_power = min(100.0, abs(temp_diff) * 2)
self._current_temperature += 0.5
else:
self._status = "Cooling Down"
self._is_heating = False
self._heating_power = 0.0
self._current_temperature -= 0.2
time.sleep(1.0)
def emergency_stop(self):
"""紧急停止"""
self._status = "Emergency Stop"
self._stop_heating_control()
self._is_heating = False
self._heating_power = 0.0
def get_status_info(self) -> dict:
"""获取完整状态信息"""
return {
"current_temperature": self._current_temperature,
"target_temperature": self._target_temperature,
"status": self._status,
"is_heating": self._is_heating,
"heating_power": self._heating_power,
"max_temperature": self._max_temperature,
"vessel": self._vessel,
"purpose": self._purpose,
"stir": self._stir,
"stir_speed": self._stir_speed
}
# 用于测试的主函数
if __name__ == "__main__":
heater = MockHeater()
print("启动加热器测试...")
print(f"初始状态: {heater.get_status_info()}")
# 设置目标温度为80度
heater.set_temperature(80.0)
# 模拟运行15秒
try:
for i in range(15):
time.sleep(1)
status = heater.get_status_info()
print(
f"\r温度: {status['current_temperature']:.1f}°C / {status['target_temperature']:.1f}°C | "
f"功率: {status['heating_power']:.1f}% | 状态: {status['status']}",
end=""
)
except KeyboardInterrupt:
heater.emergency_stop()
print("\n测试被手动停止")
print("\n测试完成")

View File

@@ -1,360 +0,0 @@
import time
import threading
from datetime import datetime, timedelta
class MockPump:
def __init__(self, port: str = "MOCK"):
self.port = port
# 设备基本状态属性
self._current_device = "MockPump1" # 设备标识符
self._status: str = "Idle" # 设备状态Idle, Running, Error, Stopped
self._pump_state: str = "Stopped" # 泵运行状态Running, Stopped, Paused
# 流量相关属性
self._flow_rate: float = 0.0 # 当前流速 (mL/min)
self._target_flow_rate: float = 0.0 # 目标流速 (mL/min)
self._max_flow_rate: float = 100.0 # 最大流速 (mL/min)
self._total_volume: float = 0.0 # 累计流量 (mL)
# 压力相关属性
self._pressure: float = 0.0 # 当前压力 (bar)
self._max_pressure: float = 10.0 # 最大压力 (bar)
# 运行控制线程
self._pump_thread = None
self._running = False
self._thread_lock = threading.Lock()
# 新增 PumpTransfer 相关属性
self._from_vessel: str = ""
self._to_vessel: str = ""
self._transfer_volume: float = 0.0
self._amount: str = ""
self._transfer_time: float = 0.0
self._is_viscous: bool = False
self._rinsing_solvent: str = ""
self._rinsing_volume: float = 0.0
self._rinsing_repeats: int = 0
self._is_solid: bool = False
# 时间追踪
self._start_time: datetime = None
self._time_spent: timedelta = timedelta()
self._time_remaining: timedelta = timedelta()
# ==================== 状态属性 ====================
# 这些属性会被Uni-Lab系统自动识别并定时对外广播
@property
def status(self) -> str:
return self._status
@property
def current_device(self) -> str:
"""当前设备标识符"""
return self._current_device
@property
def pump_state(self) -> str:
return self._pump_state
@property
def flow_rate(self) -> float:
return self._flow_rate
@property
def target_flow_rate(self) -> float:
return self._target_flow_rate
@property
def pressure(self) -> float:
return self._pressure
@property
def total_volume(self) -> float:
return self._total_volume
@property
def max_flow_rate(self) -> float:
return self._max_flow_rate
@property
def max_pressure(self) -> float:
return self._max_pressure
# 添加新的属性访问器
@property
def from_vessel(self) -> str:
return self._from_vessel
@property
def to_vessel(self) -> str:
return self._to_vessel
@property
def transfer_volume(self) -> float:
return self._transfer_volume
@property
def amount(self) -> str:
return self._amount
@property
def transfer_time(self) -> float:
return self._transfer_time
@property
def is_viscous(self) -> bool:
return self._is_viscous
@property
def rinsing_solvent(self) -> str:
return self._rinsing_solvent
@property
def rinsing_volume(self) -> float:
return self._rinsing_volume
@property
def rinsing_repeats(self) -> int:
return self._rinsing_repeats
@property
def is_solid(self) -> bool:
return self._is_solid
# 修改这两个属性装饰器
@property
def time_spent(self) -> float:
"""已用时间(秒)"""
if isinstance(self._time_spent, timedelta):
return self._time_spent.total_seconds()
return float(self._time_spent)
@property
def time_remaining(self) -> float:
"""剩余时间(秒)"""
if isinstance(self._time_remaining, timedelta):
return self._time_remaining.total_seconds()
return float(self._time_remaining)
# ==================== 设备控制方法 ====================
# 这些方法需要在注册表中添加会作为ActionServer接受控制指令
def pump_transfer(self, from_vessel: str, to_vessel: str, volume: float,
amount: str = "", time: float = 0.0, viscous: bool = False,
rinsing_solvent: str = "", rinsing_volume: float = 0.0,
rinsing_repeats: int = 0, solid: bool = False) -> dict:
"""Execute pump transfer operation"""
# Stop any existing operation first
self._stop_pump_operation()
# Set transfer parameters
self._from_vessel = from_vessel
self._to_vessel = to_vessel
self._transfer_volume = float(volume)
self._amount = amount
self._transfer_time = float(time)
self._is_viscous = viscous
self._rinsing_solvent = rinsing_solvent
self._rinsing_volume = float(rinsing_volume)
self._rinsing_repeats = int(rinsing_repeats)
self._is_solid = solid
# Calculate flow rate
if self._transfer_time > 0 and self._transfer_volume > 0:
self._target_flow_rate = (self._transfer_volume / self._transfer_time) * 60.0
else:
self._target_flow_rate = 10.0 if not self._is_viscous else 5.0
# Reset timers and counters
self._start_time = datetime.now()
self._time_spent = timedelta()
self._time_remaining = timedelta(seconds=self._transfer_time)
self._total_volume = 0.0
self._flow_rate = 0.0
# Start pump operation
self._pump_state = "Running"
self._status = "Starting Transfer"
self._running = True
# Start pump operation thread
self._pump_thread = threading.Thread(target=self._pump_operation_loop)
self._pump_thread.daemon = True
self._pump_thread.start()
# Wait briefly to ensure thread starts
time.sleep(0.1)
return {
"success": True,
"status": self._status,
"current_device": self._current_device,
"time_spent": 0.0,
"time_remaining": float(self._transfer_time)
}
def pause_pump(self) -> str:
if self._pump_state != "Running":
self._status = "Error: Pump not running"
return "Error"
self._pump_state = "Paused"
self._status = "Pump Paused"
self._stop_pump_operation()
return "Success"
def resume_pump(self) -> str:
if self._pump_state != "Paused":
self._status = "Error: Pump not paused"
return "Error"
self._pump_state = "Running"
self._status = "Resuming Pump"
self._start_pump_operation()
return "Success"
def reset_volume_counter(self) -> str:
self._total_volume = 0.0
self._status = "Volume counter reset"
return "Success"
def emergency_stop(self) -> str:
self._status = "Emergency Stop"
self._pump_state = "Stopped"
self._stop_pump_operation()
self._flow_rate = 0.0
self._pressure = 0.0
self._target_flow_rate = 0.0
return "Success"
# ==================== 内部控制方法 ====================
def _start_pump_operation(self):
with self._thread_lock:
if not self._running:
self._running = True
self._pump_thread = threading.Thread(target=self._pump_operation_loop)
self._pump_thread.daemon = True
self._pump_thread.start()
def _stop_pump_operation(self):
with self._thread_lock:
self._running = False
if self._pump_thread and self._pump_thread.is_alive():
self._pump_thread.join(timeout=2.0)
def _pump_operation_loop(self):
"""泵运行主循环"""
print("Pump operation loop started") # Debug print
while self._running and self._pump_state == "Running":
try:
# Calculate flow rate adjustment
flow_diff = self._target_flow_rate - self._flow_rate
# Adjust flow rate more aggressively (50% of difference)
adjustment = flow_diff * 0.5
self._flow_rate += adjustment
# Ensure flow rate is within bounds
self._flow_rate = max(0.1, min(self._max_flow_rate, self._flow_rate))
# Update status based on flow rate
if abs(flow_diff) < 0.1:
self._status = "Running at Target Flow Rate"
else:
self._status = "Adjusting Flow Rate"
# Calculate volume increment
volume_increment = (self._flow_rate / 60.0) # mL/s
self._total_volume += volume_increment
# Update time tracking
self._time_spent = datetime.now() - self._start_time
if self._transfer_time > 0:
remaining = self._transfer_time - self._time_spent.total_seconds()
self._time_remaining = timedelta(seconds=max(0, remaining))
# Check completion
if self._total_volume >= self._transfer_volume:
self._status = "Transfer Completed"
self._pump_state = "Stopped"
self._running = False
break
# Update pressure
self._pressure = (self._flow_rate / self._max_flow_rate) * self._max_pressure
print(f"Debug - Flow: {self._flow_rate:.1f}, Volume: {self._total_volume:.1f}") # Debug print
time.sleep(1.0)
except Exception as e:
print(f"Error in pump operation: {str(e)}")
self._status = "Error in pump operation"
self._pump_state = "Stopped"
self._running = False
break
def get_status_info(self) -> dict:
"""
获取完整的设备状态信息
Returns:
dict: 包含所有设备状态的字典
"""
return {
"status": self._status,
"pump_state": self._pump_state,
"flow_rate": self._flow_rate,
"target_flow_rate": self._target_flow_rate,
"pressure": self._pressure,
"total_volume": self._total_volume,
"max_flow_rate": self._max_flow_rate,
"max_pressure": self._max_pressure,
"current_device": self._current_device,
"from_vessel": self._from_vessel,
"to_vessel": self._to_vessel,
"transfer_volume": self._transfer_volume,
"amount": self._amount,
"transfer_time": self._transfer_time,
"is_viscous": self._is_viscous,
"rinsing_solvent": self._rinsing_solvent,
"rinsing_volume": self._rinsing_volume,
"rinsing_repeats": self._rinsing_repeats,
"is_solid": self._is_solid,
"time_spent": self._time_spent.total_seconds(),
"time_remaining": self._time_remaining.total_seconds()
}
# 用于测试的主函数
if __name__ == "__main__":
pump = MockPump()
# 测试基本功能
print("启动泵设备测试...")
print(f"初始状态: {pump.get_status_info()}")
# 设置流速并启动
pump.set_flow_rate(50.0)
pump.start_pump()
# 模拟运行10秒
for i in range(10):
time.sleep(1)
print(f"{i+1}秒: 流速={pump.flow_rate:.1f}mL/min, 压力={pump.pressure:.2f}bar, 状态={pump.status}")
# 测试方向切换
print("切换泵方向...")
pump.emergency_stop()
print("测试完成")

View File

@@ -1,390 +0,0 @@
import time
import threading
import json
class MockRotavap:
"""
模拟旋转蒸发器设备类
这个类模拟了一个实验室旋转蒸发器的行为,包括旋转控制、
真空泵控制、温度控制等功能。参考了现有的 RotavapOne 实现。
"""
def __init__(self, port: str = "MOCK"):
"""
初始化MockRotavap实例
Args:
port (str): 设备端口,默认为"MOCK"表示模拟设备
"""
self.port = port
# 设备基本状态属性
self._status: str = "Idle" # 设备状态Idle, Running, Error, Stopped
# 旋转相关属性
self._rotate_state: str = "Stopped" # 旋转状态Running, Stopped
self._rotate_time: float = 0.0 # 旋转剩余时间 (秒)
self._rotate_speed: float = 0.0 # 旋转速度 (rpm)
self._max_rotate_speed: float = 300.0 # 最大旋转速度 (rpm)
# 真空泵相关属性
self._pump_state: str = "Stopped" # 泵状态Running, Stopped
self._pump_time: float = 0.0 # 泵剩余时间 (秒)
self._vacuum_level: float = 0.0 # 真空度 (mbar)
self._target_vacuum: float = 50.0 # 目标真空度 (mbar)
# 温度相关属性
self._temperature: float = 25.0 # 水浴温度 (°C)
self._target_temperature: float = 25.0 # 目标温度 (°C)
self._max_temperature: float = 180.0 # 最大温度 (°C)
# 运行控制线程
self._operation_thread = None
self._running = False
self._thread_lock = threading.Lock()
# 操作成功标志
self.success: str = "True" # 使用字符串而不是布尔值
# ==================== 状态属性 ====================
# 这些属性会被Uni-Lab系统自动识别并定时对外广播
@property
def status(self) -> str:
return self._status
@property
def rotate_state(self) -> str:
return self._rotate_state
@property
def rotate_time(self) -> float:
return self._rotate_time
@property
def rotate_speed(self) -> float:
return self._rotate_speed
@property
def pump_state(self) -> str:
return self._pump_state
@property
def pump_time(self) -> float:
return self._pump_time
@property
def vacuum_level(self) -> float:
return self._vacuum_level
@property
def temperature(self) -> float:
return self._temperature
@property
def target_temperature(self) -> float:
return self._target_temperature
# ==================== 设备控制方法 ====================
# 这些方法需要在注册表中添加会作为ActionServer接受控制指令
def set_timer(self, command: str) -> str:
"""
设置定时器 - 兼容现有RotavapOne接口
Args:
command (str): JSON格式的命令字符串包含rotate_time和pump_time
Returns:
str: 操作结果状态 ("Success", "Error")
"""
try:
timer = json.loads(command)
rotate_time = timer.get("rotate_time", 0)
pump_time = timer.get("pump_time", 0)
self.success = "False"
self._rotate_time = float(rotate_time)
self._pump_time = float(pump_time)
self.success = "True"
self._status = "Timer Set"
return "Success"
except (json.JSONDecodeError, ValueError, KeyError) as e:
self._status = f"Error: Invalid command format - {str(e)}"
self.success = "False"
return "Error"
def set_rotate_time(self, time_seconds: float) -> str:
"""
设置旋转时间
Args:
time_seconds (float): 旋转时间 (秒)
Returns:
str: 操作结果状态 ("Success", "Error")
"""
self.success = "False"
self._rotate_time = max(0.0, float(time_seconds))
self.success = "True"
self._status = "Rotate time set"
return "Success"
def set_pump_time(self, time_seconds: float) -> str:
"""
设置泵时间
Args:
time_seconds (float): 泵时间 (秒)
Returns:
str: 操作结果状态 ("Success", "Error")
"""
self.success = "False"
self._pump_time = max(0.0, float(time_seconds))
self.success = "True"
self._status = "Pump time set"
return "Success"
def set_rotate_speed(self, speed: float) -> str:
"""
设置旋转速度
Args:
speed (float): 旋转速度 (rpm)
Returns:
str: 操作结果状态 ("Success", "Error")
"""
if speed < 0 or speed > self._max_rotate_speed:
self._status = f"Error: Speed out of range (0-{self._max_rotate_speed})"
return "Error"
self._rotate_speed = speed
self._status = "Rotate speed set"
return "Success"
def set_temperature(self, temperature: float) -> str:
"""
设置水浴温度
Args:
temperature (float): 目标温度 (°C)
Returns:
str: 操作结果状态 ("Success", "Error")
"""
if temperature < 0 or temperature > self._max_temperature:
self._status = f"Error: Temperature out of range (0-{self._max_temperature})"
return "Error"
self._target_temperature = temperature
self._status = "Temperature set"
# 启动操作线程以开始温度控制
self._start_operation()
return "Success"
def start_rotation(self) -> str:
"""
启动旋转
Returns:
str: 操作结果状态 ("Success", "Error")
"""
if self._rotate_time <= 0:
self._status = "Error: No rotate time set"
return "Error"
self._rotate_state = "Running"
self._status = "Rotation started"
return "Success"
def start_pump(self) -> str:
"""
启动真空泵
Returns:
str: 操作结果状态 ("Success", "Error")
"""
if self._pump_time <= 0:
self._status = "Error: No pump time set"
return "Error"
self._pump_state = "Running"
self._status = "Pump started"
return "Success"
def stop_all_operations(self) -> str:
"""
停止所有操作
Returns:
str: 操作结果状态 ("Success", "Error")
"""
self._rotate_state = "Stopped"
self._pump_state = "Stopped"
self._stop_operation()
self._rotate_time = 0.0
self._pump_time = 0.0
self._vacuum_level = 0.0
self._status = "All operations stopped"
return "Success"
def emergency_stop(self) -> str:
"""
紧急停止
Returns:
str: 操作结果状态 ("Success", "Error")
"""
self._status = "Emergency Stop"
self.stop_all_operations()
return "Success"
# ==================== 内部控制方法 ====================
def _start_operation(self):
"""
启动操作线程
这个方法启动一个后台线程来模拟旋蒸的实际运行过程。
"""
with self._thread_lock:
if not self._running:
self._running = True
self._operation_thread = threading.Thread(target=self._operation_loop)
self._operation_thread.daemon = True
self._operation_thread.start()
def _stop_operation(self):
"""
停止操作线程
安全地停止后台运行线程并等待其完成。
"""
with self._thread_lock:
self._running = False
if self._operation_thread and self._operation_thread.is_alive():
self._operation_thread.join(timeout=2.0)
def _operation_loop(self):
"""
操作主循环
这个方法在后台线程中运行,模拟真实旋蒸的工作过程:
1. 时间倒计时
2. 温度控制
3. 真空度控制
4. 状态更新
"""
while self._running:
try:
# 处理旋转时间倒计时
if self._rotate_time > 0:
self._rotate_state = "Running"
self._rotate_time = max(0.0, self._rotate_time - 1.0)
else:
self._rotate_state = "Stopped"
# 处理泵时间倒计时
if self._pump_time > 0:
self._pump_state = "Running"
self._pump_time = max(0.0, self._pump_time - 1.0)
# 模拟真空度变化
if self._vacuum_level > self._target_vacuum:
self._vacuum_level = max(self._target_vacuum, self._vacuum_level - 5.0)
else:
self._pump_state = "Stopped"
# 真空度逐渐回升
self._vacuum_level = min(1013.25, self._vacuum_level + 2.0)
# 模拟温度控制
temp_diff = self._target_temperature - self._temperature
if abs(temp_diff) > 0.5:
if temp_diff > 0:
self._temperature += min(1.0, temp_diff * 0.1)
else:
self._temperature += max(-1.0, temp_diff * 0.1)
# 更新整体状态
if self._rotate_state == "Running" or self._pump_state == "Running":
self._status = "Operating"
elif self._rotate_time > 0 or self._pump_time > 0:
self._status = "Ready"
else:
self._status = "Idle"
# 等待1秒后继续下一次循环
time.sleep(1.0)
except Exception as e:
self._status = f"Error in operation: {str(e)}"
break
# 循环结束时的清理工作
self._status = "Idle"
def get_status_info(self) -> dict:
"""
获取完整的设备状态信息
Returns:
dict: 包含所有设备状态的字典
"""
return {
"status": self._status,
"rotate_state": self._rotate_state,
"rotate_time": self._rotate_time,
"rotate_speed": self._rotate_speed,
"pump_state": self._pump_state,
"pump_time": self._pump_time,
"vacuum_level": self._vacuum_level,
"temperature": self._temperature,
"target_temperature": self._target_temperature,
"success": self.success,
}
# 用于测试的主函数
if __name__ == "__main__":
rotavap = MockRotavap()
# 测试基本功能
print("启动旋转蒸发器测试...")
print(f"初始状态: {rotavap.get_status_info()}")
# 设置定时器
timer_command = '{"rotate_time": 300, "pump_time": 600}'
rotavap.set_timer(timer_command)
# 设置温度和转速
rotavap.set_temperature(60.0)
rotavap.set_rotate_speed(120.0)
# 启动操作
rotavap.start_rotation()
rotavap.start_pump()
# 模拟运行10秒
for i in range(10):
time.sleep(1)
print(
f"{i+1}秒: 旋转={rotavap.rotate_time:.0f}s, 泵={rotavap.pump_time:.0f}s, "
f"温度={rotavap.temperature:.1f}°C, 真空={rotavap.vacuum_level:.1f}mbar"
)
rotavap.emergency_stop()
print("测试完成")

View File

@@ -1,399 +0,0 @@
import time
import threading
from datetime import datetime, timedelta
class MockSeparator:
def __init__(self, port: str = "MOCK"):
self.port = port
# 基本状态属性
self._status: str = "Idle" # 当前总体状态
self._valve_state: str = "Closed" # 阀门状态Open 或 Closed
self._settling_time: float = 0.0 # 静置时间(秒)
# 搅拌相关属性
self._shake_time: float = 0.0 # 剩余摇摆时间(秒)
self._shake_status: str = "Not Shaking" # 摇摆状态
# 用于后台模拟 shake 动作
self._operation_thread = None
self._thread_lock = threading.Lock()
self._running = False
# Separate action 相关属性
self._current_device: str = "MockSeparator1"
self._purpose: str = "" # wash or extract
self._product_phase: str = "" # top or bottom
self._from_vessel: str = ""
self._separation_vessel: str = ""
self._to_vessel: str = ""
self._waste_phase_to_vessel: str = ""
self._solvent: str = ""
self._solvent_volume: float = 0.0
self._through: str = ""
self._repeats: int = 1
self._stir_time: float = 0.0
self._stir_speed: float = 0.0
self._time_spent = timedelta()
self._time_remaining = timedelta()
self._start_time = datetime.now() # 添加这一行
@property
def current_device(self) -> str:
return self._current_device
@property
def purpose(self) -> str:
return self._purpose
@property
def valve_state(self) -> str:
return self._valve_state
@property
def settling_time(self) -> float:
return self._settling_time
@property
def status(self) -> str:
return self._status
@property
def shake_time(self) -> float:
with self._thread_lock:
return self._shake_time
@property
def shake_status(self) -> str:
with self._thread_lock:
return self._shake_status
@property
def product_phase(self) -> str:
return self._product_phase
@property
def from_vessel(self) -> str:
return self._from_vessel
@property
def separation_vessel(self) -> str:
return self._separation_vessel
@property
def to_vessel(self) -> str:
return self._to_vessel
@property
def waste_phase_to_vessel(self) -> str:
return self._waste_phase_to_vessel
@property
def solvent(self) -> str:
return self._solvent
@property
def solvent_volume(self) -> float:
return self._solvent_volume
@property
def through(self) -> str:
return self._through
@property
def repeats(self) -> int:
return self._repeats
@property
def stir_time(self) -> float:
return self._stir_time
@property
def stir_speed(self) -> float:
return self._stir_speed
@property
def time_spent(self) -> float:
if self._running:
self._time_spent = datetime.now() - self._start_time
return self._time_spent.total_seconds()
@property
def time_remaining(self) -> float:
if self._running:
elapsed = (datetime.now() - self._start_time).total_seconds()
total_time = (self._stir_time + self._settling_time + 10) * self._repeats
remain = max(0, total_time - elapsed)
self._time_remaining = timedelta(seconds=remain)
return self._time_remaining.total_seconds()
def separate(self, purpose: str, product_phase: str, from_vessel: str,
separation_vessel: str, to_vessel: str, waste_phase_to_vessel: str = "",
solvent: str = "", solvent_volume: float = 0.0, through: str = "",
repeats: int = 1, stir_time: float = 0.0, stir_speed: float = 0.0,
settling_time: float = 60.0) -> dict:
"""
执行分离操作
"""
with self._thread_lock:
# 检查是否已经在运行
if self._running:
return {
"success": False,
"status": "Error: Operation already in progress"
}
# 必填参数验证
if not all([from_vessel, separation_vessel, to_vessel]):
self._status = "Error: Missing required vessel parameters"
return {"success": False}
# 验证参数
if purpose not in ["wash", "extract"]:
self._status = "Error: Invalid purpose"
return {"success": False}
if product_phase not in ["top", "bottom"]:
self._status = "Error: Invalid product phase"
return {"success": False}
# 数值参数验证
try:
solvent_volume = float(solvent_volume)
repeats = int(repeats)
stir_time = float(stir_time)
stir_speed = float(stir_speed)
settling_time = float(settling_time)
except ValueError:
self._status = "Error: Invalid numeric parameters"
return {"success": False}
# 设置参数
self._purpose = purpose
self._product_phase = product_phase
self._from_vessel = from_vessel
self._separation_vessel = separation_vessel
self._to_vessel = to_vessel
self._waste_phase_to_vessel = waste_phase_to_vessel
self._solvent = solvent
self._solvent_volume = float(solvent_volume)
self._through = through
self._repeats = int(repeats)
self._stir_time = float(stir_time)
self._stir_speed = float(stir_speed)
self._settling_time = float(settling_time)
# 重置计时器
self._start_time = datetime.now()
self._time_spent = timedelta()
total_time = (self._stir_time + self._settling_time + 10) * self._repeats
self._time_remaining = timedelta(seconds=total_time)
# 启动分离操作
self._status = "Starting Separation"
self._running = True
# 在锁内创建和启动线程
self._operation_thread = threading.Thread(target=self._operation_loop)
self._operation_thread.daemon = True
self._operation_thread.start()
# 等待确认操作已经开始
time.sleep(0.1) # 短暂等待确保操作线程已启动
return {
"success": True,
"status": self._status,
"current_device": self._current_device,
"time_spent": self._time_spent.total_seconds(),
"time_remaining": self._time_remaining.total_seconds()
}
def shake(self, shake_time: float) -> str:
"""
模拟 shake搅拌操作
- 进入 "Shaking" 状态,倒计时 shake_time 秒
- shake 结束后,进入 "Settling" 状态,静置时间固定为 5 秒
- 最后恢复为 Idle
"""
try:
shake_time = float(shake_time)
except ValueError:
self._status = "Error: Invalid shake time"
return "Error"
with self._thread_lock:
self._status = "Shaking"
self._settling_time = 0.0
self._shake_time = shake_time
self._shake_status = "Shaking"
def _run_shake():
remaining = shake_time
while remaining > 0:
time.sleep(1)
remaining -= 1
with self._thread_lock:
self._shake_time = remaining
with self._thread_lock:
self._status = "Settling"
self._settling_time = 60.0 # 固定静置时间为60秒
self._shake_status = "Settling"
while True:
with self._thread_lock:
if self._settling_time <= 0:
self._status = "Idle"
self._shake_status = "Idle"
break
time.sleep(1)
with self._thread_lock:
self._settling_time = max(0.0, self._settling_time - 1)
self._operation_thread = threading.Thread(target=_run_shake)
self._operation_thread.daemon = True
self._operation_thread.start()
return "Success"
def set_valve(self, command: str) -> str:
"""
阀门控制命令:传入 "open""close"
"""
command = command.lower()
if command == "open":
self._valve_state = "Open"
self._status = "Valve Opened"
elif command == "close":
self._valve_state = "Closed"
self._status = "Valve Closed"
else:
self._status = "Error: Invalid valve command"
return "Error"
return "Success"
def _operation_loop(self):
"""分离操作主循环"""
try:
current_repeat = 1
# 立即更新状态确保不会停留在Starting Separation
with self._thread_lock:
self._status = f"Separation Cycle {current_repeat}/{self._repeats}"
while self._running and current_repeat <= self._repeats:
# 第一步:搅拌
if self._stir_time > 0:
with self._thread_lock:
self._status = f"Stirring (Repeat {current_repeat}/{self._repeats})"
remaining_stir = self._stir_time
while remaining_stir > 0 and self._running:
time.sleep(1)
remaining_stir -= 1
# 第二步:静置
if self._settling_time > 0:
with self._thread_lock:
self._status = f"Settling (Repeat {current_repeat}/{self._repeats})"
remaining_settle = self._settling_time
while remaining_settle > 0 and self._running:
time.sleep(1)
remaining_settle -= 1
# 第三步:打开阀门排出
with self._thread_lock:
self._valve_state = "Open"
self._status = f"Draining (Repeat {current_repeat}/{self._repeats})"
# 模拟排出时间5秒
time.sleep(10)
# 关闭阀门
with self._thread_lock:
self._valve_state = "Closed"
# 检查是否继续下一次重复
if current_repeat < self._repeats:
current_repeat += 1
else:
with self._thread_lock:
self._status = "Separation Complete"
break
except Exception as e:
with self._thread_lock:
self._status = f"Error in separation: {str(e)}"
finally:
with self._thread_lock:
self._running = False
self._valve_state = "Closed"
if self._status == "Starting Separation":
self._status = "Error: Operation failed to start"
elif self._status != "Separation Complete":
self._status = "Stopped"
def stop_operations(self) -> str:
"""停止任何正在执行的操作"""
with self._thread_lock:
self._running = False
if self._operation_thread and self._operation_thread.is_alive():
self._operation_thread.join(timeout=1.0)
self._operation_thread = None
self._settling_time = 0.0
self._status = "Idle"
self._shake_status = "Idle"
self._shake_time = 0.0
self._time_remaining = timedelta()
return "Success"
def get_status_info(self) -> dict:
"""获取当前设备状态信息"""
with self._thread_lock:
current_time = datetime.now()
if self._start_time:
self._time_spent = current_time - self._start_time
return {
"status": self._status,
"valve_state": self._valve_state,
"settling_time": self._settling_time,
"shake_time": self._shake_time,
"shake_status": self._shake_status,
"current_device": self._current_device,
"purpose": self._purpose,
"product_phase": self._product_phase,
"from_vessel": self._from_vessel,
"separation_vessel": self._separation_vessel,
"to_vessel": self._to_vessel,
"waste_phase_to_vessel": self._waste_phase_to_vessel,
"solvent": self._solvent,
"solvent_volume": self._solvent_volume,
"through": self._through,
"repeats": self._repeats,
"stir_time": self._stir_time,
"stir_speed": self._stir_speed,
"time_spent": self._time_spent.total_seconds(),
"time_remaining": self._time_remaining.total_seconds()
}
# 主函数用于测试
if __name__ == "__main__":
separator = MockSeparator()
print("启动简单版分离器测试...")
print("初始状态:", separator.get_status_info())
# 触发 shake 操作,模拟 10 秒的搅拌
print("执行 shake 操作...")
print(separator.shake(10.0))
# 循环显示状态变化
for i in range(20):
time.sleep(1)
info = separator.get_status_info()
print(
f"{i+1}秒: 状态={info['status']}, 静置时间={info['settling_time']:.1f}秒, "
f"阀门状态={info['valve_state']}, shake_time={info['shake_time']:.1f}, "
f"shake_status={info['shake_status']}"
)
# 模拟打开阀门
print("打开阀门...", separator.set_valve("open"))
print("最终状态:", separator.get_status_info())

View File

@@ -1,89 +0,0 @@
import time
class MockSolenoidValve:
"""
模拟电磁阀设备类 - 简化版本
这个类提供了电磁阀的基本功能:开启、关闭和状态查询
"""
def __init__(self, port: str = "MOCK"):
"""
初始化MockSolenoidValve实例
Args:
port (str): 设备端口,默认为"MOCK"表示模拟设备
"""
self.port = port
self._status: str = "Idle"
self._valve_status: str = "Closed" # 阀门位置Open, Closed
@property
def status(self) -> str:
"""设备状态 - 会被自动识别的设备属性"""
return self._status
@property
def valve_status(self) -> str:
"""阀门状态"""
return self._valve_status
def set_valve_status(self, status: str) -> str:
"""
设置阀门位置
Args:
position (str): 阀门位置,可选值:"Open", "Closed"
Returns:
str: 操作结果状态 ("Success", "Error")
"""
if status not in ["Open", "Closed"]:
self._status = "Error: Invalid position"
return "Error"
self._status = "Moving"
time.sleep(1) # 模拟阀门动作时间
self._valve_status = status
self._status = "Idle"
return "Success"
def open_valve(self) -> str:
"""打开阀门"""
return self.set_valve_status("Open")
def close_valve(self) -> str:
"""关闭阀门"""
return self.set_valve_status("Closed")
def get_valve_status(self) -> str:
"""获取阀门位置"""
return self._valve_status
def is_open(self) -> bool:
"""检查阀门是否打开"""
return self._valve_status == "Open"
def is_closed(self) -> bool:
"""检查阀门是否关闭"""
return self._valve_status == "Closed"
# 用于测试的主函数
if __name__ == "__main__":
valve = MockSolenoidValve()
print("启动电磁阀测试...")
print(f"初始状态: 位置={valve.valve_status}, 状态={valve.status}")
# 测试开启阀门
valve.open_valve()
print(f"开启后: 位置={valve.valve_status}, 状态={valve.status}")
# 测试关闭阀门
valve.close_valve()
print(f"关闭后: 位置={valve.valve_status}, 状态={valve.status}")
print("测试完成")

View File

@@ -1,307 +0,0 @@
import time
import threading
class MockStirrer:
def __init__(self, port: str = "MOCK"):
self.port = port
# 设备基本状态属性
self._status: str = "Idle" # 设备状态Idle, Running, Error, Stopped
# 搅拌相关属性
self._stir_speed: float = 0.0 # 当前搅拌速度 (rpm)
self._target_stir_speed: float = 0.0 # 目标搅拌速度 (rpm)
self._max_stir_speed: float = 2000.0 # 最大搅拌速度 (rpm)
self._stir_state: str = "Stopped" # 搅拌状态Running, Stopped
# 温度相关属性
self._temperature: float = 25.0 # 当前温度 (°C)
self._target_temperature: float = 25.0 # 目标温度 (°C)
self._max_temperature: float = 300.0 # 最大温度 (°C)
self._heating_state: str = "Off" # 加热状态On, Off
self._heating_power: float = 0.0 # 加热功率百分比 0-100
# 运行控制线程
self._operation_thread = None
self._running = False
self._thread_lock = threading.Lock()
# ==================== 状态属性 ====================
# 这些属性会被Uni-Lab系统自动识别并定时对外广播
@property
def status(self) -> str:
return self._status
@property
def stir_speed(self) -> float:
return self._stir_speed
@property
def target_stir_speed(self) -> float:
return self._target_stir_speed
@property
def stir_state(self) -> str:
return self._stir_state
@property
def temperature(self) -> float:
"""
当前温度
Returns:
float: 当前温度 (°C)
"""
return self._temperature
@property
def target_temperature(self) -> float:
"""
目标温度
Returns:
float: 目标温度 (°C)
"""
return self._target_temperature
@property
def heating_state(self) -> str:
return self._heating_state
@property
def heating_power(self) -> float:
return self._heating_power
@property
def max_stir_speed(self) -> float:
return self._max_stir_speed
@property
def max_temperature(self) -> float:
return self._max_temperature
# ==================== 设备控制方法 ====================
# 这些方法需要在注册表中添加会作为ActionServer接受控制指令
def set_stir_speed(self, speed: float) -> str:
speed = float(speed) # 确保传入的速度是浮点数
if speed < 0 or speed > self._max_stir_speed:
self._status = f"Error: Speed out of range (0-{self._max_stir_speed})"
return "Error"
self._target_stir_speed = speed
self._status = "Setting Stir Speed"
# 如果设置了非零速度,启动搅拌
if speed > 0:
self._stir_state = "Running"
else:
self._stir_state = "Stopped"
return "Success"
def set_temperature(self, temperature: float) -> str:
temperature = float(temperature) # 确保传入的温度是浮点数
if temperature < 0 or temperature > self._max_temperature:
self._status = f"Error: Temperature out of range (0-{self._max_temperature})"
return "Error"
self._target_temperature = temperature
self._status = "Setting Temperature"
return "Success"
def start_stirring(self) -> str:
if self._target_stir_speed <= 0:
self._status = "Error: No target speed set"
return "Error"
self._stir_state = "Running"
self._status = "Stirring Started"
return "Success"
def stop_stirring(self) -> str:
self._stir_state = "Stopped"
self._target_stir_speed = 0.0
self._status = "Stirring Stopped"
return "Success"
def heating_control(self, heating_state: str = "On") -> str:
if heating_state not in ["On", "Off"]:
self._status = "Error: Invalid heating state"
return "Error"
self._heating_state = heating_state
if heating_state == "On":
self._status = "Heating On"
else:
self._status = "Heating Off"
self._heating_power = 0.0
return "Success"
def stop_all_operations(self) -> str:
self._stir_state = "Stopped"
self._heating_state = "Off"
self._stop_operation()
self._stir_speed = 0.0
self._target_stir_speed = 0.0
self._heating_power = 0.0
self._status = "All operations stopped"
return "Success"
def emergency_stop(self) -> str:
"""
紧急停止
Returns:
str: 操作结果状态 ("Success", "Error")
"""
self._status = "Emergency Stop"
self.stop_all_operations()
return "Success"
# ==================== 内部控制方法 ====================
def _start_operation(self):
with self._thread_lock:
if not self._running:
self._running = True
self._operation_thread = threading.Thread(target=self._operation_loop)
self._operation_thread.daemon = True
self._operation_thread.start()
def _stop_operation(self):
"""
停止操作线程
安全地停止后台运行线程并等待其完成。
"""
with self._thread_lock:
self._running = False
if self._operation_thread and self._operation_thread.is_alive():
self._operation_thread.join(timeout=2.0)
def _operation_loop(self):
while self._running:
try:
# 处理搅拌速度控制
if self._stir_state == "Running":
speed_diff = self._target_stir_speed - self._stir_speed
if abs(speed_diff) < 1.0: # 速度接近目标值
self._stir_speed = self._target_stir_speed
if self._stir_speed > 0:
self._status = "Stirring at Target Speed"
else:
# 模拟速度调节每秒调整10%的差值
adjustment = speed_diff * 0.1
self._stir_speed += adjustment
self._status = "Adjusting Stir Speed"
# 确保速度在合理范围内
self._stir_speed = max(0.0, min(self._max_stir_speed, self._stir_speed))
else:
# 搅拌停止时速度逐渐降为0
if self._stir_speed > 0:
self._stir_speed = max(0.0, self._stir_speed - 50.0) # 每秒减少50rpm
# 处理温度控制
if self._heating_state == "On":
temp_diff = self._target_temperature - self._temperature
if abs(temp_diff) < 0.5: # 温度接近目标值
self._heating_power = 20.0 # 维持温度的最小功率
elif temp_diff > 0: # 需要加热
# 根据温差调整加热功率
if temp_diff > 50:
self._heating_power = 100.0
elif temp_diff > 20:
self._heating_power = 80.0
elif temp_diff > 10:
self._heating_power = 60.0
else:
self._heating_power = 40.0
# 模拟加热过程
heating_rate = self._heating_power / 100.0 * 1.5 # 最大每秒升温1.5度
self._temperature += heating_rate
else: # 目标温度低于当前温度
self._heating_power = 0.0
# 自然冷却
self._temperature -= 0.1
else:
self._heating_power = 0.0
# 自然冷却到室温
if self._temperature > 25.0:
self._temperature -= 0.2
# 限制温度范围
self._temperature = max(20.0, min(self._max_temperature, self._temperature))
# 更新整体状态
if self._stir_state == "Running" and self._heating_state == "On":
self._status = "Stirring and Heating"
elif self._stir_state == "Running":
self._status = "Stirring Only"
elif self._heating_state == "On":
self._status = "Heating Only"
else:
self._status = "Idle"
# 等待1秒后继续下一次循环
time.sleep(1.0)
except Exception as e:
self._status = f"Error in operation: {str(e)}"
break
# 循环结束时的清理工作
self._status = "Idle"
def get_status_info(self) -> dict:
return {
"status": self._status,
"stir_speed": self._stir_speed,
"target_stir_speed": self._target_stir_speed,
"stir_state": self._stir_state,
"temperature": self._temperature,
"target_temperature": self._target_temperature,
"heating_state": self._heating_state,
"heating_power": self._heating_power,
"max_stir_speed": self._max_stir_speed,
"max_temperature": self._max_temperature,
}
# 用于测试的主函数
if __name__ == "__main__":
stirrer = MockStirrer()
# 测试基本功能
print("启动搅拌器测试...")
print(f"初始状态: {stirrer.get_status_info()}")
# 设置搅拌速度和温度
stirrer.set_stir_speed(800.0)
stirrer.set_temperature(60.0)
stirrer.heating_control("On")
# 模拟运行15秒
for i in range(15):
time.sleep(1)
print(
f"{i+1}秒: 速度={stirrer.stir_speed:.0f}rpm, 温度={stirrer.temperature:.1f}°C, "
f"功率={stirrer.heating_power:.1f}%, 状态={stirrer.status}"
)
stirrer.emergency_stop()
print("测试完成")

View File

@@ -1,229 +0,0 @@
import time
import threading
from datetime import datetime, timedelta
class MockStirrer_new:
def __init__(self, port: str = "MOCK"):
self.port = port
# 基本状态属性
self._status: str = "Idle"
self._vessel: str = ""
self._purpose: str = ""
# 搅拌相关属性
self._stir_speed: float = 0.0
self._target_stir_speed: float = 0.0
self._max_stir_speed: float = 2000.0
self._stir_state: str = "Stopped"
# 计时相关
self._stir_time: float = 0.0
self._settling_time: float = 0.0
self._start_time = datetime.now()
self._time_remaining = timedelta()
# 运行控制
self._operation_thread = None
self._running = False
self._thread_lock = threading.Lock()
# 创建操作线程
self._operation_thread = threading.Thread(target=self._operation_loop)
self._operation_thread.daemon = True
self._operation_thread.start()
# ==================== 状态属性 ====================
@property
def status(self) -> str:
return self._status
@property
def stir_speed(self) -> float:
return self._stir_speed
@property
def target_stir_speed(self) -> float:
return self._target_stir_speed
@property
def stir_state(self) -> str:
return self._stir_state
@property
def vessel(self) -> str:
return self._vessel
@property
def purpose(self) -> str:
return self._purpose
@property
def stir_time(self) -> float:
return self._stir_time
@property
def settling_time(self) -> float:
return self._settling_time
@property
def max_stir_speed(self) -> float:
return self._max_stir_speed
@property
def progress(self) -> float:
"""返回当前操作的进度0-100"""
if not self._running:
return 0.0
elapsed = (datetime.now() - self._start_time).total_seconds()
total_time = self._stir_time + self._settling_time
if total_time <= 0:
return 100.0
return min(100.0, (elapsed / total_time) * 100)
# ==================== Action Server 方法 ====================
def start_stir(self, vessel: str, stir_speed: float = 0.0, purpose: str = "") -> dict:
"""
StartStir.action 对应的方法
"""
with self._thread_lock:
if self._running:
return {
"success": False,
"message": "Operation already in progress"
}
try:
# 重置所有参数
self._vessel = vessel
self._purpose = purpose
self._stir_time = 0.0 # 连续搅拌模式下不设置搅拌时间
self._settling_time = 0.0
self._start_time = datetime.now() # 重置开始时间
if stir_speed > 0:
self._target_stir_speed = min(stir_speed, self._max_stir_speed)
self._stir_state = "Running"
self._status = "Stirring Started"
self._running = True
return {
"success": True,
"message": "Stirring started successfully"
}
except Exception as e:
return {
"success": False,
"message": f"Error: {str(e)}"
}
def stir(self, stir_time: float, stir_speed: float, settling_time: float) -> dict:
"""
Stir.action 对应的方法
"""
with self._thread_lock:
try:
# 如果已经在运行,先停止当前操作
if self._running:
self._running = False
self._stir_state = "Stopped"
self._target_stir_speed = 0.0
time.sleep(0.1) # 给一个短暂的停止时间
# 重置所有参数
self._stir_time = float(stir_time)
self._settling_time = float(settling_time)
self._target_stir_speed = min(float(stir_speed), self._max_stir_speed)
self._start_time = datetime.now() # 重置开始时间
self._stir_state = "Running"
self._status = "Stirring"
self._running = True
return {"success": True}
except ValueError:
self._status = "Error: Invalid parameters"
return {"success": False}
def stop_stir(self, vessel: str) -> dict:
"""
StopStir.action 对应的方法
"""
with self._thread_lock:
if vessel != self._vessel:
return {
"success": False,
"message": "Vessel mismatch"
}
self._running = False
self._stir_state = "Stopped"
self._target_stir_speed = 0.0
self._status = "Stirring Stopped"
return {
"success": True,
"message": "Stirring stopped successfully"
}
# ==================== 内部控制方法 ====================
def _operation_loop(self):
"""操作主循环"""
while True:
try:
current_time = datetime.now()
with self._thread_lock: # 添加锁保护
if self._stir_state == "Running":
# 实际搅拌逻辑
speed_diff = self._target_stir_speed - self._stir_speed
if abs(speed_diff) > 0.1:
adjustment = speed_diff * 0.1
self._stir_speed += adjustment
else:
self._stir_speed = self._target_stir_speed
# 更新进度
if self._running:
if self._stir_time > 0: # 定时搅拌模式
elapsed = (current_time - self._start_time).total_seconds()
if elapsed >= self._stir_time + self._settling_time:
self._running = False
self._stir_state = "Stopped"
self._target_stir_speed = 0.0
self._stir_speed = 0.0
self._status = "Stirring Complete"
elif elapsed >= self._stir_time:
self._status = "Settling"
else: # 连续搅拌模式
self._status = "Stirring"
else:
# 停止状态下慢慢降低速度
if self._stir_speed > 0:
self._stir_speed = max(0, self._stir_speed - 20.0)
time.sleep(0.1)
except Exception as e:
print(f"Error in operation loop: {str(e)}") # 添加错误输出
self._status = f"Error: {str(e)}"
time.sleep(1.0) # 错误发生时等待较长时间
def get_status_info(self) -> dict:
"""获取设备状态信息"""
return {
"status": self._status,
"vessel": self._vessel,
"purpose": self._purpose,
"stir_speed": self._stir_speed,
"target_stir_speed": self._target_stir_speed,
"stir_state": self._stir_state,
"stir_time": self._stir_time, # 添加
"settling_time": self._settling_time, # 添加
"progress": self.progress,
"max_stir_speed": self._max_stir_speed
}

View File

@@ -1,410 +0,0 @@
import time
import threading
class MockVacuum:
"""
模拟真空泵设备类
这个类模拟了一个实验室真空泵的行为,包括真空度控制、
压力监测、运行状态管理等功能。参考了现有的 VacuumPumpMock 实现。
"""
def __init__(self, port: str = "MOCK"):
"""
初始化MockVacuum实例
Args:
port (str): 设备端口,默认为"MOCK"表示模拟设备
"""
self.port = port
# 设备基本状态属性
self._status: str = "Idle" # 设备状态Idle, Running, Error, Stopped
self._power_state: str = "Off" # 电源状态On, Off
self._pump_state: str = "Stopped" # 泵运行状态Running, Stopped, Paused
# 真空相关属性
self._vacuum_level: float = 1013.25 # 当前真空度 (mbar) - 大气压开始
self._target_vacuum: float = 50.0 # 目标真空度 (mbar)
self._min_vacuum: float = 1.0 # 最小真空度 (mbar)
self._max_vacuum: float = 1013.25 # 最大真空度 (mbar) - 大气压
# 泵性能相关属性
self._pump_speed: float = 0.0 # 泵速 (L/s)
self._max_pump_speed: float = 100.0 # 最大泵速 (L/s)
self._pump_efficiency: float = 95.0 # 泵效率百分比
# 运行控制线程
self._vacuum_thread = None
self._running = False
self._thread_lock = threading.Lock()
# ==================== 状态属性 ====================
# 这些属性会被Uni-Lab系统自动识别并定时对外广播
@property
def status(self) -> str:
"""
设备状态 - 会被自动识别的设备属性
Returns:
str: 当前设备状态 (Idle, Running, Error, Stopped)
"""
return self._status
@property
def power_state(self) -> str:
"""
电源状态
Returns:
str: 电源状态 (On, Off)
"""
return self._power_state
@property
def pump_state(self) -> str:
"""
泵运行状态
Returns:
str: 泵状态 (Running, Stopped, Paused)
"""
return self._pump_state
@property
def vacuum_level(self) -> float:
"""
当前真空度
Returns:
float: 当前真空度 (mbar)
"""
return self._vacuum_level
@property
def target_vacuum(self) -> float:
"""
目标真空度
Returns:
float: 目标真空度 (mbar)
"""
return self._target_vacuum
@property
def pump_speed(self) -> float:
"""
泵速
Returns:
float: 泵速 (L/s)
"""
return self._pump_speed
@property
def pump_efficiency(self) -> float:
"""
泵效率
Returns:
float: 泵效率百分比
"""
return self._pump_efficiency
@property
def max_pump_speed(self) -> float:
"""
最大泵速
Returns:
float: 最大泵速 (L/s)
"""
return self._max_pump_speed
# ==================== 设备控制方法 ====================
# 这些方法需要在注册表中添加会作为ActionServer接受控制指令
def power_control(self, power_state: str = "On") -> str:
"""
电源控制方法
Args:
power_state (str): 电源状态,可选值:"On", "Off"
Returns:
str: 操作结果状态 ("Success", "Error")
"""
if power_state not in ["On", "Off"]:
self._status = "Error: Invalid power state"
return "Error"
self._power_state = power_state
if power_state == "On":
self._status = "Power On"
self._start_vacuum_operation()
else:
self._status = "Power Off"
self.stop_vacuum()
return "Success"
def set_vacuum_level(self, vacuum_level: float) -> str:
"""
设置目标真空度
Args:
vacuum_level (float): 目标真空度 (mbar)
Returns:
str: 操作结果状态 ("Success", "Error")
"""
try:
vacuum_level = float(vacuum_level)
except ValueError:
self._status = "Error: Invalid vacuum level"
return "Error"
if self._power_state != "On":
self._status = "Error: Power Off"
return "Error"
if vacuum_level < self._min_vacuum or vacuum_level > self._max_vacuum:
self._status = f"Error: Vacuum level out of range ({self._min_vacuum}-{self._max_vacuum})"
return "Error"
self._target_vacuum = vacuum_level
self._status = "Setting Vacuum Level"
return "Success"
def start_vacuum(self) -> str:
"""
启动真空泵
Returns:
str: 操作结果状态 ("Success", "Error")
"""
if self._power_state != "On":
self._status = "Error: Power Off"
return "Error"
self._pump_state = "Running"
self._status = "Starting Vacuum Pump"
self._start_vacuum_operation()
return "Success"
def stop_vacuum(self) -> str:
"""
停止真空泵
Returns:
str: 操作结果状态 ("Success", "Error")
"""
self._pump_state = "Stopped"
self._status = "Stopping Vacuum Pump"
self._stop_vacuum_operation()
self._pump_speed = 0.0
return "Success"
def pause_vacuum(self) -> str:
"""
暂停真空泵
Returns:
str: 操作结果状态 ("Success", "Error")
"""
if self._pump_state != "Running":
self._status = "Error: Pump not running"
return "Error"
self._pump_state = "Paused"
self._status = "Vacuum Pump Paused"
self._stop_vacuum_operation()
return "Success"
def resume_vacuum(self) -> str:
"""
恢复真空泵运行
Returns:
str: 操作结果状态 ("Success", "Error")
"""
if self._pump_state != "Paused":
self._status = "Error: Pump not paused"
return "Error"
if self._power_state != "On":
self._status = "Error: Power Off"
return "Error"
self._pump_state = "Running"
self._status = "Resuming Vacuum Pump"
self._start_vacuum_operation()
return "Success"
def vent_to_atmosphere(self) -> str:
"""
通大气 - 将真空度恢复到大气压
Returns:
str: 操作结果状态 ("Success", "Error")
"""
self._target_vacuum = self._max_vacuum # 设置为大气压
self._status = "Venting to Atmosphere"
return "Success"
def emergency_stop(self) -> str:
"""
紧急停止
Returns:
str: 操作结果状态 ("Success", "Error")
"""
self._status = "Emergency Stop"
self._pump_state = "Stopped"
self._stop_vacuum_operation()
self._pump_speed = 0.0
return "Success"
# ==================== 内部控制方法 ====================
def _start_vacuum_operation(self):
"""
启动真空操作线程
这个方法启动一个后台线程来模拟真空泵的实际运行过程。
"""
with self._thread_lock:
if not self._running and self._power_state == "On":
self._running = True
self._vacuum_thread = threading.Thread(target=self._vacuum_operation_loop)
self._vacuum_thread.daemon = True
self._vacuum_thread.start()
def _stop_vacuum_operation(self):
"""
停止真空操作线程
安全地停止后台运行线程并等待其完成。
"""
with self._thread_lock:
self._running = False
if self._vacuum_thread and self._vacuum_thread.is_alive():
self._vacuum_thread.join(timeout=2.0)
def _vacuum_operation_loop(self):
"""
真空操作主循环
这个方法在后台线程中运行,模拟真空泵的工作过程:
1. 检查电源状态和运行状态
2. 如果泵状态为 "Running",根据目标真空调整泵速和真空度
3. 否则等待
"""
while self._running and self._power_state == "On":
try:
with self._thread_lock:
# 只有泵状态为 Running 时才进行更新
if self._pump_state == "Running":
vacuum_diff = self._vacuum_level - self._target_vacuum
if abs(vacuum_diff) < 1.0: # 真空度接近目标值
self._status = "At Target Vacuum"
self._pump_speed = self._max_pump_speed * 0.2 # 维持真空的最小泵速
elif vacuum_diff > 0: # 需要抽真空(降低压力)
self._status = "Pumping Down"
if vacuum_diff > 500:
self._pump_speed = self._max_pump_speed
elif vacuum_diff > 100:
self._pump_speed = self._max_pump_speed * 0.8
elif vacuum_diff > 50:
self._pump_speed = self._max_pump_speed * 0.6
else:
self._pump_speed = self._max_pump_speed * 0.4
# 根据泵速和效率计算真空降幅
pump_rate = (self._pump_speed / self._max_pump_speed) * self._pump_efficiency / 100.0
vacuum_reduction = pump_rate * 10.0 # 每秒最大降低10 mbar
self._vacuum_level = max(self._target_vacuum, self._vacuum_level - vacuum_reduction)
else: # 目标真空度高于当前值,需要通气
self._status = "Venting"
self._pump_speed = 0.0
self._vacuum_level = min(self._target_vacuum, self._vacuum_level + 5.0)
# 限制真空度范围
self._vacuum_level = max(self._min_vacuum, min(self._max_vacuum, self._vacuum_level))
else:
# 当泵状态不是 Running 时,可保持原状态
self._status = "Vacuum Pump Not Running"
# 释放锁后等待1秒钟
time.sleep(1.0)
except Exception as e:
with self._thread_lock:
self._status = f"Error in vacuum operation: {str(e)}"
break
# 循环结束后的清理工作
if self._pump_state == "Running":
self._status = "Idle"
# 停止泵后,真空度逐渐回升到大气压
while self._vacuum_level < self._max_vacuum * 0.9:
with self._thread_lock:
self._vacuum_level += 2.0
time.sleep(0.1)
def get_status_info(self) -> dict:
"""
获取完整的设备状态信息
Returns:
dict: 包含所有设备状态的字典
"""
return {
"status": self._status,
"power_state": self._power_state,
"pump_state": self._pump_state,
"vacuum_level": self._vacuum_level,
"target_vacuum": self._target_vacuum,
"pump_speed": self._pump_speed,
"pump_efficiency": self._pump_efficiency,
"max_pump_speed": self._max_pump_speed,
}
# 用于测试的主函数
if __name__ == "__main__":
vacuum = MockVacuum()
# 测试基本功能
print("启动真空泵测试...")
vacuum.power_control("On")
print(f"初始状态: {vacuum.get_status_info()}")
# 设置目标真空度并启动
vacuum.set_vacuum_level(10.0) # 设置为10mbar
vacuum.start_vacuum()
# 模拟运行15秒
for i in range(15):
time.sleep(1)
print(
f"{i+1}秒: 真空度={vacuum.vacuum_level:.1f}mbar, 泵速={vacuum.pump_speed:.1f}L/s, 状态={vacuum.status}"
)
# 测试通大气
print("测试通大气...")
vacuum.vent_to_atmosphere()
# 继续运行5秒观察通大气过程
for i in range(5):
time.sleep(1)
print(f"通大气第{i+1}秒: 真空度={vacuum.vacuum_level:.1f}mbar, 状态={vacuum.status}")
vacuum.emergency_stop()
print("测试完成")

View File

@@ -3,6 +3,7 @@ from threading import Lock, Event
from enum import Enum
from dataclasses import dataclass
import time
import traceback
from typing import Any, Union, Optional, overload
import serial.tools.list_ports
@@ -386,3 +387,8 @@ class RunzeSyringePump:
def list():
for item in serial.tools.list_ports.comports():
yield RunzeSyringePumpInfo(port=item.device)
if __name__ == "__main__":
r = RunzeSyringePump("/dev/tty.usbserial-D30JUGG5", "1", 25.0)
r.initialize()

View File

@@ -0,0 +1,282 @@
import sys
import threading
import serial
import serial.tools.list_ports
import re
import time
from typing import Optional, List, Dict, Tuple
class ChinweDevice:
"""
ChinWe设备控制类
提供串口通信、电机控制、传感器数据读取等功能
"""
def __init__(self, port: str, baudrate: int = 115200, debug: bool = False):
"""
初始化ChinWe设备
Args:
port: 串口名称如果为None则自动检测
baudrate: 波特率默认115200
"""
self.debug = debug
self.port = port
self.baudrate = baudrate
self.serial_port: Optional[serial.Serial] = None
self._voltage: float = 0.0
self._ec_value: float = 0.0
self._ec_adc_value: int = 0
self._is_connected = False
self.connect()
@property
def is_connected(self) -> bool:
"""获取连接状态"""
return self._is_connected and self.serial_port and self.serial_port.is_open
@property
def voltage(self) -> float:
"""获取电源电压值"""
return self._voltage
@property
def ec_value(self) -> float:
"""获取电导率值 (ms/cm)"""
return self._ec_value
@property
def ec_adc_value(self) -> int:
"""获取EC ADC原始值"""
return self._ec_adc_value
@property
def device_status(self) -> Dict[str, any]:
"""
获取设备状态信息
Returns:
包含设备状态的字典
"""
return {
"connected": self.is_connected,
"port": self.port,
"baudrate": self.baudrate,
"voltage": self.voltage,
"ec_value": self.ec_value,
"ec_adc_value": self.ec_adc_value
}
def connect(self, port: Optional[str] = None, baudrate: Optional[int] = None) -> bool:
"""
连接到串口设备
Args:
port: 串口名称如果为None则使用初始化时的port或自动检测
baudrate: 波特率如果为None则使用初始化时的baudrate
Returns:
连接是否成功
"""
if self.is_connected:
return True
target_port = port or self.port
target_baudrate = baudrate or self.baudrate
try:
self.serial_port = serial.Serial(target_port, target_baudrate, timeout=0.5)
self._is_connected = True
self.port = target_port
self.baudrate = target_baudrate
connect_allow_times = 5
while not self.serial_port.is_open and connect_allow_times > 0:
time.sleep(0.5)
connect_allow_times -= 1
print(f"尝试连接到 {target_port} @ {target_baudrate},剩余尝试次数: {connect_allow_times}", self.debug)
raise ValueError("串口未打开,请检查设备连接")
print(f"已连接到 {target_port} @ {target_baudrate}", self.debug)
threading.Thread(target=self._read_data, daemon=True).start()
return True
except Exception as e:
print(f"ChinweDevice连接失败: {e}")
self._is_connected = False
return False
def disconnect(self) -> bool:
"""
断开串口连接
Returns:
断开是否成功
"""
if self.serial_port and self.serial_port.is_open:
try:
self.serial_port.close()
self._is_connected = False
print("已断开串口连接")
return True
except Exception as e:
print(f"断开连接失败: {e}")
return False
return True
def _send_motor_command(self, command: str) -> bool:
"""
发送电机控制命令
Args:
command: 电机命令字符串,例如 "M 1 CW 1.5"
Returns:
发送是否成功
"""
if not self.is_connected:
print("设备未连接")
return False
try:
self.serial_port.write((command + "\n").encode('utf-8'))
print(f"发送命令: {command}")
return True
except Exception as e:
print(f"发送命令失败: {e}")
return False
def rotate_motor(self, motor_id: int, turns: float, clockwise: bool = True) -> bool:
"""
使电机转动指定圈数
Args:
motor_id: 电机ID1, 2, 3...
turns: 转动圈数,支持小数
clockwise: True为顺时针False为逆时针
Returns:
命令发送是否成功
"""
if clockwise:
command = f"M {motor_id} CW {turns}"
else:
command = f"M {motor_id} CCW {turns}"
return self._send_motor_command(command)
def set_motor_speed(self, motor_id: int, speed: float) -> bool:
"""
设置电机转速(如果设备支持)
Args:
motor_id: 电机ID1, 2, 3...
speed: 转速值
Returns:
命令发送是否成功
"""
command = f"M {motor_id} SPEED {speed}"
return self._send_motor_command(command)
def _read_data(self) -> List[str]:
"""
读取串口数据并解析
Returns:
读取到的数据行列表
"""
print("开始读取串口数据...")
if not self.is_connected:
return []
data_lines = []
try:
while self.serial_port.in_waiting:
time.sleep(0.1) # 等待数据稳定
try:
line = self.serial_port.readline().decode('utf-8', errors='ignore').strip()
if line:
data_lines.append(line)
self._parse_sensor_data(line)
except Exception as ex:
print(f"解码数据错误: {ex}")
except Exception as e:
print(f"读取串口数据错误: {e}")
return data_lines
def _parse_sensor_data(self, line: str) -> None:
"""
解析传感器数据
Args:
line: 接收到的数据行
"""
# 解析电源电压
if "电源电压" in line:
try:
val = float(line.split("")[1].replace("V", "").strip())
self._voltage = val
if self.debug:
print(f"电源电压更新: {val}V")
except Exception:
pass
# 解析电导率和ADC原始值支持两种格式
if "电导率" in line and "ADC原始值" in line:
try:
# 支持格式如电导率2.50ms/cm, ADC原始值2052
ec_match = re.search(r"电导率[:]\s*([\d\.]+)", line)
adc_match = re.search(r"ADC原始值[:]\s*(\d+)", line)
if ec_match:
ec_val = float(ec_match.group(1))
self._ec_value = ec_val
if self.debug:
print(f"电导率更新: {ec_val:.2f} ms/cm")
if adc_match:
adc_val = int(adc_match.group(1))
self._ec_adc_value = adc_val
if self.debug:
print(f"EC ADC原始值更新: {adc_val}")
except Exception:
pass
# 仅电导率无ADC原始值
elif "电导率" in line:
try:
val = float(line.split("")[1].replace("ms/cm", "").strip())
self._ec_value = val
if self.debug:
print(f"电导率更新: {val:.2f} ms/cm")
except Exception:
pass
# 仅ADC原始值如有分开回传场景
elif "ADC原始值" in line:
try:
adc_val = int(line.split("")[1].strip())
self._ec_adc_value = adc_val
if self.debug:
print(f"EC ADC原始值更新: {adc_val}")
except Exception:
pass
def spin_when_ec_ge_0():
pass
def main():
"""测试函数"""
print("=== ChinWe设备测试 ===")
# 创建设备实例
device = ChinweDevice("/dev/tty.usbserial-A5069RR4", debug=True)
try:
# 测试5: 发送电机命令
print("\n5. 发送电机命令测试:")
print(" 5.3 使用通用函数控制电机20顺时针转2圈:")
device.rotate_motor(2, 20.0, clockwise=True)
time.sleep(0.5)
finally:
time.sleep(10)
# 测试7: 断开连接
print("\n7. 断开连接:")
device.disconnect()
if __name__ == "__main__":
main()

View File

@@ -3,6 +3,8 @@ import logging
import time as time_module
from typing import Dict, Any, Optional
from unilabos.compile.utils.vessel_parser import get_vessel
class VirtualFilter:
"""Virtual filter device - 完全按照 Filter.action 规范 🌊"""
@@ -40,7 +42,6 @@ class VirtualFilter:
"progress": 0.0, # Filter.action feedback
"current_temp": 25.0, # Filter.action feedback
"filtered_volume": 0.0, # Filter.action feedback
"current_status": "Ready for filtration", # Filter.action feedback
"message": "Ready for filtration"
})
@@ -52,9 +53,7 @@ class VirtualFilter:
self.logger.info(f"🧹 清理虚拟过滤器 {self.device_id} 🔚")
self.data.update({
"status": "Offline",
"current_status": "System offline",
"message": "System offline"
"status": "Offline"
})
self.logger.info(f"✅ 过滤器 {self.device_id} 清理完成 💤")
@@ -62,8 +61,8 @@ class VirtualFilter:
async def filter(
self,
vessel: str,
filtrate_vessel: str = "",
vessel: dict,
filtrate_vessel: dict = {},
stir: bool = False,
stir_speed: float = 300.0,
temp: float = 25.0,
@@ -71,7 +70,9 @@ class VirtualFilter:
volume: float = 0.0
) -> bool:
"""Execute filter action - 完全按照 Filter.action 参数 🌊"""
vessel_id, _ = get_vessel(vessel)
filtrate_vessel_id, _ = get_vessel(filtrate_vessel) if filtrate_vessel else (f"{vessel_id}_filtrate", {})
# 🔧 新增:温度自动调整
original_temp = temp
if temp == 0.0:
@@ -81,7 +82,7 @@ class VirtualFilter:
temp = 4.0 # 小于4度自动设置为4度
self.logger.info(f"🌡️ 温度自动调整: {original_temp}°C → {temp}°C (最低温度) ❄️")
self.logger.info(f"🌊 开始过滤操作: {vessel}{filtrate_vessel} 🚰")
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")
@@ -93,7 +94,6 @@ class VirtualFilter:
self.logger.error(f"{error_msg}")
self.data.update({
"status": f"Error: 温度超出范围 ⚠️",
"current_status": f"Error: 温度超出范围 ⚠️",
"message": error_msg
})
return False
@@ -103,7 +103,6 @@ class VirtualFilter:
self.logger.error(f"{error_msg}")
self.data.update({
"status": f"Error: 搅拌速度超出范围 ⚠️",
"current_status": f"Error: 搅拌速度超出范围 ⚠️",
"message": error_msg
})
return False
@@ -112,8 +111,7 @@ class VirtualFilter:
error_msg = f"💧 过滤体积 {volume} mL 超出范围 (0-{self._max_volume} mL) ⚠️"
self.logger.error(f"{error_msg}")
self.data.update({
"status": f"Error: 体积超出范围 ⚠️",
"current_status": f"Error: 体积超出范围 ⚠️",
"status": f"Error",
"message": error_msg
})
return False
@@ -123,12 +121,11 @@ class VirtualFilter:
self.logger.info(f"🚀 开始过滤 {filter_volume}mL 液体 💧")
self.data.update({
"status": f"🌊 过滤中: {vessel}",
"status": f"Running",
"current_temp": temp,
"filtered_volume": 0.0,
"progress": 0.0,
"current_status": f"🌊 Filtering {vessel}{filtrate_vessel}",
"message": f"🚀 Starting filtration: {vessel}{filtrate_vessel}"
"message": f"🚀 Starting filtration: {vessel_id}{filtrate_vessel_id}"
})
try:
@@ -164,8 +161,7 @@ class VirtualFilter:
"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
"status": status_msg,
"status": "Running",
"message": f"🌊 Filtering: {progress:.1f}% complete, {current_filtered:.1f}mL filtered"
})
@@ -190,11 +186,10 @@ class VirtualFilter:
"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}"
"message": f"✅ Filtration completed: {filter_volume}mL filtered from {vessel_id}"
})
self.logger.info(f"🎉 过滤完成! 💧 {filter_volume}mL 从 {vessel} 过滤到 {filtrate_vessel}")
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
@@ -202,8 +197,7 @@ class VirtualFilter:
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)}",
"status": f"Error",
"message": f"❌ Filtration failed: {str(e)}"
})
return False
@@ -222,17 +216,17 @@ class VirtualFilter:
def current_temp(self) -> float:
"""Filter.action feedback 字段 🌡️"""
return self.data.get("current_temp", 25.0)
@property
def filtered_volume(self) -> float:
"""Filter.action feedback 字段 💧"""
return self.data.get("filtered_volume", 0.0)
@property
def current_status(self) -> str:
"""Filter.action feedback 字段 📋"""
return self.data.get("current_status", "")
@property
def filtered_volume(self) -> float:
"""Filter.action feedback 字段 💧"""
return self.data.get("filtered_volume", 0.0)
@property
def message(self) -> str:
return self.data.get("message", "")

View File

@@ -67,8 +67,8 @@ class VirtualHeatChill:
self.logger.info(f"✅ 温控设备 {self.device_id} 清理完成 💤")
return True
async def heat_chill(self, vessel: str, temp: float, time, stir: bool,
stir_speed: float, purpose: str) -> bool:
async def heat_chill(self, temp: float, time, stir: bool,
stir_speed: float, purpose: str, vessel: dict = {}) -> bool:
"""Execute heat chill action - 🔧 修复:确保参数类型正确"""
# 🔧 关键修复:确保所有参数类型正确
@@ -77,7 +77,6 @@ class VirtualHeatChill:
time_value = float(time) # 强制转换为浮点数
stir_speed = float(stir_speed)
stir = bool(stir)
vessel = str(vessel)
purpose = str(purpose)
except (ValueError, TypeError) as e:
error_msg = f"参数类型转换错误: temp={temp}({type(temp)}), time={time}({type(time)}), error={str(e)}"
@@ -102,8 +101,7 @@ class VirtualHeatChill:
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" 🎯 目标温度: {temp}°C {temp_emoji}")
self.logger.info(f" ⏰ 持续时间: {time_value}s")
self.logger.info(f" 🌪️ 搅拌: {stir} ({stir_speed} RPM)")
@@ -147,7 +145,7 @@ class VirtualHeatChill:
stir_info = f" | 🌪️ 搅拌: {stir_speed} RPM" if stir else ""
self.data.update({
"status": f"{temp_emoji} 运行中: {status_action} {vessel}{temp}°C | ⏰ 剩余: {total_time:.0f}s{stir_info}",
"status": f"{temp_emoji} 运行中: {status_action}{temp}°C | ⏰ 剩余: {total_time:.0f}s{stir_info}",
"operation_mode": operation_mode,
"is_stirring": stir,
"stir_speed": stir_speed if stir else 0.0,
@@ -165,7 +163,7 @@ class VirtualHeatChill:
# 更新剩余时间和状态
self.data.update({
"remaining_time": remaining,
"status": f"{temp_emoji} 运行中: {status_action} {vessel}{temp}°C | ⏰ 剩余: {remaining:.0f}s{stir_info}",
"status": f"{temp_emoji} 运行中: {status_action}{temp}°C | ⏰ 剩余: {remaining:.0f}s{stir_info}",
"progress": progress
})
@@ -185,7 +183,7 @@ class VirtualHeatChill:
final_stir_info = f" | 🌪️ 搅拌: {stir_speed} RPM" if stir else ""
self.data.update({
"status": f"✅ 完成: {vessel} 已达到 {temp}°C {temp_emoji} | ⏱️ 用时: {total_time:.0f}s{final_stir_info}",
"status": f"✅ 完成: 已达到 {temp}°C {temp_emoji} | ⏱️ 用时: {total_time:.0f}s{final_stir_info}",
"operation_mode": "Completed",
"remaining_time": 0.0,
"is_stirring": False,
@@ -195,7 +193,6 @@ class VirtualHeatChill:
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:
@@ -204,13 +201,12 @@ class VirtualHeatChill:
return True
async def heat_chill_start(self, vessel: str, temp: float, purpose: str) -> bool:
async def heat_chill_start(self, temp: float, purpose: str, vessel: dict = {}) -> bool:
"""Start continuous heat chill 🔄"""
# 🔧 添加类型转换
try:
temp = float(temp)
vessel = str(vessel)
purpose = str(purpose)
except (ValueError, TypeError) as e:
error_msg = f"参数类型转换错误: {str(e)}"
@@ -235,8 +231,7 @@ class VirtualHeatChill:
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" 🎯 目标温度: {temp}°C {temp_emoji}")
self.logger.info(f" 🔄 模式: {status_action}")
self.logger.info(f" 📝 目的: {purpose}")
@@ -252,7 +247,7 @@ class VirtualHeatChill:
return False
self.data.update({
"status": f"🔄 启动: {status_action} {vessel}{temp}°C {temp_emoji} | ♾️ 持续运行",
"status": f"🔄 启动: {status_action}{temp}°C {temp_emoji} | ♾️ 持续运行",
"operation_mode": operation_mode,
"is_stirring": False,
"stir_speed": 0.0,
@@ -262,28 +257,20 @@ class VirtualHeatChill:
self.logger.info(f"✅ 持续温控已启动! {temp_emoji} {status_action}模式 🚀")
return True
async def heat_chill_stop(self, vessel: str) -> bool:
async def heat_chill_stop(self, vessel: dict = {}) -> bool:
"""Stop heat chill 🛑"""
# 🔧 添加类型转换
try:
vessel = str(vessel)
except (ValueError, TypeError) as e:
error_msg = f"参数类型转换错误: {str(e)}"
self.logger.error(f"{error_msg}")
return False
self.logger.info(f"🛑 停止温控: {vessel}")
self.logger.info(f"🛑 停止温控:")
self.data.update({
"status": f"🛑 已停止: {vessel} 温控停止",
"status": f"🛑 {self.device_id} 温控停止",
"operation_mode": "Stopped",
"is_stirring": False,
"stir_speed": 0.0,
"remaining_time": 0.0,
})
self.logger.info(f"✅ 温控设备已停止 {vessel} 温度控制 🏁")
self.logger.info(f"✅ 温控设备已停止 {self.device_id} 温度控制 🏁")
return True
# 状态属性

View File

@@ -21,19 +21,6 @@ class VirtualMultiwayValve:
self._current_position = 0 # 默认在0号位transfer pump位置
self._target_position = 0
# 位置映射说明
self.position_map = {
0: "transfer_pump", # 0号位连接转移泵
1: "port_1", # 1号位
2: "port_2", # 2号位
3: "port_3", # 3号位
4: "port_4", # 4号位
5: "port_5", # 5号位
6: "port_6", # 6号位
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}")
@@ -60,7 +47,7 @@ class VirtualMultiwayValve:
def get_current_port(self) -> str:
"""获取当前连接的端口名称 🔌"""
return self.position_map.get(self._current_position, "unknown")
return self._current_position
def set_position(self, command: Union[int, str]):
"""
@@ -115,7 +102,7 @@ class VirtualMultiwayValve:
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.logger.info(f"🔄 阀门切换: {old_position}({old_port}) → {pos} {pos_emoji}")
self._status = "Busy"
self._valve_state = "Moving"
@@ -190,6 +177,17 @@ class VirtualMultiwayValve:
"""获取阀门位置 - 兼容性方法 📍"""
return self._current_position
def set_valve_position(self, command: Union[int, str]):
"""
设置阀门位置 - 兼容pump_protocol调用 🎯
这是set_position的别名方法用于兼容pump_protocol.py
Args:
command: 目标位置 (0-8) 或位置字符串
"""
# 删除debug日志self.logger.debug(f"🎯 兼容性调用: set_valve_position({command})")
return self.set_position(command)
def is_at_position(self, position: int) -> bool:
"""检查是否在指定位置 🎯"""
result = self._current_position == position
@@ -210,17 +208,6 @@ class VirtualMultiwayValve:
# 删除debug日志self.logger.debug(f"🔌 端口{port_number}检查: {port_status} (当前位置: {self._current_position})")
return result
def get_available_positions(self) -> list:
"""获取可用位置列表 📋"""
positions = list(range(0, self.max_positions + 1))
# 删除debug日志self.logger.debug(f"📋 可用位置: {positions}")
return positions
def get_available_ports(self) -> Dict[int, str]:
"""获取可用端口映射 🗺️"""
# 删除debug日志self.logger.debug(f"🗺️ 端口映射: {self.position_map}")
return self.position_map.copy()
def reset(self):
"""重置阀门到transfer pump位置0号位🔄"""
self.logger.info(f"🔄 重置阀门到泵位置...")
@@ -253,41 +240,12 @@ class VirtualMultiwayValve:
# 删除debug日志self.logger.debug(f"🌊 当前流路: {flow_path}")
return flow_path
def get_info(self) -> dict:
"""获取阀门详细信息 📊"""
info = {
"port": self.port,
"max_positions": self.max_positions,
"total_positions": self.total_positions,
"current_position": self._current_position,
"current_port": self.get_current_port(),
"target_position": self._target_position,
"status": self._status,
"valve_state": self._valve_state,
"flow_path": self.get_flow_path(),
"position_map": self.position_map
}
# 删除debug日志self.logger.debug(f"📊 阀门信息: 位置={self._current_position}, 状态={self._status}, 端口={self.get_current_port()}")
return info
def __str__(self):
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调用 🎯
这是set_position的别名方法用于兼容pump_protocol.py
Args:
command: 目标位置 (0-8) 或位置字符串
"""
# 删除debug日志self.logger.debug(f"🎯 兼容性调用: set_valve_position({command})")
return self.set_position(command)
# 使用示例
if __name__ == "__main__":
@@ -309,13 +267,6 @@ if __name__ == "__main__":
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_info()}")
# 测试切换功能
print(f"\n🔄 智能切换测试:")
print(f"当前位置: {valve._current_position}")

View File

@@ -1,197 +0,0 @@
import asyncio
import logging
from typing import Dict, Any, Optional
class VirtualPump:
"""Virtual pump device for transfer and cleaning operations"""
def __init__(self, device_id: str = None, config: Dict[str, Any] = None, **kwargs):
# 处理可能的不同调用方式
if device_id is None and 'id' in kwargs:
device_id = kwargs.pop('id')
if config is None and 'config' in kwargs:
config = kwargs.pop('config')
# 设置默认值
self.device_id = device_id or "unknown_pump"
self.config = config or {}
self.logger = logging.getLogger(f"VirtualPump.{self.device_id}")
self.data = {}
# 从config或kwargs中获取配置参数
self.port = self.config.get('port') or kwargs.get('port', 'VIRTUAL')
self._max_volume = self.config.get('max_volume') or kwargs.get('max_volume', 50.0)
self._transfer_rate = self.config.get('transfer_rate') or kwargs.get('transfer_rate', 10.0)
print(f"=== VirtualPump {self.device_id} created with max_volume={self._max_volume}, transfer_rate={self._transfer_rate} ===")
async def initialize(self) -> bool:
"""Initialize virtual pump"""
self.logger.info(f"Initializing virtual pump {self.device_id}")
self.data.update({
"status": "Idle",
"valve_position": 0,
"current_volume": 0.0,
"max_volume": self._max_volume,
"transfer_rate": self._transfer_rate,
"from_vessel": "",
"to_vessel": "",
"progress": 0.0,
"transferred_volume": 0.0,
"current_status": "Ready"
})
return True
async def cleanup(self) -> bool:
"""Cleanup virtual pump"""
self.logger.info(f"Cleaning up virtual pump {self.device_id}")
return True
async def transfer(self, from_vessel: str, to_vessel: str, volume: float,
amount: str = "", time: float = 0.0, viscous: bool = False,
rinsing_solvent: str = "", rinsing_volume: float = 0.0,
rinsing_repeats: int = 0, solid: bool = False) -> bool:
"""Execute transfer operation"""
self.logger.info(f"Transferring {volume}mL from {from_vessel} to {to_vessel}")
# 计算转移时间
transfer_time = volume / self._transfer_rate if time == 0 else time
self.data.update({
"status": "Running",
"from_vessel": from_vessel,
"to_vessel": to_vessel,
"current_status": "Transferring",
"progress": 0.0,
"transferred_volume": 0.0
})
# 模拟转移过程
steps = 10
step_time = transfer_time / steps
step_volume = volume / steps
for i in range(steps):
await asyncio.sleep(step_time)
progress = (i + 1) / steps * 100
current_volume = step_volume * (i + 1)
self.data.update({
"progress": progress,
"transferred_volume": current_volume,
"current_status": f"Transferring: {progress:.1f}%"
})
self.logger.info(f"Transfer progress: {progress:.1f}%")
self.data.update({
"status": "Idle",
"current_status": "Transfer completed",
"progress": 100.0,
"transferred_volume": volume
})
return True
async def clean_vessel(self, vessel: str, solvent: str, volume: float,
temp: float, repeats: int = 1) -> bool:
"""Execute vessel cleaning operation - matches CleanVessel action"""
self.logger.info(f"Starting vessel cleaning: {vessel} with {solvent} ({volume}mL at {temp}°C, {repeats} repeats)")
# 更新设备状态
self.data.update({
"status": "Running",
"from_vessel": f"flask_{solvent}",
"to_vessel": vessel,
"current_status": "Cleaning in progress",
"progress": 0.0,
"transferred_volume": 0.0
})
# 计算清洗时间(基于体积和重复次数)
# 假设清洗速度为 transfer_rate 的一半(因为需要加载和排放)
cleaning_rate = self._transfer_rate / 2
cleaning_time_per_cycle = volume / cleaning_rate
total_cleaning_time = cleaning_time_per_cycle * repeats
# 模拟清洗过程
steps_per_repeat = 10 # 每次重复清洗分10个步骤
total_steps = steps_per_repeat * repeats
step_time = total_cleaning_time / total_steps
for repeat in range(repeats):
self.logger.info(f"Starting cleaning cycle {repeat + 1}/{repeats}")
for step in range(steps_per_repeat):
await asyncio.sleep(step_time)
# 计算当前进度
current_step = repeat * steps_per_repeat + step + 1
progress = (current_step / total_steps) * 100
# 计算已处理的体积
volume_processed = (current_step / total_steps) * volume * repeats
# 更新状态
self.data.update({
"progress": progress,
"transferred_volume": volume_processed,
"current_status": f"Cleaning cycle {repeat + 1}/{repeats} - Step {step + 1}/{steps_per_repeat} ({progress:.1f}%)"
})
self.logger.info(f"Cleaning progress: {progress:.1f}% (Cycle {repeat + 1}/{repeats})")
# 清洗完成
self.data.update({
"status": "Idle",
"current_status": "Cleaning completed successfully",
"progress": 100.0,
"transferred_volume": volume * repeats,
"from_vessel": "",
"to_vessel": ""
})
self.logger.info(f"Vessel cleaning completed: {vessel}")
return True
# 状态属性
@property
def status(self) -> str:
return self.data.get("status", "Unknown")
@property
def valve_position(self) -> int:
return self.data.get("valve_position", 0)
@property
def current_volume(self) -> float:
return self.data.get("current_volume", 0.0)
@property
def max_volume(self) -> float:
return self.data.get("max_volume", 0.0)
@property
def transfer_rate(self) -> float:
return self.data.get("transfer_rate", 0.0)
@property
def from_vessel(self) -> str:
return self.data.get("from_vessel", "")
@property
def to_vessel(self) -> str:
return self.data.get("to_vessel", "")
@property
def progress(self) -> float:
return self.data.get("progress", 0.0)
@property
def transferred_volume(self) -> float:
return self.data.get("transferred_volume", 0.0)
@property
def current_status(self) -> str:
return self.data.get("current_status", "Ready")

View File

@@ -99,8 +99,8 @@ class VirtualRotavap:
self.logger.error(f"❌ 时间参数类型无效: {type(time)}使用默认值180.0秒")
time = 180.0
# 确保time是float类型
time = float(time)
# 确保time是float类型; 并加速
time = float(time) / 10.0
# 🔧 简化处理如果vessel就是设备自己直接操作
if vessel == self.device_id:

View File

@@ -48,20 +48,6 @@ class VirtualSolenoidValve:
"""获取阀门位置状态"""
return "OPEN" if self._is_open else "CLOSED"
@property
def state(self) -> dict:
"""获取阀门完整状态"""
return {
"device_id": self.device_id,
"port": self.port,
"voltage": self.voltage,
"response_time": self.response_time,
"is_open": self._is_open,
"valve_state": self._valve_state,
"status": self._status,
"position": self.valve_position
}
async def set_valve_position(self, command: str = None, **kwargs):
"""
设置阀门位置 - ROS动作接口

View File

@@ -319,21 +319,6 @@ class VirtualSolidDispenser:
def total_operations(self) -> int:
return self._total_operations
def get_device_info(self) -> Dict[str, Any]:
"""获取设备状态信息 📊"""
info = {
"device_id": self.device_id,
"status": self._status,
"current_reagent": self._current_reagent,
"last_dispensed_amount": self._dispensed_amount,
"total_operations": self._total_operations,
"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):
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)"
@@ -380,8 +365,6 @@ async def test_solid_dispenser():
mass="150 g" # 超过100g限制
)
print(f"📊 测试4结果: {result4}")
print(f"\n📊 最终设备信息: {dispenser.get_device_info()}")
print(f"✅ === 测试完成 === 🎉")

View File

@@ -321,7 +321,7 @@ class VirtualStirrer:
"min_speed": self._min_speed
}
self.logger.debug(f"📊 设备信息: 模式={self.operation_mode}, 速度={self.current_speed} RPM, 搅拌={self.is_stirring}")
# self.logger.debug(f"📊 设备信息: 模式={self.operation_mode}, 速度={self.current_speed} RPM, 搅拌={self.is_stirring}")
return info
def __str__(self):

View File

@@ -380,22 +380,6 @@ class VirtualTransferPump:
"""检查是否已满"""
return self._current_volume >= (self.max_volume - 0.01) # 允许小量误差
# 调试和状态信息
def get_pump_info(self) -> dict:
"""获取泵的详细信息"""
return {
"device_id": self.device_id,
"status": self._status,
"position": self._position,
"current_volume": self._current_volume,
"max_volume": self.max_volume,
"max_velocity": self._max_velocity,
"mode": self.mode.name,
"is_empty": self.is_empty(),
"is_full": self.is_full(),
"remaining_capacity": self.get_remaining_capacity()
}
def __str__(self):
return f"VirtualTransferPump({self.device_id}: {self._current_volume:.2f}/{self.max_volume} ml, {self._status})"
@@ -425,8 +409,6 @@ async def demo():
result = await pump.set_position(0.0)
print(f"Empty result: {result}")
print(f"After emptying: {pump}")
print("\nPump info:", pump.get_pump_info())
if __name__ == "__main__":