mirror of
https://github.com/dptech-corp/Uni-Lab-OS.git
synced 2026-02-06 23:15:10 +00:00
Add Mock Device for Organic Synthesis\添加有机合成的虚拟仪器和Protocol (#43)
* Add Device MockChiller Add device MockChiller * Add Device MockFilter * Add Device MockPump * Add Device MockRotavap * Add Device MockSeparator * Add Device MockStirrer * Add Device MockHeater * Add Device MockVacuum * Add Device MockSolenoidValve * Add Device Mock \_init_.py * 规范模拟设备代码与注册表信息 * 更改Mock大写文件夹名 * 删除大写目录 * Edited Mock device json * Match mock device with action * Edit mock device yaml * Add new action * Add Virtual Device, Action, YAML, Protocol for Organic Syn * 单独分类测试的protocol文件夹 * 更名Action --------- Co-authored-by: Xuwznln <18435084+Xuwznln@users.noreply.github.com>
This commit is contained in:
0
unilabos/devices/virtual/__init__.py
Normal file
0
unilabos/devices/virtual/__init__.py
Normal file
158
unilabos/devices/virtual/virtual_centrifuge.py
Normal file
158
unilabos/devices/virtual/virtual_centrifuge.py
Normal file
@@ -0,0 +1,158 @@
|
||||
import asyncio
|
||||
import logging
|
||||
from typing import Dict, Any
|
||||
|
||||
class VirtualCentrifuge:
|
||||
"""Virtual centrifuge device for CentrifugeProtocol testing"""
|
||||
|
||||
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_centrifuge"
|
||||
self.config = config or {}
|
||||
|
||||
self.logger = logging.getLogger(f"VirtualCentrifuge.{self.device_id}")
|
||||
self.data = {}
|
||||
|
||||
# 添加调试信息
|
||||
print(f"=== VirtualCentrifuge {self.device_id} is being created! ===")
|
||||
print(f"=== Config: {self.config} ===")
|
||||
print(f"=== Kwargs: {kwargs} ===")
|
||||
|
||||
# 从config或kwargs中获取配置参数
|
||||
self.port = self.config.get('port') or kwargs.get('port', 'VIRTUAL')
|
||||
self._max_speed = self.config.get('max_speed') or kwargs.get('max_speed', 15000.0)
|
||||
self._max_temp = self.config.get('max_temp') or kwargs.get('max_temp', 40.0)
|
||||
self._min_temp = self.config.get('min_temp') or kwargs.get('min_temp', 4.0)
|
||||
|
||||
# 处理其他kwargs参数,但跳过已知的配置参数
|
||||
skip_keys = {'port', 'max_speed', 'max_temp', 'min_temp'}
|
||||
for key, value in kwargs.items():
|
||||
if key not in skip_keys and not hasattr(self, key):
|
||||
setattr(self, key, value)
|
||||
|
||||
async def initialize(self) -> bool:
|
||||
"""Initialize virtual centrifuge"""
|
||||
print(f"=== VirtualCentrifuge {self.device_id} initialize() called! ===")
|
||||
self.logger.info(f"Initializing virtual centrifuge {self.device_id}")
|
||||
self.data.update({
|
||||
"status": "Idle",
|
||||
"current_speed": 0.0,
|
||||
"target_speed": 0.0,
|
||||
"current_temp": 25.0,
|
||||
"target_temp": 25.0,
|
||||
"max_speed": self._max_speed,
|
||||
"max_temp": self._max_temp,
|
||||
"min_temp": self._min_temp,
|
||||
"centrifuge_state": "Stopped",
|
||||
"time_remaining": 0.0,
|
||||
"progress": 0.0,
|
||||
"message": ""
|
||||
})
|
||||
return True
|
||||
|
||||
async def cleanup(self) -> bool:
|
||||
"""Cleanup virtual centrifuge"""
|
||||
self.logger.info(f"Cleaning up virtual centrifuge {self.device_id}")
|
||||
return True
|
||||
|
||||
async def centrifuge(self, vessel: str, speed: float, time: float, temp: float = 25.0) -> bool:
|
||||
"""Execute centrifuge action - matches Centrifuge action"""
|
||||
self.logger.info(f"Centrifuge: vessel={vessel}, speed={speed} RPM, time={time}s, temp={temp}°C")
|
||||
|
||||
# 验证参数
|
||||
if speed > self._max_speed:
|
||||
self.logger.error(f"Speed {speed} exceeds maximum {self._max_speed}")
|
||||
self.data["message"] = f"速度 {speed} 超过最大值 {self._max_speed}"
|
||||
return False
|
||||
|
||||
if temp > self._max_temp or temp < self._min_temp:
|
||||
self.logger.error(f"Temperature {temp} outside range {self._min_temp}-{self._max_temp}")
|
||||
self.data["message"] = f"温度 {temp} 超出范围 {self._min_temp}-{self._max_temp}"
|
||||
return False
|
||||
|
||||
# 开始离心
|
||||
self.data.update({
|
||||
"status": "Running",
|
||||
"centrifuge_state": "Centrifuging",
|
||||
"target_speed": speed,
|
||||
"current_speed": speed,
|
||||
"target_temp": temp,
|
||||
"current_temp": temp,
|
||||
"time_remaining": time,
|
||||
"vessel": vessel,
|
||||
"progress": 0.0,
|
||||
"message": f"离心中: {vessel} at {speed} RPM"
|
||||
})
|
||||
|
||||
# 模拟离心过程
|
||||
simulation_time = min(time, 5.0) # 最多等待5秒用于测试
|
||||
await asyncio.sleep(simulation_time)
|
||||
|
||||
# 离心完成
|
||||
self.data.update({
|
||||
"status": "Idle",
|
||||
"centrifuge_state": "Stopped",
|
||||
"current_speed": 0.0,
|
||||
"target_speed": 0.0,
|
||||
"time_remaining": 0.0,
|
||||
"progress": 100.0,
|
||||
"message": f"离心完成: {vessel}"
|
||||
})
|
||||
|
||||
self.logger.info(f"Centrifuge completed for vessel {vessel}")
|
||||
return True
|
||||
|
||||
# 状态属性
|
||||
@property
|
||||
def status(self) -> str:
|
||||
return self.data.get("status", "Unknown")
|
||||
|
||||
@property
|
||||
def current_speed(self) -> float:
|
||||
return self.data.get("current_speed", 0.0)
|
||||
|
||||
@property
|
||||
def target_speed(self) -> float:
|
||||
return self.data.get("target_speed", 0.0)
|
||||
|
||||
@property
|
||||
def current_temp(self) -> float:
|
||||
return self.data.get("current_temp", 25.0)
|
||||
|
||||
@property
|
||||
def target_temp(self) -> float:
|
||||
return self.data.get("target_temp", 25.0)
|
||||
|
||||
@property
|
||||
def max_speed(self) -> float:
|
||||
return self.data.get("max_speed", self._max_speed)
|
||||
|
||||
@property
|
||||
def max_temp(self) -> float:
|
||||
return self.data.get("max_temp", self._max_temp)
|
||||
|
||||
@property
|
||||
def min_temp(self) -> float:
|
||||
return self.data.get("min_temp", self._min_temp)
|
||||
|
||||
@property
|
||||
def centrifuge_state(self) -> str:
|
||||
return self.data.get("centrifuge_state", "Unknown")
|
||||
|
||||
@property
|
||||
def time_remaining(self) -> float:
|
||||
return self.data.get("time_remaining", 0.0)
|
||||
|
||||
@property
|
||||
def progress(self) -> float:
|
||||
return self.data.get("progress", 0.0)
|
||||
|
||||
@property
|
||||
def message(self) -> str:
|
||||
return self.data.get("message", "")
|
||||
132
unilabos/devices/virtual/virtual_column.py
Normal file
132
unilabos/devices/virtual/virtual_column.py
Normal file
@@ -0,0 +1,132 @@
|
||||
import asyncio
|
||||
import logging
|
||||
from typing import Dict, Any, Optional
|
||||
|
||||
class VirtualColumn:
|
||||
"""Virtual column device for RunColumn protocol"""
|
||||
|
||||
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_column"
|
||||
self.config = config or {}
|
||||
|
||||
self.logger = logging.getLogger(f"VirtualColumn.{self.device_id}")
|
||||
self.data = {}
|
||||
|
||||
# 从config或kwargs中获取配置参数
|
||||
self.port = self.config.get('port') or kwargs.get('port', 'VIRTUAL')
|
||||
self._max_flow_rate = self.config.get('max_flow_rate') or kwargs.get('max_flow_rate', 10.0)
|
||||
self._column_length = self.config.get('column_length') or kwargs.get('column_length', 25.0)
|
||||
self._column_diameter = self.config.get('column_diameter') or kwargs.get('column_diameter', 2.0)
|
||||
|
||||
print(f"=== VirtualColumn {self.device_id} created with max_flow_rate={self._max_flow_rate}, length={self._column_length}cm ===")
|
||||
|
||||
async def initialize(self) -> bool:
|
||||
"""Initialize virtual column"""
|
||||
self.logger.info(f"Initializing virtual column {self.device_id}")
|
||||
self.data.update({
|
||||
"status": "Idle",
|
||||
"column_state": "Ready",
|
||||
"current_flow_rate": 0.0,
|
||||
"max_flow_rate": self._max_flow_rate,
|
||||
"column_length": self._column_length,
|
||||
"column_diameter": self._column_diameter,
|
||||
"processed_volume": 0.0,
|
||||
"progress": 0.0,
|
||||
"current_status": "Ready"
|
||||
})
|
||||
return True
|
||||
|
||||
async def cleanup(self) -> bool:
|
||||
"""Cleanup virtual column"""
|
||||
self.logger.info(f"Cleaning up virtual column {self.device_id}")
|
||||
return True
|
||||
|
||||
async def run_column(self, from_vessel: str, to_vessel: str, column: str) -> bool:
|
||||
"""Execute column chromatography run - matches RunColumn action"""
|
||||
self.logger.info(f"Running column separation: {from_vessel} -> {to_vessel} using {column}")
|
||||
|
||||
# 更新设备状态
|
||||
self.data.update({
|
||||
"status": "Running",
|
||||
"column_state": "Separating",
|
||||
"current_status": "Column separation in progress",
|
||||
"progress": 0.0,
|
||||
"processed_volume": 0.0
|
||||
})
|
||||
|
||||
# 模拟柱层析分离过程
|
||||
# 假设处理时间基于流速和柱子长度
|
||||
separation_time = (self._column_length * 2) / self._max_flow_rate # 简化计算
|
||||
|
||||
steps = 20 # 分20个步骤模拟分离过程
|
||||
step_time = separation_time / steps
|
||||
|
||||
for i in range(steps):
|
||||
await asyncio.sleep(step_time)
|
||||
|
||||
progress = (i + 1) / steps * 100
|
||||
volume_processed = (i + 1) * 5.0 # 假设每步处理5mL
|
||||
|
||||
# 更新状态
|
||||
self.data.update({
|
||||
"progress": progress,
|
||||
"processed_volume": volume_processed,
|
||||
"current_status": f"Column separation: {progress:.1f}% - Processing {volume_processed:.1f}mL"
|
||||
})
|
||||
|
||||
self.logger.info(f"Column separation progress: {progress:.1f}%")
|
||||
|
||||
# 分离完成
|
||||
self.data.update({
|
||||
"status": "Idle",
|
||||
"column_state": "Ready",
|
||||
"current_status": "Column separation completed",
|
||||
"progress": 100.0
|
||||
})
|
||||
|
||||
self.logger.info(f"Column separation completed: {from_vessel} -> {to_vessel}")
|
||||
return True
|
||||
|
||||
# 状态属性
|
||||
@property
|
||||
def status(self) -> str:
|
||||
return self.data.get("status", "Unknown")
|
||||
|
||||
@property
|
||||
def column_state(self) -> str:
|
||||
return self.data.get("column_state", "Unknown")
|
||||
|
||||
@property
|
||||
def current_flow_rate(self) -> float:
|
||||
return self.data.get("current_flow_rate", 0.0)
|
||||
|
||||
@property
|
||||
def max_flow_rate(self) -> float:
|
||||
return self.data.get("max_flow_rate", 0.0)
|
||||
|
||||
@property
|
||||
def column_length(self) -> float:
|
||||
return self.data.get("column_length", 0.0)
|
||||
|
||||
@property
|
||||
def column_diameter(self) -> float:
|
||||
return self.data.get("column_diameter", 0.0)
|
||||
|
||||
@property
|
||||
def processed_volume(self) -> float:
|
||||
return self.data.get("processed_volume", 0.0)
|
||||
|
||||
@property
|
||||
def progress(self) -> float:
|
||||
return self.data.get("progress", 0.0)
|
||||
|
||||
@property
|
||||
def current_status(self) -> str:
|
||||
return self.data.get("current_status", "Ready")
|
||||
151
unilabos/devices/virtual/virtual_filter.py
Normal file
151
unilabos/devices/virtual/virtual_filter.py
Normal file
@@ -0,0 +1,151 @@
|
||||
import asyncio
|
||||
import logging
|
||||
from typing import Dict, Any
|
||||
|
||||
class VirtualFilter:
|
||||
"""Virtual filter device for FilterProtocol testing"""
|
||||
|
||||
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_filter"
|
||||
self.config = config or {}
|
||||
|
||||
self.logger = logging.getLogger(f"VirtualFilter.{self.device_id}")
|
||||
self.data = {}
|
||||
|
||||
# 添加调试信息
|
||||
print(f"=== VirtualFilter {self.device_id} is being created! ===")
|
||||
print(f"=== Config: {self.config} ===")
|
||||
print(f"=== Kwargs: {kwargs} ===")
|
||||
|
||||
# 从config或kwargs中获取配置参数
|
||||
self.port = self.config.get('port') or kwargs.get('port', 'VIRTUAL')
|
||||
self._max_temp = self.config.get('max_temp') or kwargs.get('max_temp', 100.0)
|
||||
self._max_stir_speed = self.config.get('max_stir_speed') or kwargs.get('max_stir_speed', 1000.0)
|
||||
|
||||
# 处理其他kwargs参数,但跳过已知的配置参数
|
||||
skip_keys = {'port', 'max_temp', 'max_stir_speed'}
|
||||
for key, value in kwargs.items():
|
||||
if key not in skip_keys and not hasattr(self, key):
|
||||
setattr(self, key, value)
|
||||
|
||||
async def initialize(self) -> bool:
|
||||
"""Initialize virtual filter"""
|
||||
print(f"=== VirtualFilter {self.device_id} initialize() called! ===")
|
||||
self.logger.info(f"Initializing virtual filter {self.device_id}")
|
||||
self.data.update({
|
||||
"status": "Idle",
|
||||
"filter_state": "Ready",
|
||||
"current_temp": 25.0,
|
||||
"target_temp": 25.0,
|
||||
"max_temp": self._max_temp,
|
||||
"stir_speed": 0.0,
|
||||
"max_stir_speed": self._max_stir_speed,
|
||||
"filtered_volume": 0.0,
|
||||
"progress": 0.0,
|
||||
"message": ""
|
||||
})
|
||||
return True
|
||||
|
||||
async def cleanup(self) -> bool:
|
||||
"""Cleanup virtual filter"""
|
||||
self.logger.info(f"Cleaning up virtual filter {self.device_id}")
|
||||
return True
|
||||
|
||||
async def filter_sample(self, vessel: str, filtrate_vessel: str = "", stir: bool = False,
|
||||
stir_speed: float = 300.0, temp: float = 25.0,
|
||||
continue_heatchill: bool = False, volume: float = 0.0) -> bool:
|
||||
"""Execute filter action - matches Filter action"""
|
||||
self.logger.info(f"Filter: vessel={vessel}, filtrate_vessel={filtrate_vessel}, stir={stir}, volume={volume}")
|
||||
|
||||
# 验证参数
|
||||
if temp > self._max_temp:
|
||||
self.logger.error(f"Temperature {temp} exceeds maximum {self._max_temp}")
|
||||
self.data["message"] = f"温度 {temp} 超过最大值 {self._max_temp}"
|
||||
return False
|
||||
|
||||
if stir and stir_speed > self._max_stir_speed:
|
||||
self.logger.error(f"Stir speed {stir_speed} exceeds maximum {self._max_stir_speed}")
|
||||
self.data["message"] = f"搅拌速度 {stir_speed} 超过最大值 {self._max_stir_speed}"
|
||||
return False
|
||||
|
||||
# 开始过滤
|
||||
self.data.update({
|
||||
"status": "Running",
|
||||
"filter_state": "Filtering",
|
||||
"target_temp": temp,
|
||||
"current_temp": temp,
|
||||
"stir_speed": stir_speed if stir else 0.0,
|
||||
"vessel": vessel,
|
||||
"filtrate_vessel": filtrate_vessel,
|
||||
"target_volume": volume,
|
||||
"progress": 0.0,
|
||||
"message": f"过滤中: {vessel}"
|
||||
})
|
||||
|
||||
# 模拟过滤过程
|
||||
simulation_time = min(volume / 10.0 if volume > 0 else 5.0, 10.0)
|
||||
await asyncio.sleep(simulation_time)
|
||||
|
||||
# 过滤完成
|
||||
filtered_vol = volume if volume > 0 else 50.0 # 默认过滤量
|
||||
self.data.update({
|
||||
"status": "Idle",
|
||||
"filter_state": "Ready",
|
||||
"current_temp": 25.0 if not continue_heatchill else temp,
|
||||
"target_temp": 25.0 if not continue_heatchill else temp,
|
||||
"stir_speed": 0.0 if not stir else stir_speed,
|
||||
"filtered_volume": filtered_vol,
|
||||
"progress": 100.0,
|
||||
"message": f"过滤完成: {filtered_vol}mL"
|
||||
})
|
||||
|
||||
self.logger.info(f"Filter completed: {filtered_vol}mL from {vessel}")
|
||||
return True
|
||||
|
||||
# 状态属性
|
||||
@property
|
||||
def status(self) -> str:
|
||||
return self.data.get("status", "Unknown")
|
||||
|
||||
@property
|
||||
def filter_state(self) -> str:
|
||||
return self.data.get("filter_state", "Unknown")
|
||||
|
||||
@property
|
||||
def current_temp(self) -> float:
|
||||
return self.data.get("current_temp", 25.0)
|
||||
|
||||
@property
|
||||
def target_temp(self) -> float:
|
||||
return self.data.get("target_temp", 25.0)
|
||||
|
||||
@property
|
||||
def max_temp(self) -> float:
|
||||
return self.data.get("max_temp", self._max_temp)
|
||||
|
||||
@property
|
||||
def stir_speed(self) -> float:
|
||||
return self.data.get("stir_speed", 0.0)
|
||||
|
||||
@property
|
||||
def max_stir_speed(self) -> float:
|
||||
return self.data.get("max_stir_speed", self._max_stir_speed)
|
||||
|
||||
@property
|
||||
def filtered_volume(self) -> float:
|
||||
return self.data.get("filtered_volume", 0.0)
|
||||
|
||||
@property
|
||||
def progress(self) -> float:
|
||||
return self.data.get("progress", 0.0)
|
||||
|
||||
@property
|
||||
def message(self) -> str:
|
||||
return self.data.get("message", "")
|
||||
107
unilabos/devices/virtual/virtual_heatchill.py
Normal file
107
unilabos/devices/virtual/virtual_heatchill.py
Normal file
@@ -0,0 +1,107 @@
|
||||
import asyncio
|
||||
import logging
|
||||
from typing import Dict, Any
|
||||
|
||||
class VirtualHeatChill:
|
||||
"""Virtual heat chill device for HeatChillProtocol testing"""
|
||||
|
||||
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_heatchill"
|
||||
self.config = config or {}
|
||||
|
||||
self.logger = logging.getLogger(f"VirtualHeatChill.{self.device_id}")
|
||||
self.data = {}
|
||||
|
||||
# 添加调试信息
|
||||
print(f"=== VirtualHeatChill {self.device_id} is being created! ===")
|
||||
print(f"=== Config: {self.config} ===")
|
||||
print(f"=== Kwargs: {kwargs} ===")
|
||||
|
||||
# 从config或kwargs中获取配置参数
|
||||
self.port = self.config.get('port') or kwargs.get('port', 'VIRTUAL')
|
||||
self._max_temp = self.config.get('max_temp') or kwargs.get('max_temp', 200.0)
|
||||
self._min_temp = self.config.get('min_temp') or kwargs.get('min_temp', -80.0)
|
||||
self._max_stir_speed = self.config.get('max_stir_speed') or kwargs.get('max_stir_speed', 1000.0)
|
||||
|
||||
# 处理其他kwargs参数,但跳过已知的配置参数
|
||||
skip_keys = {'port', 'max_temp', 'min_temp', 'max_stir_speed'}
|
||||
for key, value in kwargs.items():
|
||||
if key not in skip_keys and not hasattr(self, key):
|
||||
setattr(self, key, value)
|
||||
|
||||
async def initialize(self) -> bool:
|
||||
"""Initialize virtual heat chill"""
|
||||
print(f"=== VirtualHeatChill {self.device_id} initialize() called! ===")
|
||||
self.logger.info(f"Initializing virtual heat chill {self.device_id}")
|
||||
self.data.update({
|
||||
"status": "Idle"
|
||||
})
|
||||
return True
|
||||
|
||||
async def cleanup(self) -> bool:
|
||||
"""Cleanup virtual heat chill"""
|
||||
self.logger.info(f"Cleaning up virtual heat chill {self.device_id}")
|
||||
return True
|
||||
|
||||
async def heat_chill(self, vessel: str, temp: float, time: float, stir: bool,
|
||||
stir_speed: float, purpose: str) -> bool:
|
||||
"""Execute heat chill action - matches HeatChill action exactly"""
|
||||
self.logger.info(f"HeatChill: vessel={vessel}, temp={temp}°C, time={time}s, stir={stir}, stir_speed={stir_speed}, purpose={purpose}")
|
||||
|
||||
# 验证参数
|
||||
if temp > self._max_temp or temp < self._min_temp:
|
||||
self.logger.error(f"Temperature {temp} outside range {self._min_temp}-{self._max_temp}")
|
||||
self.data["status"] = f"温度 {temp} 超出范围"
|
||||
return False
|
||||
|
||||
if stir and stir_speed > self._max_stir_speed:
|
||||
self.logger.error(f"Stir speed {stir_speed} exceeds maximum {self._max_stir_speed}")
|
||||
self.data["status"] = f"搅拌速度 {stir_speed} 超出范围"
|
||||
return False
|
||||
|
||||
# 开始加热/冷却
|
||||
self.data.update({
|
||||
"status": f"加热/冷却中: {vessel} 至 {temp}°C"
|
||||
})
|
||||
|
||||
# 模拟加热/冷却时间
|
||||
simulation_time = min(time, 10.0) # 最多等待10秒用于测试
|
||||
await asyncio.sleep(simulation_time)
|
||||
|
||||
# 加热/冷却完成
|
||||
self.data["status"] = f"完成: {vessel} 已达到 {temp}°C"
|
||||
|
||||
self.logger.info(f"HeatChill completed for vessel {vessel} at {temp}°C")
|
||||
return True
|
||||
|
||||
async def heat_chill_start(self, vessel: str, temp: float, purpose: str) -> bool:
|
||||
"""Start heat chill - matches HeatChillStart action exactly"""
|
||||
self.logger.info(f"HeatChillStart: vessel={vessel}, temp={temp}°C, purpose={purpose}")
|
||||
|
||||
# 验证参数
|
||||
if temp > self._max_temp or temp < self._min_temp:
|
||||
self.logger.error(f"Temperature {temp} outside range {self._min_temp}-{self._max_temp}")
|
||||
self.data["status"] = f"温度 {temp} 超出范围"
|
||||
return False
|
||||
|
||||
self.data["status"] = f"开始加热/冷却: {vessel} 至 {temp}°C"
|
||||
return True
|
||||
|
||||
async def heat_chill_stop(self, vessel: str) -> bool:
|
||||
"""Stop heat chill - matches HeatChillStop action exactly"""
|
||||
self.logger.info(f"HeatChillStop: vessel={vessel}")
|
||||
|
||||
self.data["status"] = f"停止加热/冷却: {vessel}"
|
||||
return True
|
||||
|
||||
# 状态属性 - 只保留 action 中定义的 feedback
|
||||
@property
|
||||
def status(self) -> str:
|
||||
return self.data.get("status", "Idle")
|
||||
197
unilabos/devices/virtual/virtual_pump.py
Normal file
197
unilabos/devices/virtual/virtual_pump.py
Normal file
@@ -0,0 +1,197 @@
|
||||
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")
|
||||
104
unilabos/devices/virtual/virtual_stirrer.py
Normal file
104
unilabos/devices/virtual/virtual_stirrer.py
Normal file
@@ -0,0 +1,104 @@
|
||||
import asyncio
|
||||
import logging
|
||||
from typing import Dict, Any
|
||||
|
||||
class VirtualStirrer:
|
||||
"""Virtual stirrer device for StirProtocol testing"""
|
||||
|
||||
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_stirrer"
|
||||
self.config = config or {}
|
||||
|
||||
self.logger = logging.getLogger(f"VirtualStirrer.{self.device_id}")
|
||||
self.data = {}
|
||||
|
||||
# 添加调试信息
|
||||
print(f"=== VirtualStirrer {self.device_id} is being created! ===")
|
||||
print(f"=== Config: {self.config} ===")
|
||||
print(f"=== Kwargs: {kwargs} ===")
|
||||
|
||||
# 从config或kwargs中获取配置参数
|
||||
self.port = self.config.get('port') or kwargs.get('port', 'VIRTUAL')
|
||||
self._max_temp = self.config.get('max_temp') or kwargs.get('max_temp', 100.0)
|
||||
self._max_speed = self.config.get('max_speed') or kwargs.get('max_speed', 1000.0)
|
||||
|
||||
# 处理其他kwargs参数,但跳过已知的配置参数
|
||||
skip_keys = {'port', 'max_temp', 'max_speed'}
|
||||
for key, value in kwargs.items():
|
||||
if key not in skip_keys and not hasattr(self, key):
|
||||
setattr(self, key, value)
|
||||
|
||||
async def initialize(self) -> bool:
|
||||
"""Initialize virtual stirrer"""
|
||||
print(f"=== VirtualStirrer {self.device_id} initialize() called! ===")
|
||||
self.logger.info(f"Initializing virtual stirrer {self.device_id}")
|
||||
self.data.update({
|
||||
"status": "Idle"
|
||||
})
|
||||
return True
|
||||
|
||||
async def cleanup(self) -> bool:
|
||||
"""Cleanup virtual stirrer"""
|
||||
self.logger.info(f"Cleaning up virtual stirrer {self.device_id}")
|
||||
return True
|
||||
|
||||
async def stir(self, stir_time: float, stir_speed: float, settling_time: float) -> bool:
|
||||
"""Execute stir action - matches Stir action exactly"""
|
||||
self.logger.info(f"Stir: speed={stir_speed} RPM, time={stir_time}s, settling={settling_time}s")
|
||||
|
||||
# 验证参数
|
||||
if stir_speed > self._max_speed:
|
||||
self.logger.error(f"Stir speed {stir_speed} exceeds maximum {self._max_speed}")
|
||||
self.data["status"] = f"搅拌速度 {stir_speed} 超出范围"
|
||||
return False
|
||||
|
||||
# 开始搅拌
|
||||
self.data["status"] = f"搅拌中: {stir_speed} RPM, {stir_time}s"
|
||||
|
||||
# 模拟搅拌时间
|
||||
simulation_time = min(stir_time, 10.0) # 最多等待10秒用于测试
|
||||
await asyncio.sleep(simulation_time)
|
||||
|
||||
# 搅拌完成,开始沉降
|
||||
if settling_time > 0:
|
||||
self.data["status"] = f"沉降中: {settling_time}s"
|
||||
settling_simulation = min(settling_time, 5.0) # 最多等待5秒
|
||||
await asyncio.sleep(settling_simulation)
|
||||
|
||||
# 操作完成
|
||||
self.data["status"] = "搅拌完成"
|
||||
|
||||
self.logger.info(f"Stir completed: {stir_speed} RPM for {stir_time}s")
|
||||
return True
|
||||
|
||||
async def start_stir(self, vessel: str, stir_speed: float, purpose: str) -> bool:
|
||||
"""Start stir action - matches StartStir action exactly"""
|
||||
self.logger.info(f"StartStir: vessel={vessel}, speed={stir_speed} RPM, purpose={purpose}")
|
||||
|
||||
# 验证参数
|
||||
if stir_speed > self._max_speed:
|
||||
self.logger.error(f"Stir speed {stir_speed} exceeds maximum {self._max_speed}")
|
||||
self.data["status"] = f"搅拌速度 {stir_speed} 超出范围"
|
||||
return False
|
||||
|
||||
self.data["status"] = f"开始搅拌: {vessel} at {stir_speed} RPM"
|
||||
return True
|
||||
|
||||
async def stop_stir(self, vessel: str) -> bool:
|
||||
"""Stop stir action - matches StopStir action exactly"""
|
||||
self.logger.info(f"StopStir: vessel={vessel}")
|
||||
|
||||
self.data["status"] = f"停止搅拌: {vessel}"
|
||||
return True
|
||||
|
||||
# 状态属性 - 只保留 action 中定义的 feedback
|
||||
@property
|
||||
def status(self) -> str:
|
||||
return self.data.get("status", "Idle")
|
||||
149
unilabos/devices/virtual/virtual_transferpump.py
Normal file
149
unilabos/devices/virtual/virtual_transferpump.py
Normal file
@@ -0,0 +1,149 @@
|
||||
import asyncio
|
||||
import logging
|
||||
from typing import Dict, Any, Optional
|
||||
|
||||
class VirtualTransferPump:
|
||||
"""Virtual pump device specifically for Transfer protocol"""
|
||||
|
||||
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_transfer_pump"
|
||||
self.config = config or {}
|
||||
|
||||
self.logger = logging.getLogger(f"VirtualTransferPump.{self.device_id}")
|
||||
self.data = {}
|
||||
|
||||
# 添加调试信息
|
||||
print(f"=== VirtualTransferPump {self.device_id} is being created! ===")
|
||||
print(f"=== Config: {self.config} ===")
|
||||
print(f"=== Kwargs: {kwargs} ===")
|
||||
|
||||
# 从config或kwargs中获取配置参数
|
||||
self.port = self.config.get('port') or kwargs.get('port', 'VIRTUAL')
|
||||
self._max_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', 5.0)
|
||||
self._current_volume = 0.0
|
||||
self.is_running = False
|
||||
|
||||
async def initialize(self) -> bool:
|
||||
"""Initialize virtual transfer pump"""
|
||||
print(f"=== VirtualTransferPump {self.device_id} initialize() called! ===")
|
||||
self.logger.info(f"Initializing virtual transfer pump {self.device_id}")
|
||||
self.data.update({
|
||||
"status": "Idle",
|
||||
"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 transfer pump"""
|
||||
self.logger.info(f"Cleaning up virtual transfer pump {self.device_id}")
|
||||
return True
|
||||
|
||||
async def transfer(self, from_vessel: str, to_vessel: str, volume: float,
|
||||
amount: str = "", time: float = 0, viscous: bool = False,
|
||||
rinsing_solvent: str = "", rinsing_volume: float = 0.0,
|
||||
rinsing_repeats: int = 0, solid: bool = False) -> bool:
|
||||
"""Execute liquid transfer - matches Transfer action"""
|
||||
self.logger.info(f"Transfer: {volume}mL from {from_vessel} to {to_vessel}")
|
||||
|
||||
# 计算转移时间
|
||||
if time > 0:
|
||||
transfer_time = time
|
||||
else:
|
||||
# 如果是粘性液体,降低转移速率
|
||||
rate = self._transfer_rate * 0.5 if viscous else self._transfer_rate
|
||||
transfer_time = volume / rate
|
||||
|
||||
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
|
||||
transferred = (i + 1) * step_volume
|
||||
|
||||
self.data.update({
|
||||
"progress": progress,
|
||||
"transferred_volume": transferred,
|
||||
"current_status": f"Transferring {progress:.1f}%"
|
||||
})
|
||||
|
||||
self.logger.info(f"Transfer progress: {progress:.1f}% ({transferred:.1f}/{volume}mL)")
|
||||
|
||||
# 如果需要冲洗
|
||||
if rinsing_solvent and rinsing_volume > 0 and rinsing_repeats > 0:
|
||||
self.data["current_status"] = "Rinsing"
|
||||
for repeat in range(rinsing_repeats):
|
||||
self.logger.info(f"Rinsing cycle {repeat + 1}/{rinsing_repeats} with {rinsing_solvent}")
|
||||
await asyncio.sleep(1) # 模拟冲洗时间
|
||||
|
||||
self.data.update({
|
||||
"status": "Idle",
|
||||
"current_status": "Transfer completed",
|
||||
"progress": 100.0,
|
||||
"transferred_volume": volume
|
||||
})
|
||||
|
||||
return True
|
||||
|
||||
# 添加所有在virtual_device.yaml中定义的状态属性
|
||||
@property
|
||||
def status(self) -> str:
|
||||
return self.data.get("status", "Unknown")
|
||||
|
||||
@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", self._max_volume)
|
||||
|
||||
@property
|
||||
def transfer_rate(self) -> float:
|
||||
return self.data.get("transfer_rate", self._transfer_rate)
|
||||
|
||||
@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")
|
||||
105
unilabos/devices/virtual/virtual_valve.py
Normal file
105
unilabos/devices/virtual/virtual_valve.py
Normal file
@@ -0,0 +1,105 @@
|
||||
import asyncio
|
||||
import logging
|
||||
from typing import Dict, Any
|
||||
|
||||
class VirtualValve:
|
||||
"""Virtual valve device for AddProtocol testing"""
|
||||
|
||||
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_valve"
|
||||
self.config = config or {}
|
||||
|
||||
self.logger = logging.getLogger(f"VirtualValve.{self.device_id}")
|
||||
self.data = {}
|
||||
|
||||
print(f"=== VirtualValve {self.device_id} is being created! ===")
|
||||
print(f"=== Config: {self.config} ===")
|
||||
print(f"=== Kwargs: {kwargs} ===")
|
||||
|
||||
# 处理所有配置参数,包括port
|
||||
self.port = self.config.get('port', 'VIRTUAL')
|
||||
self.positions = self.config.get('positions', 6)
|
||||
self.current_position = 0
|
||||
|
||||
# 忽略其他可能的kwargs参数
|
||||
for key, value in kwargs.items():
|
||||
if not hasattr(self, key):
|
||||
setattr(self, key, value)
|
||||
|
||||
async def initialize(self) -> bool:
|
||||
"""Initialize virtual valve"""
|
||||
print(f"=== VirtualValve {self.device_id} initialize() called! ===")
|
||||
self.logger.info(f"Initializing virtual valve {self.device_id}")
|
||||
self.data.update({
|
||||
"status": "Idle",
|
||||
"valve_state": "Closed",
|
||||
"current_position": 0,
|
||||
"target_position": 0,
|
||||
"max_positions": self.positions
|
||||
})
|
||||
return True
|
||||
|
||||
async def cleanup(self) -> bool:
|
||||
"""Cleanup virtual valve"""
|
||||
self.logger.info(f"Cleaning up virtual valve {self.device_id}")
|
||||
return True
|
||||
|
||||
async def set_position(self, position: int) -> bool:
|
||||
"""Set valve position - matches SendCmd action"""
|
||||
if 0 <= position <= self.positions:
|
||||
self.logger.info(f"Setting valve position to {position}")
|
||||
self.data.update({
|
||||
"target_position": position,
|
||||
"current_position": position,
|
||||
"valve_state": "Open" if position > 0 else "Closed"
|
||||
})
|
||||
return True
|
||||
else:
|
||||
self.logger.error(f"Invalid position {position}. Must be 0-{self.positions}")
|
||||
return False
|
||||
|
||||
async def open(self) -> bool:
|
||||
"""Open valve - matches EmptyIn action"""
|
||||
self.logger.info("Opening valve")
|
||||
self.data.update({
|
||||
"valve_state": "Open",
|
||||
"current_position": 1
|
||||
})
|
||||
return True
|
||||
|
||||
async def close(self) -> bool:
|
||||
"""Close valve - matches EmptyIn action"""
|
||||
self.logger.info("Closing valve")
|
||||
self.data.update({
|
||||
"valve_state": "Closed",
|
||||
"current_position": 0
|
||||
})
|
||||
return True
|
||||
|
||||
# 状态属性
|
||||
@property
|
||||
def status(self) -> str:
|
||||
return self.data.get("status", "Unknown")
|
||||
|
||||
@property
|
||||
def valve_state(self) -> str:
|
||||
return self.data.get("valve_state", "Unknown")
|
||||
|
||||
@property
|
||||
def current_position(self) -> int:
|
||||
return self.data.get("current_position", 0)
|
||||
|
||||
@property
|
||||
def target_position(self) -> int:
|
||||
return self.data.get("target_position", 0)
|
||||
|
||||
@property
|
||||
def max_positions(self) -> int:
|
||||
return self.data.get("max_positions", 6)
|
||||
Reference in New Issue
Block a user