Compare commits

..

11 Commits

Author SHA1 Message Date
Guangxin Zhang
27132bbbc1 Merge branch 'dev' of https://github.com/dptech-corp/Uni-Lab-OS into dev 2025-07-19 15:16:11 +08:00
Guangxin Zhang
a8cf389b00 Update 2025-07-19 15:16:08 +08:00
Xuwznln
98e9d09583 slim 2025-07-19 15:01:00 +08:00
Xuwznln
1467b9ac91 default param simulator 2025-07-19 14:04:58 +08:00
Xuwznln
c57bb2abbd Merge branch 'dev' of https://github.com/dptech-corp/Uni-Lab-OS into dev 2025-07-19 13:32:35 +08:00
Xuwznln
34dd65d280 bump version to 0.10.0 2025-07-19 13:22:06 +08:00
Guangxin Zhang
b08026f861 Update .gitignore 2025-07-19 13:19:27 +08:00
Guangxin Zhang
826b04f515 Merge branch 'dev' of https://github.com/dptech-corp/Uni-Lab-OS into dev 2025-07-19 13:18:52 +08:00
Guangxin Zhang
de4da95616 update 2025-07-19 13:18:03 +08:00
Xuwznln
3c9cca88ea 采用http报送resource 2025-07-19 11:09:24 +08:00
Xuwznln
fd18b21147 采用http报送resource 2025-07-19 10:38:58 +08:00
15 changed files with 552 additions and 372 deletions

1
.gitignore vendored
View File

@@ -242,3 +242,4 @@ unilabos/device_mesh/view_robot.rviz
**/.certs
local_test2.py
ros-humble-unilabos-msgs-0.9.13-h6403a04_5.tar.bz2
*.bz2

View File

@@ -49,7 +49,7 @@ conda env update --file unilabos-[YOUR_OS].yml -n environment_name
# Currently, you need to install the `unilabos_msgs` package
# You can download the system-specific package from the Release page
conda install ros-humble-unilabos-msgs-0.9.13-xxxxx.tar.bz2
conda install ros-humble-unilabos-msgs-0.10.0-xxxxx.tar.bz2
# Install PyLabRobot and other prerequisites
git clone https://github.com/PyLabRobot/pylabrobot plr_repo

View File

@@ -49,7 +49,7 @@ conda env update --file unilabos-[YOUR_OS].yml -n 环境名
# 现阶段,需要安装 `unilabos_msgs` 包
# 可以前往 Release 页面下载系统对应的包进行安装
conda install ros-humble-unilabos-msgs-0.9.13-xxxxx.tar.bz2
conda install ros-humble-unilabos-msgs-0.10.0-xxxxx.tar.bz2
# 安装PyLabRobot等前置
git clone https://github.com/PyLabRobot/pylabrobot plr_repo

View File

@@ -1,6 +1,6 @@
package:
name: ros-humble-unilabos-msgs
version: 0.9.13
version: 0.10.0
source:
path: ../../unilabos_msgs
folder: ros-humble-unilabos-msgs/src/work

View File

@@ -1,6 +1,6 @@
package:
name: unilabos
version: "0.9.13"
version: "0.10.0"
source:
path: ../..

View File

