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, "timeout": 10.0,
"axis": "Left", "axis": "Left",
"channel_num": 8, "channel_num": 8,
"setup": false, "setup": true,
"debug": false, "debug": true,
"simulator": false, "simulator": true,
"matrix_id": "71593" "matrix_id": "71593"
}, },
"data": {}, "data": {},

View File

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

View File

@@ -142,9 +142,13 @@ class TaskScheduler:
# 执行相应的任务 # 执行相应的任务
should_continue = False should_continue = False
if item.task_type == "query_action_status": 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": 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: else:
logger.warning(f"[TaskScheduler] Unknown task type: {item.task_type}") logger.warning(f"[TaskScheduler] Unknown task type: {item.task_type}")
continue continue
@@ -381,21 +385,21 @@ class TaskScheduler:
# 创建取消事件todo要移动到query_state中 # 创建取消事件todo要移动到query_state中
self.cancel_events[req.job_id] = asyncio.Event() 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: 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) host_node = HostNode.get_instance(0)
if not host_node: if not host_node:
logger.error(f"[TaskScheduler] HostNode instance not available for job_id: {req.job_id}") logger.error(f"[TaskScheduler] HostNode instance not available for job_id: {req.job_id}")
@@ -409,15 +413,9 @@ class TaskScheduler:
except Exception as e: except Exception as e:
logger.error(f"[TaskScheduler] Exception during job start for job_id {req.job_id}: {str(e)}") logger.error(f"[TaskScheduler] Exception during job start for job_id {req.job_id}: {str(e)}")
traceback.print_exc() traceback.print_exc()
# 异常结束先停止callback然后发送失败状态 self.publish_job_status(
await self._stop_job_callback( {}, job_queue_item, "failed", serialize_result_info(traceback.format_exc(), False, {})
req.job_id, "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: except Exception as e:
logger.error(f"[TaskScheduler] Error handling job start: {str(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}") logger.error(f"[TaskScheduler] HostNode not available for cancel goal: {job_id}")
# 停止callback并发送取消状态 # 停止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}") logger.info(f"[TaskScheduler] Stopped job callback and sent cancel status for job_id: {job_id}")
else: else:
logger.warning(f"[TaskScheduler] Job not found in active jobs for cancellation: {job_id}") logger.warning(f"[TaskScheduler] Job not found in active jobs for cancellation: {job_id}")
@@ -511,7 +509,7 @@ class TaskScheduler:
else: else:
logger.debug(f"[TaskScheduler] Action queue not available for job callback: {job_id}") 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定时发送并发送最终结果""" """停止job的callback定时发送并发送最终结果"""
logger.info(f"[TaskScheduler] Stopping job callback for job_id: {job_id} with final status: {final_status}") 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: if job_id not in self.active_jobs:
@@ -557,10 +555,24 @@ class TaskScheduler:
# 给其他同名action至少执行一次的机会 # 给其他同名action至少执行一次的机会
with self.immediate_execution_lock: with self.immediate_execution_lock:
self.immediate_execution_flags[item.device_action_key] = time.time() + 3 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 try:
).result() 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 = { message = {
"action": "job_status", "action": "job_status",
@@ -575,9 +587,21 @@ class TaskScheduler:
"timestamp": time.time(), "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 logger.trace(f"[TaskScheduler] Job status published: {item.job_id} - {status}") # type: ignore
@@ -619,11 +643,11 @@ class WebSocketClient(BaseCommunicationClient):
self.connected = False self.connected = False
# 消息处理 # 消息处理
self.message_queue = asyncio.Queue() if not self.is_disabled else None
self.reconnect_count = 0 self.reconnect_count = 0
# 消息发送锁(解决并发写入问题)- 延迟初始化 # 消息发送队列和处理器
self.send_lock = None self.send_queue = None # 延迟初始化
self.send_queue_task = None # 发送队列处理任务
# 任务调度器 # 任务调度器
self.task_scheduler = None self.task_scheduler = None
@@ -709,8 +733,8 @@ class WebSocketClient(BaseCommunicationClient):
self.event_loop = asyncio.new_event_loop() self.event_loop = asyncio.new_event_loop()
asyncio.set_event_loop(self.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()) 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}") 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: except websockets.exceptions.ConnectionClosed:
logger.warning("[WebSocket] Connection closed") logger.warning("[WebSocket] Connection closed")
@@ -779,6 +815,67 @@ class WebSocketClient(BaseCommunicationClient):
await self.websocket.close() await self.websocket.close()
self.websocket = None 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): async def _message_handler(self):
"""处理接收到的消息""" """处理接收到的消息"""
@@ -831,24 +928,23 @@ class WebSocketClient(BaseCommunicationClient):
# MessageSender接口实现 # MessageSender接口实现
async def send_message(self, message: Dict[str, Any]) -> None: 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") logger.warning("[WebSocket] Not connected, cannot send message")
return return
# 检查是否已初始化(在事件循环启动后才会创建) # 检查发送队列是否已初始化
if not self.send_lock: if not self.send_queue:
logger.warning("[WebSocket] Send lock not initialized, cannot send message safely") logger.warning("[WebSocket] Send queue not initialized, cannot send message")
return return
message_str = json.dumps(message, ensure_ascii=False) try:
# 使用异步锁防止并发写入导致的竞态条件 # 尝试将消息放入队列(非阻塞)
async with self.send_lock: self.send_queue.put_nowait(message)
try: logger.trace(f"[WebSocket] Message queued: {message['action']}") # type: ignore
await self.websocket.send(message_str) except asyncio.QueueFull:
logger.debug(f"[WebSocket] Message sent: {message['action']}") logger.error(f"[WebSocket] Send queue full, dropping message: {message['action']}")
except Exception as e: # 可选:在队列满时采取其他策略,如等待或丢弃旧消息
logger.error(f"[WebSocket] Failed to send message: {str(e)}")
def is_connected(self) -> bool: def is_connected(self) -> bool:
"""检查是否已连接TaskScheduler调用的接口""" """检查是否已连接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}") logger.debug(f"[WebSocket] Device status published: {device_id}.{property_name}")
def publish_job_status( def publish_job_status(
@@ -888,7 +997,20 @@ class WebSocketClient(BaseCommunicationClient):
logger.warning("[WebSocket] Not connected, cannot send ping") logger.warning("[WebSocket] Not connected, cannot send ping")
return return
message = {"action": "ping", "data": {"ping_id": ping_id, "client_timestamp": timestamp}} 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}") logger.debug(f"[WebSocket] Ping sent: {ping_id}")
def cancel_goal(self, job_id: str) -> None: 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(): for name, obj in globals().items():
if isinstance(obj, type) and name.endswith("Config"): if isinstance(obj, type) and name.endswith("Config"):
if hasattr(module, name) and isinstance(getattr(module, name), type): 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认证 # 更新OSS认证
if len(OSSUploadConfig.authorization) == 0: if len(OSSUploadConfig.authorization) == 0:
OSSUploadConfig.authorization = f"Lab {MQConfig.lab_id}" 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(): def _update_config_from_env():
prefix = "UNILABOS_" prefix = "UNILABOS_"
@@ -181,7 +149,7 @@ def _update_config_from_env():
logger.warning(f"[ENV] 解析环境变量 {env_key} 失败: {e}") 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: if config_path:
env_config_path = os.environ.get("UNILABOS_BASICCONFIG_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 return
module = importlib.util.module_from_spec(spec) module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module) # type: ignore 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} 加载成功") logger.info(f"[ENV] 配置文件 {config_path} 加载成功")
_update_config_from_env() _update_config_from_env()
except Exception as e: except Exception as e:
@@ -207,4 +175,4 @@ def load_config(config_path=None, override_labid=None):
exit(1) exit(1)
else: else:
config_path = os.path.join(os.path.dirname(__file__), "local_config.py") 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 from __future__ import annotations
import traceback 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 asyncio
import time import time
import pprint as pp
from pylabrobot.liquid_handling import LiquidHandler, LiquidHandlerBackend, LiquidHandlerChatterboxBackend, Strictness from pylabrobot.liquid_handling import LiquidHandler, LiquidHandlerBackend, LiquidHandlerChatterboxBackend, Strictness
from pylabrobot.liquid_handling.liquid_handler import TipPresenceProbingMethod from pylabrobot.liquid_handling.liquid_handler import TipPresenceProbingMethod
from pylabrobot.liquid_handling.standard import GripDirection from pylabrobot.liquid_handling.standard import GripDirection
@@ -29,6 +29,7 @@ from pylabrobot.resources import (
class LiquidHandlerMiddleware(LiquidHandler): class LiquidHandlerMiddleware(LiquidHandler):
def __init__(self, backend: LiquidHandlerBackend, deck: Deck, simulator: bool = False, channel_num: int = 8): def __init__(self, backend: LiquidHandlerBackend, deck: Deck, simulator: bool = False, channel_num: int = 8):
self._simulator = simulator self._simulator = simulator
self.channel_num = channel_num
if simulator: if simulator:
self._simulate_backend = LiquidHandlerChatterboxBackend(channel_num) self._simulate_backend = LiquidHandlerChatterboxBackend(channel_num)
self._simulate_handler = LiquidHandlerAbstract(self._simulate_backend, deck, False) self._simulate_handler = LiquidHandlerAbstract(self._simulate_backend, deck, False)
@@ -104,8 +105,7 @@ class LiquidHandlerMiddleware(LiquidHandler):
offsets: Optional[List[Coordinate]] = None, offsets: Optional[List[Coordinate]] = None,
**backend_kwargs, **backend_kwargs,
): ):
print('222'*200)
print(tip_spots)
if self._simulator: if self._simulator:
return await self._simulate_handler.pick_up_tips(tip_spots, use_channels, offsets, **backend_kwargs) 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) return await super().pick_up_tips(tip_spots, use_channels, offsets, **backend_kwargs)
@@ -545,6 +545,7 @@ class LiquidHandlerAbstract(LiquidHandlerMiddleware):
deck: Deck to use. deck: Deck to use.
""" """
self._simulator = simulator self._simulator = simulator
self.group_info = dict()
super().__init__(backend, deck, simulator, channel_num) super().__init__(backend, deck, simulator, channel_num)
@classmethod @classmethod
@@ -556,6 +557,51 @@ class LiquidHandlerAbstract(LiquidHandlerMiddleware):
# REMOVE LIQUID -------------------------------------------------- # 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( async def create_protocol(
self, self,
protocol_name: str, protocol_name: str,
@@ -569,6 +615,7 @@ class LiquidHandlerAbstract(LiquidHandlerMiddleware):
"""Create a new protocol with the given metadata.""" """Create a new protocol with the given metadata."""
pass pass
async def remove_liquid( async def remove_liquid(
self, self,
vols: List[float], vols: List[float],
@@ -987,7 +1034,7 @@ class LiquidHandlerAbstract(LiquidHandlerMiddleware):
if delays is not None: if delays is not None:
await self.custom_delay(seconds=delays[1]) await self.custom_delay(seconds=delays[1])
await self.touch_tip(current_targets) 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: # except Exception as e:
# traceback.print_exc() # traceback.print_exc()

View File

@@ -135,6 +135,12 @@ class PRCXI9300Handler(LiquidHandlerAbstract):
def set_liquid(self, wells: list[Well], liquid_names: list[str], volumes: list[float]): def set_liquid(self, wells: list[Well], liquid_names: list[str], volumes: list[float]):
return super().set_liquid(wells, liquid_names, volumes) 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( async def create_protocol(
self, self,
protocol_name: str = "", protocol_name: str = "",
@@ -431,14 +437,14 @@ class PRCXI9300Backend(LiquidHandlerBackend):
async def pick_up_tips(self, ops: List[Pickup], use_channels: List[int] = None): async def pick_up_tips(self, ops: List[Pickup], use_channels: List[int] = None):
"""Pick up tips from the specified resource.""" """Pick up tips from the specified resource."""
print('where?'*200)
plate_indexes = [] plate_indexes = []
for op in ops: for op in ops:
plate = op.resource.parent plate = op.resource.parent
deck = plate.parent deck = plate.parent
plate_index = deck.children.index(plate) plate_index = deck.children.index(plate)
print(f"Plate index: {plate_index}, Plate name: {plate.name}") # print(f"Plate index: {plate_index}, Plate name: {plate.name}")
print(f"Number of children in deck: {len(deck.children)}") # print(f"Number of children in deck: {len(deck.children)}")
plate_indexes.append(plate_index) plate_indexes.append(plate_index)
@@ -452,8 +458,6 @@ class PRCXI9300Backend(LiquidHandlerBackend):
tip_columns.append(tipspot_index // 8) tip_columns.append(tipspot_index // 8)
if len(set(tip_columns)) != 1: if len(set(tip_columns)) != 1:
raise ValueError("All pickups must be from the same tip column. Found different columns: " + str(tip_columns)) 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 PlateNo = plate_indexes[0] + 1
hole_col = tip_columns[0] + 1 hole_col = tip_columns[0] + 1
hole_row = 1 hole_row = 1
@@ -977,153 +981,162 @@ if __name__ == "__main__":
# 4. # 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.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.plates import corning_96_wellplate_360ul_flat, nest_96_wellplate_2ml_deep
# def get_well_container(name: str) -> PRCXI9300Container: def get_well_container(name: str) -> PRCXI9300Container:
# well_containers = corning_96_wellplate_360ul_flat(name).serialize() well_containers = corning_96_wellplate_360ul_flat(name).serialize()
# plate = PRCXI9300Container(name=name, size_x=50, size_y=50, size_z=10, category="plate", plate = PRCXI9300Container(name=name, size_x=50, size_y=50, size_z=10, category="plate",
# ordering=collections.OrderedDict()) ordering=well_containers["ordering"])
# plate_serialized = plate.serialize() plate_serialized = plate.serialize()
# plate_serialized["parent_name"] = deck.name plate_serialized["parent_name"] = deck.name
# well_containers.update({k: v for k, v in plate_serialized.items() if k not in ["children"]}) well_containers.update({k: v for k, v in plate_serialized.items() if k not in ["children"]})
# new_plate: PRCXI9300Container = PRCXI9300Container.deserialize(well_containers) new_plate: PRCXI9300Container = PRCXI9300Container.deserialize(well_containers)
# return new_plate return new_plate
# def get_tip_rack(name: str) -> PRCXI9300Container: def get_tip_rack(name: str) -> PRCXI9300Container:
# tip_racks = opentrons_96_tiprack_300ul("name").serialize() 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", tip_rack = PRCXI9300Container(name=name, size_x=50, size_y=50, size_z=10, category="tip_rack",
# ordering=collections.OrderedDict()) ordering=tip_racks["ordering"])
# tip_rack_serialized = tip_rack.serialize() tip_rack_serialized = tip_rack.serialize()
# tip_rack_serialized["parent_name"] = deck.name 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"]}) 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) new_tip_rack: PRCXI9300Container = PRCXI9300Container.deserialize(tip_racks)
# return new_tip_rack return new_tip_rack
# plate1 = get_tip_rack("RackT1") plate1 = get_tip_rack("RackT1")
# plate1.load_state({ plate1.load_state({
# "Material": { "Material": {
# "uuid": "076250742950465b9d6ea29a225dfb00", "uuid": "076250742950465b9d6ea29a225dfb00",
# "Code": "ZX-001-300", "Code": "ZX-001-300",
# "Name": "300μL Tip头" "Name": "300μL Tip头"
# } }
# }) })
# plate2 = get_well_container("PlateT2") plate2 = get_well_container("PlateT2")
# plate2.load_state({ plate2.load_state({
# "Material": { "Material": {
# "uuid": "57b1e4711e9e4a32b529f3132fc5931f", "uuid": "57b1e4711e9e4a32b529f3132fc5931f",
# "Code": "ZX-019-2.2", "Code": "ZX-019-2.2",
# "Name": "96深孔板" "Name": "96深孔板"
# } }
# }) })
# plate3 = PRCXI9300Trash("trash", size_x=50, size_y=100, size_z=10, category="trash") plate3 = PRCXI9300Trash("trash", size_x=50, size_y=100, size_z=10, category="trash")
# plate3.load_state({ plate3.load_state({
# "Material": { "Material": {
# "uuid": "730067cf07ae43849ddf4034299030e9" "uuid": "730067cf07ae43849ddf4034299030e9"
# } }
# }) })
# plate4 = get_well_container("PlateT4") plate4 = get_well_container("PlateT4")
# plate4.load_state({ plate4.load_state({
# "Material": { "Material": {
# "uuid": "57b1e4711e9e4a32b529f3132fc5931f", "uuid": "57b1e4711e9e4a32b529f3132fc5931f",
# "Code": "ZX-019-2.2", "Code": "ZX-019-2.2",
# "Name": "96深孔板" "Name": "96深孔板"
# } }
# }) })
# plate5 = get_well_container("PlateT5") plate5 = get_well_container("PlateT5")
# plate5.load_state({ plate5.load_state({
# "Material": { "Material": {
# "uuid": "57b1e4711e9e4a32b529f3132fc5931f", "uuid": "57b1e4711e9e4a32b529f3132fc5931f",
# "Code": "ZX-019-2.2", "Code": "ZX-019-2.2",
# "Name": "96深孔板" "Name": "96深孔板"
# } }
# }) })
# plate6 = get_well_container("PlateT6") plate6 = get_well_container("PlateT6")
# plate6.load_state({ plate6.load_state({
# "Material": { "Material": {
# "uuid": "57b1e4711e9e4a32b529f3132fc5931f", "uuid": "57b1e4711e9e4a32b529f3132fc5931f",
# "Code": "ZX-019-2.2", "Code": "ZX-019-2.2",
# "Name": "96深孔板" "Name": "96深孔板"
# } }
# }) })
# deck.assign_child_resource(plate1, 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(plate2, location=Coordinate(0, 0, 0))
# deck.assign_child_resource(plate3, 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(plate4, location=Coordinate(0, 0, 0))
# deck.assign_child_resource(plate5, 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(plate6, location=Coordinate(0, 0, 0))
# # print(plate2)
# plate_2_liquids = [[('water', 500)]]*96 # plate_2_liquids = [[('water', 500)]]*96
# plate2.set_well_liquids(plate_2_liquids) # 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])
# asyncio.run(handler.setup()) # Initialize the handler and setup the connection handler = PRCXI9300Handler(deck=deck, host="10.181.214.132", port=9999,
# from pylabrobot.resources import set_volume_tracking timeout=10.0, setup=False, debug=False,
# # from pylabrobot.resources import set_tip_tracking simulator=True,
# set_volume_tracking(enabled=True) matrix_id="71593",
channel_num=8, axis="Left") # Initialize the handler with the deck and host settings
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 * # from unilabos.resources.graphio import *
# A = tree_to_list([resource_plr_to_ulab(deck)]) # # A = tree_to_list([resource_plr_to_ulab(deck)])
# with open("deck_9300_new.json", "w", encoding="utf-8") as f: # # with open("deck_9300_new.json", "w", encoding="utf-8") as f:
# json.dump(A, f, indent=4, ensure_ascii=False) # # 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.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])) # asyncio.run(handler.pick_up_tips(plate1.children[:8],[0,1,2,3,4,5,6,7]))
# # print(plate1.children[:8]) # print(plate1.children[:8])
# # asyncio.run(handler.aspirate(plate2.children[:8],[50]*8, [0,1,2,3,4,5,6,7])) # asyncio.run(handler.aspirate(plate2.children[:8],[50]*8, [0,1,2,3,4,5,6,7]))
# # print(plate2.children[:8]) # print(plate2.children[:8])
# # asyncio.run(handler.dispense(plate5.children[:8],[50]*8,[0,1,2,3,4,5,6,7])) # asyncio.run(handler.dispense(plate5.children[:8],[50]*8,[0,1,2,3,4,5,6,7]))
# # print(plate5.children[:8]) # 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.drop_tips(tip_rack.children[8:16],[0,1,2,3,4,5,6,7]))
# # asyncio.run(handler.discard_tips()) # asyncio.run(handler.discard_tips([0,1,2,3,4,5,6,7]))
# # asyncio.run(handler.mix(well_containers.children[:8 # 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)) # ], 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 # #print(json.dumps(handler._unilabos_backend.steps_todo_list, indent=2)) # Print matrix info
# # asyncio.run(handler.add_liquid( # asyncio.run(handler.add_liquid(
# # asp_vols=[100]*16, # asp_vols=[100]*16,
# # dis_vols=[100]*16, # dis_vols=[100]*16,
# # reagent_sources=plate2.children[:16], # reagent_sources=plate2.children[:16],
# # targets=plate5.children[:16], # targets=plate5.children[:16],
# # use_channels=[0, 1, 2, 3, 4, 5, 6, 7], # use_channels=[0, 1, 2, 3, 4, 5, 6, 7],
# # flow_rates=[None] * 32, # flow_rates=[None] * 32,
# # offsets=[Coordinate(0, 0, 0)] * 32, # offsets=[Coordinate(0, 0, 0)] * 32,
# # liquid_height=[None] * 16, # liquid_height=[None] * 16,
# # blow_out_air_volume=[None] * 16, # blow_out_air_volume=[None] * 16,
# # delays=None, # delays=None,
# # mix_time=3, # mix_time=3,
# # mix_vol=50, # mix_vol=50,
# # spread="wide", # spread="wide",
# # )) # ))
# # asyncio.run(handler.run_protocol()) # Run the protocol # asyncio.run(handler.run_protocol()) # Run the protocol
# # asyncio.run(handler.remove_liquid( # asyncio.run(handler.remove_liquid(
# # vols=[100]*16, # vols=[100]*16,
# # sources=plate2.children[-16:], # sources=plate2.children[-16:],
# # waste_liquid=plate5.children[:16], # 这个有些奇怪,但是好像也只能这么写 # waste_liquid=plate5.children[:16], # 这个有些奇怪,但是好像也只能这么写
# # use_channels=[0, 1, 2, 3, 4, 5, 6, 7], # use_channels=[0, 1, 2, 3, 4, 5, 6, 7],
# # flow_rates=[None] * 32, # flow_rates=[None] * 32,
# # offsets=[Coordinate(0, 0, 0)] * 32, # offsets=[Coordinate(0, 0, 0)] * 32,
# # liquid_height=[None] * 32, # liquid_height=[None] * 32,
# # blow_out_air_volume=[None] * 32, # blow_out_air_volume=[None] * 32,
# # spread="wide", # 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 # 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 # alkaline = acid[::-1] # Reverse the acid list for alkaline
@@ -1144,258 +1157,264 @@ if __name__ == "__main__":
# spread="wide", # spread="wide",
# )) # ))
# asyncio.run(handler.run_protocol()) # Run the protocol # asyncio.run(handler.run_protocol()) # Run the protocol
# # # input("Running protocol...") # # input("Running protocol...")
# # # input("Press Enter to continue...") # Wait for user input before proceeding # # input("Press Enter to continue...") # Wait for user input before proceeding
# # # print("PRCXI9300Handler initialized with deck and host settings.") # # print("PRCXI9300Handler initialized with deck and host settings.")
# # Example usage
# # 1. 用导出的json给每个T1 T2板子设定相应的物料如果是孔板和枪头盒要对应区分
# # 2. 设计一个单点动作流程,可以跑
# # 3.
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 ### 9320 ###
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: # deck = PRCXI9300Deck(name="PRCXI_Deck", size_x=100, size_y=100, size_z=100)
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") # from pylabrobot.resources.opentrons.tip_racks import tipone_96_tiprack_200ul,opentrons_96_tiprack_10ul
plate1.load_state({ # from pylabrobot.resources.opentrons.plates import corning_96_wellplate_360ul_flat, nest_96_wellplate_2ml_deep
"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()) # 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
deck.assign_child_resource(plate1, location=Coordinate(0, 0, 0)) # def get_tip_rack(name: str) -> PRCXI9300Container:
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)) # tip_racks = opentrons_96_tiprack_10ul("name").serialize()
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)) # tip_rack = PRCXI9300Container(name=name, size_x=50, size_y=50, size_z=10, category="tip_rack",
deck.assign_child_resource(trash, location=Coordinate(0, 0, 0)) # ordering=collections.OrderedDict())
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)) # tip_rack_serialized = tip_rack.serialize()
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)) # tip_rack_serialized["parent_name"] = deck.name
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)) # tip_racks.update({k: v for k, v in tip_rack_serialized.items() if k not in ["children"]})
deck.assign_child_resource(plate8, location=Coordinate(0, 0, 0)) # new_tip_rack: PRCXI9300Container = PRCXI9300Container.deserialize(tip_racks)
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)) # return new_tip_rack
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, # plate1 = get_well_container("HPLCPlateT1")
timeout=10.0, setup=False, debug=False, # plate1.load_state({
matrix_id="fd383e6d-2d0e-40b5-9c01-1b2870b1f1b1", # "Material": {
channel_num=1, axis="Right") # Initialize the handler with the deck and host settings # "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"
# }
# })
handler.set_tiprack([plate8]) # Set the tip rack for the handler # # container_for_nothing = PRCXI9300Container(name="container_for_nothing", size_x=50, size_y=50, size_z=10, category="plate", ordering=collections.OrderedDict())
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 # 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))
from unilabos.resources.graphio import * # 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
A = tree_to_list([resource_plr_to_ulab(deck)]) # handler.set_tiprack([plate8]) # Set the tip rack for the handler
# with open("deck.json", "w", encoding="utf-8") as f: # asyncio.run(handler.setup()) # Initialize the handler and setup the connection
# json.dump(A, f, indent=4, ensure_ascii=False) # from pylabrobot.resources import set_volume_tracking
# # from pylabrobot.resources import set_tip_tracking
# set_volume_tracking(enabled=True)
print(plate11.get_well(0).tracker.get_used_volume()) # 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
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])) # from unilabos.resources.graphio import *
# 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( # A = tree_to_list([resource_plr_to_ulab(deck)])
asp_vols=[10]*7, # # with open("deck.json", "w", encoding="utf-8") as f:
dis_vols=[10]*7, # # json.dump(A, f, indent=4, ensure_ascii=False)
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 # print(plate11.get_well(0).tracker.get_used_volume())
# # asyncio.run(handler.transfer_liquid( # asyncio.run(handler.create_protocol(protocol_name="Test Protocol")) # Initialize the backend and setup the connection
# # 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.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.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)) # asyncio.run(handler.add_liquid(
# #print(json.dumps(handler._unilabos_backend.steps_todo_list, indent=2)) # Print matrix info # 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, # # # asyncio.run(handler.transfer_liquid(
# # offsets=[Coordinate(0, 0, 0)] * 32, # # # asp_vols=[10]*2,
# # liquid_height=[None] * 32, # # # dis_vols=[10]*2,
# # blow_out_air_volume=[None] * 32, # # # sources=plate11.children[:2],
# # spread="wide", # # # targets=plate11.children[-2:],
# # )) # # # use_channels=[0],
# # asyncio.run(handler.transfer_liquid( # # # offsets=[Coordinate(0, 0, 0)] * 4,
# # asp_vols=[100]*16, # # # liquid_height=[None] * 2,
# # dis_vols=[100]*16, # # # blow_out_air_volume=[None] * 2,
# # tip_racks=[tip_rack], # # # delays=None,
# # sources=well_containers.children[-16:], # # # mix_times=3,
# # targets=well_containers.children[:16], # # # mix_vol=5,
# # use_channels=[0, 1, 2, 3, 4, 5, 6, 7], # # # spread="wide",
# # offsets=[Coordinate(0, 0, 0)] * 32, # # # tip_racks=[plate8]
# # asp_flow_rates=[None] * 16, # # # ))
# # dis_flow_rates=[None] * 16,
# # liquid_height=[None] * 32, # # # asyncio.run(handler.remove_liquid(
# # blow_out_air_volume=[None] * 32, # # # vols=[10]*2,
# # mix_times=3, # # # sources=plate11.children[:2],
# # mix_vol=50, # # # waste_liquid=plate11.children[43],
# # spread="wide", # # # use_channels=[0],
# # )) # # # offsets=[Coordinate(0, 0, 0)] * 4,
print(json.dumps(handler._unilabos_backend.steps_todo_list, indent=2)) # Print matrix info # # # liquid_height=[None] * 2,
# # input("pick_up_tips add step") # # # blow_out_air_volume=[None] * 2,
# #asyncio.run(handler.run_protocol()) # Run the protocol # # # delays=None,
# # input("Running protocol...") # # # spread="wide"
# # input("Press Enter to continue...") # Wait for user input before proceeding # # # ))
# # print("PRCXI9300Handler initialized with deck and host settings.") # # 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参数 title: iter_tips参数
type: object type: object
type: UniLabJsonCommand 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: auto-set_tiprack:
feedback: {} feedback: {}
goal: {} goal: {}
@@ -700,6 +732,38 @@ liquid_handler:
title: touch_tip参数 title: touch_tip参数
type: object type: object
type: UniLabJsonCommandAsync 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: discard_tips:
feedback: {} feedback: {}
goal: goal:
@@ -7226,6 +7290,151 @@ liquid_handler.prcxi:
title: LiquidHandlerRemove title: LiquidHandlerRemove
type: object type: object
type: LiquidHandlerRemove 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: set_liquid:
feedback: {} feedback: {}
goal: goal:
@@ -7601,6 +7810,56 @@ liquid_handler.prcxi:
title: Transfer title: Transfer
type: object type: object
type: Transfer 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: transfer_liquid:
feedback: {} feedback: {}
goal: goal:

View File

@@ -68,12 +68,12 @@ set(action_files
"action/LiquidHandlerSetTipRack.action" "action/LiquidHandlerSetTipRack.action"
"action/LiquidHandlerStamp.action" "action/LiquidHandlerStamp.action"
"action/LiquidHandlerTransfer.action" "action/LiquidHandlerTransfer.action"
"action/LiquidHandlerSetGroup.action"
"action/LiquidHandlerTransferBiomek.action" "action/LiquidHandlerTransferBiomek.action"
"action/LiquidHandlerIncubateBiomek.action" "action/LiquidHandlerIncubateBiomek.action"
"action/LiquidHandlerMoveBiomek.action" "action/LiquidHandlerMoveBiomek.action"
"action/LiquidHandlerOscillateBiomek.action" "action/LiquidHandlerOscillateBiomek.action"
"action/LiquidHandlerTransferGroup.action"
"action/LiquidHandlerAdd.action" "action/LiquidHandlerAdd.action"
"action/LiquidHandlerMix.action" "action/LiquidHandlerMix.action"
"action/LiquidHandlerMoveTo.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
---
# 反馈