完成物料位置标定

This commit is contained in:
q434343
2026-04-02 12:44:33 +08:00
parent 9f60e65b6d
commit 9b706236f6
9 changed files with 80 additions and 453 deletions

View File

@@ -1651,7 +1651,7 @@ class LiquidHandlerAbstract(LiquidHandlerMiddleware):
tip = [] tip = []
if pick_up: if pick_up:
tip.append(self._get_next_tip()) tip.append(self._get_next_tip())
await self.pick_up_tips(tip) await self.pick_up_tips(tip,use_channels=use_channels)
blow_out_air_volume_before_vol = 0.0 blow_out_air_volume_before_vol = 0.0
if blow_out_air_volume_before is not None and len(blow_out_air_volume_before) > 0: if blow_out_air_volume_before is not None and len(blow_out_air_volume_before) > 0:
blow_out_air_volume_before_vol = float(blow_out_air_volume_before[0] or 0.0) blow_out_air_volume_before_vol = float(blow_out_air_volume_before[0] or 0.0)

View File

@@ -781,9 +781,9 @@ class PRCXI9300Handler(LiquidHandlerAbstract):
rail_interval=0, rail_interval=0,
x_increase = -0.003636, x_increase = -0.003636,
y_increase = -0.003636, y_increase = -0.003636,
x_offset = 9.2, x_offset = -1.8,
y_offset = -27.98, y_offset = -37.48,
deck_z = 300, deck_z = 309.5,
deck_y = 400, deck_y = 400,
rail_width=27.5, rail_width=27.5,
xy_coupling = -0.0045, xy_coupling = -0.0045,
@@ -798,7 +798,7 @@ class PRCXI9300Handler(LiquidHandlerAbstract):
self.y_offset = y_offset self.y_offset = y_offset
self.xy_coupling = xy_coupling self.xy_coupling = xy_coupling
self.left_2_claw = Coordinate(-130.2, 34, -134) self.left_2_claw = Coordinate(-130.2, 34, -134)
self.right_2_left = Coordinate(22,-1, 8) self.right_2_left = Coordinate(22,-1, 11)
plate_positions = [] plate_positions = []
tablets_info = [] tablets_info = []
@@ -930,7 +930,7 @@ class PRCXI9300Handler(LiquidHandlerAbstract):
matrix_id = str(uuid.uuid4()) matrix_id = str(uuid.uuid4())
matrix_info = { matrix_info = {
"MatrixId": matrix_id, "MatrixId": matrix_id,
"MatrixName": matrix_id, "MatrixName": "matrix_" + str(time.time()),
"WorkTablets": work_tablets + "WorkTablets": work_tablets +
[{"Number": number, "Material": {"uuid": "730067cf07ae43849ddf4034299030e9"}} for number in slot_none], [{"Number": number, "Material": {"uuid": "730067cf07ae43849ddf4034299030e9"}} for number in slot_none],
} }
@@ -1152,6 +1152,12 @@ class PRCXI9300Handler(LiquidHandlerAbstract):
self._first_transfer_done = True self._first_transfer_done = True
if self.step_mode: if self.step_mode:
await self.create_protocol(f"transfer_liquid{time.time()}") await self.create_protocol(f"transfer_liquid{time.time()}")
_asp_list = asp_vols if isinstance(asp_vols, list) else [asp_vols]
_dis_list = dis_vols if isinstance(dis_vols, list) else [dis_vols]
if all(v <= 10.0 for v in _asp_list) and all(v <= 10.0 for v in _dis_list):
use_channels = [1]
res = await super().transfer_liquid( res = await super().transfer_liquid(
sources, sources,
targets, targets,

View File

@@ -489,7 +489,18 @@ class ResourceTreeSet(object):
def resource_plr_inner( def resource_plr_inner(
d: dict, parent_resource: Optional[ResourceDict], states: dict, uuids: list d: dict, parent_resource: Optional[ResourceDict], states: dict, uuids: list
) -> ResourceDictInstance: ) -> ResourceDictInstance:
current_uuid, parent_uuid, extra = uuids.pop(0) if uuids:
current_uuid, parent_uuid, extra = uuids.pop(0)
else:
# serialize() 树比 res.children 树多出了节点(虚拟子节点等),兜底生成 UUID
current_uuid = str(uuid.uuid4())
parent_uuid = parent_resource.get("uuid") if isinstance(parent_resource, dict) else (
getattr(parent_resource, "uuid", None) if parent_resource is not None else None
)
extra = {}
logger.warning(
f"from_plr_resources: UUID 列表耗尽,为节点 '{d.get('name', '?')}' 生成临时 UUID {current_uuid}"
)
raw_pos = ( raw_pos = (
{"x": d["location"]["x"], "y": d["location"]["y"], "z": d["location"]["z"]} {"x": d["location"]["x"], "y": d["location"]["y"], "z": d["location"]["z"]}

View File

@@ -1739,13 +1739,19 @@ class BaseROS2DeviceNode(Node, Generic[T]):
if arg_type == "unilabos.registry.placeholder_type:ResourceSlot": if arg_type == "unilabos.registry.placeholder_type:ResourceSlot":
resource_data = function_args[arg_name] resource_data = function_args[arg_name]
if isinstance(resource_data, dict) and "id" in resource_data: if isinstance(resource_data, dict) and "id" in resource_data:
try: uid = resource_data.get("uuid", "")
function_args[arg_name] = self._convert_resources_sync(resource_data["uuid"])[0] # 优先从本地追踪器直接取(避免服务端未同步导致的空返回)
except Exception as e: local_fast = self.resource_tracker.uuid_to_resources.get(uid) if uid else None
self.lab_logger().error( if local_fast is not None:
f"转换ResourceSlot参数 {arg_name} 失败: {e}\n{traceback.format_exc()}" function_args[arg_name] = local_fast
) else:
raise JsonCommandInitError(f"ResourceSlot参数转换失败: {arg_name}") try:
function_args[arg_name] = self._convert_resources_sync(uid)[0]
except Exception as e:
self.lab_logger().error(
f"转换ResourceSlot参数 {arg_name} 失败: {e}\n{traceback.format_exc()}"
)
raise JsonCommandInitError(f"ResourceSlot参数转换失败: {arg_name}")
# 处理 ResourceSlot 列表 # 处理 ResourceSlot 列表
elif isinstance(arg_type, tuple) and len(arg_type) == 2: elif isinstance(arg_type, tuple) and len(arg_type) == 2:
@@ -1753,14 +1759,23 @@ class BaseROS2DeviceNode(Node, Generic[T]):
if arg_type[0] == "list" and arg_type[1] == resource_slot_type: if arg_type[0] == "list" and arg_type[1] == resource_slot_type:
resource_list = function_args[arg_name] resource_list = function_args[arg_name]
if isinstance(resource_list, list): if isinstance(resource_list, list):
try: uuids = [r["uuid"] for r in resource_list if isinstance(r, dict) and "id" in r]
uuids = [r["uuid"] for r in resource_list if isinstance(r, dict) and "id" in r] # 先尝试本地追踪器批量取
function_args[arg_name] = self._convert_resources_sync(*uuids) if uuids else [] local_hits = [
except Exception as e: self.resource_tracker.uuid_to_resources[u]
self.lab_logger().error( for u in uuids
f"转换ResourceSlot列表参数 {arg_name} 失败: {e}\n{traceback.format_exc()}" if u in self.resource_tracker.uuid_to_resources
) ]
raise JsonCommandInitError(f"ResourceSlot列表参数转换失败: {arg_name}") if len(local_hits) == len(uuids):
function_args[arg_name] = local_hits
else:
try:
function_args[arg_name] = self._convert_resources_sync(*uuids) if uuids else []
except Exception as e:
self.lab_logger().error(
f"转换ResourceSlot列表参数 {arg_name} 失败: {e}\n{traceback.format_exc()}"
)
raise JsonCommandInitError(f"ResourceSlot列表参数转换失败: {arg_name}")
# todo: 默认反报送 # todo: 默认反报送
return function(**function_args) return function(**function_args)
@@ -1812,6 +1827,18 @@ class BaseROS2DeviceNode(Node, Generic[T]):
# 转换为 PLR 资源 # 转换为 PLR 资源
tree_set = ResourceTreeSet.from_raw_dict_list(raw_data) tree_set = ResourceTreeSet.from_raw_dict_list(raw_data)
if not len(tree_set.trees): if not len(tree_set.trees):
# 服务端未找到时尝试从本地追踪器兜底create_resource 刚完成但服务端未及时同步)
local_hits = [
self.resource_tracker.uuid_to_resources[uid]
for uid in uuids_list
if uid in self.resource_tracker.uuid_to_resources
]
if local_hits:
self.lab_logger().warning(
f"资源查询服务端返回空树,已从本地追踪器找到 "
f"{len(local_hits)}/{len(uuids_list)} 个资源: {uuids_list}"
)
return local_hits
raise Exception(f"资源查询返回空树: {raw_data}") raise Exception(f"资源查询返回空树: {raw_data}")
plr_resources = tree_set.to_plr_resources() plr_resources = tree_set.to_plr_resources()

View File

@@ -23,6 +23,7 @@
"port": 9999, "port": 9999,
"debug": false, "debug": false,
"setup": true, "setup": true,
"matrix_id": "1ecb1b45-6aef-456b-bd68-8f538c4e5826",
"timeout": 10, "timeout": 10,
"simulator": false, "simulator": false,
"channel_num": 8 "channel_num": 8

View File

@@ -21,13 +21,14 @@
}, },
"host": "10.20.30.184", "host": "10.20.30.184",
"port": 9999, "port": 9999,
"debug": true, "debug": false,
"setup": true, "setup": false,
"is_9320": true, "is_9320": true,
"timeout": 10, "timeout": 10,
"matrix_id": "5de524d0-3f95-406c-86dd-f83626ebc7cb", "matrix_id": "",
"simulator": true, "simulator": false,
"channel_num": 2 "channel_num": 2,
"step_mode": true
}, },
"data": { "data": {
"reset_ok": true "reset_ok": true
@@ -49,8 +50,8 @@
"type": "deck", "type": "deck",
"class": "", "class": "",
"position": { "position": {
"x": 10, "x": 0,
"y": 10, "y": 0,
"z": 0 "z": 0
}, },
"config": { "config": {
@@ -66,426 +67,7 @@
}, },
"category": "deck", "category": "deck",
"barcode": null, "barcode": null,
"preferred_pickup_location": null, "preferred_pickup_location": null
"sites": [
{
"label": "T1",
"visible": true,
"occupied_by": null,
"position": {
"x": 0,
"y": 288,
"z": 0
},
"size": {
"width": 128.0,
"height": 86,
"depth": 0
},
"content_type": [
"container",
"plate",
"tip_rack",
"plates",
"tip_racks",
"tube_rack",
"adaptor",
"plateadapter",
"module",
"trash"
]
},
{
"label": "T2",
"visible": true,
"occupied_by": null,
"position": {
"x": 138,
"y": 288,
"z": 0
},
"size": {
"width": 128.0,
"height": 86,
"depth": 0
},
"content_type": [
"plate",
"tip_rack",
"plates",
"tip_racks",
"tube_rack",
"adaptor",
"plateadapter",
"module",
"trash"
]
},
{
"label": "T3",
"visible": true,
"occupied_by": null,
"position": {
"x": 276,
"y": 288,
"z": 0
},
"size": {
"width": 128.0,
"height": 86,
"depth": 0
},
"content_type": [
"plate",
"tip_rack",
"plates",
"tip_racks",
"tube_rack",
"adaptor",
"plateadapter",
"module",
"trash"
]
},
{
"label": "T4",
"visible": true,
"occupied_by": null,
"position": {
"x": 414,
"y": 288,
"z": 0
},
"size": {
"width": 128.0,
"height": 86,
"depth": 0
},
"content_type": [
"plate",
"tip_rack",
"plates",
"tip_racks",
"tube_rack",
"adaptor",
"plateadapter",
"module",
"trash"
]
},
{
"label": "T5",
"visible": true,
"occupied_by": null,
"position": {
"x": 0,
"y": 192,
"z": 0
},
"size": {
"width": 128.0,
"height": 86,
"depth": 0
},
"content_type": [
"plate",
"tip_rack",
"plates",
"tip_racks",
"tube_rack",
"adaptor",
"plateadapter",
"module",
"trash"
]
},
{
"label": "T6",
"visible": true,
"occupied_by": null,
"position": {
"x": 138,
"y": 192,
"z": 0
},
"size": {
"width": 128.0,
"height": 86,
"depth": 0
},
"content_type": [
"plate",
"tip_rack",
"plates",
"tip_racks",
"tube_rack",
"adaptor",
"plateadapter",
"module",
"trash"
]
},
{
"label": "T7",
"visible": true,
"occupied_by": null,
"position": {
"x": 276,
"y": 192,
"z": 0
},
"size": {
"width": 128.0,
"height": 86,
"depth": 0
},
"content_type": [
"plate",
"tip_rack",
"plates",
"tip_racks",
"tube_rack",
"adaptor",
"plateadapter",
"module",
"trash"
]
},
{
"label": "T8",
"visible": true,
"occupied_by": null,
"position": {
"x": 414,
"y": 192,
"z": 0
},
"size": {
"width": 128.0,
"height": 86,
"depth": 0
},
"content_type": [
"plate",
"tip_rack",
"plates",
"tip_racks",
"tube_rack",
"adaptor",
"plateadapter",
"module",
"trash"
]
},
{
"label": "T9",
"visible": true,
"occupied_by": null,
"position": {
"x": 0,
"y": 96,
"z": 0
},
"size": {
"width": 128.0,
"height": 86,
"depth": 0
},
"content_type": [
"plate",
"tip_rack",
"plates",
"tip_racks",
"tube_rack",
"adaptor",
"plateadapter",
"module",
"trash"
]
},
{
"label": "T10",
"visible": true,
"occupied_by": null,
"position": {
"x": 138,
"y": 96,
"z": 0
},
"size": {
"width": 128.0,
"height": 86,
"depth": 0
},
"content_type": [
"plate",
"tip_rack",
"plates",
"tip_racks",
"tube_rack",
"adaptor",
"plateadapter",
"module",
"trash"
]
},
{
"label": "T11",
"visible": true,
"occupied_by": null,
"position": {
"x": 276,
"y": 96,
"z": 0
},
"size": {
"width": 128.0,
"height": 86,
"depth": 0
},
"content_type": [
"plate",
"tip_rack",
"plates",
"tip_racks",
"tube_rack",
"adaptor",
"plateadapter",
"module",
"trash"
]
},
{
"label": "T12",
"visible": true,
"occupied_by": null,
"position": {
"x": 414,
"y": 96,
"z": 0
},
"size": {
"width": 128.0,
"height": 86,
"depth": 0
},
"content_type": [
"plate",
"tip_rack",
"plates",
"tip_racks",
"tube_rack",
"adaptor",
"plateadapter",
"module",
"trash"
]
},
{
"label": "T13",
"visible": true,
"occupied_by": null,
"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",
"adaptor",
"plateadapter",
"module",
"trash"
]
},
{
"label": "T14",
"visible": true,
"occupied_by": null,
"position": {
"x": 138,
"y": 0,
"z": 0
},
"size": {
"width": 128.0,
"height": 86,
"depth": 0
},
"content_type": [
"plate",
"tip_rack",
"plates",
"tip_racks",
"tube_rack",
"adaptor",
"plateadapter",
"module",
"trash"
]
},
{
"label": "T15",
"visible": true,
"occupied_by": null,
"position": {
"x": 276,
"y": 0,
"z": 0
},
"size": {
"width": 128.0,
"height": 86,
"depth": 0
},
"content_type": [
"plate",
"tip_rack",
"plates",
"tip_racks",
"tube_rack",
"adaptor",
"plateadapter",
"module",
"trash"
]
},
{
"label": "T16",
"visible": true,
"occupied_by": null,
"position": {
"x": 414,
"y": 0,
"z": 0
},
"size": {
"width": 128.0,
"height": 86,
"depth": 0
},
"content_type": [
"plate",
"tip_rack",
"plates",
"tip_racks",
"tube_rack",
"adaptor",
"plateadapter",
"module",
"trash"
]
}
]
}, },
"data": {} "data": {}
} }

