Compare commits

..

8 Commits

Author SHA1 Message Date
Guangxin Zhang
58d1cc4720 Add set_group and transfer_group methods to PRCXI9300Handler and update liquid_handler.yaml 2025-09-10 21:23:15 +08:00
Guangxin Zhang
5676dd6589 Add LiquidHandlerSetGroup and LiquidHandlerTransferGroup actions to CMakeLists 2025-09-10 20:57:22 +08:00
Guangxin Zhang
1ae274a833 Add action definitions for LiquidHandlerSetGroup and LiquidHandlerTransferGroup
- Created LiquidHandlerSetGroup.action with fields for group name, wells, and volumes.
- Created LiquidHandlerTransferGroup.action with fields for source and target group names and unit volume.
- Both actions include response fields for return information and success status.
2025-09-10 20:57:16 +08:00
Xuwznln
22b88c8441 取消labid 和 强制config输入 2025-09-10 20:55:24 +08:00
Xuwznln
81bcc1907d fix: addr param 2025-09-10 20:14:33 +08:00
Xuwznln
8cffd3dc21 fix: addr param 2025-09-10 20:13:44 +08:00
Xuwznln
a722636938 增加addr参数 2025-09-10 20:01:10 +08:00
Xuwznln
f68340d932 修复status密集发送时,消息出错 2025-09-10 18:52:23 +08:00
11 changed files with 918 additions and 499 deletions

View File

@@ -21,9 +21,9 @@
"timeout": 10.0,
"axis": "Left",
"channel_num": 8,
"setup": false,
"debug": false,
"simulator": false,
"setup": true,
"debug": true,
"simulator": true,
"matrix_id": "71593"
},
"data": {},

View File

@@ -17,12 +17,12 @@ unilabos_dir = os.path.dirname(os.path.dirname(current_dir))
if unilabos_dir not in sys.path:
sys.path.append(unilabos_dir)
from unilabos.config.config import load_config, BasicConfig
from unilabos.config.config import load_config, BasicConfig, HTTPConfig
from unilabos.utils.banner_print import print_status, print_unilab_banner
from unilabos.resources.graphio import modify_to_backend_format
def load_config_from_file(config_path, override_labid=None):
def load_config_from_file(config_path):
if config_path is None:
config_path = os.environ.get("UNILABOS_BASICCONFIG_CONFIG_PATH", None)
if config_path:
@@ -31,10 +31,10 @@ def load_config_from_file(config_path, override_labid=None):
elif not config_path.endswith(".py"):
print_status(f"配置文件 {config_path} 不是Python文件必须以.py结尾", "error")
else:
load_config(config_path, override_labid)
load_config(config_path)
else:
print_status(f"启动 UniLab-OS时配置文件参数未正确传入 --config '{config_path}' 尝试本地配置...", "warning")
load_config(config_path, override_labid)
load_config(config_path)
def convert_argv_dashes_to_underscores(args: argparse.ArgumentParser):
@@ -52,8 +52,6 @@ def parse_args():
"""解析命令行参数"""
parser = argparse.ArgumentParser(description="Start Uni-Lab Edge server.")
parser.add_argument("-g", "--graph", help="Physical setup graph.")
# parser.add_argument("-d", "--devices", help="Devices config file.")
# parser.add_argument("-r", "--resources", help="Resources config file.")
parser.add_argument("-c", "--controllers", default=None, help="Controllers config file.")
parser.add_argument(
"--registry_path",
@@ -128,12 +126,6 @@ def parse_args():
default="disable",
help="选择可视化工具: rviz, web",
)
parser.add_argument(
"--labid",
type=str,
default="",
help="实验室唯一ID也可通过环境变量 UNILABOS_MQCONFIG_LABID 设置或传入--config设置",
)
parser.add_argument(
"--ak",
type=str,
@@ -146,6 +138,12 @@ def parse_args():
default="",
help="实验室请求的sk",
)
parser.add_argument(
"--addr",
type=str,
default="https://uni-lab.bohrium.com/api/v1",
help="实验室后端地址",
)
parser.add_argument(
"--websocket",
action="store_true",
@@ -209,13 +207,20 @@ def main():
os.path.join(os.path.dirname(os.path.dirname(__file__)), "config", "example_config.py"), config_path
)
print_status(f"已创建 local_config.py 路径: {config_path}", "info")
print_status(f"请在文件夹中配置lab_id放入下载的CA.crt、lab.crt、lab.key重新启动本程序", "info")
os._exit(1)
else:
os._exit(1)
# 加载配置文件
print_status(f"当前工作目录为 {working_dir}", "info")
load_config_from_file(config_path, args_dict["labid"])
load_config_from_file(config_path)
if args_dict["addr"] == "test":
print_status("使用测试环境地址", "info")
HTTPConfig.remote_addr = "https://uni-lab.test.bohrium.com/api/v1"
elif args_dict["addr"] == "local":
print_status("使用本地环境地址", "info")
HTTPConfig.remote_addr = "http://127.0.0.1:48197/api/v1"
else:
HTTPConfig.remote_addr = args_dict.get("addr", "")
if args_dict["use_remote_resource"]:
print_status("使用远程资源启动", "info")

View File

