Compare commits

...

9 Commits

Author SHA1 Message Date
Xuwznln
45eaf7019d registry fix 2025-07-18 23:43:22 +08:00
Xuwznln
84aeb6921d mix 2025-07-18 22:38:40 +08:00
Xuwznln
a95e4d446b container_for_nothing 2025-07-18 22:06:19 +08:00
Guangxin Zhang
de6584f7a8 update 2025-07-18 22:03:12 +08:00
Guangxin Zhang
2c06f94bcf Update 2025-07-18 21:44:57 +08:00
Xuwznln
3d9798476b registry upadte 2025-07-18 21:27:56 +08:00
Guangxin Zhang
c462540484 update 2025-07-18 20:33:15 +08:00
Guangxin Zhang
ad54308046 Merge branch 'dev' of https://github.com/dptech-corp/Uni-Lab-OS into dev 2025-07-18 17:40:48 +08:00
Guangxin Zhang
5823edec8f update 2025-07-18 15:27:10 +08:00
8 changed files with 3111 additions and 2136 deletions

4
.gitignore vendored
View File

@@ -239,4 +239,6 @@ unilabos/device_mesh/view_robot.rviz
# Certs
**/.certs
**/.certs
local_test2.py
ros-humble-unilabos-msgs-0.9.12-h6403a04_5.tar.bz2

File diff suppressed because it is too large Load Diff

View File

