mirror of
https://github.com/dptech-corp/Uni-Lab-OS.git
synced 2026-02-04 05:15:10 +00:00
Compare commits
2 Commits
56eb7e2ab4
...
e8d1263488
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e8d1263488 | ||
|
|
380b39100d |
@@ -46,7 +46,7 @@ requirements:
|
|||||||
- jinja2
|
- jinja2
|
||||||
- requests
|
- requests
|
||||||
- uvicorn
|
- uvicorn
|
||||||
- opcua
|
- opcua # [not osx]
|
||||||
- pyserial
|
- pyserial
|
||||||
- pandas
|
- pandas
|
||||||
- pymodbus
|
- pymodbus
|
||||||
|
|||||||
@@ -1,15 +1,11 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import asyncio
|
|
||||||
import time
|
import time
|
||||||
import traceback
|
import traceback
|
||||||
from collections import Counter
|
from collections import Counter
|
||||||
from typing import List, Sequence, Optional, Literal, Union, Iterator, Dict, Any, Callable, Set, cast
|
from typing import List, Sequence, Optional, Literal, Union, Iterator, Dict, Any, Callable, Set, cast
|
||||||
|
|
||||||
from typing_extensions import TypedDict
|
|
||||||
from pylabrobot.liquid_handling import LiquidHandler, LiquidHandlerBackend, LiquidHandlerChatterboxBackend, Strictness
|
from pylabrobot.liquid_handling import LiquidHandler, LiquidHandlerBackend, LiquidHandlerChatterboxBackend, Strictness
|
||||||
from unilabos.devices.liquid_handling.rviz_backend import UniLiquidHandlerRvizBackend
|
|
||||||
from unilabos.devices.liquid_handling.laiyu.backend.laiyu_v_backend import UniLiquidHandlerLaiyuBackend
|
|
||||||
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
|
||||||
from pylabrobot.resources import (
|
from pylabrobot.resources import (
|
||||||
@@ -27,26 +23,33 @@ from pylabrobot.resources import (
|
|||||||
Trash,
|
Trash,
|
||||||
Tip,
|
Tip,
|
||||||
)
|
)
|
||||||
|
from typing_extensions import TypedDict
|
||||||
|
|
||||||
|
from unilabos.devices.liquid_handling.rviz_backend import UniLiquidHandlerRvizBackend
|
||||||
from unilabos.registry.placeholder_type import ResourceSlot
|
from unilabos.registry.placeholder_type import ResourceSlot
|
||||||
from unilabos.ros.nodes.base_device_node import BaseROS2DeviceNode
|
from unilabos.resources.resource_tracker import ResourceTreeSet, ResourceDict
|
||||||
from unilabos.resources.resource_tracker import ResourceTreeSet
|
from unilabos.ros.nodes.base_device_node import BaseROS2DeviceNode, ROS2DeviceNode
|
||||||
|
|
||||||
|
|
||||||
class SimpleReturn(TypedDict):
|
class SimpleReturn(TypedDict):
|
||||||
samples: list
|
samples: List[List[ResourceDict]]
|
||||||
volumes: list
|
volumes: List[float]
|
||||||
|
|
||||||
|
|
||||||
class SetLiquidReturn(TypedDict):
|
class SetLiquidReturn(TypedDict):
|
||||||
wells: list
|
wells: List[List[ResourceDict]]
|
||||||
volumes: list
|
volumes: List[float]
|
||||||
|
|
||||||
|
|
||||||
class SetLiquidFromPlateReturn(TypedDict):
|
class SetLiquidFromPlateReturn(TypedDict):
|
||||||
plate: list
|
plate: List[List[ResourceDict]]
|
||||||
wells: list
|
wells: List[List[ResourceDict]]
|
||||||
volumes: list
|
volumes: List[float]
|
||||||
|
|
||||||
|
|
||||||
|
class TransferLiquidReturn(TypedDict):
|
||||||
|
sources: List[List[ResourceDict]]
|
||||||
|
targets: List[List[ResourceDict]]
|
||||||
|
|
||||||
|
|
||||||
class LiquidHandlerMiddleware(LiquidHandler):
|
class LiquidHandlerMiddleware(LiquidHandler):
|
||||||
@@ -682,14 +685,17 @@ class LiquidHandlerAbstract(LiquidHandlerMiddleware):
|
|||||||
wells=ResourceTreeSet.from_plr_resources(wells, known_newly_created=False).dump(), volumes=res_volumes # type: ignore
|
wells=ResourceTreeSet.from_plr_resources(wells, known_newly_created=False).dump(), volumes=res_volumes # type: ignore
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def set_liquid_from_plate(
|
def set_liquid_from_plate(
|
||||||
cls, plate: ResourceSlot, well_names: list[str], liquid_names: list[str], volumes: list[float]
|
self, plate: List[ResourceSlot], well_names: list[str], liquid_names: list[str], volumes: list[float]
|
||||||
) -> SetLiquidFromPlateReturn:
|
) -> SetLiquidFromPlateReturn:
|
||||||
"""Set the liquid in wells of a plate by well names (e.g., A1, A2, B3).
|
"""Set the liquid in wells of a plate by well names (e.g., A1, A2, B3).
|
||||||
|
|
||||||
如果 liquid_names 和 volumes 为空,但 plate 和 well_names 不为空,直接返回 plate 和 wells。
|
如果 liquid_names 和 volumes 为空,但 plate 和 well_names 不为空,直接返回 plate 和 wells。
|
||||||
"""
|
"""
|
||||||
|
if isinstance(plate, list): # 未来移除
|
||||||
|
plate = plate[0]
|
||||||
|
assert issubclass(plate.__class__, Plate), "plate must be a Plate"
|
||||||
|
plate: Plate = cast(Plate, plate)
|
||||||
# 根据 well_names 获取对应的 Well 对象
|
# 根据 well_names 获取对应的 Well 对象
|
||||||
wells = [plate.get_well(name) for name in well_names]
|
wells = [plate.get_well(name) for name in well_names]
|
||||||
res_volumes = []
|
res_volumes = []
|
||||||
@@ -706,6 +712,14 @@ class LiquidHandlerAbstract(LiquidHandlerMiddleware):
|
|||||||
well.set_liquids([(liquid_name, volume)]) # type: ignore
|
well.set_liquids([(liquid_name, volume)]) # type: ignore
|
||||||
res_volumes.append(volume)
|
res_volumes.append(volume)
|
||||||
|
|
||||||
|
task = ROS2DeviceNode.run_async_func(self._ros_node.update_resource, True, **{"resources": wells})
|
||||||
|
submit_time = time.time()
|
||||||
|
while not task.done():
|
||||||
|
if time.time() - submit_time > 10:
|
||||||
|
self._ros_node.lab_logger().info(f"set_liquid_from_plate {plate} 超时")
|
||||||
|
break
|
||||||
|
time.sleep(0.01)
|
||||||
|
|
||||||
return SetLiquidFromPlateReturn(
|
return SetLiquidFromPlateReturn(
|
||||||
plate=ResourceTreeSet.from_plr_resources([plate], known_newly_created=False).dump(), # type: ignore
|
plate=ResourceTreeSet.from_plr_resources([plate], known_newly_created=False).dump(), # type: ignore
|
||||||
wells=ResourceTreeSet.from_plr_resources(wells, known_newly_created=False).dump(), # type: ignore
|
wells=ResourceTreeSet.from_plr_resources(wells, known_newly_created=False).dump(), # type: ignore
|
||||||
@@ -1111,7 +1125,7 @@ class LiquidHandlerAbstract(LiquidHandlerMiddleware):
|
|||||||
mix_liquid_height: Optional[float] = None,
|
mix_liquid_height: Optional[float] = None,
|
||||||
delays: Optional[List[int]] = None,
|
delays: Optional[List[int]] = None,
|
||||||
none_keys: List[str] = [],
|
none_keys: List[str] = [],
|
||||||
):
|
) -> TransferLiquidReturn:
|
||||||
"""Transfer liquid with automatic mode detection.
|
"""Transfer liquid with automatic mode detection.
|
||||||
|
|
||||||
Supports three transfer modes:
|
Supports three transfer modes:
|
||||||
@@ -1251,6 +1265,11 @@ class LiquidHandlerAbstract(LiquidHandlerMiddleware):
|
|||||||
"Supported modes: 1->N, N->1, or N->N."
|
"Supported modes: 1->N, N->1, or N->N."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
return TransferLiquidReturn(
|
||||||
|
sources=ResourceTreeSet.from_plr_resources(list(sources), known_newly_created=False).dump(), # type: ignore
|
||||||
|
targets=ResourceTreeSet.from_plr_resources(list(targets), known_newly_created=False).dump(), # type: ignore
|
||||||
|
)
|
||||||
|
|
||||||
async def _transfer_one_to_one(
|
async def _transfer_one_to_one(
|
||||||
self,
|
self,
|
||||||
sources: Sequence[Container],
|
sources: Sequence[Container],
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ from unilabos.devices.liquid_handling.liquid_handler_abstract import (
|
|||||||
SimpleReturn,
|
SimpleReturn,
|
||||||
SetLiquidReturn,
|
SetLiquidReturn,
|
||||||
SetLiquidFromPlateReturn,
|
SetLiquidFromPlateReturn,
|
||||||
|
TransferLiquidReturn,
|
||||||
)
|
)
|
||||||
from unilabos.registry.placeholder_type import ResourceSlot
|
from unilabos.registry.placeholder_type import ResourceSlot
|
||||||
from unilabos.ros.nodes.base_device_node import BaseROS2DeviceNode
|
from unilabos.ros.nodes.base_device_node import BaseROS2DeviceNode
|
||||||
@@ -154,25 +155,29 @@ class PRCXI9300Plate(Plate):
|
|||||||
**kwargs,
|
**kwargs,
|
||||||
):
|
):
|
||||||
# 如果 ordered_items 不为 None,直接使用
|
# 如果 ordered_items 不为 None,直接使用
|
||||||
|
items = None
|
||||||
|
ordering_param = None
|
||||||
if ordered_items is not None:
|
if ordered_items is not None:
|
||||||
items = ordered_items
|
items = ordered_items
|
||||||
elif ordering is not None:
|
elif ordering is not None:
|
||||||
# 检查 ordering 中的值是否是字符串(从 JSON 反序列化时的情况)
|
# 检查 ordering 中的值是否是字符串(从 JSON 反序列化时的情况)
|
||||||
# 如果是字符串,说明这是位置名称,需要让 Plate 自己创建 Well 对象
|
# 如果是字符串,说明这是位置名称,需要让 Plate 自己创建 Well 对象
|
||||||
# 我们只传递位置信息(键),不传递值,使用 ordering 参数
|
# 我们只传递位置信息(键),不传递值,使用 ordering 参数
|
||||||
if ordering and isinstance(next(iter(ordering.values()), None), str):
|
if ordering:
|
||||||
# ordering 的值是字符串,只使用键(位置信息)创建新的 OrderedDict
|
values = list(ordering.values())
|
||||||
# 传递 ordering 参数而不是 ordered_items,让 Plate 自己创建 Well 对象
|
value = values[0]
|
||||||
items = None
|
if isinstance(value, str):
|
||||||
# 使用 ordering 参数,只包含位置信息(键)
|
# ordering 的值是字符串,只使用键(位置信息)创建新的 OrderedDict
|
||||||
ordering_param = collections.OrderedDict((k, None) for k in ordering.keys())
|
# 传递 ordering 参数而不是 ordered_items,让 Plate 自己创建 Well 对象
|
||||||
|
items = None
|
||||||
|
# 使用 ordering 参数,只包含位置信息(键)
|
||||||
|
ordering_param = collections.OrderedDict((k, None) for k in ordering.keys())
|
||||||
|
elif value is None:
|
||||||
|
ordering_param = ordering
|
||||||
else:
|
else:
|
||||||
# ordering 的值已经是对象,可以直接使用
|
# ordering 的值已经是对象,可以直接使用
|
||||||
items = ordering
|
items = ordering
|
||||||
ordering_param = None
|
ordering_param = None
|
||||||
else:
|
|
||||||
items = None
|
|
||||||
ordering_param = None
|
|
||||||
|
|
||||||
# 根据情况传递不同的参数
|
# 根据情况传递不同的参数
|
||||||
if items is not None:
|
if items is not None:
|
||||||
@@ -590,7 +595,7 @@ class PRCXI9300Handler(LiquidHandlerAbstract):
|
|||||||
return super().set_liquid(wells, liquid_names, volumes)
|
return super().set_liquid(wells, liquid_names, volumes)
|
||||||
|
|
||||||
def set_liquid_from_plate(
|
def set_liquid_from_plate(
|
||||||
self, plate: ResourceSlot, well_names: list[str], liquid_names: list[str], volumes: list[float]
|
self, plate: List[ResourceSlot], well_names: list[str], liquid_names: list[str], volumes: list[float]
|
||||||
) -> SetLiquidFromPlateReturn:
|
) -> SetLiquidFromPlateReturn:
|
||||||
return super().set_liquid_from_plate(plate, well_names, liquid_names, volumes)
|
return super().set_liquid_from_plate(plate, well_names, liquid_names, volumes)
|
||||||
|
|
||||||
@@ -713,7 +718,7 @@ class PRCXI9300Handler(LiquidHandlerAbstract):
|
|||||||
mix_liquid_height: Optional[float] = None,
|
mix_liquid_height: Optional[float] = None,
|
||||||
delays: Optional[List[int]] = None,
|
delays: Optional[List[int]] = None,
|
||||||
none_keys: List[str] = [],
|
none_keys: List[str] = [],
|
||||||
):
|
) -> TransferLiquidReturn:
|
||||||
return await super().transfer_liquid(
|
return await super().transfer_liquid(
|
||||||
sources,
|
sources,
|
||||||
targets,
|
targets,
|
||||||
|
|||||||
@@ -638,7 +638,7 @@ liquid_handler:
|
|||||||
placeholder_keys: {}
|
placeholder_keys: {}
|
||||||
result: {}
|
result: {}
|
||||||
schema:
|
schema:
|
||||||
description: 吸头迭代函数。用于自动管理和切换吸头架中的吸头,实现批量实验中的吸头自动分配和追踪。该函数监控吸头使用状态,自动切换到下一个可用吸头位置,确保实验流程的连续性。适用于高通量实验、批量处理、自动化流水线等需要大量吸头管理的应用场景。
|
description: 吸头迭代函数。用于自动管理和切换枪头盒中的吸头,实现批量实验中的吸头自动分配和追踪。该函数监控吸头使用状态,自动切换到下一个可用吸头位置,确保实验流程的连续性。适用于高通量实验、批量处理、自动化流水线等需要大量吸头管理的应用场景。
|
||||||
properties:
|
properties:
|
||||||
feedback: {}
|
feedback: {}
|
||||||
goal:
|
goal:
|
||||||
@@ -712,6 +712,43 @@ liquid_handler:
|
|||||||
title: set_group参数
|
title: set_group参数
|
||||||
type: object
|
type: object
|
||||||
type: UniLabJsonCommand
|
type: UniLabJsonCommand
|
||||||
|
auto-set_liquid_from_plate:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
liquid_names: null
|
||||||
|
plate: null
|
||||||
|
volumes: null
|
||||||
|
well_names: null
|
||||||
|
handles: {}
|
||||||
|
placeholder_keys: {}
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: ''
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
liquid_names:
|
||||||
|
type: string
|
||||||
|
plate:
|
||||||
|
type: string
|
||||||
|
volumes:
|
||||||
|
type: string
|
||||||
|
well_names:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- plate
|
||||||
|
- well_names
|
||||||
|
- liquid_names
|
||||||
|
- volumes
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: set_liquid_from_plate参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
auto-set_tiprack:
|
auto-set_tiprack:
|
||||||
feedback: {}
|
feedback: {}
|
||||||
goal: {}
|
goal: {}
|
||||||
@@ -721,7 +758,7 @@ liquid_handler:
|
|||||||
placeholder_keys: {}
|
placeholder_keys: {}
|
||||||
result: {}
|
result: {}
|
||||||
schema:
|
schema:
|
||||||
description: 吸头架设置函数。用于配置和初始化液体处理系统的吸头架信息,包括吸头架位置、类型、容量等参数。该函数建立吸头资源管理系统,为后续的吸头选择和使用提供基础配置。适用于系统初始化、吸头架更换、实验配置等需要吸头资源管理的操作场景。
|
description: 枪头盒设置函数。用于配置和初始化液体处理系统的枪头盒信息,包括枪头盒位置、类型、容量等参数。该函数建立吸头资源管理系统,为后续的吸头选择和使用提供基础配置。适用于系统初始化、枪头盒更换、实验配置等需要吸头资源管理的操作场景。
|
||||||
properties:
|
properties:
|
||||||
feedback: {}
|
feedback: {}
|
||||||
goal:
|
goal:
|
||||||
@@ -4093,32 +4130,32 @@ liquid_handler:
|
|||||||
- 0
|
- 0
|
||||||
handles:
|
handles:
|
||||||
input:
|
input:
|
||||||
- data_key: liquid
|
- data_key: sources
|
||||||
data_source: handle
|
data_source: handle
|
||||||
data_type: resource
|
data_type: resource
|
||||||
handler_key: sources
|
handler_key: sources
|
||||||
label: sources
|
label: 待移动液体
|
||||||
- data_key: liquid
|
- data_key: targets
|
||||||
data_source: executor
|
|
||||||
data_type: resource
|
|
||||||
handler_key: targets
|
|
||||||
label: targets
|
|
||||||
- data_key: liquid
|
|
||||||
data_source: executor
|
|
||||||
data_type: resource
|
|
||||||
handler_key: tip_rack
|
|
||||||
label: tip_rack
|
|
||||||
output:
|
|
||||||
- data_key: liquid
|
|
||||||
data_source: handle
|
data_source: handle
|
||||||
data_type: resource
|
data_type: resource
|
||||||
|
handler_key: targets
|
||||||
|
label: 转移目标
|
||||||
|
- data_key: tip_racks
|
||||||
|
data_source: handle
|
||||||
|
data_type: resource
|
||||||
|
handler_key: tip_rack
|
||||||
|
label: 枪头盒
|
||||||
|
output:
|
||||||
|
- data_key: sources.@flatten
|
||||||
|
data_source: executor
|
||||||
|
data_type: resource
|
||||||
handler_key: sources_out
|
handler_key: sources_out
|
||||||
label: sources
|
label: 移液后源孔
|
||||||
- data_key: liquid
|
- data_key: targets.@flatten
|
||||||
data_source: executor
|
data_source: executor
|
||||||
data_type: resource
|
data_type: resource
|
||||||
handler_key: targets_out
|
handler_key: targets_out
|
||||||
label: targets
|
label: 移液后目标孔
|
||||||
placeholder_keys:
|
placeholder_keys:
|
||||||
sources: unilabos_resources
|
sources: unilabos_resources
|
||||||
targets: unilabos_resources
|
targets: unilabos_resources
|
||||||
@@ -5114,19 +5151,34 @@ liquid_handler.biomek:
|
|||||||
- 0
|
- 0
|
||||||
handles:
|
handles:
|
||||||
input:
|
input:
|
||||||
- data_key: liquid
|
- data_key: sources
|
||||||
data_source: handle
|
data_source: handle
|
||||||
data_type: resource
|
data_type: resource
|
||||||
handler_key: liquid-input
|
handler_key: sources
|
||||||
io_type: target
|
io_type: target
|
||||||
label: Liquid Input
|
label: 待移动液体
|
||||||
|
- data_key: targets
|
||||||
|
data_source: handle
|
||||||
|
data_type: resource
|
||||||
|
handler_key: targets
|
||||||
|
label: 转移目标
|
||||||
|
- data_key: tip_racks
|
||||||
|
data_source: handle
|
||||||
|
data_type: resource
|
||||||
|
handler_key: tip_rack
|
||||||
|
label: 枪头盒
|
||||||
output:
|
output:
|
||||||
- data_key: liquid
|
- data_key: sources.@flatten
|
||||||
data_source: executor
|
data_source: executor
|
||||||
data_type: resource
|
data_type: resource
|
||||||
handler_key: liquid-output
|
handler_key: sources_out
|
||||||
io_type: source
|
io_type: source
|
||||||
label: Liquid Output
|
label: 移液后源孔
|
||||||
|
- data_key: targets.@flatten
|
||||||
|
data_source: executor
|
||||||
|
data_type: resource
|
||||||
|
handler_key: targets_out
|
||||||
|
label: 移液后目标孔
|
||||||
placeholder_keys:
|
placeholder_keys:
|
||||||
sources: unilabos_resources
|
sources: unilabos_resources
|
||||||
targets: unilabos_resources
|
targets: unilabos_resources
|
||||||
@@ -9451,78 +9503,81 @@ liquid_handler.prcxi:
|
|||||||
type: string
|
type: string
|
||||||
type: array
|
type: array
|
||||||
plate:
|
plate:
|
||||||
properties:
|
items:
|
||||||
category:
|
properties:
|
||||||
type: string
|
category:
|
||||||
children:
|
|
||||||
items:
|
|
||||||
type: string
|
type: string
|
||||||
type: array
|
children:
|
||||||
config:
|
items:
|
||||||
type: string
|
type: string
|
||||||
data:
|
type: array
|
||||||
type: string
|
config:
|
||||||
id:
|
type: string
|
||||||
type: string
|
data:
|
||||||
name:
|
type: string
|
||||||
type: string
|
id:
|
||||||
parent:
|
type: string
|
||||||
type: string
|
name:
|
||||||
pose:
|
type: string
|
||||||
properties:
|
parent:
|
||||||
orientation:
|
type: string
|
||||||
properties:
|
pose:
|
||||||
w:
|
properties:
|
||||||
type: number
|
orientation:
|
||||||
x:
|
properties:
|
||||||
type: number
|
w:
|
||||||
y:
|
type: number
|
||||||
type: number
|
x:
|
||||||
z:
|
type: number
|
||||||
type: number
|
y:
|
||||||
required:
|
type: number
|
||||||
- x
|
z:
|
||||||
- y
|
type: number
|
||||||
- z
|
required:
|
||||||
- w
|
- x
|
||||||
title: orientation
|
- y
|
||||||
type: object
|
- z
|
||||||
position:
|
- w
|
||||||
properties:
|
title: orientation
|
||||||
x:
|
type: object
|
||||||
type: number
|
position:
|
||||||
y:
|
properties:
|
||||||
type: number
|
x:
|
||||||
z:
|
type: number
|
||||||
type: number
|
y:
|
||||||
required:
|
type: number
|
||||||
- x
|
z:
|
||||||
- y
|
type: number
|
||||||
- z
|
required:
|
||||||
title: position
|
- x
|
||||||
type: object
|
- y
|
||||||
required:
|
- z
|
||||||
- position
|
title: position
|
||||||
- orientation
|
type: object
|
||||||
title: pose
|
required:
|
||||||
type: object
|
- position
|
||||||
sample_id:
|
- orientation
|
||||||
type: string
|
title: pose
|
||||||
type:
|
type: object
|
||||||
type: string
|
sample_id:
|
||||||
required:
|
type: string
|
||||||
- id
|
type:
|
||||||
- name
|
type: string
|
||||||
- sample_id
|
required:
|
||||||
- children
|
- id
|
||||||
- parent
|
- name
|
||||||
- type
|
- sample_id
|
||||||
- category
|
- children
|
||||||
- pose
|
- parent
|
||||||
- config
|
- type
|
||||||
- data
|
- category
|
||||||
|
- pose
|
||||||
|
- config
|
||||||
|
- data
|
||||||
|
title: plate
|
||||||
|
type: object
|
||||||
title: plate
|
title: plate
|
||||||
type: object
|
type: array
|
||||||
volumes:
|
volumes:
|
||||||
items:
|
items:
|
||||||
type: number
|
type: number
|
||||||
@@ -9544,8 +9599,7 @@ liquid_handler.prcxi:
|
|||||||
title: Plate
|
title: Plate
|
||||||
type: array
|
type: array
|
||||||
volumes:
|
volumes:
|
||||||
items:
|
items: {}
|
||||||
type: number
|
|
||||||
title: Volumes
|
title: Volumes
|
||||||
type: array
|
type: array
|
||||||
wells:
|
wells:
|
||||||
@@ -9922,18 +9976,18 @@ liquid_handler.prcxi:
|
|||||||
data_source: handle
|
data_source: handle
|
||||||
data_type: resource
|
data_type: resource
|
||||||
handler_key: tip_rack_identifier
|
handler_key: tip_rack_identifier
|
||||||
label: 墙头盒
|
label: 枪头盒
|
||||||
output:
|
output:
|
||||||
- data_key: liquid
|
- data_key: sources.@flatten
|
||||||
data_source: handle
|
data_source: executor
|
||||||
data_type: resource
|
data_type: resource
|
||||||
handler_key: sources_out
|
handler_key: sources_out
|
||||||
label: sources
|
label: 移液后源孔
|
||||||
- data_key: liquid
|
- data_key: targets.@flatten
|
||||||
data_source: executor
|
data_source: executor
|
||||||
data_type: resource
|
data_type: resource
|
||||||
handler_key: targets_out
|
handler_key: targets_out
|
||||||
label: targets
|
label: 移液后目标孔
|
||||||
placeholder_keys:
|
placeholder_keys:
|
||||||
sources: unilabos_resources
|
sources: unilabos_resources
|
||||||
targets: unilabos_resources
|
targets: unilabos_resources
|
||||||
|
|||||||
@@ -1581,7 +1581,7 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
|||||||
f"转换ResourceSlot列表参数 {arg_name} 失败: {e}\n{traceback.format_exc()}"
|
f"转换ResourceSlot列表参数 {arg_name} 失败: {e}\n{traceback.format_exc()}"
|
||||||
)
|
)
|
||||||
raise JsonCommandInitError(f"ResourceSlot列表参数转换失败: {arg_name}")
|
raise JsonCommandInitError(f"ResourceSlot列表参数转换失败: {arg_name}")
|
||||||
|
# todo: 默认反报送
|
||||||
return function(**function_args)
|
return function(**function_args)
|
||||||
except KeyError as ex:
|
except KeyError as ex:
|
||||||
raise JsonCommandInitError(
|
raise JsonCommandInitError(
|
||||||
@@ -1614,8 +1614,8 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
|||||||
timeout = 30.0
|
timeout = 30.0
|
||||||
elapsed = 0.0
|
elapsed = 0.0
|
||||||
while not future.done() and elapsed < timeout:
|
while not future.done() and elapsed < timeout:
|
||||||
time.sleep(0.05)
|
time.sleep(0.02)
|
||||||
elapsed += 0.05
|
elapsed += 0.02
|
||||||
|
|
||||||
if not future.done():
|
if not future.done():
|
||||||
raise Exception(f"资源查询超时: {uuids_list}")
|
raise Exception(f"资源查询超时: {uuids_list}")
|
||||||
|
|||||||
@@ -807,7 +807,7 @@ class HostNode(BaseROS2DeviceNode):
|
|||||||
assign_sample_id(action_kwargs)
|
assign_sample_id(action_kwargs)
|
||||||
goal_msg = convert_to_ros_msg(action_client._action_type.Goal(), action_kwargs)
|
goal_msg = convert_to_ros_msg(action_client._action_type.Goal(), action_kwargs)
|
||||||
|
|
||||||
self.lab_logger().info(f"[Host Node] Sending goal for {action_id}: {str(goal_msg)[:1000]}")
|
# self.lab_logger().trace(f"[Host Node] Sending goal for {action_id}: {str(goal_msg)[:1000]}")
|
||||||
self.lab_logger().trace(f"[Host Node] Sending goal for {action_id}: {action_kwargs}")
|
self.lab_logger().trace(f"[Host Node] Sending goal for {action_id}: {action_kwargs}")
|
||||||
self.lab_logger().trace(f"[Host Node] Sending goal for {action_id}: {goal_msg}")
|
self.lab_logger().trace(f"[Host Node] Sending goal for {action_id}: {goal_msg}")
|
||||||
action_client.wait_for_server()
|
action_client.wait_for_server()
|
||||||
@@ -1180,7 +1180,7 @@ class HostNode(BaseROS2DeviceNode):
|
|||||||
"""
|
"""
|
||||||
更新节点信息回调
|
更新节点信息回调
|
||||||
"""
|
"""
|
||||||
# self.lab_logger().info(f"[Host Node] Node info update request received: {request}")
|
self.lab_logger().trace(f"[Host Node] Node info update request received: {request}")
|
||||||
try:
|
try:
|
||||||
from unilabos.app.communication import get_communication_client
|
from unilabos.app.communication import get_communication_client
|
||||||
from unilabos.app.web.client import HTTPClient, http_client
|
from unilabos.app.web.client import HTTPClient, http_client
|
||||||
|
|||||||
795
unilabos/test/experiments/prcxi_9320_slim.json
Normal file
795
unilabos/test/experiments/prcxi_9320_slim.json
Normal file
@@ -0,0 +1,795 @@
|
|||||||
|
{
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"id": "PRCXI",
|
||||||
|
"name": "PRCXI",
|
||||||
|
"type": "device",
|
||||||
|
"class": "liquid_handler.prcxi",
|
||||||
|
"parent": "",
|
||||||
|
"pose": {
|
||||||
|
"size": {
|
||||||
|
"width": 562,
|
||||||
|
"height": 394,
|
||||||
|
"depth": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"axis": "Left",
|
||||||
|
"deck": {
|
||||||
|
"_resource_type": "unilabos.devices.liquid_handling.prcxi.prcxi:PRCXI9300Deck",
|
||||||
|
"_resource_child_name": "PRCXI_Deck"
|
||||||
|
},
|
||||||
|
"host": "10.20.30.184",
|
||||||
|
"port": 9999,
|
||||||
|
"debug": true,
|
||||||
|
"setup": true,
|
||||||
|
"is_9320": true,
|
||||||
|
"timeout": 10,
|
||||||
|
"matrix_id": "5de524d0-3f95-406c-86dd-f83626ebc7cb",
|
||||||
|
"simulator": true,
|
||||||
|
"channel_num": 2
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"reset_ok": true
|
||||||
|
},
|
||||||
|
"schema": {},
|
||||||
|
"description": "",
|
||||||
|
"model": null,
|
||||||
|
"position": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 240,
|
||||||
|
"z": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "PRCXI_Deck",
|
||||||
|
"name": "PRCXI_Deck",
|
||||||
|
|
||||||
|
"children": [],
|
||||||
|
"parent": "PRCXI",
|
||||||
|
"type": "deck",
|
||||||
|
"class": "",
|
||||||
|
"position": {
|
||||||
|
"x": 10,
|
||||||
|
"y": 10,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"type": "PRCXI9300Deck",
|
||||||
|
"size_x": 542,
|
||||||
|
"size_y": 374,
|
||||||
|
"size_z": 0,
|
||||||
|
"rotation": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"z": 0,
|
||||||
|
"type": "Rotation"
|
||||||
|
},
|
||||||
|
"category": "deck",
|
||||||
|
"barcode": null
|
||||||
|
},
|
||||||
|
"data": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "T1",
|
||||||
|
"name": "T1",
|
||||||
|
"children": [],
|
||||||
|
"parent": "PRCXI_Deck",
|
||||||
|
"type": "plate",
|
||||||
|
"class": "",
|
||||||
|
"position": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 288,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"type": "PRCXI9300Container",
|
||||||
|
"size_x": 127,
|
||||||
|
"size_y": 85.5,
|
||||||
|
"size_z": 10,
|
||||||
|
"rotation": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"z": 0,
|
||||||
|
"type": "Rotation"
|
||||||
|
},
|
||||||
|
"category": "plate",
|
||||||
|
"model": null,
|
||||||
|
"barcode": null,
|
||||||
|
"ordering": {},
|
||||||
|
"sites": [
|
||||||
|
{
|
||||||
|
"label": "T1",
|
||||||
|
"visible": true,
|
||||||
|
"position": { "x": 0, "y": 0, "z": 0 },
|
||||||
|
"size": { "width": 128.0, "height": 86, "depth": 0 },
|
||||||
|
"content_type": [
|
||||||
|
"plate",
|
||||||
|
"tip_rack",
|
||||||
|
"plates",
|
||||||
|
"tip_racks",
|
||||||
|
"tube_rack"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"data": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "T2",
|
||||||
|
"name": "T2",
|
||||||
|
"children": [],
|
||||||
|
"parent": "PRCXI_Deck",
|
||||||
|
"type": "plate",
|
||||||
|
"class": "",
|
||||||
|
"position": {
|
||||||
|
"x": 138,
|
||||||
|
"y": 288,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"type": "PRCXI9300Container",
|
||||||
|
"size_x": 127,
|
||||||
|
"size_y": 85.5,
|
||||||
|
"size_z": 10,
|
||||||
|
"rotation": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"z": 0,
|
||||||
|
"type": "Rotation"
|
||||||
|
},
|
||||||
|
"category": "plate",
|
||||||
|
"model": null,
|
||||||
|
"barcode": null,
|
||||||
|
"ordering": {},
|
||||||
|
"sites": [
|
||||||
|
{
|
||||||
|
"label": "T2",
|
||||||
|
"visible": true,
|
||||||
|
"position": { "x": 0, "y": 0, "z": 0 },
|
||||||
|
"size": { "width": 128.0, "height": 86, "depth": 0 },
|
||||||
|
"content_type": [
|
||||||
|
"plate",
|
||||||
|
"tip_rack",
|
||||||
|
"plates",
|
||||||
|
"tip_racks",
|
||||||
|
"tube_rack"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"data": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "T3",
|
||||||
|
"name": "T3",
|
||||||
|
"children": [],
|
||||||
|
"parent": "PRCXI_Deck",
|
||||||
|
"type": "plate",
|
||||||
|
"class": "",
|
||||||
|
"position": {
|
||||||
|
"x": 276,
|
||||||
|
"y": 288,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"type": "PRCXI9300Container",
|
||||||
|
"size_x": 127,
|
||||||
|
"size_y": 85.5,
|
||||||
|
"size_z": 10,
|
||||||
|
"rotation": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"z": 0,
|
||||||
|
"type": "Rotation"
|
||||||
|
},
|
||||||
|
"category": "plate",
|
||||||
|
"model": null,
|
||||||
|
"barcode": null,
|
||||||
|
"ordering": {},
|
||||||
|
"sites": [
|
||||||
|
{
|
||||||
|
"label": "T3",
|
||||||
|
"visible": true,
|
||||||
|
"position": { "x": 0, "y": 0, "z": 0 },
|
||||||
|
"size": { "width": 128.0, "height": 86, "depth": 0 },
|
||||||
|
"content_type": [
|
||||||
|
"plate",
|
||||||
|
"tip_rack",
|
||||||
|
"plates",
|
||||||
|
"tip_racks",
|
||||||
|
"tube_rack"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"data": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "T4",
|
||||||
|
"name": "T4",
|
||||||
|
"children": [],
|
||||||
|
"parent": "PRCXI_Deck",
|
||||||
|
"type": "plate",
|
||||||
|
"class": "",
|
||||||
|
"position": {
|
||||||
|
"x": 414,
|
||||||
|
"y": 288,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"type": "PRCXI9300Container",
|
||||||
|
"size_x": 127,
|
||||||
|
"size_y": 85.5,
|
||||||
|
"size_z": 10,
|
||||||
|
"rotation": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"z": 0,
|
||||||
|
"type": "Rotation"
|
||||||
|
},
|
||||||
|
"category": "plate",
|
||||||
|
"model": null,
|
||||||
|
"barcode": null,
|
||||||
|
"ordering": {},
|
||||||
|
"sites": [
|
||||||
|
{
|
||||||
|
"label": "T4",
|
||||||
|
"visible": true,
|
||||||
|
"position": { "x": 0, "y": 0, "z": 0 },
|
||||||
|
"size": { "width": 128.0, "height": 86, "depth": 0 },
|
||||||
|
"content_type": [
|
||||||
|
"plate",
|
||||||
|
"tip_rack",
|
||||||
|
"plates",
|
||||||
|
"tip_racks",
|
||||||
|
"tube_rack"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"data": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "T5",
|
||||||
|
"name": "T5",
|
||||||
|
"children": [],
|
||||||
|
"parent": "PRCXI_Deck",
|
||||||
|
"type": "plate",
|
||||||
|
"class": "",
|
||||||
|
"position": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 192,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"type": "PRCXI9300Container",
|
||||||
|
"size_x": 127,
|
||||||
|
"size_y": 85.5,
|
||||||
|
"size_z": 10,
|
||||||
|
"rotation": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"z": 0,
|
||||||
|
"type": "Rotation"
|
||||||
|
},
|
||||||
|
"category": "plate",
|
||||||
|
"model": null,
|
||||||
|
"barcode": null,
|
||||||
|
"ordering": {},
|
||||||
|
"sites": [
|
||||||
|
{
|
||||||
|
"label": "T5",
|
||||||
|
"visible": true,
|
||||||
|
"position": { "x": 0, "y": 0, "z": 0 },
|
||||||
|
"size": { "width": 128.0, "height": 86, "depth": 0 },
|
||||||
|
"content_type": [
|
||||||
|
"plate",
|
||||||
|
"tip_rack",
|
||||||
|
"plates",
|
||||||
|
"tip_racks",
|
||||||
|
"tube_rack"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"data": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "T6",
|
||||||
|
"name": "T6",
|
||||||
|
"children": [],
|
||||||
|
"parent": "PRCXI_Deck",
|
||||||
|
"type": "plate",
|
||||||
|
"class": "",
|
||||||
|
"position": {
|
||||||
|
"x": 138,
|
||||||
|
"y": 192,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"type": "PRCXI9300Container",
|
||||||
|
"size_x": 127,
|
||||||
|
"size_y": 85.5,
|
||||||
|
"size_z": 10,
|
||||||
|
"rotation": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"z": 0,
|
||||||
|
"type": "Rotation"
|
||||||
|
},
|
||||||
|
"category": "plate",
|
||||||
|
"model": null,
|
||||||
|
"barcode": null,
|
||||||
|
"ordering": {},
|
||||||
|
"sites": [
|
||||||
|
{
|
||||||
|
"label": "T6",
|
||||||
|
"visible": true,
|
||||||
|
"position": { "x": 0, "y": 0, "z": 0 },
|
||||||
|
"size": { "width": 128.0, "height": 86, "depth": 0 },
|
||||||
|
"content_type": [
|
||||||
|
"plate",
|
||||||
|
"tip_rack",
|
||||||
|
"plates",
|
||||||
|
"tip_racks",
|
||||||
|
"tube_rack"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"data": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "T7",
|
||||||
|
"name": "T7",
|
||||||
|
"children": [],
|
||||||
|
"parent": "PRCXI_Deck",
|
||||||
|
"type": "plate",
|
||||||
|
"class": "",
|
||||||
|
"position": {
|
||||||
|
"x": 276,
|
||||||
|
"y": 192,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"type": "PRCXI9300Container",
|
||||||
|
"size_x": 127,
|
||||||
|
"size_y": 85.5,
|
||||||
|
"size_z": 10,
|
||||||
|
"rotation": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"z": 0,
|
||||||
|
"type": "Rotation"
|
||||||
|
},
|
||||||
|
"category": "plate",
|
||||||
|
"model": null,
|
||||||
|
"barcode": null,
|
||||||
|
"ordering": {},
|
||||||
|
"sites": [
|
||||||
|
{
|
||||||
|
"label": "T7",
|
||||||
|
"visible": true,
|
||||||
|
"position": { "x": 0, "y": 0, "z": 0 },
|
||||||
|
"size": { "width": 128.0, "height": 86, "depth": 0 },
|
||||||
|
"content_type": [
|
||||||
|
"plate",
|
||||||
|
"tip_rack",
|
||||||
|
"plates",
|
||||||
|
"tip_racks",
|
||||||
|
"tube_rack"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"data": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "T8",
|
||||||
|
"name": "T8",
|
||||||
|
"children": [],
|
||||||
|
"parent": "PRCXI_Deck",
|
||||||
|
"type": "plate",
|
||||||
|
"class": "",
|
||||||
|
"position": {
|
||||||
|
"x": 414,
|
||||||
|
"y": 192,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"type": "PRCXI9300Container",
|
||||||
|
"size_x": 127,
|
||||||
|
"size_y": 85.5,
|
||||||
|
"size_z": 10,
|
||||||
|
"rotation": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"z": 0,
|
||||||
|
"type": "Rotation"
|
||||||
|
},
|
||||||
|
"category": "plate",
|
||||||
|
"model": null,
|
||||||
|
"barcode": null,
|
||||||
|
"ordering": {},
|
||||||
|
"sites": [
|
||||||
|
{
|
||||||
|
"label": "T8",
|
||||||
|
"visible": true,
|
||||||
|
"position": { "x": 0, "y": 0, "z": 0 },
|
||||||
|
"size": { "width": 128.0, "height": 86, "depth": 0 },
|
||||||
|
"content_type": [
|
||||||
|
"plate",
|
||||||
|
"tip_rack",
|
||||||
|
"plates",
|
||||||
|
"tip_racks",
|
||||||
|
"tube_rack"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"data": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "T9",
|
||||||
|
"name": "T9",
|
||||||
|
"children": [],
|
||||||
|
"parent": "PRCXI_Deck",
|
||||||
|
"type": "plate",
|
||||||
|
"class": "",
|
||||||
|
"position": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 96,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"type": "PRCXI9300Container",
|
||||||
|
"size_x": 127,
|
||||||
|
"size_y": 85.5,
|
||||||
|
"size_z": 10,
|
||||||
|
"rotation": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"z": 0,
|
||||||
|
"type": "Rotation"
|
||||||
|
},
|
||||||
|
"category": "plate",
|
||||||
|
"model": null,
|
||||||
|
"barcode": null,
|
||||||
|
"ordering": {},
|
||||||
|
"sites": [
|
||||||
|
{
|
||||||
|
"label": "T9",
|
||||||
|
"visible": true,
|
||||||
|
"position": { "x": 0, "y": 0, "z": 0 },
|
||||||
|
"size": { "width": 128.0, "height": 86, "depth": 0 },
|
||||||
|
"content_type": [
|
||||||
|
"plate",
|
||||||
|
"tip_rack",
|
||||||
|
"plates",
|
||||||
|
"tip_racks",
|
||||||
|
"tube_rack"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"data": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "T10",
|
||||||
|
"name": "T10",
|
||||||
|
"children": [],
|
||||||
|
"parent": "PRCXI_Deck",
|
||||||
|
"type": "plate",
|
||||||
|
"class": "",
|
||||||
|
"position": {
|
||||||
|
"x": 138,
|
||||||
|
"y": 96,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"type": "PRCXI9300Container",
|
||||||
|
"size_x": 127,
|
||||||
|
"size_y": 85.5,
|
||||||
|
"size_z": 10,
|
||||||
|
"rotation": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"z": 0,
|
||||||
|
"type": "Rotation"
|
||||||
|
},
|
||||||
|
"category": "plate",
|
||||||
|
"model": null,
|
||||||
|
"barcode": null,
|
||||||
|
"ordering": {},
|
||||||
|
"sites": [
|
||||||
|
{
|
||||||
|
"label": "T10",
|
||||||
|
"visible": true,
|
||||||
|
"position": { "x": 0, "y": 0, "z": 0 },
|
||||||
|
"size": { "width": 128.0, "height": 86, "depth": 0 },
|
||||||
|
"content_type": [
|
||||||
|
"plate",
|
||||||
|
"tip_rack",
|
||||||
|
"plates",
|
||||||
|
"tip_racks",
|
||||||
|
"tube_rack"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"data": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "T11",
|
||||||
|
"name": "T11",
|
||||||
|
"children": [],
|
||||||
|
"parent": "PRCXI_Deck",
|
||||||
|
"type": "plate",
|
||||||
|
"class": "",
|
||||||
|
"position": {
|
||||||
|
"x": 276,
|
||||||
|
"y": 96,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"type": "PRCXI9300Container",
|
||||||
|
"size_x": 127,
|
||||||
|
"size_y": 85.5,
|
||||||
|
"size_z": 10,
|
||||||
|
"rotation": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"z": 0,
|
||||||
|
"type": "Rotation"
|
||||||
|
},
|
||||||
|
"category": "plate",
|
||||||
|
"model": null,
|
||||||
|
"barcode": null,
|
||||||
|
"ordering": {},
|
||||||
|
"sites": [
|
||||||
|
{
|
||||||
|
"label": "T11",
|
||||||
|
"visible": true,
|
||||||
|
"position": { "x": 0, "y": 0, "z": 0 },
|
||||||
|
"size": { "width": 128.0, "height": 86, "depth": 0 },
|
||||||
|
"content_type": [
|
||||||
|
"plate",
|
||||||
|
"tip_rack",
|
||||||
|
"plates",
|
||||||
|
"tip_racks",
|
||||||
|
"tube_rack"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"data": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "T12",
|
||||||
|
"name": "T12",
|
||||||
|
"children": [],
|
||||||
|
"parent": "PRCXI_Deck",
|
||||||
|
"type": "plate",
|
||||||
|
"class": "",
|
||||||
|
"position": {
|
||||||
|
"x": 414,
|
||||||
|
"y": 96,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"type": "PRCXI9300Container",
|
||||||
|
"size_x": 127,
|
||||||
|
"size_y": 85.5,
|
||||||
|
"size_z": 10,
|
||||||
|
"rotation": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"z": 0,
|
||||||
|
"type": "Rotation"
|
||||||
|
},
|
||||||
|
"category": "plate",
|
||||||
|
"model": null,
|
||||||
|
"barcode": null,
|
||||||
|
"ordering": {},
|
||||||
|
"sites": [
|
||||||
|
{
|
||||||
|
"label": "T12",
|
||||||
|
"visible": true,
|
||||||
|
"position": { "x": 0, "y": 0, "z": 0 },
|
||||||
|
"size": { "width": 128.0, "height": 86, "depth": 0 },
|
||||||
|
"content_type": [
|
||||||
|
"plate",
|
||||||
|
"tip_rack",
|
||||||
|
"plates",
|
||||||
|
"tip_racks",
|
||||||
|
"tube_rack"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"data": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "T13",
|
||||||
|
"name": "T13",
|
||||||
|
"children": [],
|
||||||
|
"parent": "PRCXI_Deck",
|
||||||
|
"type": "plate",
|
||||||
|
"class": "",
|
||||||
|
"position": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"type": "PRCXI9300Container",
|
||||||
|
"size_x": 127,
|
||||||
|
"size_y": 85.5,
|
||||||
|
"size_z": 10,
|
||||||
|
"rotation": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"z": 0,
|
||||||
|
"type": "Rotation"
|
||||||
|
},
|
||||||
|
"category": "plate",
|
||||||
|
"model": null,
|
||||||
|
"barcode": null,
|
||||||
|
"ordering": {},
|
||||||
|
"sites": [
|
||||||
|
{
|
||||||
|
"label": "T13",
|
||||||
|
"visible": true,
|
||||||
|
"position": { "x": 0, "y": 0, "z": 0 },
|
||||||
|
"size": { "width": 128.0, "height": 86, "depth": 0 },
|
||||||
|
"content_type": [
|
||||||
|
"plate",
|
||||||
|
"tip_rack",
|
||||||
|
"plates",
|
||||||
|
"tip_racks",
|
||||||
|
"tube_rack"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"data": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "T14",
|
||||||
|
"name": "T14",
|
||||||
|
"children": [],
|
||||||
|
"parent": "PRCXI_Deck",
|
||||||
|
"type": "plate",
|
||||||
|
"class": "",
|
||||||
|
"position": {
|
||||||
|
"x": 138,
|
||||||
|
"y": 0,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"type": "PRCXI9300Container",
|
||||||
|
"size_x": 127,
|
||||||
|
"size_y": 85.5,
|
||||||
|
"size_z": 10,
|
||||||
|
"rotation": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"z": 0,
|
||||||
|
"type": "Rotation"
|
||||||
|
},
|
||||||
|
"category": "plate",
|
||||||
|
"model": null,
|
||||||
|
"barcode": null,
|
||||||
|
"ordering": {},
|
||||||
|
"sites": [
|
||||||
|
{
|
||||||
|
"label": "T14",
|
||||||
|
"visible": true,
|
||||||
|
"position": { "x": 0, "y": 0, "z": 0 },
|
||||||
|
"size": { "width": 128.0, "height": 86, "depth": 0 },
|
||||||
|
"content_type": [
|
||||||
|
"plate",
|
||||||
|
"tip_rack",
|
||||||
|
"plates",
|
||||||
|
"tip_racks",
|
||||||
|
"tube_rack"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"data": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "T15",
|
||||||
|
"name": "T15",
|
||||||
|
"children": [],
|
||||||
|
"parent": "PRCXI_Deck",
|
||||||
|
"type": "plate",
|
||||||
|
"class": "",
|
||||||
|
"position": {
|
||||||
|
"x": 276,
|
||||||
|
"y": 0,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"type": "PRCXI9300Container",
|
||||||
|
"size_x": 127,
|
||||||
|
"size_y": 85.5,
|
||||||
|
"size_z": 10,
|
||||||
|
"rotation": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"z": 0,
|
||||||
|
"type": "Rotation"
|
||||||
|
},
|
||||||
|
"category": "plate",
|
||||||
|
"model": null,
|
||||||
|
"barcode": null,
|
||||||
|
"ordering": {},
|
||||||
|
"sites": [
|
||||||
|
{
|
||||||
|
"label": "T15",
|
||||||
|
"visible": true,
|
||||||
|
"position": { "x": 0, "y": 0, "z": 0 },
|
||||||
|
"size": { "width": 128.0, "height": 86, "depth": 0 },
|
||||||
|
"content_type": [
|
||||||
|
"plate",
|
||||||
|
"tip_rack",
|
||||||
|
"plates",
|
||||||
|
"tip_racks",
|
||||||
|
"tube_rack"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"data": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "T16",
|
||||||
|
"name": "T16",
|
||||||
|
"children": [],
|
||||||
|
"parent": "PRCXI_Deck",
|
||||||
|
"type": "plate",
|
||||||
|
"class": "",
|
||||||
|
"position": {
|
||||||
|
"x": 414,
|
||||||
|
"y": 0,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"type": "PRCXI9300Container",
|
||||||
|
"size_x": 127,
|
||||||
|
"size_y": 85.5,
|
||||||
|
"size_z": 10,
|
||||||
|
"rotation": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"z": 0,
|
||||||
|
"type": "Rotation"
|
||||||
|
},
|
||||||
|
"category": "plate",
|
||||||
|
"model": null,
|
||||||
|
"barcode": null,
|
||||||
|
"ordering": {},
|
||||||
|
"sites": [
|
||||||
|
{
|
||||||
|
"label": "T16",
|
||||||
|
"visible": true,
|
||||||
|
"position": { "x": 0, "y": 0, "z": 0 },
|
||||||
|
"size": { "width": 128.0, "height": 86, "depth": 0 },
|
||||||
|
"content_type": [
|
||||||
|
"plate",
|
||||||
|
"tip_rack",
|
||||||
|
"plates",
|
||||||
|
"tip_racks",
|
||||||
|
"tube_rack"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"data": {}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"edges": []
|
||||||
|
}
|
||||||
@@ -19,7 +19,9 @@
|
|||||||
|
|
||||||
第一步: 按 slot 去重创建 create_resource 节点(创建板子)
|
第一步: 按 slot 去重创建 create_resource 节点(创建板子)
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
- 首先创建一个 Group 节点(type="Group", minimized=true),用于包含所有 create_resource 节点
|
||||||
- 遍历所有 reagent,按 slot 去重,为每个唯一的 slot 创建一个板子
|
- 遍历所有 reagent,按 slot 去重,为每个唯一的 slot 创建一个板子
|
||||||
|
- 所有 create_resource 节点的 parent_uuid 指向 Group 节点,minimized=true
|
||||||
- 生成参数:
|
- 生成参数:
|
||||||
res_id: plate_slot_{slot}
|
res_id: plate_slot_{slot}
|
||||||
device_id: /PRCXI
|
device_id: /PRCXI
|
||||||
@@ -29,11 +31,13 @@
|
|||||||
- 输出端口: labware(用于连接 set_liquid_from_plate)
|
- 输出端口: labware(用于连接 set_liquid_from_plate)
|
||||||
- 控制流: create_resource 之间通过 ready 端口串联
|
- 控制流: create_resource 之间通过 ready 端口串联
|
||||||
|
|
||||||
示例: slot=1, slot=4 -> 创建 2 个 create_resource 节点
|
示例: slot=1, slot=4 -> 创建 1 个 Group + 2 个 create_resource 节点
|
||||||
|
|
||||||
第二步: 为每个 reagent 创建 set_liquid_from_plate 节点(设置液体)
|
第二步: 为每个 reagent 创建 set_liquid_from_plate 节点(设置液体)
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
- 首先创建一个 Group 节点(type="Group", minimized=true),用于包含所有 set_liquid_from_plate 节点
|
||||||
- 遍历所有 reagent,为每个试剂创建 set_liquid_from_plate 节点
|
- 遍历所有 reagent,为每个试剂创建 set_liquid_from_plate 节点
|
||||||
|
- 所有 set_liquid_from_plate 节点的 parent_uuid 指向 Group 节点,minimized=true
|
||||||
- 生成参数:
|
- 生成参数:
|
||||||
plate: [](通过连接传递,来自 create_resource 的 labware)
|
plate: [](通过连接传递,来自 create_resource 的 labware)
|
||||||
well_names: ["A1", "A3", "A5"](来自 reagent 的 well 数组)
|
well_names: ["A1", "A3", "A5"](来自 reagent 的 well 数组)
|
||||||
@@ -76,6 +80,13 @@ transfer_liquid:
|
|||||||
输入: sources -> sources_identifier, targets -> targets_identifier
|
输入: sources -> sources_identifier, targets -> targets_identifier
|
||||||
输出: sources -> sources_out, targets -> targets_out
|
输出: sources -> sources_out, targets -> targets_out
|
||||||
|
|
||||||
|
==================== 设备名配置 (device_name) ====================
|
||||||
|
|
||||||
|
每个节点都有 device_name 字段,指定在哪个设备上执行:
|
||||||
|
- create_resource: device_name = "host_node"(固定)
|
||||||
|
- set_liquid_from_plate: device_name = "PRCXI"(可配置,见 DEVICE_NAME_DEFAULT)
|
||||||
|
- transfer_liquid 等动作: device_name = "PRCXI"(可配置,见 DEVICE_NAME_DEFAULT)
|
||||||
|
|
||||||
==================== 校验规则 ====================
|
==================== 校验规则 ====================
|
||||||
|
|
||||||
- 检查 sources/targets 是否在 reagent 中定义
|
- 检查 sources/targets 是否在 reagent 中定义
|
||||||
@@ -97,6 +108,13 @@ Json = Dict[str, Any]
|
|||||||
|
|
||||||
# ==================== 默认配置 ====================
|
# ==================== 默认配置 ====================
|
||||||
|
|
||||||
|
# 设备名配置
|
||||||
|
DEVICE_NAME_HOST = "host_node" # create_resource 固定在 host_node 上执行
|
||||||
|
DEVICE_NAME_DEFAULT = "PRCXI" # transfer_liquid, set_liquid_from_plate 等动作的默认设备名
|
||||||
|
|
||||||
|
# 节点类型
|
||||||
|
NODE_TYPE_DEFAULT = "ILab" # 所有节点的默认类型
|
||||||
|
|
||||||
# create_resource 节点默认参数
|
# create_resource 节点默认参数
|
||||||
CREATE_RESOURCE_DEFAULTS = {
|
CREATE_RESOURCE_DEFAULTS = {
|
||||||
"device_id": "/PRCXI",
|
"device_id": "/PRCXI",
|
||||||
@@ -367,6 +385,21 @@ def build_protocol_graph(
|
|||||||
"res_id": res_id,
|
"res_id": res_id,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# 创建 Group 节点,包含所有 create_resource 节点
|
||||||
|
group_node_id = str(uuid.uuid4())
|
||||||
|
G.add_node(
|
||||||
|
group_node_id,
|
||||||
|
name="Resources Group",
|
||||||
|
type="Group",
|
||||||
|
parent_uuid="",
|
||||||
|
lab_node_type="Device",
|
||||||
|
template_name="",
|
||||||
|
resource_name="",
|
||||||
|
footer="",
|
||||||
|
minimized=True,
|
||||||
|
param=None,
|
||||||
|
)
|
||||||
|
|
||||||
# 为每个唯一的 slot 创建 create_resource 节点
|
# 为每个唯一的 slot 创建 create_resource 节点
|
||||||
res_index = 0
|
res_index = 0
|
||||||
last_create_resource_id = None
|
last_create_resource_id = None
|
||||||
@@ -383,6 +416,10 @@ def build_protocol_graph(
|
|||||||
description=f"Create plate on slot {slot}",
|
description=f"Create plate on slot {slot}",
|
||||||
lab_node_type="Labware",
|
lab_node_type="Labware",
|
||||||
footer="create_resource-host_node",
|
footer="create_resource-host_node",
|
||||||
|
device_name=DEVICE_NAME_HOST,
|
||||||
|
type=NODE_TYPE_DEFAULT,
|
||||||
|
parent_uuid=group_node_id, # 指向 Group 节点
|
||||||
|
minimized=True, # 折叠显示
|
||||||
param={
|
param={
|
||||||
"res_id": res_id,
|
"res_id": res_id,
|
||||||
"device_id": CREATE_RESOURCE_DEFAULTS["device_id"],
|
"device_id": CREATE_RESOURCE_DEFAULTS["device_id"],
|
||||||
@@ -400,6 +437,21 @@ def build_protocol_graph(
|
|||||||
last_create_resource_id = node_id
|
last_create_resource_id = node_id
|
||||||
|
|
||||||
# ==================== 第二步:为每个 reagent 创建 set_liquid_from_plate 节点 ====================
|
# ==================== 第二步:为每个 reagent 创建 set_liquid_from_plate 节点 ====================
|
||||||
|
# 创建 Group 节点,包含所有 set_liquid_from_plate 节点
|
||||||
|
set_liquid_group_id = str(uuid.uuid4())
|
||||||
|
G.add_node(
|
||||||
|
set_liquid_group_id,
|
||||||
|
name="SetLiquid Group",
|
||||||
|
type="Group",
|
||||||
|
parent_uuid="",
|
||||||
|
lab_node_type="Device",
|
||||||
|
template_name="",
|
||||||
|
resource_name="",
|
||||||
|
footer="",
|
||||||
|
minimized=True,
|
||||||
|
param=None,
|
||||||
|
)
|
||||||
|
|
||||||
set_liquid_index = 0
|
set_liquid_index = 0
|
||||||
last_set_liquid_id = last_create_resource_id # set_liquid_from_plate 连接在 create_resource 之后
|
last_set_liquid_id = last_create_resource_id # set_liquid_from_plate 连接在 create_resource 之后
|
||||||
|
|
||||||
@@ -430,6 +482,10 @@ def build_protocol_graph(
|
|||||||
description=f"Set liquid: {labware_id}",
|
description=f"Set liquid: {labware_id}",
|
||||||
lab_node_type="Reagent",
|
lab_node_type="Reagent",
|
||||||
footer="set_liquid_from_plate-liquid_handler.prcxi",
|
footer="set_liquid_from_plate-liquid_handler.prcxi",
|
||||||
|
device_name=DEVICE_NAME_DEFAULT,
|
||||||
|
type=NODE_TYPE_DEFAULT,
|
||||||
|
parent_uuid=set_liquid_group_id, # 指向 Group 节点
|
||||||
|
minimized=True, # 折叠显示
|
||||||
param={
|
param={
|
||||||
"plate": [], # 通过连接传递
|
"plate": [], # 通过连接传递
|
||||||
"well_names": wells, # 孔位名数组,如 ["A1", "A3", "A5"]
|
"well_names": wells, # 孔位名数组,如 ["A1", "A3", "A5"]
|
||||||
@@ -544,9 +600,11 @@ def build_protocol_graph(
|
|||||||
if param_key in params:
|
if param_key in params:
|
||||||
params[param_key] = []
|
params[param_key] = []
|
||||||
|
|
||||||
# 更新 step 的 param 和 footer
|
# 更新 step 的 param、footer、device_name 和 type
|
||||||
step_copy = step.copy()
|
step_copy = step.copy()
|
||||||
step_copy["param"] = params
|
step_copy["param"] = params
|
||||||
|
step_copy["device_name"] = DEVICE_NAME_DEFAULT # 动作节点使用默认设备名
|
||||||
|
step_copy["type"] = NODE_TYPE_DEFAULT # 节点类型
|
||||||
|
|
||||||
# 如果有警告,修改 footer 添加警告标记(警告放前面)
|
# 如果有警告,修改 footer 添加警告标记(警告放前面)
|
||||||
if warnings:
|
if warnings:
|
||||||
|
|||||||
Reference in New Issue
Block a user