mirror of
https://github.com/deepmodeling/Uni-Lab-OS
synced 2026-03-30 20:05:45 +00:00
Compare commits
11 Commits
b6c9530c61
...
6ae77e0408
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6ae77e0408 | ||
|
|
bab4b1d67a | ||
|
|
12c17ec26e | ||
|
|
6577fe12eb | ||
|
|
f1fee5fad9 | ||
|
|
9b3377aedb | ||
|
|
526327727d | ||
|
|
aaa86314e3 | ||
|
|
6a14104e6b | ||
|
|
ab0c4b708b | ||
|
|
c0b7f2decd |
@@ -45,7 +45,7 @@ conda env update --file unilabos-[YOUR_OS].yml -n environment_name
|
|||||||
|
|
||||||
# Currently, you need to install the `unilabos_msgs` package
|
# Currently, you need to install the `unilabos_msgs` package
|
||||||
# You can download the system-specific package from the Release page
|
# You can download the system-specific package from the Release page
|
||||||
conda install ros-humble-unilabos-msgs-0.9.1-xxxxx.tar.bz2
|
conda install ros-humble-unilabos-msgs-0.9.2-xxxxx.tar.bz2
|
||||||
|
|
||||||
# Install PyLabRobot and other prerequisites
|
# Install PyLabRobot and other prerequisites
|
||||||
git clone https://github.com/PyLabRobot/pylabrobot plr_repo
|
git clone https://github.com/PyLabRobot/pylabrobot plr_repo
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ conda env update --file unilabos-[YOUR_OS].yml -n 环境名
|
|||||||
|
|
||||||
# 现阶段,需要安装 `unilabos_msgs` 包
|
# 现阶段,需要安装 `unilabos_msgs` 包
|
||||||
# 可以前往 Release 页面下载系统对应的包进行安装
|
# 可以前往 Release 页面下载系统对应的包进行安装
|
||||||
conda install ros-humble-unilabos-msgs-0.9.1-xxxxx.tar.bz2
|
conda install ros-humble-unilabos-msgs-0.9.2-xxxxx.tar.bz2
|
||||||
|
|
||||||
# 安装PyLabRobot等前置
|
# 安装PyLabRobot等前置
|
||||||
git clone https://github.com/PyLabRobot/pylabrobot plr_repo
|
git clone https://github.com/PyLabRobot/pylabrobot plr_repo
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package:
|
package:
|
||||||
name: ros-humble-unilabos-msgs
|
name: ros-humble-unilabos-msgs
|
||||||
version: 0.9.1
|
version: 0.9.2
|
||||||
source:
|
source:
|
||||||
path: ../../unilabos_msgs
|
path: ../../unilabos_msgs
|
||||||
folder: ros-humble-unilabos-msgs/src/work
|
folder: ros-humble-unilabos-msgs/src/work
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package:
|
package:
|
||||||
name: unilabos
|
name: unilabos
|
||||||
version: "0.9.1"
|
version: "0.9.2"
|
||||||
|
|
||||||
source:
|
source:
|
||||||
path: ../..
|
path: ../..
|
||||||
|
|||||||
2
setup.py
2
setup.py
@@ -4,7 +4,7 @@ package_name = 'unilabos'
|
|||||||
|
|
||||||
setup(
|
setup(
|
||||||
name=package_name,
|
name=package_name,
|
||||||
version='0.9.1',
|
version='0.9.2',
|
||||||
packages=find_packages(),
|
packages=find_packages(),
|
||||||
include_package_data=True,
|
include_package_data=True,
|
||||||
install_requires=['setuptools'],
|
install_requires=['setuptools'],
|
||||||
|
|||||||
22
test/experiments/biomek.json
Normal file
22
test/experiments/biomek.json
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"id": "BIOMEK",
|
||||||
|
"name": "BIOMEK",
|
||||||
|
"parent": null,
|
||||||
|
"type": "device",
|
||||||
|
"class": "liquid_handler.biomek",
|
||||||
|
"position": {
|
||||||
|
"x": 620.6111111111111,
|
||||||
|
"y": 171,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
},
|
||||||
|
"data": {},
|
||||||
|
"children": [
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"links": []
|
||||||
|
}
|
||||||
1710
test/experiments/plr_test_converted_slim.json
Normal file
1710
test/experiments/plr_test_converted_slim.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -10,6 +10,8 @@ from copy import deepcopy
|
|||||||
|
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
|
from unilabos.resources.graphio import tree_to_list
|
||||||
|
|
||||||
# 首先添加项目根目录到路径
|
# 首先添加项目根目录到路径
|
||||||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
unilabos_dir = os.path.dirname(os.path.dirname(current_dir))
|
unilabos_dir = os.path.dirname(os.path.dirname(current_dir))
|
||||||
@@ -144,19 +146,19 @@ def main():
|
|||||||
else read_graphml(args_dict["graph"])
|
else read_graphml(args_dict["graph"])
|
||||||
)
|
)
|
||||||
devices_and_resources = dict_from_graph(graph_res.physical_setup_graph)
|
devices_and_resources = dict_from_graph(graph_res.physical_setup_graph)
|
||||||
args_dict["resources_config"] = initialize_resources(list(deepcopy(devices_and_resources).values()))
|
# args_dict["resources_config"] = initialize_resources(list(deepcopy(devices_and_resources).values()))
|
||||||
|
args_dict["resources_config"] = list(devices_and_resources.values())
|
||||||
args_dict["devices_config"] = dict_to_nested_dict(deepcopy(devices_and_resources), devices_only=False)
|
args_dict["devices_config"] = dict_to_nested_dict(deepcopy(devices_and_resources), devices_only=False)
|
||||||
# args_dict["resources_config"] = dict_to_tree(devices_and_resources, devices_only=False)
|
|
||||||
|
|
||||||
args_dict["graph"] = graph_res.physical_setup_graph
|
args_dict["graph"] = graph_res.physical_setup_graph
|
||||||
else:
|
else:
|
||||||
if args_dict["devices"] is None or args_dict["resources"] is None:
|
if args_dict["devices"] is None or args_dict["resources"] is None:
|
||||||
print_status("Either graph or devices and resources must be provided.", "error")
|
print_status("Either graph or devices and resources must be provided.", "error")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
args_dict["devices_config"] = json.load(open(args_dict["devices"], encoding="utf-8"))
|
args_dict["devices_config"] = json.load(open(args_dict["devices"], encoding="utf-8"))
|
||||||
args_dict["resources_config"] = initialize_resources(
|
# args_dict["resources_config"] = initialize_resources(
|
||||||
list(json.load(open(args_dict["resources"], encoding="utf-8")).values())
|
# list(json.load(open(args_dict["resources"], encoding="utf-8")).values())
|
||||||
)
|
# )
|
||||||
|
args_dict["resources_config"] = list(json.load(open(args_dict["resources"], encoding="utf-8")).values())
|
||||||
|
|
||||||
print_status(f"{len(args_dict['resources_config'])} Resources loaded:", "info")
|
print_status(f"{len(args_dict['resources_config'])} Resources loaded:", "info")
|
||||||
for i in args_dict["resources_config"]:
|
for i in args_dict["resources_config"]:
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import json
|
import json
|
||||||
import time
|
import time
|
||||||
import traceback
|
import traceback
|
||||||
|
from typing import Optional
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
import paho.mqtt.client as mqtt
|
import paho.mqtt.client as mqtt
|
||||||
@@ -163,10 +164,12 @@ class MQTTClient:
|
|||||||
self.client.publish(address, json.dumps(status), qos=2)
|
self.client.publish(address, json.dumps(status), qos=2)
|
||||||
logger.critical(f"Device status published: address: {address}, {status}")
|
logger.critical(f"Device status published: address: {address}, {status}")
|
||||||
|
|
||||||
def publish_job_status(self, feedback_data: dict, job_id: str, status: str):
|
def publish_job_status(self, feedback_data: dict, job_id: str, status: str, return_info: Optional[str] = None):
|
||||||
if self.mqtt_disable:
|
if self.mqtt_disable:
|
||||||
return
|
return
|
||||||
jobdata = {"job_id": job_id, "data": feedback_data, "status": status}
|
if return_info is None:
|
||||||
|
return_info = "{}"
|
||||||
|
jobdata = {"job_id": job_id, "data": feedback_data, "status": status, "return_info": return_info}
|
||||||
self.client.publish(f"labs/{MQConfig.lab_id}/job/list/", json.dumps(jobdata), qos=2)
|
self.client.publish(f"labs/{MQConfig.lab_id}/job/list/", json.dumps(jobdata), qos=2)
|
||||||
|
|
||||||
def publish_registry(self, device_id: str, device_info: dict):
|
def publish_registry(self, device_id: str, device_info: dict):
|
||||||
|
|||||||
@@ -30,18 +30,18 @@ class HTTPClient:
|
|||||||
self.auth = MQConfig.lab_id
|
self.auth = MQConfig.lab_id
|
||||||
info(f"HTTPClient 初始化完成: remote_addr={self.remote_addr}")
|
info(f"HTTPClient 初始化完成: remote_addr={self.remote_addr}")
|
||||||
|
|
||||||
def resource_add(self, resources: List[Dict[str, Any]]) -> requests.Response:
|
def resource_add(self, resources: List[Dict[str, Any]], database_process_later:bool) -> requests.Response:
|
||||||
"""
|
"""
|
||||||
添加资源
|
添加资源
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
resources: 要添加的资源列表
|
resources: 要添加的资源列表
|
||||||
|
database_process_later: 后台处理资源
|
||||||
Returns:
|
Returns:
|
||||||
Response: API响应对象
|
Response: API响应对象
|
||||||
"""
|
"""
|
||||||
response = requests.post(
|
response = requests.post(
|
||||||
f"{self.remote_addr}/lab/resource/",
|
f"{self.remote_addr}/lab/resource/?database_process_later={1 if database_process_later else 0}",
|
||||||
json=resources,
|
json=resources,
|
||||||
headers={"Authorization": f"lab {self.auth}"},
|
headers={"Authorization": f"lab {self.auth}"},
|
||||||
timeout=5,
|
timeout=5,
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -276,14 +276,14 @@ class LiquidHandlerBiomek:
|
|||||||
"Class": f"LabwareClasses\\{class_name}",
|
"Class": f"LabwareClasses\\{class_name}",
|
||||||
"DataSets": {"Volume": {}},
|
"DataSets": {"Volume": {}},
|
||||||
"RuntimeDataSets": {"Volume": {}},
|
"RuntimeDataSets": {"Volume": {}},
|
||||||
"EvalAmounts": (float(liquid_volume[0]),) if liquid_volume else (0.0,),
|
"EvalAmounts": (float(liquid_volume[0]),) if liquid_volume else (0,),
|
||||||
"Nominal": False,
|
"Nominal": False,
|
||||||
"EvalLiquids": (liquid_type[0],) if liquid_type else ("Water",)
|
"EvalLiquids": (liquid_type[0],) if liquid_type else ("Water",)
|
||||||
}
|
}
|
||||||
|
|
||||||
elif instrument_type == "plate_96":
|
elif instrument_type == "plate_96":
|
||||||
# 96孔板类型配置
|
# 96孔板类型配置
|
||||||
volume_per_well = float(liquid_volume[0]) if liquid_volume else 500.0
|
volume_per_well = float(liquid_volume[0]) if liquid_volume else 0
|
||||||
liquid_per_well = liquid_type[0] if liquid_type else "Water"
|
liquid_per_well = liquid_type[0] if liquid_type else "Water"
|
||||||
|
|
||||||
config = {
|
config = {
|
||||||
@@ -468,455 +468,455 @@ class LiquidHandlerBiomek:
|
|||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|
||||||
print("=== Biomek完整流程测试 ===")
|
print("=== Biomek完整流程测试 ===")
|
||||||
print("包含: 仪器设置 + 完整实验步骤")
|
print("包含: 仪器设置 + 完整实验步骤")
|
||||||
|
|
||||||
# 完整的步骤信息(从biomek.py复制)
|
# 完整的步骤信息(从biomek.py复制)
|
||||||
steps_info = '''
|
steps_info = '''
|
||||||
{
|
{
|
||||||
"steps": [
|
"steps": [
|
||||||
{
|
{
|
||||||
"step_number": 1,
|
"step_number": 1,
|
||||||
"operation": "transfer",
|
"operation": "transfer",
|
||||||
"description": "转移PCR产物或酶促反应液至0.05ml 96孔板中",
|
"description": "转移PCR产物或酶促反应液至0.5ml 96孔板中",
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"source": "P1",
|
"source": "P1",
|
||||||
"target": "P11",
|
"target": "P11",
|
||||||
"tip_rack": "BC230",
|
"tip_rack": "BC230",
|
||||||
"volume": 50
|
"volume": 50
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"step_number": 2,
|
"step_number": 2,
|
||||||
"operation": "transfer",
|
"operation": "transfer",
|
||||||
"description": "加入2倍体积Bind Beads BC至产物中",
|
"description": "加入2倍体积的Bind Beads BC至产物中",
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"source": "P2",
|
"source": "P2",
|
||||||
"target": "P11",
|
"target": "P11",
|
||||||
"tip_rack": "BC230",
|
"tip_rack": "BC230",
|
||||||
"volume": 100
|
"volume": 100
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"step_number": 3,
|
"step_number": 3,
|
||||||
"operation": "move_labware",
|
"operation": "oscillation",
|
||||||
"description": "移动P11至Orbital1用于振荡混匀",
|
"description": "振荡混匀300秒",
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"source": "P11",
|
"rpm": 800,
|
||||||
"target": "Orbital1"
|
"time": 300
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"step_number": 4,
|
"step_number": 4,
|
||||||
"operation": "oscillation",
|
"operation": "move_labware",
|
||||||
"description": "在Orbital1上振荡混匀Bind Beads BC与PCR产物(700-900rpm,300秒)",
|
"description": "转移至96孔磁力架上吸附3分钟",
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"rpm": 800,
|
"source": "P11",
|
||||||
"time": 300
|
"target": "P12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"step_number": 5,
|
"step_number": 5,
|
||||||
"operation": "move_labware",
|
"operation": "incubation",
|
||||||
"description": "移动混匀后的板回P11",
|
"description": "吸附3分钟",
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"source": "Orbital1",
|
"time": 180
|
||||||
"target": "P11"
|
}
|
||||||
}
|
},
|
||||||
},
|
{
|
||||||
{
|
"step_number": 6,
|
||||||
"step_number": 6,
|
"operation": "transfer",
|
||||||
"operation": "move_labware",
|
"description": "吸弃或倒除上清液",
|
||||||
"description": "将P11移动到磁力架(P12)吸附3分钟",
|
"parameters": {
|
||||||
"parameters": {
|
"source": "P12",
|
||||||
"source": "P11",
|
"target": "P22",
|
||||||
"target": "P12"
|
"tip_rack": "BC230",
|
||||||
}
|
"volume": 150
|
||||||
},
|
}
|
||||||
{
|
},
|
||||||
"step_number": 7,
|
{
|
||||||
"operation": "incubation",
|
"step_number": 7,
|
||||||
"description": "磁力架上室温静置3分钟完成吸附",
|
"operation": "transfer",
|
||||||
"parameters": {
|
"description": "加入300-500μl 75%乙醇",
|
||||||
"time": 180
|
"parameters": {
|
||||||
}
|
"source": "P3",
|
||||||
},
|
"target": "P12",
|
||||||
{
|
"tip_rack": "BC230",
|
||||||
"step_number": 8,
|
"volume": 400
|
||||||
"operation": "transfer",
|
}
|
||||||
"description": "去除上清液至废液槽",
|
},
|
||||||
"parameters": {
|
{
|
||||||
"source": "P12",
|
"step_number": 8,
|
||||||
"target": "P22",
|
"operation": "move_labware",
|
||||||
"tip_rack": "BC230",
|
"description": "移动至振荡器进行振荡混匀",
|
||||||
"volume": 150
|
"parameters": {
|
||||||
}
|
"source": "P12",
|
||||||
},
|
"target": "Orbital1"
|
||||||
{
|
}
|
||||||
"step_number": 9,
|
},
|
||||||
"operation": "transfer",
|
{
|
||||||
"description": "加入300-500μl 75%乙醇清洗",
|
"step_number": 9,
|
||||||
"parameters": {
|
"operation": "oscillation",
|
||||||
"source": "P3",
|
"description": "振荡混匀60秒",
|
||||||
"target": "P12",
|
"parameters": {
|
||||||
"tip_rack": "BC230",
|
"rpm": 800,
|
||||||
"volume": 400
|
"time": 60
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"step_number": 10,
|
"step_number": 10,
|
||||||
"operation": "move_labware",
|
"operation": "move_labware",
|
||||||
"description": "移动清洗板到Orbital1进行振荡",
|
"description": "转移至96孔磁力架上吸附3分钟",
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"source": "P12",
|
"source": "Orbital1",
|
||||||
"target": "Orbital1"
|
"target": "P12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"step_number": 11,
|
"step_number": 11,
|
||||||
"operation": "oscillation",
|
"operation": "incubation",
|
||||||
"description": "乙醇清洗液振荡混匀(700-900rpm, 45秒)",
|
"description": "吸附3分钟",
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"rpm": 800,
|
"time": 180
|
||||||
"time": 45
|
}
|
||||||
}
|
},
|
||||||
},
|
{
|
||||||
{
|
"step_number": 12,
|
||||||
"step_number": 12,
|
"operation": "transfer",
|
||||||
"operation": "move_labware",
|
"description": "吸弃或倒弃废液",
|
||||||
"description": "振荡后将板移回磁力架P12吸附",
|
"parameters": {
|
||||||
"parameters": {
|
"source": "P12",
|
||||||
"source": "Orbital1",
|
"target": "P22",
|
||||||
"target": "P12"
|
"tip_rack": "BC230",
|
||||||
}
|
"volume": 400
|
||||||
},
|
}
|
||||||
{
|
},
|
||||||
"step_number": 13,
|
{
|
||||||
"operation": "incubation",
|
"step_number": 13,
|
||||||
"description": "吸附3分钟",
|
"operation": "transfer",
|
||||||
"parameters": {
|
"description": "重复加入75%乙醇",
|
||||||
"time": 180
|
"parameters": {
|
||||||
}
|
"source": "P3",
|
||||||
},
|
"target": "P12",
|
||||||
{
|
"tip_rack": "BC230",
|
||||||
"step_number": 14,
|
"volume": 400
|
||||||
"operation": "transfer",
|
}
|
||||||
"description": "去除乙醇上清液至废液槽",
|
},
|
||||||
"parameters": {
|
{
|
||||||
"source": "P12",
|
"step_number": 14,
|
||||||
"target": "P22",
|
"operation": "move_labware",
|
||||||
"tip_rack": "BC230",
|
"description": "移动至振荡器进行振荡混匀",
|
||||||
"volume": 400
|
"parameters": {
|
||||||
}
|
"source": "P12",
|
||||||
},
|
"target": "Orbital1"
|
||||||
{
|
}
|
||||||
"step_number": 15,
|
},
|
||||||
"operation": "transfer",
|
{
|
||||||
"description": "第二次加入300-500μl 75%乙醇清洗",
|
"step_number": 15,
|
||||||
"parameters": {
|
"operation": "oscillation",
|
||||||
"source": "P3",
|
"description": "振荡混匀60秒",
|
||||||
"target": "P12",
|
"parameters": {
|
||||||
"tip_rack": "BC230",
|
"rpm": 800,
|
||||||
"volume": 400
|
"time": 60
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"step_number": 16,
|
"step_number": 16,
|
||||||
"operation": "move_labware",
|
"operation": "move_labware",
|
||||||
"description": "再次移动清洗板到Orbital1振荡",
|
"description": "转移至96孔磁力架上吸附3分钟",
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"source": "P12",
|
"source": "Orbital1",
|
||||||
"target": "Orbital1"
|
"target": "P12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"step_number": 17,
|
"step_number": 17,
|
||||||
"operation": "oscillation",
|
"operation": "incubation",
|
||||||
"description": "再次乙醇清洗液振荡混匀(700-900rpm, 45秒)",
|
"description": "吸附3分钟",
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"rpm": 800,
|
"time": 180
|
||||||
"time": 45
|
}
|
||||||
}
|
},
|
||||||
},
|
{
|
||||||
{
|
"step_number": 18,
|
||||||
"step_number": 18,
|
"operation": "transfer",
|
||||||
"operation": "move_labware",
|
"description": "吸弃或倒弃废液",
|
||||||
"description": "振荡后板送回磁力架P12吸附",
|
"parameters": {
|
||||||
"parameters": {
|
"source": "P12",
|
||||||
"source": "Orbital1",
|
"target": "P22",
|
||||||
"target": "P12"
|
"tip_rack": "BC230",
|
||||||
}
|
"volume": 400
|
||||||
},
|
}
|
||||||
{
|
},
|
||||||
"step_number": 19,
|
{
|
||||||
"operation": "incubation",
|
"step_number": 19,
|
||||||
"description": "再次吸附3分钟",
|
"operation": "move_labware",
|
||||||
"parameters": {
|
"description": "正放96孔板,空气干燥15分钟",
|
||||||
"time": 180
|
"parameters": {
|
||||||
}
|
"source": "P12",
|
||||||
},
|
"target": "P13"
|
||||||
{
|
}
|
||||||
"step_number": 20,
|
},
|
||||||
"operation": "transfer",
|
{
|
||||||
"description": "去除乙醇上清液至废液槽",
|
"step_number": 20,
|
||||||
"parameters": {
|
"operation": "incubation",
|
||||||
"source": "P12",
|
"description": "空气干燥15分钟",
|
||||||
"target": "P22",
|
"parameters": {
|
||||||
"tip_rack": "BC230",
|
"time": 900
|
||||||
"volume": 400
|
}
|
||||||
}
|
},
|
||||||
},
|
{
|
||||||
{
|
"step_number": 21,
|
||||||
"step_number": 21,
|
"operation": "transfer",
|
||||||
"operation": "incubation",
|
"description": "加入30-50μl Elution Buffer",
|
||||||
"description": "空气干燥15分钟",
|
"parameters": {
|
||||||
"parameters": {
|
"source": "P4",
|
||||||
"time": 900
|
"target": "P13",
|
||||||
}
|
"tip_rack": "BC230",
|
||||||
},
|
"volume": 40
|
||||||
{
|
}
|
||||||
"step_number": 22,
|
},
|
||||||
"operation": "transfer",
|
{
|
||||||
"description": "加30-50μl Elution Buffer洗脱",
|
"step_number": 22,
|
||||||
"parameters": {
|
"operation": "move_labware",
|
||||||
"source": "P4",
|
"description": "移动至振荡器进行振荡混匀",
|
||||||
"target": "P12",
|
"parameters": {
|
||||||
"tip_rack": "BC230",
|
"source": "P13",
|
||||||
"volume": 40
|
"target": "Orbital1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"step_number": 23,
|
"step_number": 23,
|
||||||
"operation": "move_labware",
|
"operation": "oscillation",
|
||||||
"description": "移动到Orbital1振荡混匀(60秒)",
|
"description": "振荡混匀60秒",
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"source": "P12",
|
"rpm": 800,
|
||||||
"target": "Orbital1"
|
"time": 60
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"step_number": 24,
|
"step_number": 24,
|
||||||
"operation": "oscillation",
|
"operation": "move_labware",
|
||||||
"description": "Elution Buffer振荡混匀(700-900rpm, 60秒)",
|
"description": "室温静置3分钟",
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"rpm": 800,
|
"source": "Orbital1",
|
||||||
"time": 60
|
"target": "P13"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"step_number": 25,
|
"step_number": 25,
|
||||||
"operation": "move_labware",
|
"operation": "incubation",
|
||||||
"description": "振荡后送回磁力架P12",
|
"description": "室温静置3分钟",
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"source": "Orbital1",
|
"time": 180
|
||||||
"target": "P12"
|
}
|
||||||
}
|
},
|
||||||
},
|
{
|
||||||
{
|
"step_number": 26,
|
||||||
"step_number": 26,
|
"operation": "move_labware",
|
||||||
"operation": "incubation",
|
"description": "转移至96孔磁力架上吸附2分钟",
|
||||||
"description": "室温静置3分钟(洗脱反应)",
|
"parameters": {
|
||||||
"parameters": {
|
"source": "P13",
|
||||||
"time": 180
|
"target": "P12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"step_number": 27,
|
"step_number": 27,
|
||||||
"operation": "transfer",
|
"operation": "incubation",
|
||||||
"description": "将上清液(DNA)转移到新板(P13)",
|
"description": "吸附2分钟",
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"source": "P12",
|
"time": 120
|
||||||
"target": "P13",
|
}
|
||||||
"tip_rack": "BC230",
|
},
|
||||||
"volume": 40
|
{
|
||||||
|
"step_number": 28,
|
||||||
|
"operation": "transfer",
|
||||||
|
"description": "将DNA转移至新的板中",
|
||||||
|
"parameters": {
|
||||||
|
"source": "P12",
|
||||||
|
"target": "P14",
|
||||||
|
"tip_rack": "BC230",
|
||||||
|
"volume": 40
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
'''
|
'''
|
||||||
|
# 完整的labware配置信息
|
||||||
# 完整的labware配置信息(从biomek.py复制)
|
|
||||||
labware_with_liquid = '''
|
labware_with_liquid = '''
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"id": "Tip Rack BC230 on TL1",
|
"id": "Tip Rack BC230 TL1",
|
||||||
"parent": "deck",
|
"parent": "deck",
|
||||||
"slot_on_deck": "TL1",
|
"slot_on_deck": "TL1",
|
||||||
"class_name": "BC230",
|
"class_name": "BC230",
|
||||||
"liquid_type": [],
|
"liquid_type": [],
|
||||||
"liquid_volume": [],
|
"liquid_volume": [],
|
||||||
"liquid_input_wells": []
|
"liquid_input_wells": []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Tip Rack BC230 on TL2",
|
"id": "Tip Rack BC230 TL2",
|
||||||
"parent": "deck",
|
"parent": "deck",
|
||||||
"slot_on_deck": "TL2",
|
"slot_on_deck": "TL2",
|
||||||
"class_name": "BC230",
|
"class_name": "BC230",
|
||||||
"liquid_type": [],
|
"liquid_type": [],
|
||||||
"liquid_volume": [],
|
"liquid_volume": [],
|
||||||
"liquid_input_wells": []
|
"liquid_input_wells": []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Tip Rack BC230 on TL3",
|
"id": "Tip Rack BC230 TL3",
|
||||||
"parent": "deck",
|
"parent": "deck",
|
||||||
"slot_on_deck": "TL3",
|
"slot_on_deck": "TL3",
|
||||||
"class_name": "BC230",
|
"class_name": "BC230",
|
||||||
"liquid_type": [],
|
"liquid_type": [],
|
||||||
"liquid_volume": [],
|
"liquid_volume": [],
|
||||||
"liquid_input_wells": []
|
"liquid_input_wells": []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Tip Rack BC230 on TL4",
|
"id": "Tip Rack BC230 TL4",
|
||||||
"parent": "deck",
|
"parent": "deck",
|
||||||
"slot_on_deck": "TL4",
|
"slot_on_deck": "TL4",
|
||||||
"class_name": "BC230",
|
"class_name": "BC230",
|
||||||
"liquid_type": [],
|
"liquid_type": [],
|
||||||
"liquid_volume": [],
|
"liquid_volume": [],
|
||||||
"liquid_input_wells": []
|
"liquid_input_wells": []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Tip Rack BC230 on TL5",
|
"id": "Tip Rack BC230 TL5",
|
||||||
"parent": "deck",
|
"parent": "deck",
|
||||||
"slot_on_deck": "TL5",
|
"slot_on_deck": "TL5",
|
||||||
"class_name": "BC230",
|
"class_name": "BC230",
|
||||||
"liquid_type": [],
|
"liquid_type": [],
|
||||||
"liquid_volume": [],
|
"liquid_volume": [],
|
||||||
"liquid_input_wells": []
|
"liquid_input_wells": []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Tip Rack BC230 on P5",
|
"id": "Tip Rack BC230 P5",
|
||||||
"parent": "deck",
|
"parent": "deck",
|
||||||
"slot_on_deck": "P5",
|
"slot_on_deck": "P5",
|
||||||
"class_name": "BC230",
|
"class_name": "BC230",
|
||||||
"liquid_type": [],
|
"liquid_type": [],
|
||||||
"liquid_volume": [],
|
"liquid_volume": [],
|
||||||
"liquid_input_wells": []
|
"liquid_input_wells": []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Tip Rack BC230 on P6",
|
"id": "Tip Rack BC230 P6",
|
||||||
"parent": "deck",
|
"parent": "deck",
|
||||||
"slot_on_deck": "P6",
|
"slot_on_deck": "P6",
|
||||||
"class_name": "BC230",
|
"class_name": "BC230",
|
||||||
"liquid_type": [],
|
"liquid_type": [],
|
||||||
"liquid_volume": [],
|
"liquid_volume": [],
|
||||||
"liquid_input_wells": []
|
"liquid_input_wells": []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Tip Rack BC230 on P15",
|
"id": "Tip Rack BC230 P15",
|
||||||
"parent": "deck",
|
"parent": "deck",
|
||||||
"slot_on_deck": "P15",
|
"slot_on_deck": "P15",
|
||||||
"class_name": "BC230",
|
"class_name": "BC230",
|
||||||
"liquid_type": [],
|
"liquid_type": [],
|
||||||
"liquid_volume": [],
|
"liquid_volume": [],
|
||||||
"liquid_input_wells": []
|
"liquid_input_wells": []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Tip Rack BC230 on P16",
|
"id": "Tip Rack BC230 P16",
|
||||||
"parent": "deck",
|
"parent": "deck",
|
||||||
"slot_on_deck": "P16",
|
"slot_on_deck": "P16",
|
||||||
"class_name": "BC230",
|
"class_name": "BC230",
|
||||||
"liquid_type": [],
|
"liquid_type": [],
|
||||||
"liquid_volume": [],
|
"liquid_volume": [],
|
||||||
"liquid_input_wells": []
|
"liquid_input_wells": []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "stock plate on P1",
|
"id": "stock plate on P1",
|
||||||
"parent": "deck",
|
"parent": "deck",
|
||||||
"slot_on_deck": "P1",
|
"slot_on_deck": "P1",
|
||||||
"class_name": "nest_12_reservoir_15ml",
|
"class_name": "AgilentReservoir",
|
||||||
"liquid_type": [
|
"liquid_type": ["PCR product"],
|
||||||
"master_mix"
|
"liquid_volume": [5000],
|
||||||
],
|
"liquid_input_wells": ["A1"]
|
||||||
"liquid_volume": [10000],
|
},
|
||||||
"liquid_input_wells": [
|
{
|
||||||
"A1"
|
"id": "stock plate on P2",
|
||||||
]
|
"parent": "deck",
|
||||||
},
|
"slot_on_deck": "P2",
|
||||||
{
|
"class_name": "AgilentReservoir",
|
||||||
"id": "stock plate on P2",
|
"liquid_type": ["bind beads"],
|
||||||
"parent": "deck",
|
"liquid_volume": [100000],
|
||||||
"slot_on_deck": "P2",
|
"liquid_input_wells": ["A1"]
|
||||||
"class_name": "nest_12_reservoir_15ml",
|
},
|
||||||
"liquid_type": [
|
{
|
||||||
"bind beads"
|
"id": "stock plate on P3",
|
||||||
],
|
"parent": "deck",
|
||||||
"liquid_volume": [10000],
|
"slot_on_deck": "P3",
|
||||||
"liquid_input_wells": [
|
"class_name": "AgilentReservoir",
|
||||||
"A1"
|
"liquid_type": ["75% ethanol"],
|
||||||
]
|
"liquid_volume": [100000],
|
||||||
},
|
"liquid_input_wells": ["A1"]
|
||||||
{
|
},
|
||||||
"id": "stock plate on P3",
|
{
|
||||||
"parent": "deck",
|
"id": "stock plate on P4",
|
||||||
"slot_on_deck": "P3",
|
"parent": "deck",
|
||||||
"class_name": "nest_12_reservoir_15ml",
|
"slot_on_deck": "P4",
|
||||||
"liquid_type": [
|
"class_name": "AgilentReservoir",
|
||||||
"ethyl alcohol"
|
"liquid_type": ["Elution Buffer"],
|
||||||
],
|
"liquid_volume": [5000],
|
||||||
"liquid_volume": [10000],
|
"liquid_input_wells": ["A1"]
|
||||||
"liquid_input_wells": [
|
},
|
||||||
"A1"
|
{
|
||||||
]
|
"id": "working plate on P11",
|
||||||
},
|
"parent": "deck",
|
||||||
{
|
"slot_on_deck": "P11",
|
||||||
"id": "elution buffer on P4",
|
"class_name": "BCDeep96Round",
|
||||||
"parent": "deck",
|
"liquid_type": [],
|
||||||
"slot_on_deck": "P4",
|
"liquid_volume": [],
|
||||||
"class_name": "nest_12_reservoir_15ml",
|
"liquid_input_wells": []
|
||||||
"liquid_type": [
|
},
|
||||||
"elution buffer"
|
{
|
||||||
],
|
"id": "working plate on P12",
|
||||||
"liquid_volume": [5000],
|
"parent": "deck",
|
||||||
"liquid_input_wells": [
|
"slot_on_deck": "P12",
|
||||||
"A1"
|
"class_name": "BCDeep96Round",
|
||||||
]
|
"liquid_type": [],
|
||||||
},
|
"liquid_volume": [],
|
||||||
{
|
"liquid_input_wells": []
|
||||||
"id": "oscillation",
|
},
|
||||||
"parent": "deck",
|
{
|
||||||
"slot_on_deck": "Orbital1",
|
"id": "working plate on P13",
|
||||||
"class_name": "Orbital",
|
"parent": "deck",
|
||||||
"liquid_type": [],
|
"slot_on_deck": "P13",
|
||||||
"liquid_volume": [],
|
"class_name": "BCDeep96Round",
|
||||||
"liquid_input_wells": []
|
"liquid_type": [],
|
||||||
},
|
"liquid_volume": [],
|
||||||
{
|
"liquid_input_wells": []
|
||||||
"id": "working plate on P11",
|
},
|
||||||
"parent": "deck",
|
{
|
||||||
"slot_on_deck": "P11",
|
"id": "working plate on P14",
|
||||||
"class_name": "NEST 2ml Deep Well Plate",
|
"parent": "deck",
|
||||||
"liquid_type": [],
|
"slot_on_deck": "P14",
|
||||||
"liquid_volume": [],
|
"class_name": "BCDeep96Round",
|
||||||
"liquid_input_wells": []
|
"liquid_type": [],
|
||||||
},
|
"liquid_volume": [],
|
||||||
{
|
"liquid_input_wells": []
|
||||||
"id": "magnetics module on P12",
|
},
|
||||||
"parent": "deck",
|
{
|
||||||
"slot_on_deck": "P12",
|
"id": "waste on P22",
|
||||||
"class_name": "magnetics module",
|
"parent": "deck",
|
||||||
"liquid_type": [],
|
"slot_on_deck": "P22",
|
||||||
"liquid_volume": [],
|
"class_name": "AgilentReservoir",
|
||||||
"liquid_input_wells": []
|
"liquid_type": [],
|
||||||
},
|
"liquid_volume": [],
|
||||||
{
|
"liquid_input_wells": []
|
||||||
"id": "working plate on P13",
|
},
|
||||||
"parent": "deck",
|
{
|
||||||
"slot_on_deck": "P13",
|
"id": "oscillation",
|
||||||
"class_name": "NEST 2ml Deep Well Plate",
|
"parent": "deck",
|
||||||
"liquid_type": [],
|
"slot_on_deck": "Orbital1",
|
||||||
"liquid_volume": [],
|
"class_name": "Orbital",
|
||||||
"liquid_input_wells": []
|
"liquid_type": [],
|
||||||
},
|
"liquid_volume": [],
|
||||||
{
|
"liquid_input_wells": []
|
||||||
"id": "waste on P22",
|
}
|
||||||
"parent": "deck",
|
|
||||||
"slot_on_deck": "P22",
|
|
||||||
"class_name": "nest_1_reservoir_195ml",
|
|
||||||
"liquid_type": [],
|
|
||||||
"liquid_volume": [],
|
|
||||||
"liquid_input_wells": []
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
'''
|
'''
|
||||||
|
|
||||||
@@ -1000,7 +1000,7 @@ if __name__ == "__main__":
|
|||||||
script_dir = pathlib.Path(__file__).parent
|
script_dir = pathlib.Path(__file__).parent
|
||||||
|
|
||||||
# 保存完整协议
|
# 保存完整协议
|
||||||
complete_output_path = script_dir / "complete_biomek_protocol_0607.json"
|
complete_output_path = script_dir / "complete_biomek_protocol_0608.json"
|
||||||
with open(complete_output_path, 'w', encoding='utf-8') as f:
|
with open(complete_output_path, 'w', encoding='utf-8') as f:
|
||||||
json.dump(handler.temp_protocol, f, indent=4, ensure_ascii=False)
|
json.dump(handler.temp_protocol, f, indent=4, ensure_ascii=False)
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -85,7 +85,15 @@ 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": {},
|
"handles": {
|
||||||
|
"output": [{
|
||||||
|
"handler_key": "Labware",
|
||||||
|
"label": "Labware",
|
||||||
|
"data_type": "resource",
|
||||||
|
"data_source": "handle",
|
||||||
|
"data_key": "liquid"
|
||||||
|
}]
|
||||||
|
},
|
||||||
},
|
},
|
||||||
"test_latency": {
|
"test_latency": {
|
||||||
"type": self.EmptyIn,
|
"type": self.EmptyIn,
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import copy
|
import copy
|
||||||
import functools
|
|
||||||
import json
|
import json
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
@@ -20,16 +19,29 @@ from rclpy.service import Service
|
|||||||
from unilabos_msgs.action import SendCmd
|
from unilabos_msgs.action import SendCmd
|
||||||
from unilabos_msgs.srv._serial_command import SerialCommand_Request, SerialCommand_Response
|
from unilabos_msgs.srv._serial_command import SerialCommand_Request, SerialCommand_Response
|
||||||
|
|
||||||
from unilabos.resources.graphio import convert_resources_to_type, convert_resources_from_type, resource_ulab_to_plr, \
|
from unilabos.resources.graphio import (
|
||||||
initialize_resources, list_to_nested_dict, dict_to_tree, resource_plr_to_ulab, tree_to_list
|
convert_resources_to_type,
|
||||||
|
convert_resources_from_type,
|
||||||
|
resource_ulab_to_plr,
|
||||||
|
initialize_resources,
|
||||||
|
dict_to_tree,
|
||||||
|
resource_plr_to_ulab,
|
||||||
|
tree_to_list,
|
||||||
|
)
|
||||||
from unilabos.ros.msgs.message_converter import (
|
from unilabos.ros.msgs.message_converter import (
|
||||||
convert_to_ros_msg,
|
convert_to_ros_msg,
|
||||||
convert_from_ros_msg,
|
convert_from_ros_msg,
|
||||||
convert_from_ros_msg_with_mapping,
|
convert_from_ros_msg_with_mapping,
|
||||||
convert_to_ros_msg_with_mapping, ros_action_to_json_schema,
|
convert_to_ros_msg_with_mapping,
|
||||||
)
|
)
|
||||||
from unilabos_msgs.srv import ResourceAdd, ResourceGet, ResourceDelete, ResourceUpdate, ResourceList, \
|
from unilabos_msgs.srv import (
|
||||||
SerialCommand # type: ignore
|
ResourceAdd,
|
||||||
|
ResourceGet,
|
||||||
|
ResourceDelete,
|
||||||
|
ResourceUpdate,
|
||||||
|
ResourceList,
|
||||||
|
SerialCommand,
|
||||||
|
) # type: ignore
|
||||||
from unilabos_msgs.msg import Resource # type: ignore
|
from unilabos_msgs.msg import Resource # type: ignore
|
||||||
|
|
||||||
from unilabos.ros.nodes.resource_tracker import DeviceNodeResourceTracker
|
from unilabos.ros.nodes.resource_tracker import DeviceNodeResourceTracker
|
||||||
@@ -37,7 +49,7 @@ from unilabos.ros.x.rclpyx import get_event_loop
|
|||||||
from unilabos.ros.utils.driver_creator import ProtocolNodeCreator, PyLabRobotCreator, DeviceClassCreator
|
from unilabos.ros.utils.driver_creator import ProtocolNodeCreator, PyLabRobotCreator, DeviceClassCreator
|
||||||
from unilabos.utils.async_util import run_async_func
|
from unilabos.utils.async_util import run_async_func
|
||||||
from unilabos.utils.log import info, debug, warning, error, critical, logger
|
from unilabos.utils.log import info, debug, warning, error, critical, logger
|
||||||
from unilabos.utils.type_check import get_type_class, TypeEncoder
|
from unilabos.utils.type_check import get_type_class, TypeEncoder, serialize_result_info
|
||||||
|
|
||||||
T = TypeVar("T")
|
T = TypeVar("T")
|
||||||
|
|
||||||
@@ -292,7 +304,9 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
|||||||
self.create_ros_action_server(action_name, action_value_mapping)
|
self.create_ros_action_server(action_name, action_value_mapping)
|
||||||
|
|
||||||
# 创建线程池执行器
|
# 创建线程池执行器
|
||||||
self._executor = ThreadPoolExecutor(max_workers=max(len(action_value_mappings), 1), thread_name_prefix=f"ROSDevice{self.device_id}")
|
self._executor = ThreadPoolExecutor(
|
||||||
|
max_workers=max(len(action_value_mappings), 1), thread_name_prefix=f"ROSDevice{self.device_id}"
|
||||||
|
)
|
||||||
|
|
||||||
# 创建资源管理客户端
|
# 创建资源管理客户端
|
||||||
self._resource_clients: Dict[str, Client] = {
|
self._resource_clients: Dict[str, Client] = {
|
||||||
@@ -334,7 +348,9 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
|||||||
other_calling_param["slot"] = slot
|
other_calling_param["slot"] = slot
|
||||||
# 本地拿到这个物料,可能需要先做初始化?
|
# 本地拿到这个物料,可能需要先做初始化?
|
||||||
if isinstance(resources, list):
|
if isinstance(resources, list):
|
||||||
if len(resources) == 1 and isinstance(resources[0], list) and not initialize_full: # 取消,不存在的情况
|
if (
|
||||||
|
len(resources) == 1 and isinstance(resources[0], list) and not initialize_full
|
||||||
|
): # 取消,不存在的情况
|
||||||
# 预先initialize过,以整组的形式传入
|
# 预先initialize过,以整组的形式传入
|
||||||
request.resources = [convert_to_ros_msg(Resource, resource_) for resource_ in resources[0]]
|
request.resources = [convert_to_ros_msg(Resource, resource_) for resource_ in resources[0]]
|
||||||
elif initialize_full:
|
elif initialize_full:
|
||||||
@@ -373,6 +389,7 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
|||||||
from pylabrobot.resources import Coordinate
|
from pylabrobot.resources import Coordinate
|
||||||
from pylabrobot.resources import OTDeck
|
from pylabrobot.resources import OTDeck
|
||||||
from pylabrobot.resources import Plate
|
from pylabrobot.resources import Plate
|
||||||
|
|
||||||
contain_model = not isinstance(resource, Deck)
|
contain_model = not isinstance(resource, Deck)
|
||||||
if isinstance(resource, ResourcePLR):
|
if isinstance(resource, ResourcePLR):
|
||||||
# resources.list()
|
# resources.list()
|
||||||
@@ -380,25 +397,38 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
|||||||
plr_instance = resource_ulab_to_plr(resources_tree[0], contain_model)
|
plr_instance = resource_ulab_to_plr(resources_tree[0], contain_model)
|
||||||
if isinstance(plr_instance, Plate):
|
if isinstance(plr_instance, Plate):
|
||||||
empty_liquid_info_in = [(None, 0)] * plr_instance.num_items
|
empty_liquid_info_in = [(None, 0)] * plr_instance.num_items
|
||||||
for liquid_type, liquid_volume, liquid_input_slot in zip(ADD_LIQUID_TYPE, LIQUID_VOLUME, LIQUID_INPUT_SLOT):
|
for liquid_type, liquid_volume, liquid_input_slot in zip(
|
||||||
|
ADD_LIQUID_TYPE, LIQUID_VOLUME, LIQUID_INPUT_SLOT
|
||||||
|
):
|
||||||
empty_liquid_info_in[liquid_input_slot] = (liquid_type, liquid_volume)
|
empty_liquid_info_in[liquid_input_slot] = (liquid_type, liquid_volume)
|
||||||
plr_instance.set_well_liquids(empty_liquid_info_in)
|
plr_instance.set_well_liquids(empty_liquid_info_in)
|
||||||
if isinstance(resource, OTDeck) and "slot" in other_calling_param:
|
if isinstance(resource, OTDeck) and "slot" in other_calling_param:
|
||||||
resource.assign_child_at_slot(plr_instance, **other_calling_param)
|
resource.assign_child_at_slot(plr_instance, **other_calling_param)
|
||||||
else:
|
else:
|
||||||
_discard_slot = other_calling_param.pop("slot", -1)
|
_discard_slot = other_calling_param.pop("slot", -1)
|
||||||
resource.assign_child_resource(plr_instance, Coordinate(location["x"], location["y"], location["z"]), **other_calling_param)
|
resource.assign_child_resource(
|
||||||
request2.resources = [convert_to_ros_msg(Resource, r) for r in tree_to_list([resource_plr_to_ulab(resource)])]
|
plr_instance,
|
||||||
|
Coordinate(location["x"], location["y"], location["z"]),
|
||||||
|
**other_calling_param,
|
||||||
|
)
|
||||||
|
request2.resources = [
|
||||||
|
convert_to_ros_msg(Resource, r) for r in tree_to_list([resource_plr_to_ulab(resource)])
|
||||||
|
]
|
||||||
rclient2.call(request2)
|
rclient2.call(request2)
|
||||||
# 发送给ResourceMeshManager
|
# 发送给ResourceMeshManager
|
||||||
action_client = ActionClient(
|
action_client = ActionClient(
|
||||||
self, SendCmd, "/devices/resource_mesh_manager/add_resource_mesh", callback_group=self.callback_group
|
self,
|
||||||
|
SendCmd,
|
||||||
|
"/devices/resource_mesh_manager/add_resource_mesh",
|
||||||
|
callback_group=self.callback_group,
|
||||||
)
|
)
|
||||||
goal = SendCmd.Goal()
|
goal = SendCmd.Goal()
|
||||||
goal.command = json.dumps({
|
goal.command = json.dumps(
|
||||||
"resources": resources,
|
{
|
||||||
"bind_parent_id": bind_parent_id,
|
"resources": resources,
|
||||||
})
|
"bind_parent_id": bind_parent_id,
|
||||||
|
}
|
||||||
|
)
|
||||||
future = action_client.send_goal_async(goal, goal_uuid=uuid.uuid4())
|
future = action_client.send_goal_async(goal, goal_uuid=uuid.uuid4())
|
||||||
|
|
||||||
def done_cb(*args):
|
def done_cb(*args):
|
||||||
@@ -415,10 +445,16 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
|||||||
# noinspection PyTypeChecker
|
# noinspection PyTypeChecker
|
||||||
self._service_server: Dict[str, Service] = {
|
self._service_server: Dict[str, Service] = {
|
||||||
"query_host_name": self.create_service(
|
"query_host_name": self.create_service(
|
||||||
SerialCommand, f"/srv{self.namespace}/query_host_name", query_host_name_cb, callback_group=self.callback_group
|
SerialCommand,
|
||||||
|
f"/srv{self.namespace}/query_host_name",
|
||||||
|
query_host_name_cb,
|
||||||
|
callback_group=self.callback_group,
|
||||||
),
|
),
|
||||||
"append_resource": self.create_service(
|
"append_resource": self.create_service(
|
||||||
SerialCommand, f"/srv{self.namespace}/append_resource", append_resource, callback_group=self.callback_group
|
SerialCommand,
|
||||||
|
f"/srv{self.namespace}/append_resource",
|
||||||
|
append_resource,
|
||||||
|
callback_group=self.callback_group,
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -447,6 +483,7 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
|||||||
registered_devices[self.device_id] = device_info
|
registered_devices[self.device_id] = device_info
|
||||||
from unilabos.config.config import BasicConfig
|
from unilabos.config.config import BasicConfig
|
||||||
from unilabos.ros.nodes.presets.host_node import HostNode
|
from unilabos.ros.nodes.presets.host_node import HostNode
|
||||||
|
|
||||||
if not BasicConfig.is_host_mode:
|
if not BasicConfig.is_host_mode:
|
||||||
sclient = self.create_client(SerialCommand, "/node_info_update")
|
sclient = self.create_client(SerialCommand, "/node_info_update")
|
||||||
# 启动线程执行发送任务
|
# 启动线程执行发送任务
|
||||||
@@ -454,7 +491,7 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
|||||||
target=self.send_slave_node_info,
|
target=self.send_slave_node_info,
|
||||||
args=(sclient,),
|
args=(sclient,),
|
||||||
daemon=True,
|
daemon=True,
|
||||||
name=f"ROSDevice{self.device_id}_send_slave_node_info"
|
name=f"ROSDevice{self.device_id}_send_slave_node_info",
|
||||||
).start()
|
).start()
|
||||||
else:
|
else:
|
||||||
host_node = HostNode.get_instance(0)
|
host_node = HostNode.get_instance(0)
|
||||||
@@ -465,12 +502,18 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
|||||||
sclient.wait_for_service()
|
sclient.wait_for_service()
|
||||||
request = SerialCommand.Request()
|
request = SerialCommand.Request()
|
||||||
from unilabos.config.config import BasicConfig
|
from unilabos.config.config import BasicConfig
|
||||||
request.command = json.dumps({
|
|
||||||
"SYNC_SLAVE_NODE_INFO": {
|
request.command = json.dumps(
|
||||||
"machine_name": BasicConfig.machine_name,
|
{
|
||||||
"type": "slave",
|
"SYNC_SLAVE_NODE_INFO": {
|
||||||
"edge_device_id": self.device_id
|
"machine_name": BasicConfig.machine_name,
|
||||||
}}, ensure_ascii=False, cls=TypeEncoder)
|
"type": "slave",
|
||||||
|
"edge_device_id": self.device_id,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ensure_ascii=False,
|
||||||
|
cls=TypeEncoder,
|
||||||
|
)
|
||||||
|
|
||||||
# 发送异步请求并等待结果
|
# 发送异步请求并等待结果
|
||||||
future = sclient.call_async(request)
|
future = sclient.call_async(request)
|
||||||
@@ -543,6 +586,11 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
|||||||
"""创建动作执行回调函数"""
|
"""创建动作执行回调函数"""
|
||||||
|
|
||||||
async def execute_callback(goal_handle: ServerGoalHandle):
|
async def execute_callback(goal_handle: ServerGoalHandle):
|
||||||
|
# 初始化结果信息变量
|
||||||
|
execution_error = ""
|
||||||
|
execution_success = False
|
||||||
|
action_return_value = None
|
||||||
|
|
||||||
self.lab_logger().info(f"执行动作: {action_name}")
|
self.lab_logger().info(f"执行动作: {action_name}")
|
||||||
goal = goal_handle.request
|
goal = goal_handle.request
|
||||||
|
|
||||||
@@ -582,7 +630,11 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
|||||||
current_resources.extend(response.resources)
|
current_resources.extend(response.resources)
|
||||||
else:
|
else:
|
||||||
r = ResourceGet.Request()
|
r = ResourceGet.Request()
|
||||||
r.id = action_kwargs[k]["id"] if v == "unilabos_msgs/Resource" else action_kwargs[k][0]["id"]
|
r.id = (
|
||||||
|
action_kwargs[k]["id"]
|
||||||
|
if v == "unilabos_msgs/Resource"
|
||||||
|
else action_kwargs[k][0]["id"]
|
||||||
|
)
|
||||||
r.with_children = True
|
r.with_children = True
|
||||||
response = await self._resource_clients["resource_get"].call_async(r)
|
response = await self._resource_clients["resource_get"].call_async(r)
|
||||||
current_resources.extend(response.resources)
|
current_resources.extend(response.resources)
|
||||||
@@ -605,7 +657,19 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
|||||||
if asyncio.iscoroutinefunction(ACTION):
|
if asyncio.iscoroutinefunction(ACTION):
|
||||||
try:
|
try:
|
||||||
self.lab_logger().info(f"异步执行动作 {ACTION}")
|
self.lab_logger().info(f"异步执行动作 {ACTION}")
|
||||||
future = ROS2DeviceNode.run_async_func(ACTION, **action_kwargs)
|
future = ROS2DeviceNode.run_async_func(ACTION, trace_error=False, **action_kwargs)
|
||||||
|
|
||||||
|
def _handle_future_exception(fut):
|
||||||
|
nonlocal execution_error, execution_success, action_return_value
|
||||||
|
try:
|
||||||
|
action_return_value = fut.result()
|
||||||
|
execution_success = True
|
||||||
|
except Exception as e:
|
||||||
|
execution_error = traceback.format_exc()
|
||||||
|
error(f"异步任务 {ACTION.__name__} 报错了")
|
||||||
|
error(traceback.format_exc())
|
||||||
|
|
||||||
|
future.add_done_callback(_handle_future_exception)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.lab_logger().error(f"创建异步任务失败: {traceback.format_exc()}")
|
self.lab_logger().error(f"创建异步任务失败: {traceback.format_exc()}")
|
||||||
raise e
|
raise e
|
||||||
@@ -614,9 +678,12 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
|||||||
future = self._executor.submit(ACTION, **action_kwargs)
|
future = self._executor.submit(ACTION, **action_kwargs)
|
||||||
|
|
||||||
def _handle_future_exception(fut):
|
def _handle_future_exception(fut):
|
||||||
|
nonlocal execution_error, execution_success, action_return_value
|
||||||
try:
|
try:
|
||||||
fut.result()
|
action_return_value = fut.result()
|
||||||
|
execution_success = True
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
execution_error = traceback.format_exc()
|
||||||
error(f"同步任务 {ACTION.__name__} 报错了")
|
error(f"同步任务 {ACTION.__name__} 报错了")
|
||||||
error(traceback.format_exc())
|
error(traceback.format_exc())
|
||||||
|
|
||||||
@@ -707,6 +774,8 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
|||||||
for attr_name in result_msg_types.keys():
|
for attr_name in result_msg_types.keys():
|
||||||
if attr_name in ["success", "reached_goal"]:
|
if attr_name in ["success", "reached_goal"]:
|
||||||
setattr(result_msg, attr_name, True)
|
setattr(result_msg, attr_name, True)
|
||||||
|
elif attr_name == "return_info":
|
||||||
|
setattr(result_msg, attr_name, serialize_result_info(execution_error, execution_success, action_return_value))
|
||||||
|
|
||||||
self.lab_logger().info(f"动作 {action_name} 完成并返回结果")
|
self.lab_logger().info(f"动作 {action_name} 完成并返回结果")
|
||||||
return result_msg
|
return result_msg
|
||||||
@@ -752,8 +821,8 @@ class ROS2DeviceNode:
|
|||||||
return cls._loop
|
return cls._loop
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def run_async_func(cls, func, **kwargs):
|
def run_async_func(cls, func, trace_error=True, **kwargs):
|
||||||
return run_async_func(func, loop=cls._loop, **kwargs)
|
return run_async_func(func, loop=cls._loop, trace_error=trace_error, **kwargs)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def driver_instance(self):
|
def driver_instance(self):
|
||||||
@@ -805,9 +874,11 @@ class ROS2DeviceNode:
|
|||||||
self.resource_tracker = DeviceNodeResourceTracker()
|
self.resource_tracker = DeviceNodeResourceTracker()
|
||||||
|
|
||||||
# use_pylabrobot_creator 使用 cls的包路径检测
|
# use_pylabrobot_creator 使用 cls的包路径检测
|
||||||
use_pylabrobot_creator = (driver_class.__module__.startswith("pylabrobot")
|
use_pylabrobot_creator = (
|
||||||
or driver_class.__name__ == "LiquidHandlerAbstract"
|
driver_class.__module__.startswith("pylabrobot")
|
||||||
or driver_class.__name__ == "LiquidHandlerBiomek")
|
or driver_class.__name__ == "LiquidHandlerAbstract"
|
||||||
|
or driver_class.__name__ == "LiquidHandlerBiomek"
|
||||||
|
)
|
||||||
|
|
||||||
# TODO: 要在创建之前预先请求服务器是否有当前id的物料,放到resource_tracker中,让pylabrobot进行创建
|
# TODO: 要在创建之前预先请求服务器是否有当前id的物料,放到resource_tracker中,让pylabrobot进行创建
|
||||||
# 创建设备类实例
|
# 创建设备类实例
|
||||||
|
|||||||
@@ -151,7 +151,7 @@ class HostNode(BaseROS2DeviceNode):
|
|||||||
mqtt_client.publish_registry(device_info["id"], device_info)
|
mqtt_client.publish_registry(device_info["id"], device_info)
|
||||||
for resource_info in lab_registry.obtain_registry_resource_info():
|
for resource_info in lab_registry.obtain_registry_resource_info():
|
||||||
mqtt_client.publish_registry(resource_info["id"], resource_info)
|
mqtt_client.publish_registry(resource_info["id"], resource_info)
|
||||||
|
time.sleep(1) # 等待MQTT连接稳定
|
||||||
# 首次发现网络中的设备
|
# 首次发现网络中的设备
|
||||||
self._discover_devices()
|
self._discover_devices()
|
||||||
|
|
||||||
@@ -203,8 +203,12 @@ class HostNode(BaseROS2DeviceNode):
|
|||||||
try:
|
try:
|
||||||
for bridge in self.bridges:
|
for bridge in self.bridges:
|
||||||
if hasattr(bridge, "resource_add"):
|
if hasattr(bridge, "resource_add"):
|
||||||
self.lab_logger().info("[Host Node-Resource] Adding resources to bridge.")
|
resource_start_time = time.time()
|
||||||
resource_add_res = bridge.resource_add(add_schema(resource_with_parent_name))
|
resource_add_res = bridge.resource_add(add_schema(resource_with_parent_name), True)
|
||||||
|
resource_end_time = time.time()
|
||||||
|
self.lab_logger().info(
|
||||||
|
f"[Host Node-Resource] 物料上传 {round(resource_end_time - resource_start_time, 5) * 1000} ms"
|
||||||
|
)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
self.lab_logger().error("[Host Node-Resource] 添加物料出错!")
|
self.lab_logger().error("[Host Node-Resource] 添加物料出错!")
|
||||||
self.lab_logger().error(traceback.format_exc())
|
self.lab_logger().error(traceback.format_exc())
|
||||||
@@ -610,13 +614,21 @@ class HostNode(BaseROS2DeviceNode):
|
|||||||
"""获取结果回调"""
|
"""获取结果回调"""
|
||||||
result_msg = future.result().result
|
result_msg = future.result().result
|
||||||
result_data = convert_from_ros_msg(result_msg)
|
result_data = convert_from_ros_msg(result_msg)
|
||||||
|
status = "success"
|
||||||
|
try:
|
||||||
|
ret = json.loads(result_data.get("return_info", "{}")) # 确保返回信息是有效的JSON
|
||||||
|
suc = ret.get("suc", False)
|
||||||
|
if not suc:
|
||||||
|
status = "failed"
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
status = "failed"
|
||||||
self.lab_logger().info(f"[Host Node] Result for {action_id} ({uuid_str}): success")
|
self.lab_logger().info(f"[Host Node] Result for {action_id} ({uuid_str}): success")
|
||||||
self.lab_logger().debug(f"[Host Node] Result data: {result_data}")
|
self.lab_logger().debug(f"[Host Node] Result data: {result_data}")
|
||||||
|
|
||||||
if uuid_str:
|
if uuid_str:
|
||||||
for bridge in self.bridges:
|
for bridge in self.bridges:
|
||||||
if hasattr(bridge, "publish_job_status"):
|
if hasattr(bridge, "publish_job_status"):
|
||||||
bridge.publish_job_status(result_data, uuid_str, "success")
|
bridge.publish_job_status(result_data, uuid_str, status, result_data.get("return_info", "{}"))
|
||||||
|
|
||||||
def cancel_goal(self, goal_uuid: str) -> None:
|
def cancel_goal(self, goal_uuid: str) -> None:
|
||||||
"""取消目标"""
|
"""取消目标"""
|
||||||
@@ -856,7 +868,6 @@ class HostNode(BaseROS2DeviceNode):
|
|||||||
测试网络延迟的action实现
|
测试网络延迟的action实现
|
||||||
通过5次ping-pong机制校对时间误差并计算实际延迟
|
通过5次ping-pong机制校对时间误差并计算实际延迟
|
||||||
"""
|
"""
|
||||||
import time
|
|
||||||
import uuid as uuid_module
|
import uuid as uuid_module
|
||||||
|
|
||||||
self.lab_logger().info("=" * 60)
|
self.lab_logger().info("=" * 60)
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ from asyncio import get_event_loop
|
|||||||
from unilabos.utils.log import error
|
from unilabos.utils.log import error
|
||||||
|
|
||||||
|
|
||||||
def run_async_func(func, *, loop=None, **kwargs):
|
def run_async_func(func, *, loop=None, trace_error=True, **kwargs):
|
||||||
if loop is None:
|
if loop is None:
|
||||||
loop = get_event_loop()
|
loop = get_event_loop()
|
||||||
|
|
||||||
@@ -17,5 +17,6 @@ def run_async_func(func, *, loop=None, **kwargs):
|
|||||||
error(traceback.format_exc())
|
error(traceback.format_exc())
|
||||||
|
|
||||||
future = asyncio.run_coroutine_threadsafe(func(**kwargs), loop)
|
future = asyncio.run_coroutine_threadsafe(func(**kwargs), loop)
|
||||||
future.add_done_callback(_handle_future_exception)
|
if trace_error:
|
||||||
return future
|
future.add_done_callback(_handle_future_exception)
|
||||||
|
return future
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import collections
|
import collections.abc
|
||||||
import json
|
import json
|
||||||
from typing import get_origin, get_args
|
from typing import get_origin, get_args
|
||||||
|
|
||||||
@@ -21,3 +21,46 @@ class TypeEncoder(json.JSONEncoder):
|
|||||||
return str(obj)[8:-2]
|
return str(obj)[8:-2]
|
||||||
return super().default(obj)
|
return super().default(obj)
|
||||||
|
|
||||||
|
|
||||||
|
class ResultInfoEncoder(json.JSONEncoder):
|
||||||
|
"""专门用于处理任务执行结果信息的JSON编码器"""
|
||||||
|
|
||||||
|
def default(self, obj):
|
||||||
|
# 优先处理类型对象
|
||||||
|
if isinstance(obj, type):
|
||||||
|
return str(obj)[8:-2]
|
||||||
|
|
||||||
|
# 对于无法序列化的对象,统一转换为字符串
|
||||||
|
try:
|
||||||
|
# 尝试调用 __dict__ 或者其他序列化方法
|
||||||
|
if hasattr(obj, "__dict__"):
|
||||||
|
return obj.__dict__
|
||||||
|
elif hasattr(obj, "_asdict"): # namedtuple
|
||||||
|
return obj._asdict()
|
||||||
|
elif hasattr(obj, "to_dict"):
|
||||||
|
return obj.to_dict()
|
||||||
|
elif hasattr(obj, "dict"):
|
||||||
|
return obj.dict()
|
||||||
|
else:
|
||||||
|
# 如果都不行,转换为字符串
|
||||||
|
return str(obj)
|
||||||
|
except Exception:
|
||||||
|
# 如果转换失败,直接返回字符串表示
|
||||||
|
return str(obj)
|
||||||
|
|
||||||
|
|
||||||
|
def serialize_result_info(error: str, suc: bool, return_value=None) -> str:
|
||||||
|
"""
|
||||||
|
序列化任务执行结果信息
|
||||||
|
|
||||||
|
Args:
|
||||||
|
error: 错误信息字符串
|
||||||
|
suc: 是否成功的布尔值
|
||||||
|
return_value: 返回值,可以是任何类型
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
JSON字符串格式的结果信息
|
||||||
|
"""
|
||||||
|
result_info = {"error": error, "suc": suc, "return_value": return_value}
|
||||||
|
|
||||||
|
return json.dumps(result_info, ensure_ascii=False, cls=ResultInfoEncoder)
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ string from_repo_position
|
|||||||
Resource to_repo
|
Resource to_repo
|
||||||
string to_repo_position
|
string to_repo_position
|
||||||
---
|
---
|
||||||
|
string return_info
|
||||||
bool success
|
bool success
|
||||||
---
|
---
|
||||||
string status
|
string status
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ float64 volume # Optional. Volume of solvent to clean vessel with.
|
|||||||
float64 temp # Optional. Temperature to heat vessel to while cleaning.
|
float64 temp # Optional. Temperature to heat vessel to while cleaning.
|
||||||
int32 repeats # Optional. Number of cleaning cycles to perform.
|
int32 repeats # Optional. Number of cleaning cycles to perform.
|
||||||
---
|
---
|
||||||
|
string return_info
|
||||||
bool success
|
bool success
|
||||||
---
|
---
|
||||||
string status
|
string status
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
string return_info
|
||||||
---
|
---
|
||||||
@@ -3,6 +3,7 @@ string vessel
|
|||||||
string gas
|
string gas
|
||||||
int32 repeats
|
int32 repeats
|
||||||
---
|
---
|
||||||
|
string return_info
|
||||||
bool success
|
bool success
|
||||||
---
|
---
|
||||||
string status
|
string status
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ float64 temp
|
|||||||
float64 time
|
float64 time
|
||||||
float64 stir_speed
|
float64 stir_speed
|
||||||
---
|
---
|
||||||
|
string return_info
|
||||||
bool success
|
bool success
|
||||||
---
|
---
|
||||||
string status
|
string status
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
float64 float_in
|
float64 float_in
|
||||||
---
|
---
|
||||||
|
string return_info
|
||||||
bool success
|
bool success
|
||||||
---
|
---
|
||||||
@@ -6,6 +6,7 @@ bool stir
|
|||||||
float64 stir_speed
|
float64 stir_speed
|
||||||
string purpose
|
string purpose
|
||||||
---
|
---
|
||||||
|
string return_info
|
||||||
bool success
|
bool success
|
||||||
---
|
---
|
||||||
string status
|
string status
|
||||||
@@ -3,6 +3,7 @@ string vessel
|
|||||||
float64 temp
|
float64 temp
|
||||||
string purpose
|
string purpose
|
||||||
---
|
---
|
||||||
|
string return_info
|
||||||
bool success
|
bool success
|
||||||
---
|
---
|
||||||
string status
|
string status
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
# Organic
|
# Organic
|
||||||
string vessel
|
string vessel
|
||||||
---
|
---
|
||||||
|
string return_info
|
||||||
bool success
|
bool success
|
||||||
---
|
---
|
||||||
string status
|
string status
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
int32 int_input
|
int32 int_input
|
||||||
---
|
---
|
||||||
|
string return_info
|
||||||
bool success
|
bool success
|
||||||
---
|
---
|
||||||
@@ -15,6 +15,7 @@ int32 mix_rate
|
|||||||
float64 mix_liquid_height
|
float64 mix_liquid_height
|
||||||
string[] none_keys
|
string[] none_keys
|
||||||
---
|
---
|
||||||
|
string return_info
|
||||||
bool success
|
bool success
|
||||||
---
|
---
|
||||||
# 反馈
|
# 反馈
|
||||||
@@ -7,5 +7,6 @@ float64[] liquid_height
|
|||||||
float64[] blow_out_air_volume
|
float64[] blow_out_air_volume
|
||||||
string spread
|
string spread
|
||||||
---
|
---
|
||||||
|
string return_info
|
||||||
bool success
|
bool success
|
||||||
---
|
---
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
int32[] use_channels
|
int32[] use_channels
|
||||||
---
|
---
|
||||||
# 结果字段
|
# 结果字段
|
||||||
|
string return_info
|
||||||
bool success
|
bool success
|
||||||
---
|
---
|
||||||
# 反馈字段
|
# 反馈字段
|
||||||
@@ -8,6 +8,7 @@ int32[] blow_out_air_volume
|
|||||||
string spread
|
string spread
|
||||||
---
|
---
|
||||||
# 结果字段
|
# 结果字段
|
||||||
|
string return_info
|
||||||
bool success
|
bool success
|
||||||
---
|
---
|
||||||
# 反馈字段
|
# 反馈字段
|
||||||
@@ -6,6 +6,7 @@ geometry_msgs/Point[] offsets
|
|||||||
bool allow_nonzero_volume
|
bool allow_nonzero_volume
|
||||||
---
|
---
|
||||||
# 结果字段
|
# 结果字段
|
||||||
|
string return_info
|
||||||
bool success
|
bool success
|
||||||
---
|
---
|
||||||
# 反馈字段
|
# 反馈字段
|
||||||
@@ -5,6 +5,7 @@ geometry_msgs/Point offset
|
|||||||
bool allow_nonzero_volume
|
bool allow_nonzero_volume
|
||||||
---
|
---
|
||||||
# 结果字段
|
# 结果字段
|
||||||
|
string return_info
|
||||||
bool success
|
bool success
|
||||||
---
|
---
|
||||||
# 反馈字段
|
# 反馈字段
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
int32 time
|
int32 time
|
||||||
|
|
||||||
---
|
---
|
||||||
|
string return_info
|
||||||
bool success
|
bool success
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ geometry_msgs/Point[] offsets
|
|||||||
float64 mix_rate
|
float64 mix_rate
|
||||||
string[] none_keys
|
string[] none_keys
|
||||||
---
|
---
|
||||||
|
string return_info
|
||||||
bool success
|
bool success
|
||||||
---
|
---
|
||||||
# 反馈
|
# 反馈
|
||||||
@@ -2,5 +2,6 @@ string source
|
|||||||
string target
|
string target
|
||||||
|
|
||||||
---
|
---
|
||||||
|
string return_info
|
||||||
bool success
|
bool success
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ string put_direction
|
|||||||
float64 pickup_distance_from_top
|
float64 pickup_distance_from_top
|
||||||
---
|
---
|
||||||
# 结果字段
|
# 结果字段
|
||||||
|
string return_info
|
||||||
bool success
|
bool success
|
||||||
---
|
---
|
||||||
# 反馈字段
|
# 反馈字段
|
||||||
@@ -13,6 +13,7 @@ string put_direction
|
|||||||
float64 pickup_distance_from_top
|
float64 pickup_distance_from_top
|
||||||
---
|
---
|
||||||
# 结果字段
|
# 结果字段
|
||||||
|
string return_info
|
||||||
bool success
|
bool success
|
||||||
---
|
---
|
||||||
# 反馈字段
|
# 反馈字段
|
||||||
@@ -12,6 +12,7 @@ string get_direction
|
|||||||
string put_direction
|
string put_direction
|
||||||
---
|
---
|
||||||
# 结果字段
|
# 结果字段
|
||||||
|
string return_info
|
||||||
bool success
|
bool success
|
||||||
---
|
---
|
||||||
# 反馈字段
|
# 反馈字段
|
||||||
@@ -2,6 +2,7 @@ Resource well
|
|||||||
float64 dis_to_top
|
float64 dis_to_top
|
||||||
int32 channel
|
int32 channel
|
||||||
---
|
---
|
||||||
|
string return_info
|
||||||
bool success
|
bool success
|
||||||
---
|
---
|
||||||
# 反馈
|
# 反馈
|
||||||
@@ -2,5 +2,6 @@ int32 rpm
|
|||||||
int32 time
|
int32 time
|
||||||
|
|
||||||
---
|
---
|
||||||
|
string return_info
|
||||||
bool success
|
bool success
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ int32[] use_channels
|
|||||||
geometry_msgs/Point[] offsets
|
geometry_msgs/Point[] offsets
|
||||||
---
|
---
|
||||||
# 结果字段
|
# 结果字段
|
||||||
|
string return_info
|
||||||
bool success
|
bool success
|
||||||
---
|
---
|
||||||
# 反馈字段
|
# 反馈字段
|
||||||
@@ -4,6 +4,7 @@ Resource tip_rack
|
|||||||
geometry_msgs/Point offset
|
geometry_msgs/Point offset
|
||||||
---
|
---
|
||||||
# 结果字段
|
# 结果字段
|
||||||
|
string return_info
|
||||||
bool success
|
bool success
|
||||||
---
|
---
|
||||||
# 反馈字段
|
# 反馈字段
|
||||||
@@ -6,4 +6,5 @@ string protocol_date
|
|||||||
string protocol_type
|
string protocol_type
|
||||||
string[] none_keys
|
string[] none_keys
|
||||||
---
|
---
|
||||||
|
string return_info
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ bool is_96_well
|
|||||||
float64[] top
|
float64[] top
|
||||||
string[] none_keys
|
string[] none_keys
|
||||||
---
|
---
|
||||||
|
string return_info
|
||||||
bool success
|
bool success
|
||||||
---
|
---
|
||||||
# 反馈
|
# 反馈
|
||||||
@@ -4,6 +4,7 @@ int32[] use_channels
|
|||||||
bool allow_nonzero_volume
|
bool allow_nonzero_volume
|
||||||
---
|
---
|
||||||
# 结果字段
|
# 结果字段
|
||||||
|
string return_info
|
||||||
bool success
|
bool success
|
||||||
---
|
---
|
||||||
# 反馈字段
|
# 反馈字段
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
bool allow_nonzero_volume
|
bool allow_nonzero_volume
|
||||||
---
|
---
|
||||||
# 结果字段
|
# 结果字段
|
||||||
|
string return_info
|
||||||
bool success
|
bool success
|
||||||
---
|
---
|
||||||
# 反馈字段
|
# 反馈字段
|
||||||
@@ -7,6 +7,7 @@ float64 aspiration_flow_rate
|
|||||||
float64 dispense_flow_rate
|
float64 dispense_flow_rate
|
||||||
---
|
---
|
||||||
# 结果字段
|
# 结果字段
|
||||||
|
string return_info
|
||||||
bool success
|
bool success
|
||||||
---
|
---
|
||||||
# 反馈字段
|
# 反馈字段
|
||||||
@@ -20,6 +20,7 @@ float64 mix_liquid_height
|
|||||||
int32[] delays
|
int32[] delays
|
||||||
string[] none_keys
|
string[] none_keys
|
||||||
---
|
---
|
||||||
|
string return_info
|
||||||
bool success
|
bool success
|
||||||
---
|
---
|
||||||
# 反馈
|
# 反馈
|
||||||
@@ -6,5 +6,6 @@ string aspirate_technique
|
|||||||
string dispense_technique
|
string dispense_technique
|
||||||
|
|
||||||
---
|
---
|
||||||
|
string return_info
|
||||||
bool success
|
bool success
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -2,5 +2,6 @@ float64 x
|
|||||||
float64 y
|
float64 y
|
||||||
float64 z
|
float64 z
|
||||||
---
|
---
|
||||||
|
string return_info
|
||||||
bool success
|
bool success
|
||||||
---
|
---
|
||||||
@@ -10,6 +10,7 @@ float64 rinsing_volume
|
|||||||
int32 rinsing_repeats
|
int32 rinsing_repeats
|
||||||
bool solid
|
bool solid
|
||||||
---
|
---
|
||||||
|
string return_info
|
||||||
bool success
|
bool success
|
||||||
---
|
---
|
||||||
string status
|
string status
|
||||||
|
|||||||
@@ -4,5 +4,6 @@ string[] bind_parent_ids
|
|||||||
geometry_msgs/Point[] bind_locations
|
geometry_msgs/Point[] bind_locations
|
||||||
string[] other_calling_params
|
string[] other_calling_params
|
||||||
---
|
---
|
||||||
|
string return_info
|
||||||
bool success
|
bool success
|
||||||
---
|
---
|
||||||
@@ -8,5 +8,6 @@ string[] liquid_type
|
|||||||
float32[] liquid_volume
|
float32[] liquid_volume
|
||||||
int32 slot_on_deck
|
int32 slot_on_deck
|
||||||
---
|
---
|
||||||
|
string return_info
|
||||||
bool success
|
bool success
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
# Simple
|
# Simple
|
||||||
string command
|
string command
|
||||||
---
|
---
|
||||||
|
string return_info
|
||||||
bool success
|
bool success
|
||||||
---
|
---
|
||||||
string status
|
string status
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ float64 stir_time # Optional. Time stir for after adding solvent, before separat
|
|||||||
float64 stir_speed # Optional. Speed to stir at after adding solvent, before separation of phases.
|
float64 stir_speed # Optional. Speed to stir at after adding solvent, before separation of phases.
|
||||||
float64 settling_time # Optional. Time
|
float64 settling_time # Optional. Time
|
||||||
---
|
---
|
||||||
|
string return_info
|
||||||
bool success
|
bool success
|
||||||
---
|
---
|
||||||
string status
|
string status
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ int32 powder_tube_number
|
|||||||
string target_tube_position
|
string target_tube_position
|
||||||
float64 compound_mass
|
float64 compound_mass
|
||||||
---
|
---
|
||||||
|
string return_info
|
||||||
float64 actual_mass_mg
|
float64 actual_mass_mg
|
||||||
bool success
|
bool success
|
||||||
---
|
---
|
||||||
@@ -3,6 +3,7 @@ float64 stir_time
|
|||||||
float64 stir_speed
|
float64 stir_speed
|
||||||
float64 settling_time
|
float64 settling_time
|
||||||
---
|
---
|
||||||
|
string return_info
|
||||||
bool success
|
bool success
|
||||||
---
|
---
|
||||||
string status
|
string status
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
string string
|
string string
|
||||||
---
|
---
|
||||||
|
string return_info
|
||||||
bool success
|
bool success
|
||||||
---
|
---
|
||||||
@@ -3,6 +3,7 @@ string wf_name
|
|||||||
string params
|
string params
|
||||||
Resource resource
|
Resource resource
|
||||||
---
|
---
|
||||||
|
string return_info
|
||||||
bool success
|
bool success
|
||||||
---
|
---
|
||||||
string status
|
string status
|
||||||
|
|||||||
Reference in New Issue
Block a user