@@ -5,9 +5,18 @@
"sample_id": null,
"children": [
"HPLCPlateT1",
"container_for_nothing1",
"container_for_nothing2",
"trash",
"container_for_nothing3",
"container_for_nothing",
"container_for_nothing4",
"RackT8",
"PlateT11"
"container_for_nothing5",
"container_for_nothing6",
"PlateT11",
"container_for_nothing7",
"container_for_nothing8"
],
"parent": "lh_PRCXI_Deck",
"type": "deck",
@@ -4007,6 +4016,68 @@
"liquid_history": []
}
},
{
"id": "container_for_nothing1",
"name": "container_for_nothing1",
"sample_id": null,
"children": [],
"parent": "PRCXI_Deck",
"type": "plate",
"class": "",
"position": {
"x": 0,
"y": 0,
"z": 0
},
"config": {
"type": "PRCXI9300Container",
"size_x": 50,
"size_y": 50,
"size_z": 10,
"rotation": {
"x": 0,
"y": 0,
"z": 0,
"type": "Rotation"
},
"category": "plate",
"model": null,
"barcode": null,
"ordering": {}
},
"data": {}
},
{
"id": "container_for_nothing2",
"name": "container_for_nothing2",
"sample_id": null,
"children": [],
"parent": "PRCXI_Deck",
"type": "plate",
"class": "",
"position": {
"x": 0,
"y": 0,
"z": 0
},
"config": {
"type": "PRCXI9300Container",
"size_x": 50,
"size_y": 50,
"size_z": 10,
"rotation": {
"x": 0,
"y": 0,
"z": 0,
"type": "Rotation"
},
"category": "plate",
"model": null,
"barcode": null,
"ordering": {}
},
"data": {}
},
{
"id": "trash",
"name": "trash",
@@ -4048,6 +4119,99 @@
}
}
},
{
"id": "container_for_nothing3",
"name": "container_for_nothing3",
"sample_id": null,
"children": [],
"parent": "PRCXI_Deck",
"type": "plate",
"class": "",
"position": {
"x": 0,
"y": 0,
"z": 0
},
"config": {
"type": "PRCXI9300Container",
"size_x": 50,
"size_y": 50,
"size_z": 10,
"rotation": {
"x": 0,
"y": 0,
"z": 0,
"type": "Rotation"
},
"category": "plate",
"model": null,
"barcode": null,
"ordering": {}
},
"data": {}
},
{
"id": "container_for_nothing",
"name": "container_for_nothing",
"sample_id": null,
"children": [],
"parent": "PRCXI_Deck",
"type": "plate",
"class": "",
"position": {
"x": 0,
"y": 0,
"z": 0
},
"config": {
"type": "PRCXI9300Container",
"size_x": 50,
"size_y": 50,
"size_z": 10,
"rotation": {
"x": 0,
"y": 0,
"z": 0,
"type": "Rotation"
},
"category": "plate",
"model": null,
"barcode": null,
"ordering": {}
},
"data": {}
},
{
"id": "container_for_nothing4",
"name": "container_for_nothing4",
"sample_id": null,
"children": [],
"parent": "PRCXI_Deck",
"type": "plate",
"class": "",
"position": {
"x": 0,
"y": 0,
"z": 0
},
"config": {
"type": "PRCXI9300Container",
"size_x": 50,
"size_y": 50,
"size_z": 10,
"rotation": {
"x": 0,
"y": 0,
"z": 0,
"type": "Rotation"
},
"category": "plate",
"model": null,
"barcode": null,
"ordering": {}
},
"data": {}
},
{
"id": "RackT8",
"name": "RackT8",
@@ -8118,6 +8282,68 @@
"pending_tip": null
}
},
{
"id": "container_for_nothing5",
"name": "container_for_nothing5",
"sample_id": null,
"children": [],
"parent": "PRCXI_Deck",
"type": "plate",
"class": "",
"position": {
"x": 0,
"y": 0,
"z": 0
},
"config": {
"type": "PRCXI9300Container",
"size_x": 50,
"size_y": 50,
"size_z": 10,
"rotation": {
"x": 0,
"y": 0,
"z": 0,
"type": "Rotation"
},
"category": "plate",
"model": null,
"barcode": null,
"ordering": {}
},
"data": {}
},
{
"id": "container_for_nothing6",
"name": "container_for_nothing6",
"sample_id": null,
"children": [],
"parent": "PRCXI_Deck",
"type": "plate",
"class": "",
"position": {
"x": 0,
"y": 0,
"z": 0
},
"config": {
"type": "PRCXI9300Container",
"size_x": 50,
"size_y": 50,
"size_z": 10,
"rotation": {
"x": 0,
"y": 0,
"z": 0,
"type": "Rotation"
},
"category": "plate",
"model": null,
"barcode": null,
"ordering": {}
},
"data": {}
},
{
"id": "PlateT11",
"name": "PlateT11",
@@ -13241,5 +13467,67 @@
null
]
}
},
{
"id": "container_for_nothing7",
"name": "container_for_nothing7",
"sample_id": null,
"children": [],
"parent": "PRCXI_Deck",
"type": "plate",
"class": "",
"position": {
"x": 0,
"y": 0,
"z": 0
},
"config": {
"type": "PRCXI9300Container",
"size_x": 50,
"size_y": 50,
"size_z": 10,
"rotation": {
"x": 0,
"y": 0,
"z": 0,
"type": "Rotation"
},
"category": "plate",
"model": null,
"barcode": null,
"ordering": {}
},
"data": {}
},
{
"id": "container_for_nothing8",
"name": "container_for_nothing8",
"sample_id": null,
"children": [],
"parent": "PRCXI_Deck",
"type": "plate",
"class": "",
"position": {
"x": 0,
"y": 0,
"z": 0
},
"config": {
"type": "PRCXI9300Container",
"size_x": 50,
"size_y": 50,
"size_z": 10,
"rotation": {
"x": 0,
"y": 0,
"z": 0,
"type": "Rotation"
},
"category": "plate",
"model": null,
"barcode": null,
"ordering": {}
},
"data": {}
}
]

View File

