Merge remote-tracking branch 'upstream/dev' into device_visualization

This commit is contained in:
zhangshixiang
2025-07-24 19:03:36 +08:00
11 changed files with 344 additions and 42816 deletions

21293
deck.json

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -21,10 +21,10 @@
"timeout": 10.0, "timeout": 10.0,
"axis": "Left", "axis": "Left",
"channel_num": 8, "channel_num": 8,
"setup": true, "setup": false,
"debug": false, "debug": false,
"simulator": false, "simulator": false,
"matrix_id": "fd383e6d-2d0e-40b5-9c01-1b2870b1f1b1" "matrix_id": "71593"
}, },
"data": {}, "data": {},
"children": [ "children": [

View File

@@ -716,14 +716,14 @@ class LiquidHandlerAbstract(LiquidHandlerMiddleware):
raise ValueError(f"Length of `asp_vols` {len(asp_vols)} must match `targets` {len(targets)}.") raise ValueError(f"Length of `asp_vols` {len(asp_vols)} must match `targets` {len(targets)}.")
# 首先应该对任务分组然后每次1个/8个进行操作处理 # 首先应该对任务分组然后每次1个/8个进行操作处理
if len(use_channels) == 1: if len(use_channels) == 1:
tip = []
for x in range(len(use_channels)):
tip.extend(next(self.current_tip))
await self.pick_up_tips(tip)
for _ in range(len(targets)): for _ in range(len(targets)):
tip = []
for x in range(len(use_channels)):
tip.extend(next(self.current_tip))
await self.pick_up_tips(tip)
await self.aspirate( await self.aspirate(
resources=reagent_sources, resources=[reagent_sources[_]],
vols=[asp_vols[_]], vols=[asp_vols[_]],
use_channels=use_channels, use_channels=use_channels,
flow_rates=[flow_rates[0]] if flow_rates else None, flow_rates=[flow_rates[0]] if flow_rates else None,
@@ -759,17 +759,18 @@ class LiquidHandlerAbstract(LiquidHandlerMiddleware):
if delays is not None: if delays is not None:
await self.custom_delay(seconds=delays[1]) await self.custom_delay(seconds=delays[1])
await self.touch_tip(targets[_]) await self.touch_tip(targets[_])
await self.discard_tips() await self.discard_tips()
elif len(use_channels) == 8: elif len(use_channels) == 8:
# 对于8个的情况需要判断此时任务是不是能被8通道移液站来成功处理 # 对于8个的情况需要判断此时任务是不是能被8通道移液站来成功处理
if len(targets) % 8 != 0: if len(targets) % 8 != 0:
raise ValueError(f"Length of `targets` {len(targets)} must be a multiple of 8 for 8-channel mode.") raise ValueError(f"Length of `targets` {len(targets)} must be a multiple of 8 for 8-channel mode.")
tip = []
for _ in range(len(use_channels)):
tip.extend(next(self.current_tip))
await self.pick_up_tips(tip)
for i in range(0, len(targets), 8): for i in range(0, len(targets), 8):
tip = []
for _ in range(len(use_channels)):
tip.extend(next(self.current_tip))
await self.pick_up_tips(tip)
current_targets = targets[i:i + 8] current_targets = targets[i:i + 8]
current_reagent_sources = reagent_sources[i:i + 8] current_reagent_sources = reagent_sources[i:i + 8]
current_asp_vols = asp_vols[i:i + 8] current_asp_vols = asp_vols[i:i + 8]
@@ -819,7 +820,7 @@ class LiquidHandlerAbstract(LiquidHandlerMiddleware):
if delays is not None: if delays is not None:
await self.custom_delay(seconds=delays[1]) await self.custom_delay(seconds=delays[1])
await self.touch_tip(current_targets) await self.touch_tip(current_targets)
await self.discard_tips() await self.discard_tips()
# except Exception as e: # except Exception as e:

View File

@@ -400,6 +400,7 @@ class PRCXI9300Backend(LiquidHandlerBackend):
) )
print(f"PRCXI9300Backend created solution with ID: {solution_id}") print(f"PRCXI9300Backend created solution with ID: {solution_id}")
self.api_client.load_solution(solution_id) self.api_client.load_solution(solution_id)
print(json.dumps(self.steps_todo_list, indent=2))
return self.api_client.start() return self.api_client.start()
@classmethod @classmethod
@@ -976,9 +977,186 @@ if __name__ == "__main__":
# 4. # 4.
deck = PRCXI9300Deck(name="PRCXI_Deck_9300", size_x=100, size_y=100, size_z=100) # deck = PRCXI9300Deck(name="PRCXI_Deck_9300", size_x=100, size_y=100, size_z=100)
from pylabrobot.resources.opentrons.tip_racks import opentrons_96_tiprack_300ul,opentrons_96_tiprack_10ul # from pylabrobot.resources.opentrons.tip_racks import opentrons_96_tiprack_300ul,opentrons_96_tiprack_10ul
# from pylabrobot.resources.opentrons.plates import corning_96_wellplate_360ul_flat, nest_96_wellplate_2ml_deep
# def get_well_container(name: str) -> PRCXI9300Container:
# well_containers = corning_96_wellplate_360ul_flat(name).serialize()
# plate = PRCXI9300Container(name=name, size_x=50, size_y=50, size_z=10, category="plate",
# ordering=collections.OrderedDict())
# plate_serialized = plate.serialize()
# plate_serialized["parent_name"] = deck.name
# well_containers.update({k: v for k, v in plate_serialized.items() if k not in ["children"]})
# new_plate: PRCXI9300Container = PRCXI9300Container.deserialize(well_containers)
# return new_plate
# def get_tip_rack(name: str) -> PRCXI9300Container:
# tip_racks = opentrons_96_tiprack_300ul("name").serialize()
# tip_rack = PRCXI9300Container(name=name, size_x=50, size_y=50, size_z=10, category="tip_rack",
# ordering=collections.OrderedDict())
# tip_rack_serialized = tip_rack.serialize()
# tip_rack_serialized["parent_name"] = deck.name
# tip_racks.update({k: v for k, v in tip_rack_serialized.items() if k not in ["children"]})
# new_tip_rack: PRCXI9300Container = PRCXI9300Container.deserialize(tip_racks)
# return new_tip_rack
# plate1 = get_tip_rack("RackT1")
# plate1.load_state({
# "Material": {
# "uuid": "076250742950465b9d6ea29a225dfb00",
# "Code": "ZX-001-300",
# "Name": "300μL Tip头"
# }
# })
# plate2 = get_well_container("PlateT2")
# plate2.load_state({
# "Material": {
# "uuid": "57b1e4711e9e4a32b529f3132fc5931f",
# "Code": "ZX-019-2.2",
# "Name": "96深孔板"
# }
# })
# plate3 = PRCXI9300Trash("trash", size_x=50, size_y=100, size_z=10, category="trash")
# plate3.load_state({
# "Material": {
# "uuid": "730067cf07ae43849ddf4034299030e9"
# }
# })
# plate4 = get_well_container("PlateT4")
# plate4.load_state({
# "Material": {
# "uuid": "57b1e4711e9e4a32b529f3132fc5931f",
# "Code": "ZX-019-2.2",
# "Name": "96深孔板"
# }
# })
# plate5 = get_well_container("PlateT5")
# plate5.load_state({
# "Material": {
# "uuid": "57b1e4711e9e4a32b529f3132fc5931f",
# "Code": "ZX-019-2.2",
# "Name": "96深孔板"
# }
# })
# plate6 = get_well_container("PlateT6")
# plate6.load_state({
# "Material": {
# "uuid": "57b1e4711e9e4a32b529f3132fc5931f",
# "Code": "ZX-019-2.2",
# "Name": "96深孔板"
# }
# })
# deck.assign_child_resource(plate1, location=Coordinate(0, 0, 0))
# deck.assign_child_resource(plate2, location=Coordinate(0, 0, 0))
# deck.assign_child_resource(plate3, location=Coordinate(0, 0, 0))
# deck.assign_child_resource(plate4, location=Coordinate(0, 0, 0))
# deck.assign_child_resource(plate5, location=Coordinate(0, 0, 0))
# deck.assign_child_resource(plate6, location=Coordinate(0, 0, 0))
# # print(plate2)
# plate_2_liquids = [[('water', 500)]]*96
# plate2.set_well_liquids(plate_2_liquids)
# handler = PRCXI9300Handler(deck=deck, host="10.181.214.132", port=9999,
# timeout=10.0, setup=False, debug=False,
# matrix_id="71593",
# channel_num=8, axis="Left") # Initialize the handler with the deck and host settings
# handler.set_tiprack([plate1])
# asyncio.run(handler.setup()) # Initialize the handler and setup the connection
# from pylabrobot.resources import set_volume_tracking
# # from pylabrobot.resources import set_tip_tracking
# set_volume_tracking(enabled=True)
# from unilabos.resources.graphio import *
# A = tree_to_list([resource_plr_to_ulab(deck)])
# with open("deck_9300_new.json", "w", encoding="utf-8") as f:
# json.dump(A, f, indent=4, ensure_ascii=False)
# asyncio.run(handler.create_protocol(protocol_name="Test Protocol")) # Initialize the backend and setup the connection
# # asyncio.run(handler.pick_up_tips(plate1.children[:8],[0,1,2,3,4,5,6,7]))
# # print(plate1.children[:8])
# # asyncio.run(handler.aspirate(plate2.children[:8],[50]*8, [0,1,2,3,4,5,6,7]))
# # print(plate2.children[:8])
# # asyncio.run(handler.dispense(plate5.children[:8],[50]*8,[0,1,2,3,4,5,6,7]))
# # print(plate5.children[:8])
# # # # # asyncio.run(handler.drop_tips(tip_rack.children[8:16],[0,1,2,3,4,5,6,7]))
# # asyncio.run(handler.discard_tips())
# # asyncio.run(handler.mix(well_containers.children[:8
# # ], mix_time=3, mix_vol=50, height_to_bottom=0.5, offsets=Coordinate(0, 0, 0), mix_rate=100))
# # #print(json.dumps(handler._unilabos_backend.steps_todo_list, indent=2)) # Print matrix info
# # asyncio.run(handler.add_liquid(
# # asp_vols=[100]*16,
# # dis_vols=[100]*16,
# # reagent_sources=plate2.children[:16],
# # targets=plate5.children[:16],
# # use_channels=[0, 1, 2, 3, 4, 5, 6, 7],
# # flow_rates=[None] * 32,
# # offsets=[Coordinate(0, 0, 0)] * 32,
# # liquid_height=[None] * 16,
# # blow_out_air_volume=[None] * 16,
# # delays=None,
# # mix_time=3,
# # mix_vol=50,
# # spread="wide",
# # ))
# # asyncio.run(handler.run_protocol()) # Run the protocol
# # asyncio.run(handler.remove_liquid(
# # vols=[100]*16,
# # sources=plate2.children[-16:],
# # waste_liquid=plate5.children[:16], # 这个有些奇怪,但是好像也只能这么写
# # use_channels=[0, 1, 2, 3, 4, 5, 6, 7],
# # flow_rates=[None] * 32,
# # offsets=[Coordinate(0, 0, 0)] * 32,
# # liquid_height=[None] * 32,
# # blow_out_air_volume=[None] * 32,
# # spread="wide",
# # ))
# acid = [20]*8+[40]*8+[60]*8+[80]*8+[100]*8+[120]*8+[140]*8+[160]*8+[180]*8+[200]*8+[220]*8+[240]*8
# alkaline = acid[::-1] # Reverse the acid list for alkaline
# asyncio.run(handler.transfer_liquid(
# asp_vols=acid,
# dis_vols=acid,
# tip_racks=[plate1],
# sources=plate2.children[:],
# targets=plate5.children[:],
# use_channels=[0, 1, 2, 3, 4, 5, 6, 7],
# offsets=[Coordinate(0, 0, 0)] * 32,
# asp_flow_rates=[None] * 16,
# dis_flow_rates=[None] * 16,
# liquid_height=[None] * 32,
# blow_out_air_volume=[None] * 32,
# mix_times=3,
# mix_vol=50,
# spread="wide",
# ))
# asyncio.run(handler.run_protocol()) # Run the protocol
# # # input("Running protocol...")
# # # input("Press Enter to continue...") # Wait for user input before proceeding
# # # print("PRCXI9300Handler initialized with deck and host settings.")
# # Example usage
# # 1. 用导出的json给每个T1 T2板子设定相应的物料如果是孔板和枪头盒要对应区分
# # 2. 设计一个单点动作流程,可以跑
# # 3.
deck = PRCXI9300Deck(name="PRCXI_Deck", size_x=100, size_y=100, size_z=100)
from pylabrobot.resources.opentrons.tip_racks import tipone_96_tiprack_200ul,opentrons_96_tiprack_10ul
from pylabrobot.resources.opentrons.plates import corning_96_wellplate_360ul_flat, nest_96_wellplate_2ml_deep from pylabrobot.resources.opentrons.plates import corning_96_wellplate_360ul_flat, nest_96_wellplate_2ml_deep
def get_well_container(name: str) -> PRCXI9300Container: def get_well_container(name: str) -> PRCXI9300Container:
@@ -992,7 +1170,7 @@ if __name__ == "__main__":
return new_plate return new_plate
def get_tip_rack(name: str) -> PRCXI9300Container: def get_tip_rack(name: str) -> PRCXI9300Container:
tip_racks = opentrons_96_tiprack_300ul("name").serialize() tip_racks = opentrons_96_tiprack_10ul("name").serialize()
tip_rack = PRCXI9300Container(name=name, size_x=50, size_y=50, size_z=10, category="tip_rack", tip_rack = PRCXI9300Container(name=name, size_x=50, size_y=50, size_z=10, category="tip_rack",
ordering=collections.OrderedDict()) ordering=collections.OrderedDict())
tip_rack_serialized = tip_rack.serialize() tip_rack_serialized = tip_rack.serialize()
@@ -1001,333 +1179,157 @@ if __name__ == "__main__":
new_tip_rack: PRCXI9300Container = PRCXI9300Container.deserialize(tip_racks) new_tip_rack: PRCXI9300Container = PRCXI9300Container.deserialize(tip_racks)
return new_tip_rack return new_tip_rack
plate1 = get_tip_rack("RackT1") plate1 = get_well_container("HPLCPlateT1")
plate1.load_state({ plate1.load_state({
"Material": { "Material": {
"uuid": "076250742950465b9d6ea29a225dfb00", "uuid": "548bbc3df0d4447586f2c19d2c0c0c55",
"Code": "ZX-001-300", "Code": "HPLC01",
"Name": "300μL Tip头" "Name": "HPLC料盘"
} }
}) })
plate2 = get_well_container("PlateT2") plate2 = get_well_container("PlateT2")
plate2.load_state({ plate2.load_state({
"Material": { "Material": {
"uuid": "57b1e4711e9e4a32b529f3132fc5931f", "uuid": "04211a2dc93547fe9bf6121eac533650",
"Code": "ZX-019-2.2",
"Name": "96深孔板"
} }
}) })
plate3 = get_well_container("PlateT3")
plate3 = PRCXI9300Trash("trash", size_x=50, size_y=100, size_z=10, category="trash")
plate3.load_state({ plate3.load_state({
"Material": {
"uuid": "04211a2dc93547fe9bf6121eac533650",
}
})
trash = PRCXI9300Trash(name="trash", size_x=50, size_y=50, size_z=10, category="trash")
trash.load_state({
"Material": { "Material": {
"uuid": "730067cf07ae43849ddf4034299030e9" "uuid": "730067cf07ae43849ddf4034299030e9"
} }
}) })
plate4 = get_well_container("PlateT4")
plate4.load_state({
"Material": {
"uuid": "57b1e4711e9e4a32b529f3132fc5931f",
"Code": "ZX-019-2.2",
"Name": "96深孔板"
}
})
plate5 = get_well_container("PlateT5") plate5 = get_well_container("PlateT5")
plate5.load_state({ plate5.load_state({
"Material": { "Material": {
"uuid": "57b1e4711e9e4a32b529f3132fc5931f", "uuid": "04211a2dc93547fe9bf6121eac533650",
"Code": "ZX-019-2.2",
"Name": "96深孔板"
} }
}) })
plate6 = get_well_container("PlateT6") plate6 = get_well_container("PlateT6")
plate6.load_state({ plate6.load_state({
"Material": {
"uuid": "04211a2dc93547fe9bf6121eac533650"
}
})
plate7 = PRCXI9300Container(name="plateT7", size_x=50, size_y=50, size_z=10, category="plate", ordering=collections.OrderedDict())
plate7.load_state({
"Material": {
"uuid": "04211a2dc93547fe9bf6121eac533650"
}
})
plate8 = get_tip_rack("RackT8")
plate8.load_state({
"Material": {
"uuid": "068b3815e36b4a72a59bae017011b29f",
"Code": "ZX-001-10+",
"Name": "10μL加长 Tip头"
}
})
plate9 = get_well_container("PlateT9")
plate9.load_state({
"Material": {
"uuid": "04211a2dc93547fe9bf6121eac533650"
}
})
plate10 = get_well_container("PlateT10")
plate10.load_state({
"Material": {
"uuid": "04211a2dc93547fe9bf6121eac533650"
}
})
plate11 = get_well_container("PlateT11")
plate11.load_state({
"Material": { "Material": {
"uuid": "57b1e4711e9e4a32b529f3132fc5931f", "uuid": "57b1e4711e9e4a32b529f3132fc5931f",
"Code": "ZX-019-2.2", }
"Name": "96深孔板" })
plate12 = get_well_container("PlateT12")
plate12.load_state({
"Material": {
"uuid": "04211a2dc93547fe9bf6121eac533650"
}
})
plate13 = get_well_container("PlateT13")
plate13.load_state({
"Material": {
"uuid": "04211a2dc93547fe9bf6121eac533650"
} }
}) })
# container_for_nothing = PRCXI9300Container(name="container_for_nothing", size_x=50, size_y=50, size_z=10, category="plate", ordering=collections.OrderedDict())
deck.assign_child_resource(plate1, location=Coordinate(0, 0, 0)) deck.assign_child_resource(plate1, location=Coordinate(0, 0, 0))
deck.assign_child_resource(plate2, location=Coordinate(0, 0, 0)) deck.assign_child_resource(PRCXI9300Container(name="container_for_nothing1", size_x=50, size_y=50, size_z=10, category="plate", ordering=collections.OrderedDict()), location=Coordinate(0, 0, 0))
deck.assign_child_resource(plate3, location=Coordinate(0, 0, 0)) deck.assign_child_resource(PRCXI9300Container(name="container_for_nothing2", size_x=50, size_y=50, size_z=10, category="plate", ordering=collections.OrderedDict()), location=Coordinate(0, 0, 0))
deck.assign_child_resource(plate4, location=Coordinate(0, 0, 0)) deck.assign_child_resource(trash, location=Coordinate(0, 0, 0))
deck.assign_child_resource(plate5, location=Coordinate(0, 0, 0)) deck.assign_child_resource(PRCXI9300Container(name="container_for_nothing3", size_x=50, size_y=50, size_z=10, category="plate", ordering=collections.OrderedDict()), location=Coordinate(0, 0, 0))
deck.assign_child_resource(plate6, location=Coordinate(0, 0, 0)) deck.assign_child_resource(PRCXI9300Container(name="container_for_nothing", size_x=50, size_y=50, size_z=10, category="plate", ordering=collections.OrderedDict()), location=Coordinate(0, 0, 0))
deck.assign_child_resource(PRCXI9300Container(name="container_for_nothing4", size_x=50, size_y=50, size_z=10, category="plate", ordering=collections.OrderedDict()), location=Coordinate(0, 0, 0))
deck.assign_child_resource(plate8, location=Coordinate(0, 0, 0))
deck.assign_child_resource(PRCXI9300Container(name="container_for_nothing5", size_x=50, size_y=50, size_z=10, category="plate", ordering=collections.OrderedDict()), location=Coordinate(0, 0, 0))
deck.assign_child_resource(PRCXI9300Container(name="container_for_nothing6", size_x=50, size_y=50, size_z=10, category="plate", ordering=collections.OrderedDict()), location=Coordinate(0, 0, 0))
deck.assign_child_resource(plate11, location=Coordinate(0, 0, 0))
deck.assign_child_resource(PRCXI9300Container(name="container_for_nothing7", size_x=50, size_y=50, size_z=10, category="plate", ordering=collections.OrderedDict()), location=Coordinate(0, 0, 0))
deck.assign_child_resource(PRCXI9300Container(name="container_for_nothing8", size_x=50, size_y=50, size_z=10, category="plate", ordering=collections.OrderedDict()), location=Coordinate(0, 0, 0))
# print(plate2) handler = PRCXI9300Handler(deck=deck, host="10.181.102.13", port=9999,
plate_2_liquids = [[('water', 500)]]*96
plate2.set_well_liquids(plate_2_liquids)
handler = PRCXI9300Handler(deck=deck, host="10.181.214.132", port=9999,
timeout=10.0, setup=False, debug=False, timeout=10.0, setup=False, debug=False,
matrix_id="71593", matrix_id="fd383e6d-2d0e-40b5-9c01-1b2870b1f1b1",
channel_num=8, axis="Left") # Initialize the handler with the deck and host settings channel_num=1, axis="Right") # Initialize the handler with the deck and host settings
handler.set_tiprack([plate8]) # Set the tip rack for the handler
handler.set_tiprack([plate1])
asyncio.run(handler.setup()) # Initialize the handler and setup the connection asyncio.run(handler.setup()) # Initialize the handler and setup the connection
from pylabrobot.resources import set_volume_tracking from pylabrobot.resources import set_volume_tracking
# from pylabrobot.resources import set_tip_tracking # from pylabrobot.resources import set_tip_tracking
set_volume_tracking(enabled=True) set_volume_tracking(enabled=True)
plate11.set_well_liquids([("Water", 100) if (i % 8 == 0 and i // 8 < 6) else (None, 100) for i in range(96)]) # Set liquids for every 8 wells in plate8
from unilabos.resources.graphio import * from unilabos.resources.graphio import *
A = tree_to_list([resource_plr_to_ulab(deck)]) A = tree_to_list([resource_plr_to_ulab(deck)])
with open("deck_9300_new.json", "w", encoding="utf-8") as f: # with open("deck.json", "w", encoding="utf-8") as f:
json.dump(A, f, indent=4, ensure_ascii=False) # json.dump(A, f, indent=4, ensure_ascii=False)
print(plate11.get_well(0).tracker.get_used_volume())
asyncio.run(handler.create_protocol(protocol_name="Test Protocol")) # Initialize the backend and setup the connection asyncio.run(handler.create_protocol(protocol_name="Test Protocol")) # Initialize the backend and setup the connection
# asyncio.run(handler.pick_up_tips(plate1.children[:8],[0,1,2,3,4,5,6,7])) # asyncio.run(handler.pick_up_tips([plate8.children[8]],[0]))
# print(plate1.children[:8]) # print(plate8.children[8])
# asyncio.run(handler.aspirate(plate2.children[:8],[50]*8, [0,1,2,3,4,5,6,7])) # # asyncio.run(handler.run_protocol())
# print(plate2.children[:8]) # asyncio.run(handler.aspirate([plate11.children[0]],[10], [0]))
# asyncio.run(handler.dispense(plate5.children[:8],[50]*8,[0,1,2,3,4,5,6,7])) # print(plate11.children[0])
# print(plate5.children[:8]) # # asyncio.run(handler.run_protocol())
# asyncio.run(handler.dispense([plate1.children[0]],[10],[0]))
# # # # asyncio.run(handler.drop_tips(tip_rack.children[8:16],[0,1,2,3,4,5,6,7])) # print(plate1.children[0])
# # asyncio.run(handler.run_protocol())
# asyncio.run(handler.mix([plate1.children[0]], mix_time=3, mix_vol=5, height_to_bottom=0.5, offsets=Coordinate(0, 0, 0), mix_rate=100))
# print(plate1.children[0])
# asyncio.run(handler.discard_tips()) # asyncio.run(handler.discard_tips())
# asyncio.run(handler.mix(well_containers.children[:8 asyncio.run(handler.add_liquid(
# ], mix_time=3, mix_vol=50, height_to_bottom=0.5, offsets=Coordinate(0, 0, 0), mix_rate=100)) asp_vols=[10]*7,
# #print(json.dumps(handler._unilabos_backend.steps_todo_list, indent=2)) # Print matrix info dis_vols=[10]*7,
# asyncio.run(handler.add_liquid( reagent_sources=plate11.children[:7],
# asp_vols=[100]*16, targets=plate1.children[2:9],
# dis_vols=[100]*16, use_channels=[0],
# reagent_sources=plate2.children[:16], flow_rates=[None] * 7,
# targets=plate5.children[:16], offsets=[Coordinate(0, 0, 0)] * 7,
# use_channels=[0, 1, 2, 3, 4, 5, 6, 7], liquid_height=[None] * 7,
# flow_rates=[None] * 32, blow_out_air_volume=[None] * 2,
# offsets=[Coordinate(0, 0, 0)] * 32, delays=None,
# liquid_height=[None] * 16, mix_time=3,
# blow_out_air_volume=[None] * 16, mix_vol=5,
# delays=None, spread="custom",
# mix_time=3, ))
# mix_vol=50,
# spread="wide",
# ))
# asyncio.run(handler.run_protocol()) # Run the protocol
# asyncio.run(handler.remove_liquid(
# vols=[100]*16,
# sources=plate2.children[-16:],
# waste_liquid=plate5.children[:16], # 这个有些奇怪,但是好像也只能这么写
# use_channels=[0, 1, 2, 3, 4, 5, 6, 7],
# flow_rates=[None] * 32,
# offsets=[Coordinate(0, 0, 0)] * 32,
# liquid_height=[None] * 32,
# blow_out_air_volume=[None] * 32,
# spread="wide",
# ))
acid = [20]*8+[40]*8+[60]*8+[80]*8+[100]*8+[120]*8+[140]*8+[160]*8+[180]*8+[200]*8+[220]*8+[240]*8
alkaline = acid[::-1] # Reverse the acid list for alkaline
asyncio.run(handler.transfer_liquid(
asp_vols=acid,
dis_vols=acid,
tip_racks=[plate1],
sources=plate2.children[:],
targets=plate5.children[:],
use_channels=[0, 1, 2, 3, 4, 5, 6, 7],
offsets=[Coordinate(0, 0, 0)] * 32,
asp_flow_rates=[None] * 16,
dis_flow_rates=[None] * 16,
liquid_height=[None] * 32,
blow_out_air_volume=[None] * 32,
mix_times=3,
mix_vol=50,
spread="wide",
))
asyncio.run(handler.run_protocol()) # Run the protocol asyncio.run(handler.run_protocol()) # Run the protocol
# # input("Running protocol...")
# # input("Press Enter to continue...") # Wait for user input before proceeding
# # print("PRCXI9300Handler initialized with deck and host settings.")
# Example usage
# 1. 用导出的json给每个T1 T2板子设定相应的物料如果是孔板和枪头盒要对应区分
# 2. 设计一个单点动作流程,可以跑
# 3.
# deck = PRCXI9300Deck(name="PRCXI_Deck", size_x=100, size_y=100, size_z=100)
# from pylabrobot.resources.opentrons.tip_racks import tipone_96_tiprack_200ul,opentrons_96_tiprack_10ul
# from pylabrobot.resources.opentrons.plates import corning_96_wellplate_360ul_flat, nest_96_wellplate_2ml_deep
# def get_well_container(name: str) -> PRCXI9300Container:
# well_containers = corning_96_wellplate_360ul_flat(name).serialize()
# plate = PRCXI9300Container(name=name, size_x=50, size_y=50, size_z=10, category="plate",
# ordering=collections.OrderedDict())
# plate_serialized = plate.serialize()
# plate_serialized["parent_name"] = deck.name
# well_containers.update({k: v for k, v in plate_serialized.items() if k not in ["children"]})
# new_plate: PRCXI9300Container = PRCXI9300Container.deserialize(well_containers)
# return new_plate
# def get_tip_rack(name: str) -> PRCXI9300Container:
# tip_racks = opentrons_96_tiprack_10ul("name").serialize()
# tip_rack = PRCXI9300Container(name=name, size_x=50, size_y=50, size_z=10, category="tip_rack",
# ordering=collections.OrderedDict())
# tip_rack_serialized = tip_rack.serialize()
# tip_rack_serialized["parent_name"] = deck.name
# tip_racks.update({k: v for k, v in tip_rack_serialized.items() if k not in ["children"]})
# new_tip_rack: PRCXI9300Container = PRCXI9300Container.deserialize(tip_racks)
# return new_tip_rack
# plate1 = get_well_container("HPLCPlateT1")
# plate1.load_state({
# "Material": {
# "uuid": "548bbc3df0d4447586f2c19d2c0c0c55",
# "Code": "HPLC01",
# "Name": "HPLC料盘"
# }
# })
# plate2 = get_well_container("PlateT2")
# plate2.load_state({
# "Material": {
# "uuid": "04211a2dc93547fe9bf6121eac533650",
# }
# })
# plate3 = get_well_container("PlateT3")
# plate3.load_state({
# "Material": {
# "uuid": "04211a2dc93547fe9bf6121eac533650",
# }
# })
# trash = PRCXI9300Trash(name="trash", size_x=50, size_y=50, size_z=10, category="trash")
# trash.load_state({
# "Material": {
# "uuid": "730067cf07ae43849ddf4034299030e9"
# }
# })
# plate5 = get_well_container("PlateT5")
# plate5.load_state({
# "Material": {
# "uuid": "04211a2dc93547fe9bf6121eac533650",
# }
# })
# plate6 = get_well_container("PlateT6")
# plate6.load_state({
# "Material": {
# "uuid": "04211a2dc93547fe9bf6121eac533650"
# }
# })
# plate7 = PRCXI9300Container(name="plateT7", size_x=50, size_y=50, size_z=10, category="plate", ordering=collections.OrderedDict())
# plate7.load_state({
# "Material": {
# "uuid": "04211a2dc93547fe9bf6121eac533650"
# }
# })
# plate8 = get_tip_rack("RackT8")
# plate8.load_state({
# "Material": {
# "uuid": "068b3815e36b4a72a59bae017011b29f",
# "Code": "ZX-001-10+",
# "Name": "10μL加长 Tip头"
# }
# })
# plate9 = get_well_container("PlateT9")
# plate9.load_state({
# "Material": {
# "uuid": "04211a2dc93547fe9bf6121eac533650"
# }
# })
# plate10 = get_well_container("PlateT10")
# plate10.load_state({
# "Material": {
# "uuid": "04211a2dc93547fe9bf6121eac533650"
# }
# })
# plate11 = get_well_container("PlateT11")
# plate11.load_state({
# "Material": {
# "uuid": "57b1e4711e9e4a32b529f3132fc5931f",
# }
# })
# plate12 = get_well_container("PlateT12")
# plate12.load_state({
# "Material": {
# "uuid": "04211a2dc93547fe9bf6121eac533650"
# }
# })
# plate13 = get_well_container("PlateT13")
# plate13.load_state({
# "Material": {
# "uuid": "04211a2dc93547fe9bf6121eac533650"
# }
# })
# # container_for_nothing = PRCXI9300Container(name="container_for_nothing", size_x=50, size_y=50, size_z=10, category="plate", ordering=collections.OrderedDict())
# deck.assign_child_resource(plate1, location=Coordinate(0, 0, 0))
# deck.assign_child_resource(PRCXI9300Container(name="container_for_nothing1", size_x=50, size_y=50, size_z=10, category="plate", ordering=collections.OrderedDict()), location=Coordinate(0, 0, 0))
# deck.assign_child_resource(PRCXI9300Container(name="container_for_nothing2", size_x=50, size_y=50, size_z=10, category="plate", ordering=collections.OrderedDict()), location=Coordinate(0, 0, 0))
# deck.assign_child_resource(trash, location=Coordinate(0, 0, 0))
# deck.assign_child_resource(PRCXI9300Container(name="container_for_nothing3", size_x=50, size_y=50, size_z=10, category="plate", ordering=collections.OrderedDict()), location=Coordinate(0, 0, 0))
# deck.assign_child_resource(PRCXI9300Container(name="container_for_nothing", size_x=50, size_y=50, size_z=10, category="plate", ordering=collections.OrderedDict()), location=Coordinate(0, 0, 0))
# deck.assign_child_resource(PRCXI9300Container(name="container_for_nothing4", size_x=50, size_y=50, size_z=10, category="plate", ordering=collections.OrderedDict()), location=Coordinate(0, 0, 0))
# deck.assign_child_resource(plate8, location=Coordinate(0, 0, 0))
# deck.assign_child_resource(PRCXI9300Container(name="container_for_nothing5", size_x=50, size_y=50, size_z=10, category="plate", ordering=collections.OrderedDict()), location=Coordinate(0, 0, 0))
# deck.assign_child_resource(PRCXI9300Container(name="container_for_nothing6", size_x=50, size_y=50, size_z=10, category="plate", ordering=collections.OrderedDict()), location=Coordinate(0, 0, 0))
# deck.assign_child_resource(plate11, location=Coordinate(0, 0, 0))
# deck.assign_child_resource(PRCXI9300Container(name="container_for_nothing7", size_x=50, size_y=50, size_z=10, category="plate", ordering=collections.OrderedDict()), location=Coordinate(0, 0, 0))
# deck.assign_child_resource(PRCXI9300Container(name="container_for_nothing8", size_x=50, size_y=50, size_z=10, category="plate", ordering=collections.OrderedDict()), location=Coordinate(0, 0, 0))
# handler = PRCXI9300Handler(deck=deck, host="10.181.102.13", port=9999,
# timeout=10.0, setup=False, debug=False,
# matrix_id="fd383e6d-2d0e-40b5-9c01-1b2870b1f1b1",
# channel_num=1, axis="Right") # Initialize the handler with the deck and host settings
# handler.set_tiprack([plate8]) # Set the tip rack for the handler
# asyncio.run(handler.setup()) # Initialize the handler and setup the connection
# from pylabrobot.resources import set_volume_tracking
# # from pylabrobot.resources import set_tip_tracking
# set_volume_tracking(enabled=True)
# plate11.set_well_liquids([("Water", 100) if (i % 8 == 0 and i // 8 < 6) else (None, 100) for i in range(96)]) # Set liquids for every 8 wells in plate8
# from unilabos.resources.graphio import *
# A = tree_to_list([resource_plr_to_ulab(deck)])
# # with open("deck.json", "w", encoding="utf-8") as f:
# # json.dump(A, f, indent=4, ensure_ascii=False)
# print(plate11.get_well(0).tracker.get_used_volume())
# asyncio.run(handler.create_protocol(protocol_name="Test Protocol")) # Initialize the backend and setup the connection
# # asyncio.run(handler.pick_up_tips([plate8.children[8]],[0]))
# # print(plate8.children[8])
# # # asyncio.run(handler.run_protocol())
# # asyncio.run(handler.aspirate([plate11.children[0]],[10], [0]))
# # print(plate11.children[0])
# # # asyncio.run(handler.run_protocol())
# # asyncio.run(handler.dispense([plate1.children[0]],[10],[0]))
# # print(plate1.children[0])
# # # asyncio.run(handler.run_protocol())
# # asyncio.run(handler.mix([plate1.children[0]], mix_time=3, mix_vol=5, height_to_bottom=0.5, offsets=Coordinate(0, 0, 0), mix_rate=100))
# # print(plate1.children[0])
# # asyncio.run(handler.discard_tips())
# asyncio.run(handler.add_liquid(
# asp_vols=[10]*7,
# dis_vols=[10]*7,
# reagent_sources=[plate11.children[0]],
# targets=plate1.children[2:9],
# use_channels=[0],
# flow_rates=[None] * 4,
# offsets=[Coordinate(0, 0, 0)] * 4,
# liquid_height=[None] * 2,
# blow_out_air_volume=[None] * 2,
# delays=None,
# mix_time=3,
# mix_vol=5,
# spread="wide",
# ))
# # asyncio.run(handler.transfer_liquid( # # asyncio.run(handler.transfer_liquid(
# # asp_vols=[10]*2, # # asp_vols=[10]*2,
# # dis_vols=[10]*2, # # dis_vols=[10]*2,
@@ -1390,7 +1392,7 @@ if __name__ == "__main__":
# # mix_vol=50, # # mix_vol=50,
# # spread="wide", # # spread="wide",
# # )) # # ))
# print(json.dumps(handler._unilabos_backend.steps_todo_list, indent=2)) # Print matrix info print(json.dumps(handler._unilabos_backend.steps_todo_list, indent=2)) # Print matrix info
# # input("pick_up_tips add step") # # input("pick_up_tips add step")
# #asyncio.run(handler.run_protocol()) # Run the protocol # #asyncio.run(handler.run_protocol()) # Run the protocol
# # input("Running protocol...") # # input("Running protocol...")

View File

@@ -4388,11 +4388,11 @@ liquid_handler:
deck: deck:
type: string type: string
simulator: simulator:
default: false
type: boolean type: boolean
required: required:
- backend - backend
- deck - deck
- simulator
type: object type: object
data: data:
properties: {} properties: {}

View File

@@ -2187,65 +2187,65 @@ virtual_multiway_valve:
data_source: executor data_source: executor
data_type: fluid data_type: fluid
description: 八通阀门端口1 description: 八通阀门端口1
handler_key: 1 handler_key: "1"
io_type: source io_type: source
label: 1 label: "1"
side: NORTH side: NORTH
- data_key: fluid_port_2 - data_key: fluid_port_2
data_source: executor data_source: executor
data_type: fluid data_type: fluid
description: 八通阀门端口2 description: 八通阀门端口2
handler_key: 2 handler_key: "2"
io_type: source io_type: source
label: 2 label: "2"
side: EAST side: EAST
- data_key: fluid_port_3 - data_key: fluid_port_3
data_source: executor data_source: executor
data_type: fluid data_type: fluid
description: 八通阀门端口3 description: 八通阀门端口3
handler_key: 3 handler_key: "3"
io_type: source io_type: source
label: 3 label: "3"
side: EAST side: EAST
- data_key: fluid_port_4 - data_key: fluid_port_4
data_source: executor data_source: executor
data_type: fluid data_type: fluid
description: 八通阀门端口4 description: 八通阀门端口4
handler_key: 4 handler_key: "4"
io_type: source io_type: source
label: 4 label: "4"
side: SOUTH side: SOUTH
- data_key: fluid_port_5 - data_key: fluid_port_5
data_source: executor data_source: executor
data_type: fluid data_type: fluid
description: 八通阀门端口5 description: 八通阀门端口5
handler_key: 5 handler_key: "5"
io_type: source io_type: source
label: 5 label: "5"
side: SOUTH side: SOUTH
- data_key: fluid_port_6 - data_key: fluid_port_6
data_source: executor data_source: executor
data_type: fluid data_type: fluid
description: 八通阀门端口6 description: 八通阀门端口6
handler_key: 6 handler_key: "6"
io_type: source io_type: source
label: 6 label: "6"
side: WEST side: WEST
- data_key: fluid_port_7 - data_key: fluid_port_7
data_source: executor data_source: executor
data_type: fluid data_type: fluid
description: 八通阀门端口7 description: 八通阀门端口7
handler_key: 7 handler_key: "7"
io_type: source io_type: source
label: 7 label: "7"
side: WEST side: WEST
- data_key: fluid_port_8 - data_key: fluid_port_8
data_source: executor data_source: executor
data_type: fluid data_type: fluid
description: 八通阀门端口8 description: 八通阀门端口8
handler_key: 8 handler_key: "8"
io_type: source io_type: source
label: 8 label: "8"
side: NORTH side: NORTH
icon: EightPipeline.webp icon: EightPipeline.webp
init_param_schema: init_param_schema:

View File

@@ -110,7 +110,7 @@ class Registry:
"placeholder_keys": { "placeholder_keys": {
"res_id": "unilabos_resources", # 将当前实验室的全部物料id作为下拉框可选择 "res_id": "unilabos_resources", # 将当前实验室的全部物料id作为下拉框可选择
"device_id": "unilabos_devices", # 将当前实验室的全部设备id作为下拉框可选择 "device_id": "unilabos_devices", # 将当前实验室的全部设备id作为下拉框可选择
"parent": "unilabos_resources", # 将当前实验室的全部物料id作为下拉框可选择 "parent": "unilabos_nodes", # 将当前实验室的设备/物料作为下拉框可选择
}, },
}, },
"test_latency": { "test_latency": {
@@ -131,7 +131,7 @@ class Registry:
"config_info": [], "config_info": [],
"icon": "icon_device.webp", "icon": "icon_device.webp",
"registry_type": "device", "registry_type": "device",
"handles": [], "handles": [], # virtue采用了不同的handle
"init_param_schema": {}, "init_param_schema": {},
"file_path": "/", "file_path": "/",
} }
@@ -499,7 +499,7 @@ class Registry:
) )
for action_name, action_config in device_config["class"]["action_value_mappings"].items(): for action_name, action_config in device_config["class"]["action_value_mappings"].items():
if "handles" not in action_config: if "handles" not in action_config:
action_config["handles"] = [] action_config["handles"] = {}
if "type" in action_config: if "type" in action_config:
action_type_str: str = action_config["type"] action_type_str: str = action_config["type"]
# 通过Json发放指令而不是通过特殊的ros action进行处理 # 通过Json发放指令而不是通过特殊的ros action进行处理
@@ -544,7 +544,7 @@ class Registry:
) )
) )
), ),
"handles": [], "handles": {},
} }
if "registry_type" not in device_config: if "registry_type" not in device_config:
device_config["registry_type"] = "device" device_config["registry_type"] = "device"