@@ -4,7 +4,7 @@ package_name = 'unilabos'
setup(
name=package_name,
version='0.9.13',
version='0.10.0',
packages=find_packages(),
include_package_data=True,
install_requires=['setuptools'],

View File

@@ -0,0 +1,70 @@
{
"nodes": [
{
"id": "OrganicSynthesisStation",
"name": "有机化学流程综合测试工作站",
"children": [
"heater_1"
],
"parent": null,
"type": "device",
"class": "workstation",
"position": {
"x": 600,
"y": 400,
"z": 0
},
"config": {
"protocol_type": [
"AddProtocol",
"TransferProtocol",
"StartStirProtocol",
"StopStirProtocol",
"StirProtocol",
"RunColumnProtocol",
"CentrifugeProtocol",
"FilterProtocol",
"CleanVesselProtocol",
"DissolveProtocol",
"FilterThroughProtocol",
"WashSolidProtocol",
"SeparateProtocol",
"EvaporateProtocol",
"HeatChillProtocol",
"HeatChillStartProtocol",
"HeatChillStopProtocol",
"EvacuateAndRefillProtocol",
"PumpTransferProtocol",
"AdjustPHProtocol",
"ResetHandlingProtocol",
"DryProtocol",
"HydrogenateProtocol",
"RecrystallizeProtocol"
]
},
"data": {}
},
{
"id": "heater_1",
"name": "加热器",
"children": [],
"parent": "OrganicSynthesisStation",
"type": "device",
"class": "virtual_heatchill",
"position": {
"x": 450,
"y": 450,
"z": 0
},
"config": {
"max_temp": 200.0,
"min_temp": -20.0
},
"data": {
"status": "Idle",
"current_temp": 25.0
}
}
],
"links": []
}

View File

@@ -21,7 +21,7 @@
"timeout": 10.0,
"axis": "Right",
"channel_num": 1,
"setup": false,
"setup": true,
"debug": false,
"simulator": false,
"matrix_id": "fd383e6d-2d0e-40b5-9c01-1b2870b1f1b1"

View File

@@ -18,10 +18,22 @@ def register_devices_and_resources(mqtt_client, lab_registry):
mqtt_client.publish_registry(device_info["id"], device_info, False)
logger.debug(f"[UniLab Register] 注册设备: {device_info['id']}")
# 注册资源信息
# 注册资源信息 - 使用HTTP方式
from unilabos.app.web.client import http_client
resources_to_register = {}
for resource_info in lab_registry.obtain_registry_resource_info():
mqtt_client.publish_registry(resource_info["id"], resource_info, False)
logger.debug(f"[UniLab Register] 注册资源: {resource_info['id']}")
resources_to_register[resource_info["id"]] = resource_info
logger.debug(f"[UniLab Register] 准备注册资源: {resource_info['id']}")
if resources_to_register:
start_time = time.time()
response = http_client.resource_registry(resources_to_register)
cost_time = time.time() - start_time
if response.status_code in [200, 201]:
logger.info(f"[UniLab Register] 成功通过HTTP注册 {len(resources_to_register)} 个资源 {cost_time}ms")
else:
logger.error(f"[UniLab Register] HTTP注册资源失败: {response.status_code}, {response.text} {cost_time}ms")
time.sleep(10)
@@ -53,11 +65,9 @@ def main():
help="是否补全注册表",
)
args = parser.parse_args()
load_config_from_file(args.config)
# 构建注册表
build_registry(args.registry, args.complete_registry)
load_config_from_file(args.config)
from unilabos.app.mq import mqtt_client
# 连接mqtt
@@ -70,4 +80,4 @@ def main():
if __name__ == "__main__":
main()
main()

View File