@@ -142,9 +142,13 @@ class TaskScheduler:
# 执行相应的任务
should_continue = False
if item.task_type == "query_action_status":
should_continue = asyncio.run_coroutine_threadsafe(self._process_query_status_item(item), self.message_sender.event_loop).result()
should_continue = asyncio.run_coroutine_threadsafe(
self._process_query_status_item(item), self.message_sender.event_loop
).result()
elif item.task_type == "job_call_back_status":
should_continue = asyncio.run_coroutine_threadsafe(self._process_job_callback_item(item), self.message_sender.event_loop).result()
should_continue = asyncio.run_coroutine_threadsafe(
self._process_job_callback_item(item), self.message_sender.event_loop
).result()
else:
logger.warning(f"[TaskScheduler] Unknown task type: {item.task_type}")
continue
@@ -381,21 +385,21 @@ class TaskScheduler:
# 创建取消事件todo要移动到query_state中
self.cancel_events[req.job_id] = asyncio.Event()
# 启动callback定时发送
await self._start_job_callback(req.job_id, req.device_id, req.action, req.task_id, device_action_key)
# 创建兼容HostNode的QueueItem对象
job_queue_item = QueueItem(
task_type="job_call_back_status",
device_id=req.device_id,
action_name=req.action,
task_id=req.task_id,
job_id=req.job_id,
device_action_key=device_action_key,
next_run_time=time.time(),
)
try:
# 启动callback定时发送
await self._start_job_callback(req.job_id, req.device_id, req.action, req.task_id, device_action_key)
# 创建兼容HostNode的QueueItem对象
job_queue_item = QueueItem(
task_type="job_call_back_status",
device_id=req.device_id,
action_name=req.action,
task_id=req.task_id,
job_id=req.job_id,
device_action_key=device_action_key,
next_run_time=time.time(),
)
host_node = HostNode.get_instance(0)
if not host_node:
logger.error(f"[TaskScheduler] HostNode instance not available for job_id: {req.job_id}")
@@ -409,15 +413,9 @@ class TaskScheduler:
except Exception as e:
logger.error(f"[TaskScheduler] Exception during job start for job_id {req.job_id}: {str(e)}")
traceback.print_exc()
# 异常结束先停止callback然后发送失败状态
await self._stop_job_callback(
req.job_id, "failed", serialize_result_info(traceback.format_exc(), False, {})
self.publish_job_status(
{}, job_queue_item, "failed", serialize_result_info(traceback.format_exc(), False, {})
)
host_node = HostNode.get_instance(0)
if host_node:
host_node._device_action_status[device_action_key].job_ids.pop(req.job_id, None)
logger.warning(f"[TaskScheduler] Cleaned up failed job from HostNode: {req.job_id}")
except Exception as e:
logger.error(f"[TaskScheduler] Error handling job start: {str(e)}")
@@ -454,7 +452,7 @@ class TaskScheduler:
logger.error(f"[TaskScheduler] HostNode not available for cancel goal: {job_id}")
# 停止callback并发送取消状态
await self._stop_job_callback(job_id, "cancelled", "Job was cancelled by user request")
await self._stop_job_callback(job_id, "cancelled")
logger.info(f"[TaskScheduler] Stopped job callback and sent cancel status for job_id: {job_id}")
else:
logger.warning(f"[TaskScheduler] Job not found in active jobs for cancellation: {job_id}")
@@ -511,7 +509,7 @@ class TaskScheduler:
else:
logger.debug(f"[TaskScheduler] Action queue not available for job callback: {job_id}")
async def _stop_job_callback(self, job_id: str, final_status: str, return_info: Optional[str] = None) -> None:
async def _stop_job_callback(self, job_id: str, final_status: str) -> None:
"""停止job的callback定时发送并发送最终结果"""
logger.info(f"[TaskScheduler] Stopping job callback for job_id: {job_id} with final status: {final_status}")
if job_id not in self.active_jobs:
@@ -557,10 +555,24 @@ class TaskScheduler:
# 给其他同名action至少执行一次的机会
with self.immediate_execution_lock:
self.immediate_execution_flags[item.device_action_key] = time.time() + 3
# 如果是最终状态通过_stop_job_callback处理
asyncio.run_coroutine_threadsafe(
self._stop_job_callback(item.job_id, status, return_info), self.message_sender.event_loop
).result()
# 检查是否在同一个事件循环中
try:
current_loop = asyncio.get_running_loop()
if current_loop == self.message_sender.event_loop:
# 在同一个事件循环中,直接创建任务
asyncio.create_task(self._stop_job_callback(item.job_id, status))
else:
# 不在同一个事件循环中,使用 run_coroutine_threadsafe
asyncio.run_coroutine_threadsafe(
self._stop_job_callback(item.job_id, status), self.message_sender.event_loop
)
except RuntimeError:
# 没有运行中的事件循环,使用 run_coroutine_threadsafe
asyncio.run_coroutine_threadsafe(
self._stop_job_callback(item.job_id, status), self.message_sender.event_loop
)
# 执行结果信息上传
message = {
"action": "job_status",
@@ -575,9 +587,21 @@ class TaskScheduler:
"timestamp": time.time(),
},
}
asyncio.run_coroutine_threadsafe(
self.message_sender.send_message(message), self.message_sender.event_loop
).result()
# 同样检查事件循环
try:
current_loop = asyncio.get_running_loop()
if current_loop == self.message_sender.event_loop:
# 在同一个事件循环中,直接创建任务
asyncio.create_task(self.message_sender.send_message(message))
else:
# 不在同一个事件循环中,使用 run_coroutine_threadsafe
asyncio.run_coroutine_threadsafe(
self.message_sender.send_message(message), self.message_sender.event_loop
)
except RuntimeError:
# 没有运行中的事件循环,使用 run_coroutine_threadsafe
asyncio.run_coroutine_threadsafe(self.message_sender.send_message(message), self.message_sender.event_loop)
logger.trace(f"[TaskScheduler] Job status published: {item.job_id} - {status}") # type: ignore
@@ -619,11 +643,11 @@ class WebSocketClient(BaseCommunicationClient):
self.connected = False
# 消息处理
self.message_queue = asyncio.Queue() if not self.is_disabled else None
self.reconnect_count = 0
# 消息发送锁(解决并发写入问题)- 延迟初始化
self.send_lock = None
# 消息发送队列和处理器
self.send_queue = None # 延迟初始化
self.send_queue_task = None # 发送队列处理任务
# 任务调度器
self.task_scheduler = None
@@ -709,8 +733,8 @@ class WebSocketClient(BaseCommunicationClient):
self.event_loop = asyncio.new_event_loop()
asyncio.set_event_loop(self.event_loop)
# 在正确的事件循环中创建
self.send_lock = asyncio.Lock()
# 在正确的事件循环中创建发送队列
self.send_queue = asyncio.Queue(maxsize=1000) # 限制队列大小防止内存溢出
# 运行连接逻辑
self.event_loop.run_until_complete(self._connection_handler())
@@ -746,8 +770,20 @@ class WebSocketClient(BaseCommunicationClient):
logger.info(f"[WebSocket] Connected to {self.websocket_url}")
# 处理消息
await self._message_handler()
# 启动发送队列处理器
self.send_queue_task = asyncio.create_task(self._send_queue_processor())
try:
# 处理消息
await self._message_handler()
finally:
# 停止发送队列处理器
if self.send_queue_task and not self.send_queue_task.done():
self.send_queue_task.cancel()
try:
await self.send_queue_task
except asyncio.CancelledError:
pass
except websockets.exceptions.ConnectionClosed:
logger.warning("[WebSocket] Connection closed")
@@ -779,6 +815,67 @@ class WebSocketClient(BaseCommunicationClient):
await self.websocket.close()
self.websocket = None
async def _send_queue_processor(self):
"""处理发送队列中的消息"""
logger.debug("[WebSocket] Send queue processor started")
if not self.send_queue:
logger.error("[WebSocket] Send queue not initialized")
return
try:
while self.connected and self.websocket:
try:
# 使用超时避免无限等待
message = await asyncio.wait_for(self.send_queue.get(), timeout=1.0)
# 批量处理:收集短时间内的多个消息
messages_to_send = [message]
batch_size = 0
max_batch_size = 10 # 最大批处理数量
# 尝试获取更多消息(非阻塞)
while batch_size < max_batch_size and not self.send_queue.empty():
try:
additional_msg = self.send_queue.get_nowait()
messages_to_send.append(additional_msg)
batch_size += 1
except asyncio.QueueEmpty:
break
# 发送消息
for msg in messages_to_send:
if self.websocket and self.connected:
try:
message_str = json.dumps(msg, ensure_ascii=False)
await self.websocket.send(message_str)
logger.trace( # type: ignore
f"[WebSocket] Message sent: {msg.get('action', 'unknown')}"
)
except Exception as e:
logger.error(f"[WebSocket] Failed to send message: {str(e)}")
# 如果发送失败,将消息重新放回队列(可选)
# await self.send_queue.put(msg)
break
# 在批量发送之间添加小延迟,避免过载
if batch_size > 5:
await asyncio.sleep(0.001)
except asyncio.TimeoutError:
# 超时是正常的,继续循环
continue
except asyncio.CancelledError:
logger.debug("[WebSocket] Send queue processor cancelled")
break
except Exception as e:
logger.error(f"[WebSocket] Error in send queue processor: {str(e)}")
await asyncio.sleep(0.1)
except Exception as e:
logger.error(f"[WebSocket] Fatal error in send queue processor: {str(e)}")
finally:
logger.debug("[WebSocket] Send queue processor stopped")
# 消息处理方法
async def _message_handler(self):
"""处理接收到的消息"""
@@ -831,24 +928,23 @@ class WebSocketClient(BaseCommunicationClient):
# MessageSender接口实现
async def send_message(self, message: Dict[str, Any]) -> None:
"""内部发送消息方法,使用锁确保线程安全"""
if not self.connected or not self.websocket:
"""内部发送消息方法,将消息放入发送队列"""
if not self.connected:
logger.warning("[WebSocket] Not connected, cannot send message")
return
# 检查是否已初始化(在事件循环启动后才会创建)
if not self.send_lock:
logger.warning("[WebSocket] Send lock not initialized, cannot send message safely")
# 检查发送队列是否已初始化
if not self.send_queue:
logger.warning("[WebSocket] Send queue not initialized, cannot send message")
return
message_str = json.dumps(message, ensure_ascii=False)
# 使用异步锁防止并发写入导致的竞态条件
async with self.send_lock:
try:
await self.websocket.send(message_str)
logger.debug(f"[WebSocket] Message sent: {message['action']}")
except Exception as e:
logger.error(f"[WebSocket] Failed to send message: {str(e)}")
try:
# 尝试将消息放入队列(非阻塞)
self.send_queue.put_nowait(message)
logger.trace(f"[WebSocket] Message queued: {message['action']}") # type: ignore
except asyncio.QueueFull:
logger.error(f"[WebSocket] Send queue full, dropping message: {message['action']}")
# 可选:在队列满时采取其他策略,如等待或丢弃旧消息
def is_connected(self) -> bool:
"""检查是否已连接TaskScheduler调用的接口"""
@@ -870,7 +966,20 @@ class WebSocketClient(BaseCommunicationClient):
},
},
}
asyncio.run_coroutine_threadsafe(self.send_message(message), self.event_loop).result()
# 检查是否在同一个事件循环中
try:
current_loop = asyncio.get_running_loop()
if current_loop == self.event_loop:
# 在同一个事件循环中,直接创建任务
asyncio.create_task(self.send_message(message))
else:
# 不在同一个事件循环中,使用 run_coroutine_threadsafe
asyncio.run_coroutine_threadsafe(self.send_message(message), self.event_loop)
except RuntimeError:
# 没有运行中的事件循环,使用 run_coroutine_threadsafe
asyncio.run_coroutine_threadsafe(self.send_message(message), self.event_loop)
logger.debug(f"[WebSocket] Device status published: {device_id}.{property_name}")
def publish_job_status(
@@ -888,7 +997,20 @@ class WebSocketClient(BaseCommunicationClient):
logger.warning("[WebSocket] Not connected, cannot send ping")
return
message = {"action": "ping", "data": {"ping_id": ping_id, "client_timestamp": timestamp}}
asyncio.run_coroutine_threadsafe(self.send_message(message), self.event_loop).result()
# 检查是否在同一个事件循环中
try:
current_loop = asyncio.get_running_loop()
if current_loop == self.event_loop:
# 在同一个事件循环中,直接创建任务
asyncio.create_task(self.send_message(message))
else:
# 不在同一个事件循环中,使用 run_coroutine_threadsafe
asyncio.run_coroutine_threadsafe(self.send_message(message), self.event_loop)
except RuntimeError:
# 没有运行中的事件循环,使用 run_coroutine_threadsafe
asyncio.run_coroutine_threadsafe(self.send_message(message), self.event_loop)
logger.debug(f"[WebSocket] Ping sent: {ping_id}")
def cancel_goal(self, job_id: str) -> None:

View File

@@ -87,7 +87,7 @@ class ROSConfig:
]
def _update_config_from_module(module, override_labid: Optional[str]):
def _update_config_from_module(module):
for name, obj in globals().items():
if isinstance(obj, type) and name.endswith("Config"):
if hasattr(module, name) and isinstance(getattr(module, name), type):
@@ -97,38 +97,6 @@ def _update_config_from_module(module, override_labid: Optional[str]):
# 更新OSS认证
if len(OSSUploadConfig.authorization) == 0:
OSSUploadConfig.authorization = f"Lab {MQConfig.lab_id}"
# 对 ca_file cert_file key_file 进行初始化
if override_labid:
MQConfig.lab_id = override_labid
logger.warning(f"[ENV] 当前实验室启动的ID被设置为{override_labid}")
if len(MQConfig.ca_content) == 0:
# 需要先判断是否为相对路径
if MQConfig.ca_file.startswith("."):
MQConfig.ca_file = os.path.join(BasicConfig.config_path, MQConfig.ca_file)
if len(MQConfig.ca_file) != 0:
with open(MQConfig.ca_file, "r", encoding="utf-8") as f:
MQConfig.ca_content = f.read()
else:
logger.warning("Skipping CA file loading, ca_file is empty")
if len(MQConfig.cert_content) == 0:
# 需要先判断是否为相对路径
if MQConfig.cert_file.startswith("."):
MQConfig.cert_file = os.path.join(BasicConfig.config_path, MQConfig.cert_file)
if len(MQConfig.ca_file) != 0:
with open(MQConfig.cert_file, "r", encoding="utf-8") as f:
MQConfig.cert_content = f.read()
else:
logger.warning("Skipping cert file loading, cert_file is empty")
if len(MQConfig.key_content) == 0:
# 需要先判断是否为相对路径
if MQConfig.key_file.startswith("."):
MQConfig.key_file = os.path.join(BasicConfig.config_path, MQConfig.key_file)
if len(MQConfig.ca_file) != 0:
with open(MQConfig.key_file, "r", encoding="utf-8") as f:
MQConfig.key_content = f.read()
else:
logger.warning("Skipping key file loading, key_file is empty")
def _update_config_from_env():
prefix = "UNILABOS_"
@@ -181,7 +149,7 @@ def _update_config_from_env():
logger.warning(f"[ENV] 解析环境变量 {env_key} 失败: {e}")
def load_config(config_path=None, override_labid=None):
def load_config(config_path=None):
# 如果提供了配置文件路径,从该文件导入配置
if config_path:
env_config_path = os.environ.get("UNILABOS_BASICCONFIG_CONFIG_PATH")
@@ -198,7 +166,7 @@ def load_config(config_path=None, override_labid=None):
return
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module) # type: ignore
_update_config_from_module(module, override_labid)
_update_config_from_module(module)
logger.info(f"[ENV] 配置文件 {config_path} 加载成功")
_update_config_from_env()
except Exception as e:
@@ -207,4 +175,4 @@ def load_config(config_path=None, override_labid=None):
exit(1)
else:
config_path = os.path.join(os.path.dirname(__file__), "local_config.py")
load_config(config_path, override_labid)
load_config(config_path)