View File

@@ -84,7 +84,7 @@ def canonicalize_links_ports(data: dict) -> dict:
# 第一遍处理将字符串类型的port转换为字典格式 # 第一遍处理将字符串类型的port转换为字典格式
for link in data.get("links", []): for link in data.get("links", []):
port = link.get("port") port = link.get("port")
if link["type"] == "physical": if link.get("type", "physical") == "physical":
link["type"] = "fluid" link["type"] = "fluid"
if isinstance(port, int): if isinstance(port, int):
port = str(port) port = str(port)

View File

@@ -364,7 +364,16 @@ class HostNode(BaseROS2DeviceNode):
resources, device_ids, bind_parent_ids, bind_locations, other_calling_params resources, device_ids, bind_parent_ids, bind_locations, other_calling_params
): ):
# 这里要求device_id传入必须是edge_device_id # 这里要求device_id传入必须是edge_device_id
namespace = "/devices/" + device_id if device_id not in self.devices_names:
self.lab_logger().error(f"[Host Node] Device {device_id} not found in devices_names. Create resource failed.")
raise ValueError(f"[Host Node] Device {device_id} not found in devices_names. Create resource failed.")
device_key = f"{self.devices_names[device_id]}/{device_id}"
if device_key not in self._online_devices:
self.lab_logger().error(f"[Host Node] Device {device_key} is offline. Create resource failed.")
raise ValueError(f"[Host Node] Device {device_key} is offline. Create resource failed.")
namespace = self.devices_names[device_id]
srv_address = f"/srv{namespace}/append_resource" srv_address = f"/srv{namespace}/append_resource"
sclient = self.create_client(SerialCommand, srv_address) sclient = self.create_client(SerialCommand, srv_address)
sclient.wait_for_service() sclient.wait_for_service()

View File

@@ -84,7 +84,11 @@ class ROS2ProtocolNode(BaseROS2DeviceNode):
self.communication_node_id_to_instance[device_id] = d self.communication_node_id_to_instance[device_id] = d
continue continue
for device_id, device_config in self.children.items():
if device_config.get("type", "device") != "device":
continue
# 设置硬件接口代理 # 设置硬件接口代理
d = self.sub_devices[device_id]
if d: if d:
hardware_interface = d.ros_node_instance._hardware_interface hardware_interface = d.ros_node_instance._hardware_interface
if ( if (
@@ -108,6 +112,8 @@ class ROS2ProtocolNode(BaseROS2DeviceNode):
f"添加了{write}方法(来源:{name} {communicate_hardware_info['read']})" f"添加了{write}方法(来源:{name} {communicate_hardware_info['read']})"
) )
self.lab_logger().info(f"ROS2ProtocolNode {device_id} initialized with protocols: {self.protocol_names}")
def _setup_protocol_names(self, protocol_type): def _setup_protocol_names(self, protocol_type):
# 处理协议类型 # 处理协议类型
if isinstance(protocol_type, str): if isinstance(protocol_type, str):