@@ -40,8 +40,9 @@ class HTTPClient:
Returns:
Response: API响应对象
"""
database_param = 1 if database_process_later else 0
response = requests.post(
f"{self.remote_addr}/lab/resource/edge/batch_create/?database_process_later={1 if database_process_later else 0}",
f"{self.remote_addr}/lab/resource/edge/batch_create/?database_process_later={database_param}",
json=resources,
headers={"Authorization": f"lab {self.auth}"},
timeout=100,
@@ -149,6 +150,26 @@ class HTTPClient:
)
return response
def resource_registry(self, registry_data: Dict[str, Any]) -> requests.Response:
"""
注册资源到服务器
Args:
registry_data: 注册表数据,格式为 {resource_id: resource_info}
Returns:
Response: API响应对象
"""
response = requests.post(
f"{self.remote_addr}/lab/registry/",
json=registry_data,
headers={"Authorization": f"lab {self.auth}"},
timeout=30,
)
if response.status_code not in [200, 201]:
logger.error(f"注册资源失败: {response.status_code}, {response.text}")
return response
# 创建默认客户端实例
http_client = HTTPClient()

View File

@@ -104,6 +104,8 @@ class LiquidHandlerMiddleware(LiquidHandler):
offsets: Optional[List[Coordinate]] = None,
**backend_kwargs,
):
print('222'*200)
print(tip_spots)
if self._simulator:
return await self._simulate_handler.pick_up_tips(tip_spots, use_channels, offsets, **backend_kwargs)
return await super().pick_up_tips(tip_spots, use_channels, offsets, **backend_kwargs)
@@ -533,7 +535,7 @@ class LiquidHandlerAbstract(LiquidHandlerMiddleware):
"""Extended LiquidHandler with additional operations."""
support_touch_tip = True
def __init__(self, backend: LiquidHandlerBackend, deck: Deck, simulator: bool, channel_num:int = 8):
def __init__(self, backend: LiquidHandlerBackend, deck: Deck, simulator: bool=False, channel_num:int = 8):
"""Initialize a LiquidHandler.
Args:
@@ -620,12 +622,10 @@ class LiquidHandlerAbstract(LiquidHandlerMiddleware):
spread=spread,
)
await self.discard_tips()
elif len(use_channels) == 8 and self.backend.num_channels == 8:
tip = []
for _ in range(len(use_channels)):
tip.extend(next(self.current_tip))
await self.pick_up_tips(tip)
elif len(use_channels) == 8 and self.backend.num_channels == 8:
# 对于8个的情况需要判断此时任务是不是能被8通道移液站来成功处理
if len(sources) % 8 != 0:
raise ValueError(f"Length of `sources` {len(sources)} must be a multiple of 8 for 8-channel mode.")
@@ -633,7 +633,6 @@ class LiquidHandlerAbstract(LiquidHandlerMiddleware):
# 8个8个来取任务序列
for i in range(0, len(sources), 8):
# 取出8个任务
tip = []
for _ in range(len(use_channels)):
tip.extend(next(self.current_tip))
@@ -762,19 +761,16 @@ class LiquidHandlerAbstract(LiquidHandlerMiddleware):
await self.custom_delay(seconds=delays[1])
await self.touch_tip(targets[_])
await self.discard_tips()
elif len(use_channels) == 8:
elif len(use_channels) == 8:
# 对于8个的情况需要判断此时任务是不是能被8通道移液站来成功处理
if len(targets) % 8 != 0:
raise ValueError(f"Length of `targets` {len(targets)} must be a multiple of 8 for 8-channel mode.")
# 8个8个来取任务序列
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):
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_reagent_sources = reagent_sources[i:i + 8]
current_asp_vols = asp_vols[i:i + 8]
@@ -824,7 +820,7 @@ class LiquidHandlerAbstract(LiquidHandlerMiddleware):
if delays is not None:
await self.custom_delay(seconds=delays[1])
await self.touch_tip(current_targets)
await self.discard_tips()
await self.discard_tips()
# except Exception as e:

View File