View File

@@ -1,18 +1 @@
# MQTT配置
class MQConfig:
lab_id = ""
instance_id = ""
access_key = ""
secret_key = ""
group_id = ""
broker_url = ""
port = 1883
ca_file = "./CA.crt"
cert_file = "./lab.crt"
key_file = "./lab.key"
# HTTP配置
class HTTPConfig:
remote_addr = "https://uni-lab.bohrium.com/api/v1"
# 暂无配置

View File

@@ -1,11 +1,11 @@
from __future__ import annotations
import traceback
from typing import List, Sequence, Optional, Literal, Union, Iterator, Dict, Any, Callable, Set
from typing import List, Sequence, Optional, Literal, Union, Iterator, Dict, Any, Callable, Set, cast
from collections import Counter
import asyncio
import time
import pprint as pp
from pylabrobot.liquid_handling import LiquidHandler, LiquidHandlerBackend, LiquidHandlerChatterboxBackend, Strictness
from pylabrobot.liquid_handling.liquid_handler import TipPresenceProbingMethod
from pylabrobot.liquid_handling.standard import GripDirection
@@ -29,6 +29,7 @@ from pylabrobot.resources import (
class LiquidHandlerMiddleware(LiquidHandler):
def __init__(self, backend: LiquidHandlerBackend, deck: Deck, simulator: bool = False, channel_num: int = 8):
self._simulator = simulator
self.channel_num = channel_num
if simulator:
self._simulate_backend = LiquidHandlerChatterboxBackend(channel_num)
self._simulate_handler = LiquidHandlerAbstract(self._simulate_backend, deck, False)
@@ -104,8 +105,7 @@ class LiquidHandlerMiddleware(LiquidHandler):
offsets: Optional[List[Coordinate]] = None,
**backend_kwargs,
):
print('222'*200)
print(tip_spots)
if self._simulator:
return await self._simulate_handler.pick_up_tips(tip_spots, use_channels, offsets, **backend_kwargs)
return await super().pick_up_tips(tip_spots, use_channels, offsets, **backend_kwargs)
@@ -545,6 +545,7 @@ class LiquidHandlerAbstract(LiquidHandlerMiddleware):
deck: Deck to use.
"""
self._simulator = simulator
self.group_info = dict()
super().__init__(backend, deck, simulator, channel_num)
@classmethod
@@ -556,6 +557,51 @@ class LiquidHandlerAbstract(LiquidHandlerMiddleware):
# REMOVE LIQUID --------------------------------------------------
# ---------------------------------------------------------------
def set_group(self, group_name: str, wells: List[Well], volumes: List[float]):
if len(wells) != 8:
raise RuntimeError(f"Expected 8 wells, got {len(wells)}")
self.group_info[group_name] = wells
self.set_liquid(wells, [group_name] * len(wells), volumes)
async def transfer_group(self, source_group_name: str, target_group_name: str, unit_volume: float):
source_wells = self.group_info.get(source_group_name, [])
target_wells = self.group_info.get(target_group_name, [])
rack_info = dict()
for child in self.deck.children:
if issubclass(child.__class__, TipRack):
rack: TipRack = cast(TipRack, child)
if "plate" not in rack.name.lower():
for tip in rack.get_all_tips():
if unit_volume > tip.maximal_volume:
break
else:
rack_info[rack.name] = (rack, tip.maximal_volume - unit_volume)
if len(rack_info) == 0:
raise ValueError(f"No tip rack can support volume {unit_volume}.")
rack_info = sorted(rack_info.items(), key=lambda x: x[1][1])
for child in self.deck.children:
if child.name == rack_info[0][0]:
target_rack = child
target_rack = cast(TipRack, target_rack)
available_tips = {}
for (idx, name), tipSpot in zip(target_rack._ordering.items(), target_rack.get_all_items()):
if tipSpot.has_tip():
available_tips[idx] = tipSpot
continue
colnum_list = [int(holename[1:]) for holename in available_tips.keys()]
available_cols = [colnum for colnum, count in dict(Counter(colnum_list)).items() if count == 8]
available_cols.sort() # 这样就确定了列号
tips_to_use = [available_tips[f"{chr(65 + i)}{available_cols[0]}"] for i in range(8)]
await self.pick_up_tips(tips_to_use, use_channels=list(range(0, 8)))
await self.aspirate(source_wells, [10] * 8, use_channels=list(range(0, 8)))
await self.dispense(target_wells, [10] * 8, use_channels=list(range(0, 8)))
await self.discard_tips(use_channels=list(range(0, 8)))
async def create_protocol(
self,
protocol_name: str,
@@ -569,6 +615,7 @@ class LiquidHandlerAbstract(LiquidHandlerMiddleware):
"""Create a new protocol with the given metadata."""
pass
async def remove_liquid(
self,
vols: List[float],
@@ -987,8 +1034,8 @@ class LiquidHandlerAbstract(LiquidHandlerMiddleware):
if delays is not None:
await self.custom_delay(seconds=delays[1])
await self.touch_tip(current_targets)
await self.discard_tips()
await self.discard_tips([0,1,2,3,4,5,6,7])
# except Exception as e:
# traceback.print_exc()
# raise RuntimeError(f"Liquid addition failed: {e}") from e

View File

@@ -134,6 +134,12 @@ class PRCXI9300Handler(LiquidHandlerAbstract):
def set_liquid(self, wells: list[Well], liquid_names: list[str], volumes: list[float]):
return super().set_liquid(wells, liquid_names, volumes)
def set_group(self, group_name: str, wells: List[Well], volumes: List[float]):
return super().set_group(group_name, wells, volumes)
async def transfer_group(self, source_group_name: str, target_group_name: str, unit_volume: float):
return await super().transfer_group(source_group_name, target_group_name, unit_volume)
async def create_protocol(
self,
@@ -431,14 +437,14 @@ class PRCXI9300Backend(LiquidHandlerBackend):
async def pick_up_tips(self, ops: List[Pickup], use_channels: List[int] = None):
"""Pick up tips from the specified resource."""
print('where?'*200)
plate_indexes = []
for op in ops:
plate = op.resource.parent
deck = plate.parent
plate_index = deck.children.index(plate)
print(f"Plate index: {plate_index}, Plate name: {plate.name}")
print(f"Number of children in deck: {len(deck.children)}")
# print(f"Plate index: {plate_index}, Plate name: {plate.name}")
# print(f"Number of children in deck: {len(deck.children)}")
plate_indexes.append(plate_index)
@@ -452,8 +458,6 @@ class PRCXI9300Backend(LiquidHandlerBackend):
tip_columns.append(tipspot_index // 8)
if len(set(tip_columns)) != 1:
raise ValueError("All pickups must be from the same tip column. Found different columns: " + str(tip_columns))
# print('111'*99)
# print(plate_indexes[0])
PlateNo = plate_indexes[0] + 1
hole_col = tip_columns[0] + 1
hole_row = 1
@@ -977,153 +981,162 @@ if __name__ == "__main__":
# 4.
# deck = PRCXI9300Deck(name="PRCXI_Deck_9300", size_x=100, size_y=100, size_z=100)
deck = PRCXI9300Deck(name="PRCXI_Deck_9300", size_x=100, size_y=100, size_z=100)
# from pylabrobot.resources.opentrons.tip_racks import opentrons_96_tiprack_300ul,opentrons_96_tiprack_10ul
# from pylabrobot.resources.opentrons.plates import corning_96_wellplate_360ul_flat, nest_96_wellplate_2ml_deep
from pylabrobot.resources.opentrons.tip_racks import opentrons_96_tiprack_300ul,opentrons_96_tiprack_10ul
from pylabrobot.resources.opentrons.plates import corning_96_wellplate_360ul_flat, nest_96_wellplate_2ml_deep
# 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()
# plate_serialized["parent_name"] = deck.name
# 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_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=well_containers["ordering"])
plate_serialized = plate.serialize()
plate_serialized["parent_name"] = deck.name
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_300ul("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_rack_serialized["parent_name"] = deck.name
# 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 get_tip_rack(name: str) -> PRCXI9300Container:
tip_racks = opentrons_96_tiprack_300ul("name").serialize()
tip_rack = PRCXI9300Container(name=name, size_x=50, size_y=50, size_z=10, category="tip_rack",
ordering=tip_racks["ordering"])
tip_rack_serialized = tip_rack.serialize()
tip_rack_serialized["parent_name"] = deck.name
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
# plate1 = get_tip_rack("RackT1")
# plate1.load_state({
# "Material": {
# "uuid": "076250742950465b9d6ea29a225dfb00",
# "Code": "ZX-001-300",
# "Name": "300μL Tip头"
# }
# })
plate1 = get_tip_rack("RackT1")
plate1.load_state({
"Material": {
"uuid": "076250742950465b9d6ea29a225dfb00",
"Code": "ZX-001-300",
"Name": "300μL Tip头"
}
})
# plate2 = get_well_container("PlateT2")
# plate2.load_state({
# "Material": {
# "uuid": "57b1e4711e9e4a32b529f3132fc5931f",
# "Code": "ZX-019-2.2",
# "Name": "96深孔板"
# }
# })
plate2 = get_well_container("PlateT2")
plate2.load_state({
"Material": {
"uuid": "57b1e4711e9e4a32b529f3132fc5931f",
"Code": "ZX-019-2.2",
"Name": "96深孔板"
}
})
# plate3 = PRCXI9300Trash("trash", size_x=50, size_y=100, size_z=10, category="trash")
# plate3.load_state({
# "Material": {
# "uuid": "730067cf07ae43849ddf4034299030e9"
# }
# })
plate3 = PRCXI9300Trash("trash", size_x=50, size_y=100, size_z=10, category="trash")
plate3.load_state({
"Material": {
"uuid": "730067cf07ae43849ddf4034299030e9"
}
})
# plate4 = get_well_container("PlateT4")
# plate4.load_state({
# "Material": {
# "uuid": "57b1e4711e9e4a32b529f3132fc5931f",
# "Code": "ZX-019-2.2",
# "Name": "96深孔板"
# }
# })
plate4 = get_well_container("PlateT4")
plate4.load_state({
"Material": {
"uuid": "57b1e4711e9e4a32b529f3132fc5931f",
"Code": "ZX-019-2.2",
"Name": "96深孔板"
}
})
# plate5 = get_well_container("PlateT5")
# plate5.load_state({
# "Material": {
# "uuid": "57b1e4711e9e4a32b529f3132fc5931f",
# "Code": "ZX-019-2.2",
# "Name": "96深孔板"
# }
# })
# plate6 = get_well_container("PlateT6")
plate5 = get_well_container("PlateT5")
plate5.load_state({
"Material": {
"uuid": "57b1e4711e9e4a32b529f3132fc5931f",
"Code": "ZX-019-2.2",
"Name": "96深孔板"
}
})
plate6 = get_well_container("PlateT6")
# plate6.load_state({
# "Material": {
# "uuid": "57b1e4711e9e4a32b529f3132fc5931f",
# "Code": "ZX-019-2.2",
# "Name": "96深孔板"
# }
# })
plate6.load_state({
"Material": {
"uuid": "57b1e4711e9e4a32b529f3132fc5931f",
"Code": "ZX-019-2.2",
"Name": "96深孔板"
}
})
# deck.assign_child_resource(plate1, location=Coordinate(0, 0, 0))
# deck.assign_child_resource(plate2, location=Coordinate(0, 0, 0))
# deck.assign_child_resource(plate3, location=Coordinate(0, 0, 0))
# deck.assign_child_resource(plate4, location=Coordinate(0, 0, 0))
# deck.assign_child_resource(plate5, location=Coordinate(0, 0, 0))
# deck.assign_child_resource(plate6, location=Coordinate(0, 0, 0))
deck.assign_child_resource(plate1, location=Coordinate(0, 0, 0))
deck.assign_child_resource(plate2, location=Coordinate(0, 0, 0))
deck.assign_child_resource(plate3, location=Coordinate(0, 0, 0))
deck.assign_child_resource(plate4, location=Coordinate(0, 0, 0))
deck.assign_child_resource(plate5, location=Coordinate(0, 0, 0))
deck.assign_child_resource(plate6, location=Coordinate(0, 0, 0))
# # print(plate2)
# plate_2_liquids = [[('water', 500)]]*96
# plate2.set_well_liquids(plate_2_liquids)
# handler = PRCXI9300Handler(deck=deck, host="10.181.214.132", port=9999,
# timeout=10.0, setup=False, debug=False,
# matrix_id="71593",
# channel_num=8, axis="Left") # Initialize the handler with the deck and host settings
# handler.set_tiprack([plate1])
handler = PRCXI9300Handler(deck=deck, host="10.181.214.132", port=9999,
timeout=10.0, setup=False, debug=False,
simulator=True,
matrix_id="71593",
channel_num=8, axis="Left") # Initialize the handler with the deck and host settings
# asyncio.run(handler.setup()) # Initialize the handler and setup the connection
# from pylabrobot.resources import set_volume_tracking
# # from pylabrobot.resources import set_tip_tracking
# set_volume_tracking(enabled=True)
plate_2_liquids = handler.set_group("water", plate2.children[:8], [200]*8)
plate5_liquids = handler.set_group("master_mix", plate5.children[:8], [100]*8)
handler.set_tiprack([plate1])
asyncio.run(handler.setup()) # Initialize the handler and setup the connection
from pylabrobot.resources import set_volume_tracking
# from pylabrobot.resources import set_tip_tracking
set_volume_tracking(enabled=True)
# from unilabos.resources.graphio import *
# A = tree_to_list([resource_plr_to_ulab(deck)])
# with open("deck_9300_new.json", "w", encoding="utf-8") as f:
# json.dump(A, f, indent=4, ensure_ascii=False)
# asyncio.run(handler.create_protocol(protocol_name="Test Protocol")) # Initialize the backend and setup the connection
# # A = tree_to_list([resource_plr_to_ulab(deck)])
# # with open("deck_9300_new.json", "w", encoding="utf-8") as f:
# # json.dump(A, f, indent=4, ensure_ascii=False)
asyncio.run(handler.create_protocol(protocol_name="Test Protocol")) # Initialize the backend and setup the connection
asyncio.run(handler.transfer_group("water", "master_mix", 100)) # Reset tip tracking
# # asyncio.run(handler.pick_up_tips(plate1.children[:8],[0,1,2,3,4,5,6,7]))
# # print(plate1.children[:8])
# # asyncio.run(handler.aspirate(plate2.children[:8],[50]*8, [0,1,2,3,4,5,6,7]))
# # print(plate2.children[:8])
# # asyncio.run(handler.dispense(plate5.children[:8],[50]*8,[0,1,2,3,4,5,6,7]))
# # print(plate5.children[:8])
# asyncio.run(handler.pick_up_tips(plate1.children[:8],[0,1,2,3,4,5,6,7]))
# print(plate1.children[:8])
# asyncio.run(handler.aspirate(plate2.children[:8],[50]*8, [0,1,2,3,4,5,6,7]))
# print(plate2.children[:8])
# asyncio.run(handler.dispense(plate5.children[:8],[50]*8,[0,1,2,3,4,5,6,7]))
# print(plate5.children[:8])
# # # # # asyncio.run(handler.drop_tips(tip_rack.children[8:16],[0,1,2,3,4,5,6,7]))
# # asyncio.run(handler.discard_tips())
# #asyncio.run(handler.drop_tips(tip_rack.children[8:16],[0,1,2,3,4,5,6,7]))
# asyncio.run(handler.discard_tips([0,1,2,3,4,5,6,7]))
# # asyncio.run(handler.mix(well_containers.children[:8
# # ], mix_time=3, mix_vol=50, height_to_bottom=0.5, offsets=Coordinate(0, 0, 0), mix_rate=100))
# # #print(json.dumps(handler._unilabos_backend.steps_todo_list, indent=2)) # Print matrix info
# # asyncio.run(handler.add_liquid(
# # asp_vols=[100]*16,
# # dis_vols=[100]*16,
# # reagent_sources=plate2.children[:16],
# # targets=plate5.children[:16],
# # use_channels=[0, 1, 2, 3, 4, 5, 6, 7],
# # flow_rates=[None] * 32,
# # offsets=[Coordinate(0, 0, 0)] * 32,
# # liquid_height=[None] * 16,
# # blow_out_air_volume=[None] * 16,
# # delays=None,
# # mix_time=3,
# # mix_vol=50,
# # spread="wide",
# # ))
# # asyncio.run(handler.run_protocol()) # Run the protocol
# # asyncio.run(handler.remove_liquid(
# # vols=[100]*16,
# # sources=plate2.children[-16:],
# # waste_liquid=plate5.children[:16], # 这个有些奇怪,但是好像也只能这么写
# # use_channels=[0, 1, 2, 3, 4, 5, 6, 7],
# # flow_rates=[None] * 32,
# # offsets=[Coordinate(0, 0, 0)] * 32,
# # liquid_height=[None] * 32,
# # blow_out_air_volume=[None] * 32,
# # spread="wide",
# # ))
# asyncio.run(handler.mix(well_containers.children[:8
# ], mix_time=3, mix_vol=50, height_to_bottom=0.5, offsets=Coordinate(0, 0, 0), mix_rate=100))
# #print(json.dumps(handler._unilabos_backend.steps_todo_list, indent=2)) # Print matrix info
# asyncio.run(handler.add_liquid(
# asp_vols=[100]*16,
# dis_vols=[100]*16,
# reagent_sources=plate2.children[:16],
# targets=plate5.children[:16],
# use_channels=[0, 1, 2, 3, 4, 5, 6, 7],
# flow_rates=[None] * 32,
# offsets=[Coordinate(0, 0, 0)] * 32,
# liquid_height=[None] * 16,
# blow_out_air_volume=[None] * 16,
# delays=None,
# mix_time=3,
# mix_vol=50,
# spread="wide",
# ))
# asyncio.run(handler.run_protocol()) # Run the protocol
# asyncio.run(handler.remove_liquid(
# vols=[100]*16,
# sources=plate2.children[-16:],
# waste_liquid=plate5.children[:16], # 这个有些奇怪,但是好像也只能这么写
# use_channels=[0, 1, 2, 3, 4, 5, 6, 7],
# flow_rates=[None] * 32,
# offsets=[Coordinate(0, 0, 0)] * 32,
# liquid_height=[None] * 32,
# blow_out_air_volume=[None] * 32,
# spread="wide",
# ))
# acid = [20]*8+[40]*8+[60]*8+[80]*8+[100]*8+[120]*8+[140]*8+[160]*8+[180]*8+[200]*8+[220]*8+[240]*8
# alkaline = acid[::-1] # Reverse the acid list for alkaline
@@ -1144,258 +1157,264 @@ if __name__ == "__main__":
# spread="wide",
# ))
# asyncio.run(handler.run_protocol()) # Run the protocol
# # # input("Running protocol...")
# # # input("Press Enter to continue...") # Wait for user input before proceeding
# # # print("PRCXI9300Handler initialized with deck and host settings.")
# # Example usage
# # 1. 用导出的json给每个T1 T2板子设定相应的物料如果是孔板和枪头盒要对应区分
# # 2. 设计一个单点动作流程,可以跑
# # 3.
# # input("Running protocol...")
# # input("Press Enter to continue...") # Wait for user input before proceeding
# # print("PRCXI9300Handler initialized with deck and host settings.")
deck = PRCXI9300Deck(name="PRCXI_Deck", size_x=100, size_y=100, size_z=100)
from pylabrobot.resources.opentrons.tip_racks import tipone_96_tiprack_200ul,opentrons_96_tiprack_10ul
from pylabrobot.resources.opentrons.plates import corning_96_wellplate_360ul_flat, nest_96_wellplate_2ml_deep
### 9320 ###
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()
plate_serialized["parent_name"] = deck.name
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_rack_serialized["parent_name"] = deck.name
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
# deck = PRCXI9300Deck(name="PRCXI_Deck", size_x=100, size_y=100, size_z=100)
# from pylabrobot.resources.opentrons.tip_racks import tipone_96_tiprack_200ul,opentrons_96_tiprack_10ul
# from pylabrobot.resources.opentrons.plates import corning_96_wellplate_360ul_flat, nest_96_wellplate_2ml_deep
# 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()
# plate_serialized["parent_name"] = deck.name
# 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_rack_serialized["parent_name"] = deck.name
# 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
plate1 = get_well_container("HPLCPlateT1")
plate1.load_state({
"Material": {
"uuid": "548bbc3df0d4447586f2c19d2c0c0c55",
"Code": "HPLC01",
"Name": "HPLC料盘"
}
})
plate2 = get_well_container("PlateT2")
plate2.load_state({
"Material": {
"uuid": "04211a2dc93547fe9bf6121eac533650",
}
})
plate3 = get_well_container("PlateT3")
plate3.load_state({
"Material": {
"uuid": "04211a2dc93547fe9bf6121eac533650",
}
})
trash = PRCXI9300Trash(name="trash", size_x=50, size_y=50, size_z=10, category="trash")
trash.load_state({
"Material": {
"uuid": "730067cf07ae43849ddf4034299030e9"
}
})
plate5 = get_well_container("PlateT5")
plate5.load_state({
"Material": {
"uuid": "04211a2dc93547fe9bf6121eac533650",
}
})
plate6 = get_well_container("PlateT6")
plate6.load_state({
"Material": {
"uuid": "04211a2dc93547fe9bf6121eac533650"
}
})
plate7 = PRCXI9300Container(name="plateT7", size_x=50, size_y=50, size_z=10, category="plate", ordering=collections.OrderedDict())
plate7.load_state({
"Material": {
"uuid": "04211a2dc93547fe9bf6121eac533650"
}
})
plate8 = get_tip_rack("RackT8")
plate8.load_state({
"Material": {
"uuid": "068b3815e36b4a72a59bae017011b29f",
"Code": "ZX-001-10+",
"Name": "10μL加长 Tip头"
}
})
plate9 = get_well_container("PlateT9")
plate9.load_state({
"Material": {
"uuid": "04211a2dc93547fe9bf6121eac533650"
}
})
plate10 = get_well_container("PlateT10")
plate10.load_state({
"Material": {
"uuid": "04211a2dc93547fe9bf6121eac533650"
}
})
plate11 = get_well_container("PlateT11")
plate11.load_state({
"Material": {
"uuid": "57b1e4711e9e4a32b529f3132fc5931f",
}
})
plate12 = get_well_container("PlateT12")
plate12.load_state({
"Material": {
"uuid": "04211a2dc93547fe9bf6121eac533650"
}
})
plate13 = get_well_container("PlateT13")
plate13.load_state({
"Material": {
"uuid": "04211a2dc93547fe9bf6121eac533650"
}
})
# plate1 = get_well_container("HPLCPlateT1")
# plate1.load_state({
# "Material": {
# "uuid": "548bbc3df0d4447586f2c19d2c0c0c55",
# "Code": "HPLC01",
# "Name": "HPLC料盘"
# }
# })
# plate2 = get_well_container("PlateT2")
# plate2.load_state({
# "Material": {
# "uuid": "04211a2dc93547fe9bf6121eac533650",
# }
# })
# plate3 = get_well_container("PlateT3")
# plate3.load_state({
# "Material": {
# "uuid": "04211a2dc93547fe9bf6121eac533650",
# }
# })
# trash = PRCXI9300Trash(name="trash", size_x=50, size_y=50, size_z=10, category="trash")
# trash.load_state({
# "Material": {
# "uuid": "730067cf07ae43849ddf4034299030e9"
# }
# })
# plate5 = get_well_container("PlateT5")
# plate5.load_state({
# "Material": {
# "uuid": "04211a2dc93547fe9bf6121eac533650",
# }
# })
# plate6 = get_well_container("PlateT6")
# plate6.load_state({
# "Material": {
# "uuid": "04211a2dc93547fe9bf6121eac533650"
# }
# })
# plate7 = PRCXI9300Container(name="plateT7", size_x=50, size_y=50, size_z=10, category="plate", ordering=collections.OrderedDict())
# plate7.load_state({
# "Material": {
# "uuid": "04211a2dc93547fe9bf6121eac533650"
# }
# })
# plate8 = get_tip_rack("RackT8")
# plate8.load_state({
# "Material": {
# "uuid": "068b3815e36b4a72a59bae017011b29f",
# "Code": "ZX-001-10+",
# "Name": "10μL加长 Tip头"
# }
# })
# plate9 = get_well_container("PlateT9")
# plate9.load_state({
# "Material": {
# "uuid": "04211a2dc93547fe9bf6121eac533650"
# }
# })
# plate10 = get_well_container("PlateT10")
# plate10.load_state({
# "Material": {
# "uuid": "04211a2dc93547fe9bf6121eac533650"
# }
# })
# plate11 = get_well_container("PlateT11")
# plate11.load_state({
# "Material": {
# "uuid": "57b1e4711e9e4a32b529f3132fc5931f",
# }
# })
# plate12 = get_well_container("PlateT12")
# plate12.load_state({
# "Material": {
# "uuid": "04211a2dc93547fe9bf6121eac533650"
# }
# })
# plate13 = get_well_container("PlateT13")
# plate13.load_state({
# "Material": {
# "uuid": "04211a2dc93547fe9bf6121eac533650"
# }
# })
# container_for_nothing = PRCXI9300Container(name="container_for_nothing", size_x=50, size_y=50, size_z=10, category="plate", ordering=collections.OrderedDict())
# # container_for_nothing = PRCXI9300Container(name="container_for_nothing", size_x=50, size_y=50, size_z=10, category="plate", ordering=collections.OrderedDict())
deck.assign_child_resource(plate1, location=Coordinate(0, 0, 0))
deck.assign_child_resource(PRCXI9300Container(name="container_for_nothing1", size_x=50, size_y=50, size_z=10, category="plate", ordering=collections.OrderedDict()), location=Coordinate(0, 0, 0))
deck.assign_child_resource(PRCXI9300Container(name="container_for_nothing2", size_x=50, size_y=50, size_z=10, category="plate", ordering=collections.OrderedDict()), location=Coordinate(0, 0, 0))
deck.assign_child_resource(trash, location=Coordinate(0, 0, 0))
deck.assign_child_resource(PRCXI9300Container(name="container_for_nothing3", size_x=50, size_y=50, size_z=10, category="plate", ordering=collections.OrderedDict()), location=Coordinate(0, 0, 0))
deck.assign_child_resource(PRCXI9300Container(name="container_for_nothing", size_x=50, size_y=50, size_z=10, category="plate", ordering=collections.OrderedDict()), location=Coordinate(0, 0, 0))
deck.assign_child_resource(PRCXI9300Container(name="container_for_nothing4", size_x=50, size_y=50, size_z=10, category="plate", ordering=collections.OrderedDict()), location=Coordinate(0, 0, 0))
deck.assign_child_resource(plate8, location=Coordinate(0, 0, 0))
deck.assign_child_resource(PRCXI9300Container(name="container_for_nothing5", size_x=50, size_y=50, size_z=10, category="plate", ordering=collections.OrderedDict()), location=Coordinate(0, 0, 0))
deck.assign_child_resource(PRCXI9300Container(name="container_for_nothing6", size_x=50, size_y=50, size_z=10, category="plate", ordering=collections.OrderedDict()), location=Coordinate(0, 0, 0))
deck.assign_child_resource(plate11, location=Coordinate(0, 0, 0))
deck.assign_child_resource(PRCXI9300Container(name="container_for_nothing7", size_x=50, size_y=50, size_z=10, category="plate", ordering=collections.OrderedDict()), location=Coordinate(0, 0, 0))
deck.assign_child_resource(PRCXI9300Container(name="container_for_nothing8", size_x=50, size_y=50, size_z=10, category="plate", ordering=collections.OrderedDict()), location=Coordinate(0, 0, 0))
# deck.assign_child_resource(plate1, location=Coordinate(0, 0, 0))
# deck.assign_child_resource(PRCXI9300Container(name="container_for_nothing1", size_x=50, size_y=50, size_z=10, category="plate", ordering=collections.OrderedDict()), location=Coordinate(0, 0, 0))
# deck.assign_child_resource(PRCXI9300Container(name="container_for_nothing2", size_x=50, size_y=50, size_z=10, category="plate", ordering=collections.OrderedDict()), location=Coordinate(0, 0, 0))
# deck.assign_child_resource(trash, location=Coordinate(0, 0, 0))
# deck.assign_child_resource(PRCXI9300Container(name="container_for_nothing3", size_x=50, size_y=50, size_z=10, category="plate", ordering=collections.OrderedDict()), location=Coordinate(0, 0, 0))
# deck.assign_child_resource(PRCXI9300Container(name="container_for_nothing", size_x=50, size_y=50, size_z=10, category="plate", ordering=collections.OrderedDict()), location=Coordinate(0, 0, 0))
# deck.assign_child_resource(PRCXI9300Container(name="container_for_nothing4", size_x=50, size_y=50, size_z=10, category="plate", ordering=collections.OrderedDict()), location=Coordinate(0, 0, 0))
# deck.assign_child_resource(plate8, location=Coordinate(0, 0, 0))
# deck.assign_child_resource(PRCXI9300Container(name="container_for_nothing5", size_x=50, size_y=50, size_z=10, category="plate", ordering=collections.OrderedDict()), location=Coordinate(0, 0, 0))
# deck.assign_child_resource(PRCXI9300Container(name="container_for_nothing6", size_x=50, size_y=50, size_z=10, category="plate", ordering=collections.OrderedDict()), location=Coordinate(0, 0, 0))
# deck.assign_child_resource(plate11, location=Coordinate(0, 0, 0))
# deck.assign_child_resource(PRCXI9300Container(name="container_for_nothing7", size_x=50, size_y=50, size_z=10, category="plate", ordering=collections.OrderedDict()), location=Coordinate(0, 0, 0))
# deck.assign_child_resource(PRCXI9300Container(name="container_for_nothing8", size_x=50, size_y=50, size_z=10, category="plate", ordering=collections.OrderedDict()), location=Coordinate(0, 0, 0))
handler = PRCXI9300Handler(deck=deck, host="10.181.102.13", port=9999,
timeout=10.0, setup=False, debug=False,
matrix_id="fd383e6d-2d0e-40b5-9c01-1b2870b1f1b1",
channel_num=1, axis="Right") # Initialize the handler with the deck and host settings
# handler = PRCXI9300Handler(deck=deck, host="10.181.102.13", port=9999,
# timeout=10.0, setup=False, debug=False,
# matrix_id="fd383e6d-2d0e-40b5-9c01-1b2870b1f1b1",
# channel_num=8, axis="Right") # Initialize the handler with the deck and host settings
handler.set_tiprack([plate8]) # Set the tip rack for the handler
asyncio.run(handler.setup()) # Initialize the handler and setup the connection
from pylabrobot.resources import set_volume_tracking
# from pylabrobot.resources import set_tip_tracking
set_volume_tracking(enabled=True)
# handler.set_tiprack([plate8]) # Set the tip rack for the handler
# asyncio.run(handler.setup()) # Initialize the handler and setup the connection
# from pylabrobot.resources import set_volume_tracking
# # from pylabrobot.resources import set_tip_tracking
# set_volume_tracking(enabled=True)
plate11.set_well_liquids([("Water", 100) if (i % 8 == 0 and i // 8 < 6) else (None, 100) for i in range(96)]) # Set liquids for every 8 wells in plate8
# plate11.set_well_liquids([("Water", 100) if (i % 8 == 0 and i // 8 < 6) else (None, 100) for i in range(96)]) # Set liquids for every 8 wells in plate8
from unilabos.resources.graphio import *
# from unilabos.resources.graphio import *
A = tree_to_list([resource_plr_to_ulab(deck)])
# with open("deck.json", "w", encoding="utf-8") as f:
# json.dump(A, f, indent=4, ensure_ascii=False)
# A = tree_to_list([resource_plr_to_ulab(deck)])
# # with open("deck.json", "w", encoding="utf-8") as f:
# # json.dump(A, f, indent=4, ensure_ascii=False)
print(plate11.get_well(0).tracker.get_used_volume())
asyncio.run(handler.create_protocol(protocol_name="Test Protocol")) # Initialize the backend and setup the connection
# asyncio.run(handler.pick_up_tips([plate8.children[8]],[0]))
# print(plate8.children[8])
# # asyncio.run(handler.run_protocol())
# asyncio.run(handler.aspirate([plate11.children[0]],[10], [0]))
# print(plate11.children[0])
# # asyncio.run(handler.run_protocol())
# asyncio.run(handler.dispense([plate1.children[0]],[10],[0]))
# print(plate1.children[0])
# # asyncio.run(handler.run_protocol())
# asyncio.run(handler.mix([plate1.children[0]], mix_time=3, mix_vol=5, height_to_bottom=0.5, offsets=Coordinate(0, 0, 0), mix_rate=100))
# print(plate1.children[0])
# asyncio.run(handler.discard_tips())
asyncio.run(handler.add_liquid(
asp_vols=[10]*7,
dis_vols=[10]*7,
reagent_sources=plate11.children[:7],
targets=plate1.children[2:9],
use_channels=[0],
flow_rates=[None] * 7,
offsets=[Coordinate(0, 0, 0)] * 7,
liquid_height=[None] * 7,
blow_out_air_volume=[None] * 2,
delays=None,
mix_time=3,
mix_vol=5,
spread="custom",
))
asyncio.run(handler.run_protocol()) # Run the protocol
# # asyncio.run(handler.transfer_liquid(
# # asp_vols=[10]*2,
# # dis_vols=[10]*2,
# # sources=plate11.children[:2],
# # targets=plate11.children[-2:],
# # use_channels=[0],
# # offsets=[Coordinate(0, 0, 0)] * 4,
# # liquid_height=[None] * 2,
# # blow_out_air_volume=[None] * 2,
# # delays=None,
# # mix_times=3,
# # mix_vol=5,
# # spread="wide",
# # tip_racks=[plate8]
# # ))
# # asyncio.run(handler.remove_liquid(
# # vols=[10]*2,
# # sources=plate11.children[:2],
# # waste_liquid=plate11.children[43],
# # use_channels=[0],
# # offsets=[Coordinate(0, 0, 0)] * 4,
# # liquid_height=[None] * 2,
# # blow_out_air_volume=[None] * 2,
# # delays=None,
# # spread="wide"
# # ))
# asyncio.run(handler.run_protocol())
# print(plate11.get_well(0).tracker.get_used_volume())
# asyncio.run(handler.create_protocol(protocol_name="Test Protocol")) # Initialize the backend and setup the connection
# # asyncio.run(handler.pick_up_tips([plate8.children[8]],[0]))
# # print(plate8.children[8])
# # # asyncio.run(handler.run_protocol())
# # asyncio.run(handler.aspirate([plate11.children[0]],[10], [0]))
# # print(plate11.children[0])
# # # asyncio.run(handler.run_protocol())
# # asyncio.run(handler.dispense([plate1.children[0]],[10],[0]))
# # print(plate1.children[0])
# # # asyncio.run(handler.run_protocol())
# # asyncio.run(handler.mix([plate1.children[0]], mix_time=3, mix_vol=5, height_to_bottom=0.5, offsets=Coordinate(0, 0, 0), mix_rate=100))
# # print(plate1.children[0])
# # asyncio.run(handler.discard_tips())
# # asyncio.run(handler.mix(well_containers.children[:8
# # ], mix_time=3, mix_vol=50, height_to_bottom=0.5, offsets=Coordinate(0, 0, 0), mix_rate=100))
# #print(json.dumps(handler._unilabos_backend.steps_todo_list, indent=2)) # Print matrix info
# asyncio.run(handler.add_liquid(
# asp_vols=[10]*7,
# dis_vols=[10]*7,
# reagent_sources=plate11.children[:7],
# targets=plate1.children[2:9],
# use_channels=[0],
# flow_rates=[None] * 7,
# offsets=[Coordinate(0, 0, 0)] * 7,
# liquid_height=[None] * 7,
# blow_out_air_volume=[None] * 2,
# delays=None,
# mix_time=3,
# mix_vol=5,
# spread="custom",
# ))
# asyncio.run(handler.run_protocol()) # Run the protocol
# # asyncio.run(handler.remove_liquid(
# # vols=[100]*16,
# # sources=well_containers.children[-16:],
# # waste_liquid=well_containers.children[:16], # 这个有些奇怪,但是好像也只能这么写
# # use_channels=[0, 1, 2, 3, 4, 5, 6, 7],
# # flow_rates=[None] * 32,
# # offsets=[Coordinate(0, 0, 0)] * 32,
# # liquid_height=[None] * 32,
# # blow_out_air_volume=[None] * 32,
# # spread="wide",
# # ))
# # asyncio.run(handler.transfer_liquid(
# # asp_vols=[100]*16,
# # dis_vols=[100]*16,
# # tip_racks=[tip_rack],
# # sources=well_containers.children[-16:],
# # targets=well_containers.children[:16],
# # use_channels=[0, 1, 2, 3, 4, 5, 6, 7],
# # offsets=[Coordinate(0, 0, 0)] * 32,
# # asp_flow_rates=[None] * 16,
# # dis_flow_rates=[None] * 16,
# # liquid_height=[None] * 32,
# # blow_out_air_volume=[None] * 32,
# # mix_times=3,
# # mix_vol=50,
# # spread="wide",
# # ))
print(json.dumps(handler._unilabos_backend.steps_todo_list, indent=2)) # Print matrix info
# # input("pick_up_tips add step")
# #asyncio.run(handler.run_protocol()) # Run the protocol
# # input("Running protocol...")
# # input("Press Enter to continue...") # Wait for user input before proceeding
# # print("PRCXI9300Handler initialized with deck and host settings.")
# # # asyncio.run(handler.transfer_liquid(
# # # asp_vols=[10]*2,
# # # dis_vols=[10]*2,
# # # sources=plate11.children[:2],
# # # targets=plate11.children[-2:],
# # # use_channels=[0],
# # # offsets=[Coordinate(0, 0, 0)] * 4,
# # # liquid_height=[None] * 2,
# # # blow_out_air_volume=[None] * 2,
# # # delays=None,
# # # mix_times=3,
# # # mix_vol=5,
# # # spread="wide",
# # # tip_racks=[plate8]
# # # ))
# # # asyncio.run(handler.remove_liquid(
# # # vols=[10]*2,
# # # sources=plate11.children[:2],
# # # waste_liquid=plate11.children[43],
# # # use_channels=[0],
# # # offsets=[Coordinate(0, 0, 0)] * 4,
# # # liquid_height=[None] * 2,
# # # blow_out_air_volume=[None] * 2,
# # # delays=None,
# # # spread="wide"
# # # ))
# # asyncio.run(handler.run_protocol())
# # # asyncio.run(handler.discard_tips())
# # # asyncio.run(handler.mix(well_containers.children[:8
# # # ], mix_time=3, mix_vol=50, height_to_bottom=0.5, offsets=Coordinate(0, 0, 0), mix_rate=100))
# # #print(json.dumps(handler._unilabos_backend.steps_todo_list, indent=2)) # Print matrix info
# # # asyncio.run(handler.remove_liquid(
# # # vols=[100]*16,
# # # sources=well_containers.children[-16:],
# # # waste_liquid=well_containers.children[:16], # 这个有些奇怪,但是好像也只能这么写
# # # use_channels=[0, 1, 2, 3, 4, 5, 6, 7],
# # # flow_rates=[None] * 32,
# # # offsets=[Coordinate(0, 0, 0)] * 32,
# # # liquid_height=[None] * 32,
# # # blow_out_air_volume=[None] * 32,
# # # spread="wide",
# # # ))
# # # asyncio.run(handler.transfer_liquid(
# # # asp_vols=[100]*16,
# # # dis_vols=[100]*16,
# # # tip_racks=[tip_rack],
# # # sources=well_containers.children[-16:],
# # # targets=well_containers.children[:16],
# # # use_channels=[0, 1, 2, 3, 4, 5, 6, 7],
# # # offsets=[Coordinate(0, 0, 0)] * 32,
# # # asp_flow_rates=[None] * 16,
# # # dis_flow_rates=[None] * 16,
# # # liquid_height=[None] * 32,
# # # blow_out_air_volume=[None] * 32,
# # # mix_times=3,
# # # mix_vol=50,
# # # spread="wide",
# # # ))
# # print(json.dumps(handler._unilabos_backend.steps_todo_list, indent=2)) # Print matrix info
# # # input("pick_up_tips add step")
# # #asyncio.run(handler.run_protocol()) # Run the protocol
# # # input("Running protocol...")
# # # input("Press Enter to continue...") # Wait for user input before proceeding
# # # print("PRCXI9300Handler initialized with deck and host settings.")

View File

@@ -652,6 +652,38 @@ liquid_handler:
title: iter_tips参数
type: object
type: UniLabJsonCommand
auto-set_group:
feedback: {}
goal: {}
goal_default:
group_name: null
volumes: null
wells: null
handles: {}
result: {}
schema:
description: ''
properties:
feedback: {}
goal:
properties:
group_name:
type: string
volumes:
type: string
wells:
type: string
required:
- group_name
- wells
- volumes
type: object
result: {}
required:
- goal
title: set_group参数
type: object
type: UniLabJsonCommand
auto-set_tiprack:
feedback: {}
goal: {}
@@ -700,6 +732,38 @@ liquid_handler:
title: touch_tip参数
type: object
type: UniLabJsonCommandAsync
auto-transfer_group:
feedback: {}
goal: {}
goal_default:
source_group_name: null
target_group_name: null
unit_volume: null
handles: {}
result: {}
schema:
description: ''
properties:
feedback: {}
goal:
properties:
source_group_name:
type: string
target_group_name:
type: string
unit_volume:
type: number
required:
- source_group_name
- target_group_name
- unit_volume
type: object
result: {}
required:
- goal
title: transfer_group参数
type: object
type: UniLabJsonCommandAsync
discard_tips:
feedback: {}
goal:
@@ -7226,6 +7290,151 @@ liquid_handler.prcxi:
title: LiquidHandlerRemove
type: object
type: LiquidHandlerRemove
set_group:
feedback: {}
goal:
group_name: group_name
volumes: volumes
wells: wells
goal_default:
group_name: ''
volumes:
- 0.0
wells:
- category: ''
children: []
config: ''
data: ''
id: ''
name: ''
parent: ''
pose:
orientation:
w: 1.0
x: 0.0
y: 0.0
z: 0.0
position:
x: 0.0
y: 0.0
z: 0.0
sample_id: ''
type: ''
handles: {}
result: {}
schema:
description: ''
properties:
feedback:
properties: {}
required: []
title: LiquidHandlerSetGroup_Feedback
type: object
goal:
properties:
group_name:
type: string
volumes:
items:
type: number
type: array
wells:
items:
properties:
category:
type: string
children:
items:
type: string
type: array
config:
type: string
data:
type: string
id:
type: string
name:
type: string
parent:
type: string
pose:
properties:
orientation:
properties:
w:
type: number
x:
type: number
y:
type: number
z:
type: number
required:
- x
- y
- z
- w
title: orientation
type: object
position:
properties:
x:
type: number
y:
type: number
z:
type: number
required:
- x
- y
- z
title: position
type: object
required:
- position
- orientation
title: pose
type: object
sample_id:
type: string
type:
type: string
required:
- id
- name
- sample_id
- children
- parent
- type
- category
- pose
- config
- data
title: wells
type: object
type: array
required:
- group_name
- wells
- volumes
title: LiquidHandlerSetGroup_Goal
type: object
result:
properties:
return_info:
type: string
success:
type: boolean
required:
- return_info
- success
title: LiquidHandlerSetGroup_Result
type: object
required:
- goal
title: LiquidHandlerSetGroup
type: object
type: LiquidHandlerSetGroup
set_liquid:
feedback: {}
goal:
@@ -7601,6 +7810,56 @@ liquid_handler.prcxi:
title: Transfer
type: object
type: Transfer
transfer_group:
feedback: {}
goal:
source_group_name: source_group_name
target_group_name: target_group_name
unit_volume: unit_volume
goal_default:
source_group_name: ''
target_group_name: ''
unit_volume: 0.0
handles: {}
result: {}
schema:
description: ''
properties:
feedback:
properties: {}
required: []
title: LiquidHandlerTransferGroup_Feedback
type: object
goal:
properties:
source_group_name:
type: string
target_group_name:
type: string
unit_volume:
type: number
required:
- source_group_name
- target_group_name
- unit_volume
title: LiquidHandlerTransferGroup_Goal
type: object
result:
properties:
return_info:
type: string
success:
type: boolean
required:
- return_info
- success
title: LiquidHandlerTransferGroup_Result
type: object
required:
- goal
title: LiquidHandlerTransferGroup
type: object
type: LiquidHandlerTransferGroup
transfer_liquid:
feedback: {}
goal:

View File

@@ -68,12 +68,12 @@ set(action_files
"action/LiquidHandlerSetTipRack.action"
"action/LiquidHandlerStamp.action"
"action/LiquidHandlerTransfer.action"
"action/LiquidHandlerSetGroup.action"
"action/LiquidHandlerTransferBiomek.action"
"action/LiquidHandlerIncubateBiomek.action"
"action/LiquidHandlerMoveBiomek.action"
"action/LiquidHandlerOscillateBiomek.action"
"action/LiquidHandlerTransferGroup.action"
"action/LiquidHandlerAdd.action"
"action/LiquidHandlerMix.action"
"action/LiquidHandlerMoveTo.action"

View File

@@ -0,0 +1,8 @@
string group_name
Resource[] wells
float64[] volumes
---
string return_info
bool success
---
# 反馈

View File

@@ -0,0 +1,8 @@
string source_group_name
string target_group_name
float64 unit_volume
---
string return_info
bool success
---
# 反馈