mirror of
https://github.com/dptech-corp/Uni-Lab-OS.git
synced 2026-02-15 04:05:10 +00:00
Compare commits
8 Commits
361eae2f6d
...
58d1cc4720
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
58d1cc4720 | ||
|
|
5676dd6589 | ||
|
|
1ae274a833 | ||
|
|
22b88c8441 | ||
|
|
81bcc1907d | ||
|
|
8cffd3dc21 | ||
|
|
a722636938 | ||
|
|
f68340d932 |
@@ -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": {},
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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"
|
|
||||||
@@ -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,8 +1034,8 @@ 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()
|
||||||
# raise RuntimeError(f"Liquid addition failed: {e}") from e
|
# raise RuntimeError(f"Liquid addition failed: {e}") from e
|
||||||
|
|||||||
@@ -134,6 +134,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,
|
||||||
@@ -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])
|
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
|
plate_2_liquids = handler.set_group("water", plate2.children[:8], [200]*8)
|
||||||
# set_volume_tracking(enabled=True)
|
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",
|
# from pylabrobot.resources.opentrons.tip_racks import tipone_96_tiprack_200ul,opentrons_96_tiprack_10ul
|
||||||
ordering=collections.OrderedDict())
|
# from pylabrobot.resources.opentrons.plates import corning_96_wellplate_360ul_flat, nest_96_wellplate_2ml_deep
|
||||||
tip_rack_serialized = tip_rack.serialize()
|
|
||||||
tip_rack_serialized["parent_name"] = deck.name
|
# def get_well_container(name: str) -> PRCXI9300Container:
|
||||||
tip_racks.update({k: v for k, v in tip_rack_serialized.items() if k not in ["children"]})
|
# well_containers = corning_96_wellplate_360ul_flat(name).serialize()
|
||||||
new_tip_rack: PRCXI9300Container = PRCXI9300Container.deserialize(tip_racks)
|
# plate = PRCXI9300Container(name=name, size_x=50, size_y=50, size_z=10, category="plate",
|
||||||
return new_tip_rack
|
# 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 = get_well_container("HPLCPlateT1")
|
||||||
plate1.load_state({
|
# plate1.load_state({
|
||||||
"Material": {
|
# "Material": {
|
||||||
"uuid": "548bbc3df0d4447586f2c19d2c0c0c55",
|
# "uuid": "548bbc3df0d4447586f2c19d2c0c0c55",
|
||||||
"Code": "HPLC01",
|
# "Code": "HPLC01",
|
||||||
"Name": "HPLC料盘"
|
# "Name": "HPLC料盘"
|
||||||
}
|
# }
|
||||||
})
|
# })
|
||||||
plate2 = get_well_container("PlateT2")
|
# plate2 = get_well_container("PlateT2")
|
||||||
plate2.load_state({
|
# plate2.load_state({
|
||||||
"Material": {
|
# "Material": {
|
||||||
"uuid": "04211a2dc93547fe9bf6121eac533650",
|
# "uuid": "04211a2dc93547fe9bf6121eac533650",
|
||||||
}
|
# }
|
||||||
})
|
# })
|
||||||
plate3 = get_well_container("PlateT3")
|
# plate3 = get_well_container("PlateT3")
|
||||||
plate3.load_state({
|
# plate3.load_state({
|
||||||
"Material": {
|
# "Material": {
|
||||||
"uuid": "04211a2dc93547fe9bf6121eac533650",
|
# "uuid": "04211a2dc93547fe9bf6121eac533650",
|
||||||
}
|
# }
|
||||||
})
|
# })
|
||||||
trash = PRCXI9300Trash(name="trash", size_x=50, size_y=50, size_z=10, category="trash")
|
# trash = PRCXI9300Trash(name="trash", size_x=50, size_y=50, size_z=10, category="trash")
|
||||||
trash.load_state({
|
# trash.load_state({
|
||||||
"Material": {
|
# "Material": {
|
||||||
"uuid": "730067cf07ae43849ddf4034299030e9"
|
# "uuid": "730067cf07ae43849ddf4034299030e9"
|
||||||
}
|
# }
|
||||||
})
|
# })
|
||||||
plate5 = get_well_container("PlateT5")
|
# plate5 = get_well_container("PlateT5")
|
||||||
plate5.load_state({
|
# plate5.load_state({
|
||||||
"Material": {
|
# "Material": {
|
||||||
"uuid": "04211a2dc93547fe9bf6121eac533650",
|
# "uuid": "04211a2dc93547fe9bf6121eac533650",
|
||||||
}
|
# }
|
||||||
})
|
# })
|
||||||
plate6 = get_well_container("PlateT6")
|
# plate6 = get_well_container("PlateT6")
|
||||||
plate6.load_state({
|
# plate6.load_state({
|
||||||
"Material": {
|
# "Material": {
|
||||||
"uuid": "04211a2dc93547fe9bf6121eac533650"
|
# "uuid": "04211a2dc93547fe9bf6121eac533650"
|
||||||
}
|
# }
|
||||||
})
|
# })
|
||||||
plate7 = PRCXI9300Container(name="plateT7", size_x=50, size_y=50, size_z=10, category="plate", ordering=collections.OrderedDict())
|
# plate7 = PRCXI9300Container(name="plateT7", size_x=50, size_y=50, size_z=10, category="plate", ordering=collections.OrderedDict())
|
||||||
plate7.load_state({
|
# plate7.load_state({
|
||||||
"Material": {
|
# "Material": {
|
||||||
"uuid": "04211a2dc93547fe9bf6121eac533650"
|
# "uuid": "04211a2dc93547fe9bf6121eac533650"
|
||||||
}
|
# }
|
||||||
})
|
# })
|
||||||
plate8 = get_tip_rack("RackT8")
|
# plate8 = get_tip_rack("RackT8")
|
||||||
plate8.load_state({
|
# plate8.load_state({
|
||||||
"Material": {
|
# "Material": {
|
||||||
"uuid": "068b3815e36b4a72a59bae017011b29f",
|
# "uuid": "068b3815e36b4a72a59bae017011b29f",
|
||||||
"Code": "ZX-001-10+",
|
# "Code": "ZX-001-10+",
|
||||||
"Name": "10μL加长 Tip头"
|
# "Name": "10μL加长 Tip头"
|
||||||
}
|
# }
|
||||||
})
|
# })
|
||||||
plate9 = get_well_container("PlateT9")
|
# plate9 = get_well_container("PlateT9")
|
||||||
plate9.load_state({
|
# plate9.load_state({
|
||||||
"Material": {
|
# "Material": {
|
||||||
"uuid": "04211a2dc93547fe9bf6121eac533650"
|
# "uuid": "04211a2dc93547fe9bf6121eac533650"
|
||||||
}
|
# }
|
||||||
})
|
# })
|
||||||
plate10 = get_well_container("PlateT10")
|
# plate10 = get_well_container("PlateT10")
|
||||||
plate10.load_state({
|
# plate10.load_state({
|
||||||
"Material": {
|
# "Material": {
|
||||||
"uuid": "04211a2dc93547fe9bf6121eac533650"
|
# "uuid": "04211a2dc93547fe9bf6121eac533650"
|
||||||
}
|
# }
|
||||||
})
|
# })
|
||||||
plate11 = get_well_container("PlateT11")
|
# plate11 = get_well_container("PlateT11")
|
||||||
plate11.load_state({
|
# plate11.load_state({
|
||||||
"Material": {
|
# "Material": {
|
||||||
"uuid": "57b1e4711e9e4a32b529f3132fc5931f",
|
# "uuid": "57b1e4711e9e4a32b529f3132fc5931f",
|
||||||
}
|
# }
|
||||||
})
|
# })
|
||||||
plate12 = get_well_container("PlateT12")
|
# plate12 = get_well_container("PlateT12")
|
||||||
plate12.load_state({
|
# plate12.load_state({
|
||||||
"Material": {
|
# "Material": {
|
||||||
"uuid": "04211a2dc93547fe9bf6121eac533650"
|
# "uuid": "04211a2dc93547fe9bf6121eac533650"
|
||||||
}
|
# }
|
||||||
})
|
# })
|
||||||
plate13 = get_well_container("PlateT13")
|
# plate13 = get_well_container("PlateT13")
|
||||||
plate13.load_state({
|
# plate13.load_state({
|
||||||
"Material": {
|
# "Material": {
|
||||||
"uuid": "04211a2dc93547fe9bf6121eac533650"
|
# "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(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_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(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(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_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_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(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(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_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(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(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_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(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,
|
# handler = PRCXI9300Handler(deck=deck, host="10.181.102.13", port=9999,
|
||||||
timeout=10.0, setup=False, debug=False,
|
# timeout=10.0, setup=False, debug=False,
|
||||||
matrix_id="fd383e6d-2d0e-40b5-9c01-1b2870b1f1b1",
|
# matrix_id="fd383e6d-2d0e-40b5-9c01-1b2870b1f1b1",
|
||||||
channel_num=1, axis="Right") # Initialize the handler with the deck and host settings
|
# 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
|
# handler.set_tiprack([plate8]) # Set the tip rack for the handler
|
||||||
asyncio.run(handler.setup()) # Initialize the handler and setup the connection
|
# asyncio.run(handler.setup()) # Initialize the handler and setup the connection
|
||||||
from pylabrobot.resources import set_volume_tracking
|
# from pylabrobot.resources import set_volume_tracking
|
||||||
# from pylabrobot.resources import set_tip_tracking
|
# # from pylabrobot.resources import set_tip_tracking
|
||||||
set_volume_tracking(enabled=True)
|
# 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)])
|
# A = tree_to_list([resource_plr_to_ulab(deck)])
|
||||||
# with open("deck.json", "w", encoding="utf-8") as f:
|
# # with open("deck.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)
|
||||||
|
|
||||||
print(plate11.get_well(0).tracker.get_used_volume())
|
# 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.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())
|
|
||||||
|
|
||||||
|
# # 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.")
|
||||||
|
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
8
unilabos_msgs/action/LiquidHandlerSetGroup.action
Normal file
8
unilabos_msgs/action/LiquidHandlerSetGroup.action
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
string group_name
|
||||||
|
Resource[] wells
|
||||||
|
float64[] volumes
|
||||||
|
---
|
||||||
|
string return_info
|
||||||
|
bool success
|
||||||
|
---
|
||||||
|
# 反馈
|
||||||
8
unilabos_msgs/action/LiquidHandlerTransferGroup.action
Normal file
8
unilabos_msgs/action/LiquidHandlerTransferGroup.action
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
string source_group_name
|
||||||
|
string target_group_name
|
||||||
|
float64 unit_volume
|
||||||
|
---
|
||||||
|
string return_info
|
||||||
|
bool success
|
||||||
|
---
|
||||||
|
# 反馈
|
||||||
Reference in New Issue
Block a user