@@ -455,7 +455,7 @@ class PRCXI9300Backend(LiquidHandlerBackend):
# print(plate_indexes[0])
PlateNo = plate_indexes[0] + 1
hole_col = tip_columns[0] + 1
hole_row = 1
if self._num_channels == 1:
hole_row = tipspot_index % 8 + 1
@@ -476,7 +476,7 @@ class PRCXI9300Backend(LiquidHandlerBackend):
dosage=0,
plate_no=PlateNo,
is_whole_plate=False,
hole_row=3,
hole_row=1,
hole_col=3,
blending_times=0,
balance_height=0,
@@ -974,156 +974,9 @@ if __name__ == "__main__":
# # 2. backend需要支持num channel为1的情况
# # 3. 设计一个单点动作流程,可以跑
# # 4.
# deck = PRCXI9300Deck(name="PRCXI_Deck", size_x=100, size_y=100, size_z=100)
# plate1 = PRCXI9300Container(name="rackT1", size_x=50, size_y=50, size_z=10, category="plate", ordering=collections.OrderedDict())
# plate1.load_state({
# "Material": {
# "uuid": "80652665f6a54402b2408d50b40398df",
# "Co#de": "ZX-001-1000",
# "Name": "1000μL Tip头",
# "SummaryName": "1000μL Tip头",
# "PipetteHeight": 100,
# "materialEnum": 1
# }
# })
# plate2 = PRCXI9300Container(name="plateT2", size_x=50, size_y=50, size_z=10, category="plate", ordering=collections.OrderedDict())
# plate2.load_state({
# "Material": {
# "uuid": "57b1e4711e9e4a32b529f3132fc5931f",
# }
# })
# plate3 = PRCXI9300Container(name="plateT3", size_x=50, size_y=50, size_z=10, category="plate", ordering=collections.OrderedDict())
# plate3.load_state({
# "Material": {
# "uuid": "57b1e4711e9e4a32b529f3132fc5931f",
# }
# })
# plate4 = PRCXI9300Container(name="rackT4", size_x=50, size_y=50, size_z=10, category="plate", ordering=collections.OrderedDict())
# plate4.load_state({
# "Material": {
# "uuid": "80652665f6a54402b2408d50b40398df",
# "Code": "ZX-001-1000",
# "Name": "1000μL Tip头",
# "SummaryName": "1000μL Tip头",
# "PipetteHeight": 100,
# "materialEnum": 1
# }
# })
# plate5 = PRCXI9300Container(name="plateT5", size_x=50, size_y=50, size_z=10, category="plate", ordering=collections.OrderedDict())
# plate5.load_state({
# "Material": {
# "uuid": "57b1e4711e9e4a32b529f3132fc5931f",
# }
# })
# plate6 = PRCXI9300Trash(name="trash", size_x=50, size_y=500, size_z=10, category="trash")
# plate6.load_state({
# "Material": {
# "uuid": "57b1e4711e9e4a32b529f3132fc5931f",
# }
# })
# from pylabrobot.resources.opentrons.tip_racks import tipone_96_tiprack_200ul
# from pylabrobot.resources.opentrons.plates import corning_96_wellplate_360ul_flat
# tip_rack = tipone_96_tiprack_200ul("TipRack")
# well_containers = corning_96_wellplate_360ul_flat("Plate")
# # from pprint import pprint
# # pprint(well_containers.children)
# plate1.assign_child_resource(tip_rack, location=Coordinate(0, 0, 0))
# plate2.assign_child_resource(well_containers, 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(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 = [[(None, 500)]]*96
# # plate2.set_well_liquids(plate_2_liquids)
# handler = PRCXI9300Handler(deck=deck, host="192.168.3.9", port=9999, timeout=10.0, setup=False, debug=True, matrix_id="fd383e6d-2d0e-40b5-9c01-1b2870b1f1b1")
# handler.set_tiprack([tip_rack]) # 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)
# plate2.set_well_liquids([("Water", 100)] * plate2.num_items)
# asyncio.run(handler.create_protocol(protocol_name="Test Protocol")) # Initialize the backend and setup the connection
# # asyncio.run(handler.pick_up_tips(tip_rack.children[:8],[0,1,2,3,4,5,6,7]))
# # asyncio.run(handler.aspirate(well_containers.children[:8],[50]*8, [0,1,2,3,4,5,6,7]))
# # asyncio.run(handler.dispense(well_containers.children[:8],[50]*8,[0,1,2,3,4,5,6,7]))
# # 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=well_containers.children[-16:],
# targets=well_containers.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.remove_liquid(
# # vols=[100]*16,
# # sources=well_containers.children[-16:],
# # waste_liquid=well_containers.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",
# # ))
# # asyncio.run(handler.transfer_liquid(
# # asp_vols=[100]*16,
# # dis_vols=[100]*16,
# # tip_racks=[tip_rack],
# # sources=well_containers.children[-16:],
# # targets=well_containers.children[:16],
# # 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",
# # ))
# print(json.dumps(handler._unilabos_backend.steps_todo_list, indent=2)) # Print matrix info
# # input("pick_up_tips add step")
# #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.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:
@@ -1137,7 +990,7 @@ if __name__ == "__main__":
return new_plate
def get_tip_rack(name: str) -> PRCXI9300Container:
tip_racks = opentrons_96_tiprack_10ul("name").serialize()
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()
@@ -1146,145 +999,320 @@ if __name__ == "__main__":
new_tip_rack: PRCXI9300Container = PRCXI9300Container.deserialize(tip_racks)
return new_tip_rack
plate1 = get_well_container("HPLCPlateT1")
plate1 = get_tip_rack("RackT1")
plate1.load_state({
"Material": {
"uuid": "548bbc3df0d4447586f2c19d2c0c0c55",
"Code": "HPLC01",
"Name": "HPLC料盘"
"uuid": "076250742950465b9d6ea29a225dfb00",
"Code": "ZX-001-300",
"Name": "300μL Tip头"
}
})
plate2 = get_well_container("PlateT2")
plate2.load_state({
"Material": {
"uuid": "04211a2dc93547fe9bf6121eac533650",
"uuid": "57b1e4711e9e4a32b529f3132fc5931f",
"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({
"Material": {
"uuid": "04211a2dc93547fe9bf6121eac533650",
}
})
trash = PRCXI9300Trash(name="trash", size_x=50, size_y=50, size_z=10, category="trash")
trash.load_state({
"Material": {
"uuid": "730067cf07ae43849ddf4034299030e9"
}
})
plate4 = get_tip_rack("RackT4")
plate4.load_state({
"Material": {
"uuid": "076250742950465b9d6ea29a225dfb00",
"Code": "ZX-001-300",
"Name": "300μL Tip头"
}
})
plate5 = get_well_container("PlateT5")
plate5.load_state({
"Material": {
"uuid": "04211a2dc93547fe9bf6121eac533650",
"uuid": "57b1e4711e9e4a32b529f3132fc5931f",
"Code": "ZX-019-2.2",
"Name": "96深孔板"
}
})
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",
"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(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))
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))
handler = PRCXI9300Handler(deck=deck, host="10.181.102.13", port=9999,
# 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="fd383e6d-2d0e-40b5-9c01-1b2870b1f1b1",
channel_num=1, axis="Right") # Initialize the handler with the deck and host settings
matrix_id="71593",
channel_num=8, axis="Left") # 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
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(plate1.children[:8],[0,1,2,3,4,5,6,7]))
# 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.aspirate(plate2.children[:8],[50]*8, [0,1,2,3,4,5,6,7]))
# asyncio.run(handler.dispense(plate5.children[:8],[50]*8,[0,1,2,3,4,5,6,7]))
# # # 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.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",
# ))
asyncio.run(handler.transfer_liquid(
asp_vols=[100]*16,
dis_vols=[100]*16,
tip_racks=[plate1],
sources=plate2.children[-16:],
targets=plate5.children[:16],
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",
))
print(json.dumps(handler._unilabos_backend.steps_todo_list, indent=2)) # Print matrix info
# # input("pick_up_tips add step")
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]*2,
# dis_vols=[10]*2,
# asp_vols=[10]*7,
# dis_vols=[10]*7,
# reagent_sources=[plate11.children[0]],
# targets=plate11.children[-2:],
# targets=plate1.children[2:9],
# use_channels=[0],
# flow_rates=[None] * 4,
# offsets=[Coordinate(0, 0, 0)] * 4,
@@ -1296,33 +1324,33 @@ if __name__ == "__main__":
# spread="wide",
# ))
# asyncio.run(handler.transfer_liquid(
# asp_vols=[10]*2,
# dis_vols=[10]*2,
# sources=plate11.children[:2],
# targets=plate11.children[-2:],
# use_channels=[0],
# offsets=[Coordinate(0, 0, 0)] * 4,
# liquid_height=[None] * 2,
# blow_out_air_volume=[None] * 2,
# delays=None,
# mix_times=3,
# mix_vol=5,
# spread="wide",
# tip_racks=[plate8]
# ))
# # asyncio.run(handler.transfer_liquid(
# # asp_vols=[10]*2,
# # dis_vols=[10]*2,
# # sources=plate11.children[:2],
# # targets=plate11.children[-2:],
# # use_channels=[0],
# # offsets=[Coordinate(0, 0, 0)] * 4,
# # liquid_height=[None] * 2,
# # blow_out_air_volume=[None] * 2,
# # delays=None,
# # mix_times=3,
# # mix_vol=5,
# # spread="wide",
# # tip_racks=[plate8]
# # ))
asyncio.run(handler.remove_liquid(
vols=[10]*2,
sources=plate11.children[:2],
waste_liquid=plate11.children[43],
use_channels=[0],
offsets=[Coordinate(0, 0, 0)] * 4,
liquid_height=[None] * 2,
blow_out_air_volume=[None] * 2,
delays=None,
spread="wide"
))
# # asyncio.run(handler.remove_liquid(
# # vols=[10]*2,
# # sources=plate11.children[:2],
# # waste_liquid=plate11.children[43],
# # use_channels=[0],
# # offsets=[Coordinate(0, 0, 0)] * 4,
# # liquid_height=[None] * 2,
# # blow_out_air_volume=[None] * 2,
# # delays=None,
# # spread="wide"
# # ))
@@ -1331,45 +1359,45 @@ if __name__ == "__main__":
asyncio.run(handler.run_protocol())
# asyncio.run(handler.run_protocol())
# 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.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.remove_liquid(
# vols=[100]*16,
# sources=well_containers.children[-16:],
# waste_liquid=well_containers.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",
# ))
# asyncio.run(handler.transfer_liquid(
# asp_vols=[100]*16,
# dis_vols=[100]*16,
# tip_racks=[tip_rack],
# sources=well_containers.children[-16:],
# targets=well_containers.children[:16],
# 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",
# ))
print(json.dumps(handler._unilabos_backend.steps_todo_list, indent=2)) # Print matrix info
# input("pick_up_tips add step")
#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.")
# # asyncio.run(handler.remove_liquid(
# # vols=[100]*16,
# # sources=well_containers.children[-16:],
# # waste_liquid=well_containers.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",
# # ))
# # asyncio.run(handler.transfer_liquid(
# # asp_vols=[100]*16,
# # dis_vols=[100]*16,
# # tip_racks=[tip_rack],
# # sources=well_containers.children[-16:],
# # targets=well_containers.children[:16],
# # 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",
# # ))
# print(json.dumps(handler._unilabos_backend.steps_todo_list, indent=2)) # Print matrix info
# # input("pick_up_tips add step")
# #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.")

