Compare commits

...

11 Commits

Author SHA1 Message Date
Xuwznln
c7b9c6a825 fix handles not as default entry 2025-06-06 18:13:53 +08:00
Xuwznln
48c43d3303 fix biomek startup
add action handles
2025-06-06 17:45:54 +08:00
Xuwznln
55be5e8188 registry 2025-06-06 17:21:19 +08:00
qxw138
1b9f3c666d 1 2025-06-06 14:44:17 +08:00
qxw138
097114d38c new actions 2025-06-06 14:31:10 +08:00
qxw138
5bec899479 new actions 2025-06-06 13:56:39 +08:00
Guangxin Zhang
5e86112ebf Merge branch '37-biomek-i5i7' of https://github.com/dptech-corp/Uni-Lab-OS into 37-biomek-i5i7 2025-06-06 13:25:34 +08:00
Guangxin Zhang
24ecb13b79 Update 2025-06-06 13:22:15 +08:00
qxw138
2573d34713 Merge branch '37-biomek-i5i7' of https://github.com/dptech-corp/Uni-Lab-OS into 37-biomek-i5i7 2025-06-06 13:18:42 +08:00
qxw138
46da42deef Merge branch '37-biomek-i5i7' of https://github.com/dptech-corp/Uni-Lab-OS into 37-biomek-i5i7 2025-06-06 00:13:11 +08:00
qxw138
a62112ae26 new action 2025-06-05 17:26:36 +08:00
8 changed files with 466 additions and 277 deletions

View File