@@ -462,6 +462,27 @@ class PRCXI9300Backend(LiquidHandlerBackend):
async def drop_tips(self, ops: List[Drop], use_channels: List[int] = None):
"""Pick up tips from the specified resource."""
# 检查trash #
if ops[0].resource.name == "trash":
PlateNo = ops[0].resource.parent.children.index(ops[0].resource) + 1
step = self.api_client.UnLoad(
dosage=0,
plate_no=PlateNo,
is_whole_plate=False,
hole_row=3,
hole_col=3,
blending_times=0,
balance_height=0,
plate_or_hole=f"H{1}-8,T{PlateNo}",
hole_numbers="1,2,3,4,5,6,7,8",
)
self.steps_todo_list.append(step)
return
#print(ops[0].resource.parent.children.index(ops[0].resource))
plate_indexes = []
for op in ops:
plate = op.resource.parent.parent
@@ -482,21 +503,6 @@ class PRCXI9300Backend(LiquidHandlerBackend):
PlateNo = plate_indexes[0] + 1
hole_col = tip_columns[0] + 1
if deck.children[plate_index].name == "trash":
step = self.api_client.UnLoad(
dosage=0,
plate_no=PlateNo,
is_whole_plate=False,
hole_row=3,
hole_col=3,
blending_times=0,
balance_height=0,
plate_or_hole=f"H{hole_col}-8,T{PlateNo}",
hole_numbers="1,2,3,4,5,6,7,8",
)
self.steps_todo_list.append(step)
return
if self.channel_num == 1:
hole_row = tipspot_index % 8 + 1
@@ -526,8 +532,6 @@ class PRCXI9300Backend(LiquidHandlerBackend):
"""Mix liquid in the specified resources."""
if len(targets) != 8:
raise ValueError(f"PRCXI9300Backend aspirate: Expected 8 aspirate, got {len(targets)}")
plate_indexes = []
for op in targets:
@@ -549,12 +553,16 @@ class PRCXI9300Backend(LiquidHandlerBackend):
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
assert mix_time > 0
step = self.api_client.Blending(
dosage=mix_vol,
plate_no=PlateNo,
is_whole_plate=False,
hole_row=1,
hole_row=hole_row,
hole_col=hole_col,
blending_times=mix_time,
balance_height=0,
@@ -592,7 +600,7 @@ class PRCXI9300Backend(LiquidHandlerBackend):
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
@@ -633,6 +641,7 @@ class PRCXI9300Backend(LiquidHandlerBackend):
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
@@ -1132,7 +1141,7 @@ if __name__ == "__main__":
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": {
@@ -1215,24 +1224,28 @@ if __name__ == "__main__":
"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(plate2, location=Coordinate(0, 0, 0))
# deck.assign_child_resource(plate3, 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(plate5, location=Coordinate(0, 0, 0))
# deck.assign_child_resource(plate6, location=Coordinate(0, 0, 0))
# deck.assign_child_resource(plate7, 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(plate9, location=Coordinate(0, 0, 0))
# deck.assign_child_resource(plate10, 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(plate12, location=Coordinate(0, 0, 0))
# deck.assign_child_resource(plate13, 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=True,
timeout=10.0, setup=False, debug=False,
matrix_id="fd383e6d-2d0e-40b5-9c01-1b2870b1f1b1",
channel_num=1)
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
@@ -1249,8 +1262,9 @@ if __name__ == "__main__":
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
print(plate8.children[3])
asyncio.run(handler.pick_up_tips([plate8.children[3]],[0]))
asyncio.run(handler.aspirate([plate11.children[0]],[9], [0]))
asyncio.run(handler.aspirate([plate11.children[0]],[10], [0]))
asyncio.run(handler.dispense([plate1.children[3]],[10],[0]))
asyncio.run(handler.mix([plate1.children[3]], mix_time=3, mix_vol=5, height_to_bottom=0.5, offsets=Coordinate(0, 0, 0), mix_rate=100))
asyncio.run(handler.discard_tips())

View File