View File

@@ -4580,6 +4580,7 @@ virtual_solid_dispenser:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
@@ -4587,6 +4588,30 @@ virtual_solid_dispenser:
title: cleanup参数
type: object
type: UniLabJsonCommandAsync
auto-find_solid_reagent_bottle:
feedback: {}
goal: {}
goal_default:
reagent_name: null
handles: []
result: {}
schema:
description: ''
properties:
feedback: {}
goal:
properties:
reagent_name:
type: string
required:
- reagent_name
type: object
result: {}
required:
- goal
title: find_solid_reagent_bottle参数
type: object
type: UniLabJsonCommand
auto-initialize:
feedback: {}
goal: {}
@@ -4599,6 +4624,7 @@ virtual_solid_dispenser:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
@@ -4606,9 +4632,58 @@ virtual_solid_dispenser:
title: initialize参数
type: object
type: UniLabJsonCommandAsync
auto-parse_mass_string:
feedback: {}
goal: {}
goal_default:
mass_str: null
handles: []
result: {}
schema:
description: ''
properties:
feedback: {}
goal:
properties:
mass_str:
type: string
required:
- mass_str
type: object
result: {}
required:
- goal
title: parse_mass_string参数
type: object
type: UniLabJsonCommand
auto-parse_mol_string:
feedback: {}
goal: {}
goal_default:
mol_str: null
handles: []
result: {}
schema:
description: ''
properties:
feedback: {}
goal:
properties:
mol_str:
type: string
required:
- mol_str
type: object
result: {}
required:
- goal
title: parse_mol_string参数
type: object
type: UniLabJsonCommand
module: unilabos.devices.virtual.virtual_solid_dispenser:VirtualSolidDispenser
status_types:
current_reagent: str
device_info: dict
dispensed_amount: float
status: str
total_operations: int
@@ -4641,18 +4716,14 @@ virtual_solid_dispenser:
type: object
device_id:
type: string
max_capacity:
default: 100.0
type: number
precision:
default: 0.001
type: number
required: []
type: object
data:
properties:
current_reagent:
type: string
device_info:
type: object
dispensed_amount:
type: number
status:
@@ -4664,6 +4735,7 @@ virtual_solid_dispenser:
- current_reagent
- dispensed_amount
- total_operations
- device_info
type: object
version: 1.0.0
virtual_stirrer:

