From dd5a7cab75948443db64491876b03e211f221927 Mon Sep 17 00:00:00 2001 From: Xuwznln <18435084+Xuwznln@users.noreply.github.com> Date: Thu, 5 Jun 2025 16:04:44 +0800 Subject: [PATCH] =?UTF-8?q?=E6=94=AF=E6=8C=81Biomek=E5=88=9B=E5=BB=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- unilabos/devices/liquid_handling/biomek.py | 968 +++++++++++---------- unilabos/ros/nodes/base_device_node.py | 4 +- 2 files changed, 491 insertions(+), 481 deletions(-) diff --git a/unilabos/devices/liquid_handling/biomek.py b/unilabos/devices/liquid_handling/biomek.py index 4f0a3137..35c5f6f7 100644 --- a/unilabos/devices/liquid_handling/biomek.py +++ b/unilabos/devices/liquid_handling/biomek.py @@ -1,7 +1,8 @@ import requests from typing import List, Sequence, Optional, Union, Literal from geometry_msgs.msg import Point -#from unilabos_msgs.msg import Resource +from pylabrobot.liquid_handling import LiquidHandler +from unilabos_msgs.msg import Resource from pylabrobot.resources import ( TipRack, @@ -22,8 +23,8 @@ class LiquidHandlerBiomek(LiquidHandlerAbstract): 该类用于处理Biomek液体处理器的特定操作。 """ - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) + def __init__(self, backend=None, deck=None, *args, **kwargs): + super().__init__(backend, deck, *args, **kwargs) self._status = "Idle" # 初始状态为 Idle self._success = False # 初始成功状态为 False self._status_queue = kwargs.get("status_queue", None) # 状态队列 @@ -71,6 +72,10 @@ class LiquidHandlerBiomek(LiquidHandlerAbstract): } } + @classmethod + def deserialize(cls, data: dict, allow_marshal: bool = False) -> LiquidHandler: + return LiquidHandler.deserialize(data, allow_marshal) + def create_protocol( self, @@ -259,7 +264,7 @@ class LiquidHandlerBiomek(LiquidHandlerAbstract): } transfer_params["items"] = items - transfer_params["Solvent"] = solvent if solvent else "Water" + transfer_params["Solvent"] = "Water" TipLocation = tip_racks[0].name transfer_params["TipLocation"] = TipLocation @@ -340,495 +345,498 @@ class LiquidHandlerBiomek(LiquidHandlerAbstract): return -steps_info = ''' -{ - "steps": [ + +if __name__ == "__main__": + + steps_info = ''' { - "step_number": 1, - "operation": "transfer", - "description": "转移PCR产物或酶促反应液至0.05ml 96孔板中", - "parameters": { - "source": "P1", - "target": "P11", - "tip_rack": "BC230", - "volume": 50 - } - }, - { - "step_number": 2, - "operation": "transfer", - "description": "加入2倍体积Bind Beads BC至产物中", - "parameters": { - "source": "P2", - "target": "P11", - "tip_rack": "BC230", - "volume": 100 - } - }, - { - "step_number": 3, - "operation": "move_labware", - "description": "移动P11至Orbital1用于振荡混匀", - "parameters": { - "source": "P11", - "target": "Orbital1" - } - }, - { - "step_number": 4, - "operation": "oscillation", - "description": "在Orbital1上振荡混匀Bind Beads BC与PCR产物(700-900rpm,300秒)", - "parameters": { - "rpm": 800, - "time": 300 - } - }, - { - "step_number": 5, - "operation": "move_labware", - "description": "移动混匀后的板回P11", - "parameters": { - "source": "Orbital1", - "target": "P11" - } - }, - { - "step_number": 6, - "operation": "move_labware", - "description": "将P11移动到磁力架(P12)吸附3分钟", - "parameters": { - "source": "P11", - "target": "P12" - } - }, - { - "step_number": 7, - "operation": "incubation", - "description": "磁力架上室温静置3分钟完成吸附", - "parameters": { - "time": 180 - } - }, - { - "step_number": 8, - "operation": "transfer", - "description": "去除上清液至废液槽", - "parameters": { - "source": "P12", - "target": "P22", - "tip_rack": "BC230", - "volume": 150 - } - }, - { - "step_number": 9, - "operation": "transfer", - "description": "加入300-500μl 75%乙醇清洗", - "parameters": { - "source": "P3", - "target": "P12", - "tip_rack": "BC230", - "volume": 400 - } - }, - { - "step_number": 10, - "operation": "move_labware", - "description": "移动清洗板到Orbital1进行振荡", - "parameters": { - "source": "P12", - "target": "Orbital1" - } - }, - { - "step_number": 11, - "operation": "oscillation", - "description": "乙醇清洗液振荡混匀(700-900rpm, 45秒)", - "parameters": { - "rpm": 800, - "time": 45 - } - }, - { - "step_number": 12, - "operation": "move_labware", - "description": "振荡后将板移回磁力架P12吸附", - "parameters": { - "source": "Orbital1", - "target": "P12" - } - }, - { - "step_number": 13, - "operation": "incubation", - "description": "吸附3分钟", - "parameters": { - "time": 180 - } - }, - { - "step_number": 14, - "operation": "transfer", - "description": "去除乙醇上清液至废液槽", - "parameters": { - "source": "P12", - "target": "P22", - "tip_rack": "BC230", - "volume": 400 - } - }, - { - "step_number": 15, - "operation": "transfer", - "description": "第二次加入300-500μl 75%乙醇清洗", - "parameters": { - "source": "P3", - "target": "P12", - "tip_rack": "BC230", - "volume": 400 - } - }, - { - "step_number": 16, - "operation": "move_labware", - "description": "再次移动清洗板到Orbital1振荡", - "parameters": { - "source": "P12", - "target": "Orbital1" - } - }, - { - "step_number": 17, - "operation": "oscillation", - "description": "再次乙醇清洗液振荡混匀(700-900rpm, 45秒)", - "parameters": { - "rpm": 800, - "time": 45 - } - }, - { - "step_number": 18, - "operation": "move_labware", - "description": "振荡后板送回磁力架P12吸附", - "parameters": { - "source": "Orbital1", - "target": "P12" - } - }, - { - "step_number": 19, - "operation": "incubation", - "description": "再次吸附3分钟", - "parameters": { - "time": 180 - } - }, - { - "step_number": 20, - "operation": "transfer", - "description": "去除乙醇上清液至废液槽", - "parameters": { - "source": "P12", - "target": "P22", - "tip_rack": "BC230", - "volume": 400 - } - }, - { - "step_number": 21, - "operation": "incubation", - "description": "空气干燥15分钟", - "parameters": { - "time": 900 - } - }, - { - "step_number": 22, - "operation": "transfer", - "description": "加30-50μl Elution Buffer洗脱", - "parameters": { - "source": "P4", - "target": "P12", - "tip_rack": "BC230", - "volume": 40 - } - }, - { - "step_number": 23, - "operation": "move_labware", - "description": "移动到Orbital1振荡混匀(60秒)", - "parameters": { - "source": "P12", - "target": "Orbital1" - } - }, - { - "step_number": 24, - "operation": "oscillation", - "description": "Elution Buffer振荡混匀(700-900rpm, 60秒)", - "parameters": { - "rpm": 800, - "time": 60 - } - }, - { - "step_number": 25, - "operation": "move_labware", - "description": "振荡后送回磁力架P12", - "parameters": { - "source": "Orbital1", - "target": "P12" - } - }, - { - "step_number": 26, - "operation": "incubation", - "description": "室温静置3分钟(洗脱反应)", - "parameters": { - "time": 180 - } - }, - { - "step_number": 27, - "operation": "transfer", - "description": "将上清液(DNA)转移到新板(P13)", - "parameters": { - "source": "P12", - "target": "P13", - "tip_rack": "BC230", - "volume": 40 - } + "steps": [ + { + "step_number": 1, + "operation": "transfer", + "description": "转移PCR产物或酶促反应液至0.05ml 96孔板中", + "parameters": { + "source": "P1", + "target": "P11", + "tip_rack": "BC230", + "volume": 50 + } + }, + { + "step_number": 2, + "operation": "transfer", + "description": "加入2倍体积Bind Beads BC至产物中", + "parameters": { + "source": "P2", + "target": "P11", + "tip_rack": "BC230", + "volume": 100 + } + }, + { + "step_number": 3, + "operation": "move_labware", + "description": "移动P11至Orbital1用于振荡混匀", + "parameters": { + "source": "P11", + "target": "Orbital1" + } + }, + { + "step_number": 4, + "operation": "oscillation", + "description": "在Orbital1上振荡混匀Bind Beads BC与PCR产物(700-900rpm,300秒)", + "parameters": { + "rpm": 800, + "time": 300 + } + }, + { + "step_number": 5, + "operation": "move_labware", + "description": "移动混匀后的板回P11", + "parameters": { + "source": "Orbital1", + "target": "P11" + } + }, + { + "step_number": 6, + "operation": "move_labware", + "description": "将P11移动到磁力架(P12)吸附3分钟", + "parameters": { + "source": "P11", + "target": "P12" + } + }, + { + "step_number": 7, + "operation": "incubation", + "description": "磁力架上室温静置3分钟完成吸附", + "parameters": { + "time": 180 + } + }, + { + "step_number": 8, + "operation": "transfer", + "description": "去除上清液至废液槽", + "parameters": { + "source": "P12", + "target": "P22", + "tip_rack": "BC230", + "volume": 150 + } + }, + { + "step_number": 9, + "operation": "transfer", + "description": "加入300-500μl 75%乙醇清洗", + "parameters": { + "source": "P3", + "target": "P12", + "tip_rack": "BC230", + "volume": 400 + } + }, + { + "step_number": 10, + "operation": "move_labware", + "description": "移动清洗板到Orbital1进行振荡", + "parameters": { + "source": "P12", + "target": "Orbital1" + } + }, + { + "step_number": 11, + "operation": "oscillation", + "description": "乙醇清洗液振荡混匀(700-900rpm, 45秒)", + "parameters": { + "rpm": 800, + "time": 45 + } + }, + { + "step_number": 12, + "operation": "move_labware", + "description": "振荡后将板移回磁力架P12吸附", + "parameters": { + "source": "Orbital1", + "target": "P12" + } + }, + { + "step_number": 13, + "operation": "incubation", + "description": "吸附3分钟", + "parameters": { + "time": 180 + } + }, + { + "step_number": 14, + "operation": "transfer", + "description": "去除乙醇上清液至废液槽", + "parameters": { + "source": "P12", + "target": "P22", + "tip_rack": "BC230", + "volume": 400 + } + }, + { + "step_number": 15, + "operation": "transfer", + "description": "第二次加入300-500μl 75%乙醇清洗", + "parameters": { + "source": "P3", + "target": "P12", + "tip_rack": "BC230", + "volume": 400 + } + }, + { + "step_number": 16, + "operation": "move_labware", + "description": "再次移动清洗板到Orbital1振荡", + "parameters": { + "source": "P12", + "target": "Orbital1" + } + }, + { + "step_number": 17, + "operation": "oscillation", + "description": "再次乙醇清洗液振荡混匀(700-900rpm, 45秒)", + "parameters": { + "rpm": 800, + "time": 45 + } + }, + { + "step_number": 18, + "operation": "move_labware", + "description": "振荡后板送回磁力架P12吸附", + "parameters": { + "source": "Orbital1", + "target": "P12" + } + }, + { + "step_number": 19, + "operation": "incubation", + "description": "再次吸附3分钟", + "parameters": { + "time": 180 + } + }, + { + "step_number": 20, + "operation": "transfer", + "description": "去除乙醇上清液至废液槽", + "parameters": { + "source": "P12", + "target": "P22", + "tip_rack": "BC230", + "volume": 400 + } + }, + { + "step_number": 21, + "operation": "incubation", + "description": "空气干燥15分钟", + "parameters": { + "time": 900 + } + }, + { + "step_number": 22, + "operation": "transfer", + "description": "加30-50μl Elution Buffer洗脱", + "parameters": { + "source": "P4", + "target": "P12", + "tip_rack": "BC230", + "volume": 40 + } + }, + { + "step_number": 23, + "operation": "move_labware", + "description": "移动到Orbital1振荡混匀(60秒)", + "parameters": { + "source": "P12", + "target": "Orbital1" + } + }, + { + "step_number": 24, + "operation": "oscillation", + "description": "Elution Buffer振荡混匀(700-900rpm, 60秒)", + "parameters": { + "rpm": 800, + "time": 60 + } + }, + { + "step_number": 25, + "operation": "move_labware", + "description": "振荡后送回磁力架P12", + "parameters": { + "source": "Orbital1", + "target": "P12" + } + }, + { + "step_number": 26, + "operation": "incubation", + "description": "室温静置3分钟(洗脱反应)", + "parameters": { + "time": 180 + } + }, + { + "step_number": 27, + "operation": "transfer", + "description": "将上清液(DNA)转移到新板(P13)", + "parameters": { + "source": "P12", + "target": "P13", + "tip_rack": "BC230", + "volume": 40 + } + } + ] } - ] -} -''' + ''' -labware_with_liquid = ''' -[ { - "id": "stock plate on P1", - "parent": "deck", - "slot_on_deck": "P1", - "class_name": "nest_12_reservoir_15ml", - "liquid_type": [ - "master_mix" - ], - "liquid_volume": [10000], - "liquid_input_wells": [ - "A1" - ] - }, - { - "id": "Tip Rack BC230 TL2", - "parent": "deck", - "slot_on_deck": "TL2", - "class_name": "BC230", - "liquid_type": [], - "liquid_volume": [], - "liquid_input_wells": [ - ] - }, + labware_with_liquid = ''' + [ { + "id": "stock plate on P1", + "parent": "deck", + "slot_on_deck": "P1", + "class_name": "nest_12_reservoir_15ml", + "liquid_type": [ + "master_mix" + ], + "liquid_volume": [10000], + "liquid_input_wells": [ + "A1" + ] + }, { - "id": "Tip Rack BC230 on TL3", - "parent": "deck", - "slot_on_deck": "TL3", - "class_name": "BC230", - "liquid_type": [], - "liquid_volume": [], - "liquid_input_wells": [ - ] - }, - { - "id": "Tip Rack BC230 on TL4", - "parent": "deck", - "slot_on_deck": "TL4", - "class_name": "BC230", - "liquid_type": [], - "liquid_volume": [], - "liquid_input_wells": [ - ] - }, - { - "id": "Tip Rack BC230 on TL5", - "parent": "deck", - "slot_on_deck": "TL5", - "class_name": "BC230", - "liquid_type": [], - "liquid_volume": [], - "liquid_input_wells": [ - ] - }, + "id": "Tip Rack BC230 TL2", + "parent": "deck", + "slot_on_deck": "TL2", + "class_name": "BC230", + "liquid_type": [], + "liquid_volume": [], + "liquid_input_wells": [ + ] + }, { - "id": "Tip Rack BC230 on P5", - "parent": "deck", - "slot_on_deck": "P5", - "class_name": "BC230", - "liquid_type": [], - "liquid_volume": [], - "liquid_input_wells": [ - ] - }, + "id": "Tip Rack BC230 on TL3", + "parent": "deck", + "slot_on_deck": "TL3", + "class_name": "BC230", + "liquid_type": [], + "liquid_volume": [], + "liquid_input_wells": [ + ] + }, + { + "id": "Tip Rack BC230 on TL4", + "parent": "deck", + "slot_on_deck": "TL4", + "class_name": "BC230", + "liquid_type": [], + "liquid_volume": [], + "liquid_input_wells": [ + ] + }, + { + "id": "Tip Rack BC230 on TL5", + "parent": "deck", + "slot_on_deck": "TL5", + "class_name": "BC230", + "liquid_type": [], + "liquid_volume": [], + "liquid_input_wells": [ + ] + }, { - "id": "Tip Rack BC230 on P6", - "parent": "deck", - "slot_on_deck": "P6", - "class_name": "BC230", - "liquid_type": [], - "liquid_volume": [], - "liquid_input_wells": [ - ] - }, + "id": "Tip Rack BC230 on P5", + "parent": "deck", + "slot_on_deck": "P5", + "class_name": "BC230", + "liquid_type": [], + "liquid_volume": [], + "liquid_input_wells": [ + ] + }, { - "id": "Tip Rack BC230 on P7", - "parent": "deck", - "slot_on_deck": "P7", - "class_name": "BC230", - "liquid_type": [], - "liquid_volume": [], - "liquid_input_wells": [ - ] - }, - { - "id": "Tip Rack BC230 on P8", - "parent": "deck", - "slot_on_deck": "P8", - "class_name": "BC230", - "liquid_type": [], - "liquid_volume": [], - "liquid_input_wells": [ - ] - }, + "id": "Tip Rack BC230 on P6", + "parent": "deck", + "slot_on_deck": "P6", + "class_name": "BC230", + "liquid_type": [], + "liquid_volume": [], + "liquid_input_wells": [ + ] + }, + { + "id": "Tip Rack BC230 on P7", + "parent": "deck", + "slot_on_deck": "P7", + "class_name": "BC230", + "liquid_type": [], + "liquid_volume": [], + "liquid_input_wells": [ + ] + }, { - "id": "Tip Rack BC230 P16", - "parent": "deck", - "slot_on_deck": "P16", - "class_name": "BC230", - "liquid_type": [], - "liquid_volume": [], - "liquid_input_wells": [ - ] - }, - { - "id": "stock plate on 4", - "parent": "deck", - "slot_on_deck": "P2", - "class_name": "nest_12_reservoir_15ml", - "liquid_type": [ - "bind beads" - ], - "liquid_volume": [10000], - "liquid_input_wells": [ - "A1" - ] - }, - { - "id": "stock plate on P2", - "parent": "deck", - "slot_on_deck": "P2", - "class_name": "nest_12_reservoir_15ml", - "liquid_type": [ - "bind beads" - ], - "liquid_volume": [10000], - "liquid_input_wells": [ - "A1" - ] - }, - { - "id": "stock plate on P3", - "parent": "deck", - "slot_on_deck": "P3", - "class_name": "nest_12_reservoir_15ml", - "liquid_type": [ - "ethyl alcohol" - ], - "liquid_volume": [10000], - "liquid_input_wells": [ - "A1" - ] - }, - - { - "id": "oscillation", - "parent": "deck", - "slot_on_deck": "Orbital1", - "class_name": "Orbital", - "liquid_type": [], - "liquid_volume": [], - "liquid_input_wells": [ - ] - }, - { - "id": "working plate on P11", - "parent": "deck", - "slot_on_deck": "P11", - "class_name": "NEST 2ml Deep Well Plate", - "liquid_type": [ - ], - "liquid_volume": [], - "liquid_input_wells": [ - ] - }, + "id": "Tip Rack BC230 on P8", + "parent": "deck", + "slot_on_deck": "P8", + "class_name": "BC230", + "liquid_type": [], + "liquid_volume": [], + "liquid_input_wells": [ + ] + }, { - "id": "magnetics module on P12", - "parent": "deck", - "slot_on_deck": "P12", - "class_name": "magnetics module", - "liquid_type": [], - "liquid_volume": [], - "liquid_input_wells": [ - ] - }, - { - "id": "working plate on P13", - "parent": "deck", - "slot_on_deck": "P13", - "class_name": "NEST 2ml Deep Well Plate", - "liquid_type": [ - ], - "liquid_volume": [], - "liquid_input_wells": [ - ] - }, + "id": "Tip Rack BC230 P16", + "parent": "deck", + "slot_on_deck": "P16", + "class_name": "BC230", + "liquid_type": [], + "liquid_volume": [], + "liquid_input_wells": [ + ] + }, { - "id": "waste on P22", - "parent": "deck", - "slot_on_deck": "P22", - "class_name": "nest_1_reservoir_195ml", - "liquid_type": [ - ], - "liquid_volume": [], - "liquid_input_wells": [ - ] + "id": "stock plate on 4", + "parent": "deck", + "slot_on_deck": "P2", + "class_name": "nest_12_reservoir_15ml", + "liquid_type": [ + "bind beads" + ], + "liquid_volume": [10000], + "liquid_input_wells": [ + "A1" + ] + }, + { + "id": "stock plate on P2", + "parent": "deck", + "slot_on_deck": "P2", + "class_name": "nest_12_reservoir_15ml", + "liquid_type": [ + "bind beads" + ], + "liquid_volume": [10000], + "liquid_input_wells": [ + "A1" + ] + }, + { + "id": "stock plate on P3", + "parent": "deck", + "slot_on_deck": "P3", + "class_name": "nest_12_reservoir_15ml", + "liquid_type": [ + "ethyl alcohol" + ], + "liquid_volume": [10000], + "liquid_input_wells": [ + "A1" + ] + }, + + { + "id": "oscillation", + "parent": "deck", + "slot_on_deck": "Orbital1", + "class_name": "Orbital", + "liquid_type": [], + "liquid_volume": [], + "liquid_input_wells": [ + ] + }, + { + "id": "working plate on P11", + "parent": "deck", + "slot_on_deck": "P11", + "class_name": "NEST 2ml Deep Well Plate", + "liquid_type": [ + ], + "liquid_volume": [], + "liquid_input_wells": [ + ] + }, + { + "id": "magnetics module on P12", + "parent": "deck", + "slot_on_deck": "P12", + "class_name": "magnetics module", + "liquid_type": [], + "liquid_volume": [], + "liquid_input_wells": [ + ] + }, + { + "id": "working plate on P13", + "parent": "deck", + "slot_on_deck": "P13", + "class_name": "NEST 2ml Deep Well Plate", + "liquid_type": [ + ], + "liquid_volume": [], + "liquid_input_wells": [ + ] + }, + { + "id": "waste on P22", + "parent": "deck", + "slot_on_deck": "P22", + "class_name": "nest_1_reservoir_195ml", + "liquid_type": [ + ], + "liquid_volume": [], + "liquid_input_wells": [ + ] + } + ] + + ''' + + handler = LiquidHandlerBiomek() + handler.temp_protocol = { + "meta": {}, + "labwares": [], + "steps": [] } -] -''' + input_steps = json.loads(steps_info) + labwares = json.loads(labware_with_liquid) -handler = LiquidHandlerBiomek() -handler.temp_protocol = { - "meta": {}, - "labwares": [], - "steps": [] -} + for step in input_steps['steps']: + if step['operation'] != 'transfer': + continue + parameters = step['parameters'] + tip_rack=parameters['tip_rack'] + # 找到labwares中与tip_rack匹配的项的id + tip_rack_id = [lw['id'] for lw in labwares if lw['class_name'] == tip_rack][0] -input_steps = json.loads(steps_info) -labwares = json.loads(labware_with_liquid) - -for step in input_steps['steps']: - if step['operation'] != 'transfer': - continue - parameters = step['parameters'] - tip_rack=parameters['tip_rack'] - # 找到labwares中与tip_rack匹配的项的id - tip_rack_id = [lw['id'] for lw in labwares if lw['class_name'] == tip_rack][0] - - handler.transfer_biomek(source=parameters['source'], - target=parameters['target'], - volume=parameters['volume'], - tip_rack=tip_rack_id, - aspirate_techniques='MC P300 high', - dispense_techniques='MC P300 high' - ) -print(json.dumps(handler.temp_protocol['steps'],indent=4, ensure_ascii=False)) + handler.transfer_biomek(source=parameters['source'], + target=parameters['target'], + volume=parameters['volume'], + tip_rack=tip_rack_id, + aspirate_techniques='MC P300 high', + dispense_techniques='MC P300 high' + ) + print(json.dumps(handler.temp_protocol['steps'],indent=4, ensure_ascii=False)) diff --git a/unilabos/ros/nodes/base_device_node.py b/unilabos/ros/nodes/base_device_node.py index daf52300..e7991303 100644 --- a/unilabos/ros/nodes/base_device_node.py +++ b/unilabos/ros/nodes/base_device_node.py @@ -805,7 +805,9 @@ class ROS2DeviceNode: self.resource_tracker = DeviceNodeResourceTracker() # use_pylabrobot_creator 使用 cls的包路径检测 - use_pylabrobot_creator = driver_class.__module__.startswith("pylabrobot") or driver_class.__name__ == "LiquidHandlerAbstract" + use_pylabrobot_creator = (driver_class.__module__.startswith("pylabrobot") + or driver_class.__name__ == "LiquidHandlerAbstract" + or driver_class.__name__ == "LiquidHandlerBiomek") # TODO: 要在创建之前预先请求服务器是否有当前id的物料,放到resource_tracker中,让pylabrobot进行创建 # 创建设备类实例