View File

@@ -624,7 +624,7 @@ def build_protocol_graph(
workstation_name: str, workstation_name: str,
action_resource_mapping: Optional[Dict[str, str]] = None, action_resource_mapping: Optional[Dict[str, str]] = None,
labware_defs: Optional[List[Dict[str, Any]]] = None, labware_defs: Optional[List[Dict[str, Any]]] = None,
preserve_tip_rack_incoming_class: bool = True, preserve_tip_rack_incoming_class: bool = False,
) -> WorkflowGraph: ) -> WorkflowGraph:
"""统一的协议图构建函数,根据设备类型自动选择构建逻辑 """统一的协议图构建函数,根据设备类型自动选择构建逻辑

View File

@@ -210,7 +210,7 @@ def convert_from_json(
data: Union[str, PathLike, Dict[str, Any]], data: Union[str, PathLike, Dict[str, Any]],
workstation_name: str = DEFAULT_WORKSTATION, workstation_name: str = DEFAULT_WORKSTATION,
validate: bool = True, validate: bool = True,
preserve_tip_rack_incoming_class: bool = True, preserve_tip_rack_incoming_class: bool = False,
) -> WorkflowGraph: ) -> WorkflowGraph:
""" """
从 JSON 数据或文件转换为 WorkflowGraph 从 JSON 数据或文件转换为 WorkflowGraph
@@ -295,7 +295,7 @@ def convert_from_json(
def convert_json_to_node_link( def convert_json_to_node_link(
data: Union[str, PathLike, Dict[str, Any]], data: Union[str, PathLike, Dict[str, Any]],
workstation_name: str = DEFAULT_WORKSTATION, workstation_name: str = DEFAULT_WORKSTATION,
preserve_tip_rack_incoming_class: bool = True, preserve_tip_rack_incoming_class: bool = False,
) -> Dict[str, Any]: ) -> Dict[str, Any]:
""" """
将 JSON 数据转换为 node-link 格式的字典 将 JSON 数据转换为 node-link 格式的字典

View File

@@ -234,7 +234,7 @@ def convert_from_json(
data: Union[str, PathLike, Dict[str, Any]], data: Union[str, PathLike, Dict[str, Any]],
workstation_name: str = "PRCXi", workstation_name: str = "PRCXi",
validate: bool = True, validate: bool = True,
preserve_tip_rack_incoming_class: bool = True, preserve_tip_rack_incoming_class: bool = False,
) -> WorkflowGraph: ) -> WorkflowGraph:
""" """
从 JSON 数据或文件转换为 WorkflowGraph 从 JSON 数据或文件转换为 WorkflowGraph