@@ -5466,6 +5466,354 @@ liquid_handler.biomek:
liquid_handler.prcxi:
class:
action_value_mappings:
add_liquid:
feedback: {}
goal:
asp_vols: asp_vols
blow_out_air_volume: blow_out_air_volume
dis_vols: dis_vols
flow_rates: flow_rates
is_96_well: is_96_well
liquid_height: liquid_height
mix_liquid_height: mix_liquid_height
mix_rate: mix_rate
mix_time: mix_time
mix_vol: mix_vol
none_keys: none_keys
offsets: offsets
reagent_sources: reagent_sources
spread: spread
targets: targets
use_channels: use_channels
goal_default:
asp_vols:
- 0.0
blow_out_air_volume:
- 0.0
dis_vols:
- 0.0
flow_rates:
- 0.0
is_96_well: false
liquid_height:
- 0.0
mix_liquid_height: 0.0
mix_rate: 0
mix_time: 0
mix_vol: 0
none_keys:
- ''
offsets:
- x: 0.0
y: 0.0
z: 0.0
reagent_sources:
- category: ''
children: []
config: ''
data: ''
id: ''
name: ''
parent: ''
pose:
orientation:
w: 1.0
x: 0.0
y: 0.0
z: 0.0
position:
x: 0.0
y: 0.0
z: 0.0
sample_id: ''
type: ''
spread: ''
targets:
- category: ''
children: []
config: ''
data: ''
id: ''
name: ''
parent: ''
pose:
orientation:
w: 1.0
x: 0.0
y: 0.0
z: 0.0
position:
x: 0.0
y: 0.0
z: 0.0
sample_id: ''
type: ''
use_channels:
- 0
handles: []
placeholder_keys:
reagent_sources: unilabos_resources
targets: unilabos_resources
result: {}
schema:
description: ''
properties:
feedback:
properties: {}
required: []
title: LiquidHandlerAdd_Feedback
type: object
goal:
properties:
asp_vols:
items:
type: number
type: array
blow_out_air_volume:
items:
type: number
type: array
dis_vols:
items:
type: number
type: array
flow_rates:
items:
type: number
type: array
is_96_well:
type: boolean
liquid_height:
items:
type: number
type: array
mix_liquid_height:
type: number
mix_rate:
maximum: 2147483647
minimum: -2147483648
type: integer
mix_time:
maximum: 2147483647
minimum: -2147483648
type: integer
mix_vol:
maximum: 2147483647
minimum: -2147483648
type: integer
none_keys:
items:
type: string
type: array
offsets:
items:
properties:
x:
type: number
y:
type: number
z:
type: number
required:
- x
- y
- z
title: Point
type: object
type: array
reagent_sources:
items:
properties:
category:
type: string
children:
items:
type: string
type: array
config:
type: string
data:
type: string
id:
type: string
name:
type: string
parent:
type: string
pose:
properties:
orientation:
properties:
w:
type: number
x:
type: number
y:
type: number
z:
type: number
required:
- x
- y
- z
- w
title: Quaternion
type: object
position:
properties:
x:
type: number
y:
type: number
z:
type: number
required:
- x
- y
- z
title: Point
type: object
required:
- position
- orientation
title: Pose
type: object
sample_id:
type: string
type:
type: string
required:
- id
- name
- sample_id
- children
- parent
- type
- category
- pose
- config
- data
title: Resource
type: object
type: array
spread:
type: string
targets:
items:
properties:
category:
type: string
children:
items:
type: string
type: array
config:
type: string
data:
type: string
id:
type: string
name:
type: string
parent:
type: string
pose:
properties:
orientation:
properties:
w:
type: number
x:
type: number
y:
type: number
z:
type: number
required:
- x
- y
- z
- w
title: Quaternion
type: object
position:
properties:
x:
type: number
y:
type: number
z:
type: number
required:
- x
- y
- z
title: Point
type: object
required:
- position
- orientation
title: Pose
type: object
sample_id:
type: string
type:
type: string
required:
- id
- name
- sample_id
- children
- parent
- type
- category
- pose
- config
- data
title: Resource
type: object
type: array
use_channels:
items:
maximum: 2147483647
minimum: -2147483648
type: integer
type: array
required:
- asp_vols
- dis_vols
- reagent_sources
- targets
- use_channels
- flow_rates
- offsets
- liquid_height
- blow_out_air_volume
- spread
- is_96_well
- mix_time
- mix_vol
- mix_rate
- mix_liquid_height
- none_keys
title: LiquidHandlerAdd_Goal
type: object
result:
properties:
return_info:
type: string
success:
type: boolean
required:
- return_info
- success
title: LiquidHandlerAdd_Result
type: object
required:
- goal
title: LiquidHandlerAdd
type: object
type: LiquidHandlerAdd
aspirate:
feedback: {}
goal:
@@ -5669,84 +6017,6 @@ liquid_handler.prcxi:
title: LiquidHandlerAspirate
type: object
type: LiquidHandlerAspirate
auto-add_liquid:
feedback: {}
goal: {}
goal_default:
asp_vols: null
blow_out_air_volume: null
delays: null
dis_vols: null
flow_rates: null
is_96_well: false
liquid_height: null
mix_liquid_height: null
mix_rate: null
mix_time: null
mix_vol: null
none_keys: []
offsets: null
reagent_sources: null
spread: wide
targets: null
use_channels: null
handles: []
result: {}
schema:
description: add_liquid的参数schema
properties:
feedback: {}
goal:
properties:
asp_vols:
type: string
blow_out_air_volume:
type: string
delays:
type: string
dis_vols:
type: string
flow_rates:
type: string
is_96_well:
default: false
type: boolean
liquid_height:
type: string
mix_liquid_height:
type: string
mix_rate:
type: string
mix_time:
type: string
mix_vol:
type: string
none_keys:
default: []
type: array
offsets:
type: string
reagent_sources:
type: string
spread:
default: wide
type: string
targets:
type: string
use_channels:
type: string
required:
- asp_vols
- dis_vols
- reagent_sources
- targets
type: object
result: {}
required:
- goal
title: add_liquid参数
type: object
type: UniLabJsonCommandAsync
auto-create_protocol:
feedback: {}
goal: {}
@@ -6178,6 +6448,8 @@ liquid_handler.prcxi:
vols:
- 0.0
handles: []
placeholder_keys:
resources: unilabos_resources
result: {}
schema:
description: ''
@@ -6535,6 +6807,8 @@ liquid_handler.prcxi:
sample_id: ''
type: ''
handles: []
placeholder_keys:
targets: unilabos_resources
result: {}
schema:
description: ''
@@ -6847,7 +7121,10 @@ liquid_handler.prcxi:
type: LiquidHandlerPickUpTips
set_liquid:
feedback: {}
goal: {}
goal:
liquid_names: liquid_names
volumes: volumes
wells: wells
goal_default:
liquid_names:
- ''

View File

@@ -305,6 +305,8 @@ workstation:
data_type: resource
handler_key: VesselOut
label: Vessel
placeholder_keys:
vessel: unilabos_resources
result: {}
schema:
description: ''

View File

@@ -461,7 +461,6 @@ def resource_plr_to_ulab(resource_plr: "ResourcePLR", parent_name: str = None):
"data": all_states[d["name"]],
}
return r
d = resource_plr.serialize()
all_states = resource_plr.serialize_all_state()
r = resource_plr_to_ulab_inner(d, all_states)

View File

@@ -10,6 +10,15 @@ class DeviceNodeResourceTracker(object):
self.resource2parent_resource = {}
pass
def prefix_path(self, resource):
resource_prefix_path = "/"
resource_parent = getattr(resource, "parent", None)
while resource_parent is not None:
resource_prefix_path = f"/{resource_parent.name}" + resource_prefix_path
resource_parent = resource_parent.parent
return resource_prefix_path
def parent_resource(self, resource):
if id(resource) in self.resource2parent_resource:
return self.resource2parent_resource[id(resource)]