@@ -32,46 +32,63 @@ class LiquidHandlerBiomek(LiquidHandlerAbstract):
self.py32_path = "/opt/py32" # Biomek的Python 3.2路径 self.py32_path = "/opt/py32" # Biomek的Python 3.2路径
self.aspirate_techniques = { self.aspirate_techniques = {
'MC P300 high':{ 'MC P300 high':{
"Solvent": "Water", 'Position': 'P1',
} 'Height': -2.0,
'Volume': '50',
'liquidtype': 'Well Contents',
'WellsX': 12,
'LabwareClass': 'Matrix96_750uL',
'AutoSelectPrototype': True,
'ColsFirst': True,
'CustomHeight': False,
'DataSetPattern': False,
'HeightFrom': 0,
'LocalPattern': True,
'Operation': 'Aspirate',
'OverrideHeight': False,
'Pattern': (True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True),
'Prototype': 'MC P300 High',
'ReferencedPattern': '',
'RowsFirst': False,
'SectionExpression': '',
'SelectionInfo': (1,),
'SetMark': True,
'Source': True,
'StartAtMark': False,
'StartAtSelection': True,
'UseExpression': False},
} }
self.dispense_techniques = { self.dispense_techniques = {
'MC P300 high':{ 'MC P300 high':{
"Span8": False, 'Position': 'P11',
"Pod": "Pod1", 'Height': -2.0,
"Wash": False, 'Volume': '50',
"Dynamic?": True, 'liquidtype': 'Tip Contents',
"AutoSelectActiveWashTechnique": False, 'WellsX': 12,
"ActiveWashTechnique": "", 'LabwareClass': 'Matrix96_750uL',
"ChangeTipsBetweenDests": True, 'AutoSelectPrototype': True,
"ChangeTipsBetweenSources": False, 'ColsFirst': True,
"DefaultCaption": "", 'CustomHeight': False,
"UseExpression": False, 'DataSetPattern': False,
"LeaveTipsOn": False, 'HeightFrom': 0,
"MandrelExpression": "", 'LocalPattern': True,
"Repeats": "1", 'Operation': 'Dispense',
"RepeatsByVolume": False, 'OverrideHeight': False,
"Replicates": "1", 'Pattern': (True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True),
"ShowTipHandlingDetails": False, 'Prototype': 'MC P300 High',
"ShowTransferDetails": True, 'ReferencedPattern': '',
"Span8Wash": False, 'RowsFirst': False,
"Span8WashVolume": "2", 'SectionExpression': '',
"Span8WasteVolume": "1", 'SelectionInfo': (1,),
"SplitVolume": False, 'SetMark': True,
"SplitVolumeCleaning": False, 'Source': False,
"Stop": "Destinations", 'StartAtMark': False,
"UseCurrentTips": False, 'StartAtSelection': True,
"UseDisposableTips": False, 'UseExpression': False}
"UseFixedTips": False,
"UseJIT": True,
"UseMandrelSelection": True,
"UseProbes": [True, True, True, True, True, True, True, True],
"WashCycles": "3",
"WashVolume": "110%",
"Wizard": False
}
} }
@classmethod @classmethod
def deserialize(cls, data: dict, allow_marshal: bool = False) -> LiquidHandler: def deserialize(cls, data: dict, allow_marshal: bool = False) -> LiquidHandler:
return LiquidHandler.deserialize(data, allow_marshal) return LiquidHandler.deserialize(data, allow_marshal)
@@ -347,9 +364,63 @@ class LiquidHandlerBiomek(LiquidHandlerAbstract):
return return
def move_biomek(
self,
source: str,
target: str,
):
"""
处理Biomek移动板子的操作。
"""
move_params = {
"Pod": "Pod1",
"GripSide": "A1 near",
"Source": source,
"Target": target,
"LeaveBottomLabware": False,
}
self.temp_protocol["steps"].append(move_params)
return
def incubation_biomek(
self,
time: int,
):
"""
处理Biomek的孵育操作。
"""
incubation_params = {
"Message": "Paused",
"Location": "the whole system",
"Time": time,
"Mode": "TimedResource"
}
self.temp_protocol["steps"].append(incubation_params)
return
def oscillation_biomek(
self,
rpm: int,
time: int,
):
"""
处理Biomek的振荡操作。
"""
oscillation_params = {
'Device': 'OrbitalShaker0',
'Parameters': (str(rpm), '2', str(time), 'CounterClockwise'),
'Command': 'Timed Shake'
}
self.temp_protocol["steps"].append(oscillation_params)
return
if __name__ == "__main__": if __name__ == "__main__":
steps_info = ''' steps_info = '''
{ {
"steps": [ "steps": [
@@ -612,11 +683,8 @@ if __name__ == "__main__":
] ]
} }
''' '''
labware_with_liquid = '''
[ {
labware_with_liquid = '''
[ {
"id": "stock plate on P1", "id": "stock plate on P1",
"parent": "deck", "parent": "deck",
"slot_on_deck": "P1", "slot_on_deck": "P1",
@@ -825,31 +893,37 @@ labware_with_liquid = '''
] ]
''' '''
handler = LiquidHandlerBiomek()
handler.temp_protocol = {
handler = LiquidHandlerBiomek()
handler.temp_protocol = {
"meta": {}, "meta": {},
"labwares": [], "labwares": [],
"steps": [] "steps": []
} }
input_steps = json.loads(steps_info) input_steps = json.loads(steps_info)
labwares = json.loads(labware_with_liquid) labwares = json.loads(labware_with_liquid)
for step in input_steps['steps']: for step in input_steps['steps']:
if step['operation'] != 'transfer': operation = step['operation']
continue
parameters = step['parameters'] parameters = step['parameters']
if operation == 'transfer':
handler.transfer_biomek(source=parameters['source'], handler.transfer_biomek(source=parameters['source'],
target=parameters['target'], target=parameters['target'],
volume=parameters['volume'], volume=parameters['volume'],
tip_rack=parameters['tip_rack'], tip_rack=parameters['tip_rack'],
aspirate_techniques='MC P300 high', aspirate_techniques='MC P300 high',
dispense_techniques='MC P300 high' dispense_techniques='MC P300 high')
) elif operation == 'move_labware':
handler.move_biomek(source=parameters['source'],
target=parameters['target'])
elif operation == 'oscillation':
handler.oscillation_biomek(rpm=parameters['rpm'],
time=parameters['time'])
elif operation == 'incubation':
handler.incubation_biomek(time=parameters['time'])
print(json.dumps(handler.temp_protocol, indent=4))

View File

@@ -1,14 +1,14 @@
import requests # import requests
from typing import List, Sequence, Optional, Union, Literal from typing import List, Sequence, Optional, Union, Literal
# from geometry_msgs.msg import Point # from geometry_msgs.msg import Point
# from unilabos_msgs.msg import Resource # from unilabos_msgs.msg import Resource
from pylabrobot.resources import ( # from pylabrobot.resources import (
Resource, # Resource,
TipRack, # TipRack,
Container, # Container,
Coordinate, # Coordinate,
Well # Well
) # )
# from unilabos.ros.nodes.resource_tracker import DeviceNodeResourceTracker # type: ignore # from unilabos.ros.nodes.resource_tracker import DeviceNodeResourceTracker # type: ignore
# from .liquid_handler_abstract import LiquidHandlerAbstract # from .liquid_handler_abstract import LiquidHandlerAbstract
@@ -270,6 +270,62 @@ class LiquidHandlerBiomek:
return return
def move_biomek(
self,
source: str,
target: str,
):
"""
处理Biomek移动板子的操作。
"""
move_params = {
"Pod": "Pod1",
"GripSide": "A1 near",
"Source": source,
"Target": target,
"LeaveBottomLabware": False,
}
self.temp_protocol["steps"].append(move_params)
return
def incubation_biomek(
self,
time: int,
):
"""
处理Biomek的孵育操作。
"""
incubation_params = {
"Message": "Paused",
"Location": "the whole system",
"Time": time,
"Mode": "TimedResource"
}
self.temp_protocol["steps"].append(incubation_params)
return
def oscillation_biomek(
self,
rpm: int,
time: int,
):
"""
处理Biomek的振荡操作。
"""
oscillation_params = {
'Device': 'OrbitalShaker0',
'Parameters': (str(rpm), '2', str(time), 'CounterClockwise'),
'Command': 'Timed Shake'
}
self.temp_protocol["steps"].append(oscillation_params)
return
if __name__ == "__main__": if __name__ == "__main__":
@@ -763,16 +819,23 @@ input_steps = json.loads(steps_info)
labwares = json.loads(labware_with_liquid) labwares = json.loads(labware_with_liquid)
for step in input_steps['steps']: for step in input_steps['steps']:
if step['operation'] != 'transfer': operation = step['operation']
continue
parameters = step['parameters'] parameters = step['parameters']
if operation == 'transfer':
handler.transfer_biomek(source=parameters['source'], handler.transfer_biomek(source=parameters['source'],
target=parameters['target'], target=parameters['target'],
volume=parameters['volume'], volume=parameters['volume'],
tip_rack=parameters['tip_rack'], tip_rack=parameters['tip_rack'],
aspirate_techniques='MC P300 high', aspirate_techniques='MC P300 high',
dispense_techniques='MC P300 high' dispense_techniques='MC P300 high')
) elif operation == 'move_labware':
handler.move_biomek(source=parameters['source'],
target=parameters['target'])
elif operation == 'oscillation':
handler.oscillation_biomek(rpm=parameters['rpm'],
time=parameters['time'])
elif operation == 'incubation':
handler.incubation_biomek(time=parameters['time'])
print(json.dumps(handler.temp_protocol, indent=4)) print(json.dumps(handler.temp_protocol, indent=4))

View File

@@ -340,6 +340,21 @@ liquid_handler.biomek:
none_keys: none_keys none_keys: none_keys
feedback: {} feedback: {}
result: {} result: {}
handles:
input:
- handler_key: liquid-input
label: Liquid Input
data_type: resource
io_type: target
data_source: handle
data_key: liquid
output:
- handler_key: liquid-output
label: Liquid Output
data_type: resource
io_type: source
data_source: executor
data_key: liquid
transfer_biomek: transfer_biomek:
type: LiquidHandlerTransferBiomek type: LiquidHandlerTransferBiomek
goal: goal:
@@ -351,6 +366,21 @@ liquid_handler.biomek:
dispense_techniques: dispense_techniques dispense_techniques: dispense_techniques
feedback: {} feedback: {}
result: {} result: {}
handles:
input:
- handler_key: liquid-input
label: Liquid Input
data_type: resource
io_type: target
data_source: handle
data_key: liquid
output:
- handler_key: liquid-output
label: Liquid Output
data_type: resource
io_type: source
data_source: executor
data_key: liquid
schema: schema:
type: object type: object
properties: {} properties: {}

View File

@@ -64,6 +64,7 @@ class Registry:
"goal_default": yaml.safe_load( "goal_default": yaml.safe_load(
io.StringIO(get_yaml_from_goal_type(self.ResourceCreateFromOuter.Goal)) io.StringIO(get_yaml_from_goal_type(self.ResourceCreateFromOuter.Goal))
), ),
"handles": {},
}, },
"create_resource": { "create_resource": {
"type": self.ResourceCreateFromOuterEasy, "type": self.ResourceCreateFromOuterEasy,
@@ -84,6 +85,7 @@ class Registry:
"goal_default": yaml.safe_load( "goal_default": yaml.safe_load(
io.StringIO(get_yaml_from_goal_type(self.ResourceCreateFromOuterEasy.Goal)) io.StringIO(get_yaml_from_goal_type(self.ResourceCreateFromOuterEasy.Goal))
), ),
"handles": {},
}, },
"test_latency": { "test_latency": {
"type": self.EmptyIn, "type": self.EmptyIn,
@@ -92,6 +94,7 @@ class Registry:
"result": {"latency_ms": "latency_ms", "time_diff_ms": "time_diff_ms"}, "result": {"latency_ms": "latency_ms", "time_diff_ms": "time_diff_ms"},
"schema": ros_action_to_json_schema(self.EmptyIn), "schema": ros_action_to_json_schema(self.EmptyIn),
"goal_default": {}, "goal_default": {},
"handles": {},
}, },
}, },
}, },
@@ -214,6 +217,8 @@ class Registry:
# 处理动作值映射 # 处理动作值映射
if "action_value_mappings" in device_config["class"]: if "action_value_mappings" in device_config["class"]:
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:
action_config["handles"] = []
if "type" in action_config: if "type" in action_config:
action_config["type"] = self._replace_type_with_class( action_config["type"] = self._replace_type_with_class(
action_config["type"], device_id, f"动作 {action_name}" action_config["type"], device_id, f"动作 {action_name}"

View File

@@ -0,0 +1,6 @@
string source
string target
---
bool success
---

View File

@@ -0,0 +1,6 @@
int32 rpm
int32 time
---
bool success
---

View File

@@ -0,0 +1,5 @@
int32 time
---
bool success
---

View File

@@ -6,5 +6,5 @@ string aspirate_technique
string dispense_technique string dispense_technique
--- ---
bool success
--- ---