View File

@@ -474,12 +474,12 @@ workstation:
goal:
ph_value: ph_value
reagent: reagent
vessel: vessel
volume: volume
settling_time: settling_time
stir: stir
stir_speed: stir_speed
stir_time: stir_time
settling_time: settling_time
vessel: vessel
volume: volume
goal_default:
ph_value: 0.0
reagent: ''
@@ -503,11 +503,6 @@ workstation:
z: 0.0
sample_id: ''
type: ''
volume: 0.0
stir: false
stir_speed: 300.0
stir_time: 60.0
settling_time: 30.0
handles:
input:
- data_key: vessel
@@ -622,21 +617,6 @@ workstation:
- data
title: Resource
type: object
volume:
type: number
description: 'Volume of the solution to adjust pH'
stir:
type: boolean
description: "是否启用搅拌"
stir_speed:
type: number
description: "搅拌速度(RPM)"
stir_time:
type: number
description: "搅拌时间(秒)"
settling_time:
type: number
description: "pH平衡时间(秒)"
required:
- vessel
- ph_value
@@ -2248,7 +2228,7 @@ workstation:
type: number
required:
- vessel
- #filtrate_vessel
- filtrate_vessel
- stir
- stir_speed
- temp

View File

@@ -127,6 +127,8 @@ class Registry:
},
},
"version": "1.0.0",
"category": [],
"config_info": [],
"icon": "icon_device.webp",
"registry_type": "device",
"handles": [],