diff --git a/.conda/base/recipe.yaml b/.conda/base/recipe.yaml index 3c5bb88..fd20564 100644 --- a/.conda/base/recipe.yaml +++ b/.conda/base/recipe.yaml @@ -46,7 +46,7 @@ requirements: - jinja2 - requests - uvicorn - - opcua + - opcua # [not osx] - pyserial - pandas - pymodbus diff --git a/unilabos/devices/liquid_handling/liquid_handler_abstract.py b/unilabos/devices/liquid_handling/liquid_handler_abstract.py index aa695a0..e084551 100644 --- a/unilabos/devices/liquid_handling/liquid_handler_abstract.py +++ b/unilabos/devices/liquid_handling/liquid_handler_abstract.py @@ -684,12 +684,16 @@ class LiquidHandlerAbstract(LiquidHandlerMiddleware): @classmethod def set_liquid_from_plate( - cls, plate: ResourceSlot, well_names: list[str], liquid_names: list[str], volumes: list[float] + cls, plate: List[ResourceSlot], well_names: list[str], liquid_names: list[str], volumes: list[float] ) -> SetLiquidFromPlateReturn: """Set the liquid in wells of a plate by well names (e.g., A1, A2, B3). 如果 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 对象 wells = [plate.get_well(name) for name in well_names] res_volumes = [] diff --git a/unilabos/devices/liquid_handling/prcxi/prcxi.py b/unilabos/devices/liquid_handling/prcxi/prcxi.py index 4f96255..453f506 100644 --- a/unilabos/devices/liquid_handling/prcxi/prcxi.py +++ b/unilabos/devices/liquid_handling/prcxi/prcxi.py @@ -590,7 +590,7 @@ class PRCXI9300Handler(LiquidHandlerAbstract): return super().set_liquid(wells, liquid_names, volumes) 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: return super().set_liquid_from_plate(plate, well_names, liquid_names, volumes) diff --git a/unilabos/registry/devices/liquid_handler.yaml b/unilabos/registry/devices/liquid_handler.yaml index b2612e7..1cd48f2 100644 --- a/unilabos/registry/devices/liquid_handler.yaml +++ b/unilabos/registry/devices/liquid_handler.yaml @@ -9451,78 +9451,81 @@ liquid_handler.prcxi: type: string type: array plate: - properties: - category: - type: string - children: - items: + items: + properties: + category: 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 + 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: plate + type: object title: plate - type: object + type: array volumes: items: type: number @@ -9544,8 +9547,7 @@ liquid_handler.prcxi: title: Plate type: array volumes: - items: - type: number + items: {} title: Volumes type: array wells: diff --git a/unilabos/ros/nodes/presets/host_node.py b/unilabos/ros/nodes/presets/host_node.py index e95b393..6d5e2b4 100644 --- a/unilabos/ros/nodes/presets/host_node.py +++ b/unilabos/ros/nodes/presets/host_node.py @@ -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: from unilabos.app.communication import get_communication_client from unilabos.app.web.client import HTTPClient, http_client diff --git a/unilabos/test/experiments/prcxi_9320_slim.json b/unilabos/test/experiments/prcxi_9320_slim.json new file mode 100644 index 0000000..85bc90c --- /dev/null +++ b/unilabos/test/experiments/prcxi_9320_slim.json @@ -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": [] +} diff --git a/unilabos/workflow/common.py b/unilabos/workflow/common.py index ad073d9..f4c0ac8 100644 --- a/unilabos/workflow/common.py +++ b/unilabos/workflow/common.py @@ -19,7 +19,9 @@ 第一步: 按 slot 去重创建 create_resource 节点(创建板子) -------------------------------------------------------------------------------- +- 首先创建一个 Group 节点(type="Group", minimized=true),用于包含所有 create_resource 节点 - 遍历所有 reagent,按 slot 去重,为每个唯一的 slot 创建一个板子 +- 所有 create_resource 节点的 parent_uuid 指向 Group 节点,minimized=true - 生成参数: res_id: plate_slot_{slot} device_id: /PRCXI @@ -29,11 +31,13 @@ - 输出端口: labware(用于连接 set_liquid_from_plate) - 控制流: create_resource 之间通过 ready 端口串联 -示例: slot=1, slot=4 -> 创建 2 个 create_resource 节点 +示例: slot=1, slot=4 -> 创建 1 个 Group + 2 个 create_resource 节点 第二步: 为每个 reagent 创建 set_liquid_from_plate 节点(设置液体) -------------------------------------------------------------------------------- +- 首先创建一个 Group 节点(type="Group", minimized=true),用于包含所有 set_liquid_from_plate 节点 - 遍历所有 reagent,为每个试剂创建 set_liquid_from_plate 节点 +- 所有 set_liquid_from_plate 节点的 parent_uuid 指向 Group 节点,minimized=true - 生成参数: plate: [](通过连接传递,来自 create_resource 的 labware) well_names: ["A1", "A3", "A5"](来自 reagent 的 well 数组) @@ -76,6 +80,13 @@ transfer_liquid: 输入: sources -> sources_identifier, targets -> targets_identifier 输出: 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 中定义 @@ -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_DEFAULTS = { "device_id": "/PRCXI", @@ -367,6 +385,21 @@ def build_protocol_graph( "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 节点 res_index = 0 last_create_resource_id = None @@ -383,6 +416,10 @@ def build_protocol_graph( description=f"Create plate on slot {slot}", lab_node_type="Labware", footer="create_resource-host_node", + device_name=DEVICE_NAME_HOST, + type=NODE_TYPE_DEFAULT, + parent_uuid=group_node_id, # 指向 Group 节点 + minimized=True, # 折叠显示 param={ "res_id": res_id, "device_id": CREATE_RESOURCE_DEFAULTS["device_id"], @@ -400,6 +437,21 @@ def build_protocol_graph( last_create_resource_id = node_id # ==================== 第二步:为每个 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 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}", lab_node_type="Reagent", 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={ "plate": [], # 通过连接传递 "well_names": wells, # 孔位名数组,如 ["A1", "A3", "A5"] @@ -544,9 +600,11 @@ def build_protocol_graph( if param_key in params: params[param_key] = [] - # 更新 step 的 param 和 footer + # 更新 step 的 param、footer、device_name 和 type step_copy = step.copy() step_copy["param"] = params + step_copy["device_name"] = DEVICE_NAME_DEFAULT # 动作节点使用默认设备名 + step_copy["type"] = NODE_TYPE_DEFAULT # 节点类型 # 如果有警告,修改 footer 添加警告标记(警告放前面) if warnings: