mirror of
https://github.com/deepmodeling/Uni-Lab-OS
synced 2026-03-27 18:53:11 +00:00
Compare commits
193 Commits
v0.9.1
...
6fd95fdb08
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6fd95fdb08 | ||
|
|
31993594e6 | ||
|
|
6c471553c4 | ||
|
|
e193bc493c | ||
|
|
9f8f6e55c4 | ||
|
|
8d56c523bb | ||
|
|
57cb120c8c | ||
|
|
a303bd7c5b | ||
|
|
47e58e13c7 | ||
|
|
5b9e13555c | ||
|
|
7b04f3fa50 | ||
|
|
f7db8d17c5 | ||
|
|
ff6998501e | ||
|
|
a354965f8e | ||
|
|
934276d2f7 | ||
|
|
803809480b | ||
|
|
b875f86bbb | ||
|
|
d058de3702 | ||
|
|
6385065ba3 | ||
|
|
3b32dcf066 | ||
|
|
7c714721db | ||
|
|
5478ba3237 | ||
|
|
49f1aa9c28 | ||
|
|
d5d516f0ef | ||
|
|
4471fed4b8 | ||
|
|
30d143e1a5 | ||
|
|
75ea45f21e | ||
|
|
66af337d6c | ||
|
|
ae3c65c1d3 | ||
|
|
11e4f053f1 | ||
|
|
aa1c67de29 | ||
|
|
96f37b3b0d | ||
|
|
d7d0a27976 | ||
|
|
34151f5cb2 | ||
|
|
369a21b904 | ||
|
|
90169981c1 | ||
|
|
d297abfd19 | ||
|
|
9c515a252a | ||
|
|
ea5e7a5ce2 | ||
|
|
2e9a0a4677 | ||
|
|
4c7aa8a89a | ||
|
|
d8a0c5e715 | ||
|
|
133ffaac17 | ||
|
|
729a0fcf0c | ||
|
|
6ae77e0408 | ||
|
|
bab4b1d67a | ||
|
|
12c17ec26e | ||
|
|
6577fe12eb | ||
|
|
f1fee5fad9 | ||
|
|
9b3377aedb | ||
|
|
526327727d | ||
|
|
aaa86314e3 | ||
|
|
6a14104e6b | ||
|
|
ab0c4b708b | ||
|
|
c0b7f2decd | ||
|
|
b6c9530c61 | ||
|
|
8698821c52 | ||
|
|
3f53f88390 | ||
|
|
e840516ba4 | ||
|
|
146d8c5296 | ||
|
|
6573c9e02e | ||
|
|
c7b9c6a825 | ||
|
|
48c43d3303 | ||
|
|
55be5e8188 | ||
|
|
1b9f3c666d | ||
|
|
097114d38c | ||
|
|
5bec899479 | ||
|
|
3470a1cb69 | ||
|
|
5e86112ebf | ||
|
|
24ecb13b79 | ||
|
|
2573d34713 | ||
|
|
106d71e1db | ||
|
|
3c2a4a64ac | ||
|
|
1e00a66a65 | ||
|
|
46da42deef | ||
|
|
101c1bc3cc | ||
|
|
a62112ae26 | ||
|
|
dd5a7cab75 | ||
|
|
39de3ac58e | ||
|
|
b99969278c | ||
|
|
b957ad2f71 | ||
|
|
e1a7c3a103 | ||
|
|
e63c15997c | ||
|
|
c5a495f409 | ||
|
|
5b240cb0ea | ||
|
|
147b8f47c0 | ||
|
|
6d2489af5f | ||
|
|
807dcdd226 | ||
|
|
8a29bc5597 | ||
|
|
6f6c70ee57 | ||
|
|
478a85951c | ||
|
|
0f2555c90c | ||
|
|
d2dda6ee03 | ||
|
|
208540b307 | ||
|
|
cb7c56a1d9 | ||
|
|
ea2e9c3e3a | ||
|
|
0452a68180 | ||
|
|
90a0f3db9b | ||
|
|
055d120ba8 | ||
|
|
a948f09f60 | ||
|
|
6f69df440c | ||
|
|
c2c2c2f020 | ||
|
|
4decd9a174 | ||
|
|
83c765f0ab | ||
|
|
3600b6f934 | ||
|
|
f0576e5666 | ||
|
|
8e1dbb56b1 | ||
|
|
013c25f3aa | ||
|
|
3d71c8bc78 | ||
|
|
42f0994147 | ||
|
|
4223f9b72c | ||
|
|
bec58e1301 | ||
|
|
6f9773157c | ||
|
|
da50e435c1 | ||
|
|
34e03bbd6e | ||
|
|
ad5168c3eb | ||
|
|
2dde5b6aae | ||
|
|
45a73e2f6d | ||
|
|
fbff27a52d | ||
|
|
1b190ee62f | ||
|
|
83abf877b5 | ||
|
|
f3637d4043 | ||
|
|
c12c2a876c | ||
|
|
6cdd8c18e8 | ||
|
|
3d60cb36b8 | ||
|
|
5df304bc64 | ||
|
|
6d5ada06de | ||
|
|
aad23596b6 | ||
|
|
b43f2321cd | ||
|
|
8617b1284f | ||
|
|
cd1e9a9f7d | ||
|
|
3d607db49a | ||
|
|
3dc62e3e99 | ||
|
|
d199fda9a5 | ||
|
|
ed2858a610 | ||
|
|
de28c50d8b | ||
|
|
e373220ce3 | ||
|
|
b6a3f17e9b | ||
|
|
49a9f05c51 | ||
|
|
32e370a562 | ||
|
|
852d10d751 | ||
|
|
b47f67d129 | ||
|
|
194985222e | ||
|
|
948f590b47 | ||
|
|
164417e1cf | ||
|
|
1a107cfd18 | ||
|
|
65d0cbe28a | ||
|
|
3c98c77cab | ||
|
|
d6b8104824 | ||
|
|
1223e05dcc | ||
|
|
a52133b7d0 | ||
|
|
80380d1f4b | ||
|
|
5668310401 | ||
|
|
78239ab1a3 | ||
|
|
fa5db06347 | ||
|
|
2b428080e7 | ||
|
|
9eb271f64e | ||
|
|
752442cb37 | ||
|
|
9d2bfec1dd | ||
|
|
5212d2d8eb | ||
|
|
44c191fe90 | ||
|
|
7a51b2adc1 | ||
|
|
2d034f728a | ||
|
|
8ab108c489 | ||
|
|
4dbb6649b4 | ||
|
|
dc197bffe8 | ||
|
|
49bb11b2a3 | ||
|
|
d407423aaa | ||
|
|
111c3f42e4 | ||
|
|
2990e70c25 | ||
|
|
0d24606d46 | ||
|
|
2baa232b86 | ||
|
|
b7a16cdfc8 | ||
|
|
8921bcd9fb | ||
|
|
5038219fe6 | ||
|
|
0d2f1be37a | ||
|
|
6b649bfdec | ||
|
|
ba6a43c594 | ||
|
|
ea6f25d1ce | ||
|
|
e5749a8058 | ||
|
|
09fc17429e | ||
|
|
bdf97be256 | ||
|
|
dbd1557095 | ||
|
|
ff8b75bf1f | ||
|
|
bed9720de3 | ||
|
|
1e01eae896 | ||
|
|
6155ec2798 | ||
|
|
279c5ed519 | ||
|
|
5b4f580a6f | ||
|
|
e971424220 | ||
|
|
82881f5882 | ||
|
|
bb1cac0dbd | ||
|
|
275e3a36f7 |
132
.github/workflows/multi-platform-build.yml
vendored
Normal file
132
.github/workflows/multi-platform-build.yml
vendored
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
name: Multi-Platform Conda Build
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ main, dev ]
|
||||||
|
tags: [ 'v*' ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ main, dev ]
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
platforms:
|
||||||
|
description: '选择构建平台 (逗号分隔): linux-64, osx-64, osx-arm64, win-64'
|
||||||
|
required: false
|
||||||
|
default: 'osx-arm64'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- os: ubuntu-latest
|
||||||
|
platform: linux-64
|
||||||
|
env_file: unilabos-linux-64.yaml
|
||||||
|
- os: macos-13 # Intel
|
||||||
|
platform: osx-64
|
||||||
|
env_file: unilabos-osx-64.yaml
|
||||||
|
- os: macos-latest # ARM64
|
||||||
|
platform: osx-arm64
|
||||||
|
env_file: unilabos-osx-arm64.yaml
|
||||||
|
- os: windows-latest
|
||||||
|
platform: win-64
|
||||||
|
env_file: unilabos-win64.yaml
|
||||||
|
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
shell: bash -l {0}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Check if platform should be built
|
||||||
|
id: should_build
|
||||||
|
run: |
|
||||||
|
if [[ "${{ github.event_name }}" != "workflow_dispatch" ]]; then
|
||||||
|
echo "should_build=true" >> $GITHUB_OUTPUT
|
||||||
|
elif [[ -z "${{ github.event.inputs.platforms }}" ]]; then
|
||||||
|
echo "should_build=true" >> $GITHUB_OUTPUT
|
||||||
|
elif [[ "${{ github.event.inputs.platforms }}" == *"${{ matrix.platform }}"* ]]; then
|
||||||
|
echo "should_build=true" >> $GITHUB_OUTPUT
|
||||||
|
else
|
||||||
|
echo "should_build=false" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Setup Miniconda
|
||||||
|
if: steps.should_build.outputs.should_build == 'true'
|
||||||
|
uses: conda-incubator/setup-miniconda@v3
|
||||||
|
with:
|
||||||
|
miniconda-version: "latest"
|
||||||
|
channels: conda-forge,robostack-staging,defaults
|
||||||
|
channel-priority: strict
|
||||||
|
activate-environment: build-env
|
||||||
|
auto-activate-base: false
|
||||||
|
auto-update-conda: false
|
||||||
|
show-channel-urls: true
|
||||||
|
|
||||||
|
- name: Install boa and build tools
|
||||||
|
if: steps.should_build.outputs.should_build == 'true'
|
||||||
|
run: |
|
||||||
|
conda install -c conda-forge boa conda-build
|
||||||
|
|
||||||
|
- name: Show environment info
|
||||||
|
if: steps.should_build.outputs.should_build == 'true'
|
||||||
|
run: |
|
||||||
|
conda info
|
||||||
|
conda list | grep -E "(boa|conda-build)"
|
||||||
|
echo "Platform: ${{ matrix.platform }}"
|
||||||
|
echo "OS: ${{ matrix.os }}"
|
||||||
|
|
||||||
|
- name: Build conda package
|
||||||
|
if: steps.should_build.outputs.should_build == 'true'
|
||||||
|
run: |
|
||||||
|
if [[ "${{ matrix.platform }}" == "osx-arm64" ]]; then
|
||||||
|
boa build -m ./recipes/conda_build_config.yaml -m ./recipes/macos_sdk_config.yaml ./recipes/ros-humble-unilabos-msgs
|
||||||
|
else
|
||||||
|
boa build -m ./recipes/conda_build_config.yaml ./recipes/ros-humble-unilabos-msgs
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: List built packages
|
||||||
|
if: steps.should_build.outputs.should_build == 'true'
|
||||||
|
run: |
|
||||||
|
echo "Built packages in conda-bld:"
|
||||||
|
find $CONDA_PREFIX/conda-bld -name "*.tar.bz2" | head -10
|
||||||
|
ls -la $CONDA_PREFIX/conda-bld/${{ matrix.platform }}/ || echo "${{ matrix.platform }} directory not found"
|
||||||
|
ls -la $CONDA_PREFIX/conda-bld/noarch/ || echo "noarch directory not found"
|
||||||
|
echo "CONDA_PREFIX: $CONDA_PREFIX"
|
||||||
|
echo "Full path would be: $CONDA_PREFIX/conda-bld/**/*.tar.bz2"
|
||||||
|
|
||||||
|
- name: Prepare artifacts for upload
|
||||||
|
if: steps.should_build.outputs.should_build == 'true'
|
||||||
|
run: |
|
||||||
|
mkdir -p ${{ runner.temp }}/conda-packages
|
||||||
|
find $CONDA_PREFIX/conda-bld -name "*.tar.bz2" -exec cp {} ${{ runner.temp }}/conda-packages/ \;
|
||||||
|
echo "Copied files to temp directory:"
|
||||||
|
ls -la ${{ runner.temp }}/conda-packages/
|
||||||
|
|
||||||
|
- name: Upload conda package artifacts
|
||||||
|
if: steps.should_build.outputs.should_build == 'true'
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: conda-package-${{ matrix.platform }}
|
||||||
|
path: ${{ runner.temp }}/conda-packages
|
||||||
|
if-no-files-found: warn
|
||||||
|
retention-days: 30
|
||||||
|
|
||||||
|
- name: Create release assets (on tags)
|
||||||
|
if: steps.should_build.outputs.should_build == 'true' && startsWith(github.ref, 'refs/tags/')
|
||||||
|
run: |
|
||||||
|
mkdir -p release-assets
|
||||||
|
find $CONDA_PREFIX/conda-bld -name "*.tar.bz2" -exec cp {} release-assets/ \;
|
||||||
|
|
||||||
|
- name: Upload to release
|
||||||
|
if: steps.should_build.outputs.should_build == 'true' && startsWith(github.ref, 'refs/tags/')
|
||||||
|
uses: softprops/action-gh-release@v1
|
||||||
|
with:
|
||||||
|
files: release-assets/*
|
||||||
|
draft: false
|
||||||
|
prerelease: false
|
||||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -233,3 +233,8 @@ CATKIN_IGNORE
|
|||||||
/**/local_config.py
|
/**/local_config.py
|
||||||
|
|
||||||
*.graphml
|
*.graphml
|
||||||
|
unilabos/device_mesh/view_robot.rviz
|
||||||
|
|
||||||
|
|
||||||
|
# Certs
|
||||||
|
**/.certs
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
recursive-include unilabos/registry *.yaml
|
recursive-include unilabos/registry *.yaml
|
||||||
recursive-include unilabos/app/web *.html
|
recursive-include unilabos/app/web *.html
|
||||||
recursive-include unilabos/app/web *.css
|
recursive-include unilabos/app/web *.css
|
||||||
|
recursive-include unilabos/device_mesh/devices *
|
||||||
|
recursive-include unilabos/device_mesh/resources *
|
||||||
|
|||||||
@@ -12,7 +12,11 @@
|
|||||||
[](https://github.com/dptech-corp/Uni-Lab-OS/issues)
|
[](https://github.com/dptech-corp/Uni-Lab-OS/issues)
|
||||||
[](https://github.com/dptech-corp/Uni-Lab-OS/blob/main/LICENSE)
|
[](https://github.com/dptech-corp/Uni-Lab-OS/blob/main/LICENSE)
|
||||||
|
|
||||||
Uni-Lab Operating System is a platform for laboratory automation, designed to connect and control various experimental equipment, enabling automation and standardization of experimental workflows.
|
Uni-Lab-OS is a platform for laboratory automation, designed to connect and control various experimental equipment, enabling automation and standardization of experimental workflows.
|
||||||
|
|
||||||
|
## 🏆 Competition
|
||||||
|
|
||||||
|
Join the [Intelligent Organic Chemistry Synthesis Competition](https://bohrium.dp.tech/competitions/1451645258) to explore automated synthesis with Uni-Lab-OS!
|
||||||
|
|
||||||
## Key Features
|
## Key Features
|
||||||
|
|
||||||
@@ -45,7 +49,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.6-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
|
||||||
|
|||||||
@@ -12,7 +12,11 @@
|
|||||||
[](https://github.com/dptech-corp/Uni-Lab-OS/issues)
|
[](https://github.com/dptech-corp/Uni-Lab-OS/issues)
|
||||||
[](https://github.com/dptech-corp/Uni-Lab-OS/blob/main/LICENSE)
|
[](https://github.com/dptech-corp/Uni-Lab-OS/blob/main/LICENSE)
|
||||||
|
|
||||||
Uni-Lab 操作系统是一个用于实验室自动化的综合平台,旨在连接和控制各种实验设备,实现实验流程的自动化和标准化。
|
Uni-Lab-OS是一个用于实验室自动化的综合平台,旨在连接和控制各种实验设备,实现实验流程的自动化和标准化。
|
||||||
|
|
||||||
|
## 🏆 比赛
|
||||||
|
|
||||||
|
欢迎参加[有机化学合成智能实验大赛](https://bohrium.dp.tech/competitions/1451645258),使用 Uni-Lab-OS 探索自动化合成!
|
||||||
|
|
||||||
## 核心特点
|
## 核心特点
|
||||||
|
|
||||||
@@ -45,7 +49,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.6-xxxxx.tar.bz2
|
||||||
|
|
||||||
# 安装PyLabRobot等前置
|
# 安装PyLabRobot等前置
|
||||||
git clone https://github.com/PyLabRobot/pylabrobot plr_repo
|
git clone https://github.com/PyLabRobot/pylabrobot plr_repo
|
||||||
|
|||||||
7
recipes/macos_sdk_config.yaml
Normal file
7
recipes/macos_sdk_config.yaml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
CONDA_BUILD_SYSROOT:
|
||||||
|
- /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk
|
||||||
|
MACOSX_DEPLOYMENT_TARGET:
|
||||||
|
- "11.0"
|
||||||
|
CONDA_SUBDIR:
|
||||||
|
- osx-arm64
|
||||||
|
# boa build -m ./recipes/conda_build_config.yaml -m ./recipes/macos_sdk_config.yaml ./recipes/ros-humble-unilabos-msgs
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
package:
|
package:
|
||||||
name: ros-humble-unilabos-msgs
|
name: ros-humble-unilabos-msgs
|
||||||
version: 0.9.1
|
version: 0.9.6
|
||||||
source:
|
source:
|
||||||
path: ../../unilabos_msgs
|
path: ../../unilabos_msgs
|
||||||
folder: ros-humble-unilabos-msgs/src/work
|
folder: ros-humble-unilabos-msgs/src/work
|
||||||
@@ -50,12 +50,12 @@ requirements:
|
|||||||
- robostack-staging::ros-humble-rosidl-default-generators
|
- robostack-staging::ros-humble-rosidl-default-generators
|
||||||
- robostack-staging::ros-humble-std-msgs
|
- robostack-staging::ros-humble-std-msgs
|
||||||
- robostack-staging::ros-humble-geometry-msgs
|
- robostack-staging::ros-humble-geometry-msgs
|
||||||
- robostack-staging::ros2-distro-mutex=0.6.*
|
- robostack-staging::ros2-distro-mutex=0.5.*
|
||||||
run:
|
run:
|
||||||
- robostack-staging::ros-humble-action-msgs
|
- robostack-staging::ros-humble-action-msgs
|
||||||
- robostack-staging::ros-humble-ros-workspace
|
- robostack-staging::ros-humble-ros-workspace
|
||||||
- robostack-staging::ros-humble-rosidl-default-runtime
|
- robostack-staging::ros-humble-rosidl-default-runtime
|
||||||
- robostack-staging::ros-humble-std-msgs
|
- robostack-staging::ros-humble-std-msgs
|
||||||
- robostack-staging::ros-humble-geometry-msgs
|
- robostack-staging::ros-humble-geometry-msgs
|
||||||
- robostack-staging::ros2-distro-mutex=0.6.*
|
# - robostack-staging::ros2-distro-mutex=0.6.*
|
||||||
- sel(osx and x86_64): __osx >={{ MACOSX_DEPLOYMENT_TARGET|default('10.14') }}
|
- sel(osx and x86_64): __osx >={{ MACOSX_DEPLOYMENT_TARGET|default('10.14') }}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package:
|
package:
|
||||||
name: unilabos
|
name: unilabos
|
||||||
version: "0.9.1"
|
version: "0.9.6"
|
||||||
|
|
||||||
source:
|
source:
|
||||||
path: ../..
|
path: ../..
|
||||||
|
|||||||
@@ -1,4 +1,2 @@
|
|||||||
[develop]
|
|
||||||
script_dir=$base/lib/unilabos
|
|
||||||
[install]
|
[install]
|
||||||
install_scripts=$base/lib/unilabos
|
install_scripts=$base/lib/unilabos
|
||||||
|
|||||||
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.6',
|
||||||
packages=find_packages(),
|
packages=find_packages(),
|
||||||
include_package_data=True,
|
include_package_data=True,
|
||||||
install_requires=['setuptools'],
|
install_requires=['setuptools'],
|
||||||
|
|||||||
@@ -3,3 +3,9 @@
|
|||||||
```bash
|
```bash
|
||||||
ros2 action send_goal /devices/host_node/create_resource_detailed unilabos_msgs/action/_resource_create_from_outer/ResourceCreateFromOuter "{ resources: [ { 'category': '', 'children': [], 'config': { 'type': 'Well', 'size_x': 6.86, 'size_y': 6.86, 'size_z': 10.67, 'rotation': { 'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation' }, 'category': 'well', 'model': null, 'max_volume': 360, 'material_z_thickness': 0.5, 'compute_volume_from_height': null, 'compute_height_from_volume': null, 'bottom_type': 'flat', 'cross_section_type': 'circle' }, 'data': { 'liquids': [], 'pending_liquids': [], 'liquid_history': [] }, 'id': 'plate_well_11_7', 'name': 'plate_well_11_7', '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': '', 'parent': 'plate', 'type': 'device' } ], device_ids: [ 'PLR_STATION' ], bind_parent_ids: [ 'plate' ], bind_locations: [ { 'x': 0.0, 'y': 0.0, 'z': 0.0 } ], other_calling_params: [ '{}' ] }"
|
ros2 action send_goal /devices/host_node/create_resource_detailed unilabos_msgs/action/_resource_create_from_outer/ResourceCreateFromOuter "{ resources: [ { 'category': '', 'children': [], 'config': { 'type': 'Well', 'size_x': 6.86, 'size_y': 6.86, 'size_z': 10.67, 'rotation': { 'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation' }, 'category': 'well', 'model': null, 'max_volume': 360, 'material_z_thickness': 0.5, 'compute_volume_from_height': null, 'compute_height_from_volume': null, 'bottom_type': 'flat', 'cross_section_type': 'circle' }, 'data': { 'liquids': [], 'pending_liquids': [], 'liquid_history': [] }, 'id': 'plate_well_11_7', 'name': 'plate_well_11_7', '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': '', 'parent': 'plate', 'type': 'device' } ], device_ids: [ 'PLR_STATION' ], bind_parent_ids: [ 'plate' ], bind_locations: [ { 'x': 0.0, 'y': 0.0, 'z': 0.0 } ], other_calling_params: [ '{}' ] }"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
使用mock_all.json启动,重新捕获MockContainerForChiller1
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ros2 action send_goal /devices/host_node/create_resource unilabos_msgs/action/_resource_create_from_outer_easy/ResourceCreateFromOuterEasy "{ 'res_id': 'MockContainerForChiller1', 'device_id': 'MockChiller1', 'class_name': 'container', 'parent': 'MockChiller1', 'bind_locations': { 'x': 0.0, 'y': 0.0, 'z': 0.0 }, 'liquid_input_slot': [ -1 ], 'liquid_type': [ 'CuCl2' ], 'liquid_volume': [ 100.0 ], 'slot_on_deck': '' }"
|
||||||
|
```
|
||||||
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": []
|
||||||
|
}
|
||||||
32
test/experiments/comprehensive_protocol/checklist.md
Normal file
32
test/experiments/comprehensive_protocol/checklist.md
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
1. 用到的仪器
|
||||||
|
virtual_multiway_valve() 八通阀门
|
||||||
|
virtual_transfer_pump() 转移泵
|
||||||
|
virtual_centrifuge() 离心机
|
||||||
|
virtual_rotavap() 旋蒸仪
|
||||||
|
virtual_heatchill() 加热器
|
||||||
|
virtual_stirrer() 搅拌器
|
||||||
|
virtual_solenoid_valve() 电磁阀
|
||||||
|
vacuum_pump() vacuum_pump.mock 真空泵
|
||||||
|
gas_source() 气源
|
||||||
|
virtual_filter() 过滤器
|
||||||
|
virtual_column(√) 层析柱
|
||||||
|
separator() homemade_grbl_conductivity 分液漏斗
|
||||||
|
2. 用到的protocol
|
||||||
|
AddProtocol()
|
||||||
|
TransferProtocol() 应该用pump_protocol.py删掉transfer
|
||||||
|
StartStirProtocol()
|
||||||
|
StopStirProtocol()
|
||||||
|
StirProtocol()
|
||||||
|
RunColumnProtocol()
|
||||||
|
CentrifugeProtocol()
|
||||||
|
FilterProtocol()
|
||||||
|
CleanVesselProtocol()
|
||||||
|
DissolveProtocol()
|
||||||
|
FilterThroughProtocol()
|
||||||
|
WashSolidProtocol()
|
||||||
|
SeparateProtocol(√)
|
||||||
|
EvaporateProtocol(√)
|
||||||
|
HeatChillProtocol()
|
||||||
|
HeatChillStartProtocol()
|
||||||
|
HeatChillStopProtocol()
|
||||||
|
EvacuateAndRefillProtocol(√)
|
||||||
@@ -0,0 +1,882 @@
|
|||||||
|
{
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"id": "ComprehensiveProtocolStation",
|
||||||
|
"name": "综合协议测试工作站",
|
||||||
|
"children": [
|
||||||
|
"multiway_valve_1",
|
||||||
|
"multiway_valve_2",
|
||||||
|
"transfer_pump_1",
|
||||||
|
"transfer_pump_2",
|
||||||
|
"reagent_bottle_1",
|
||||||
|
"reagent_bottle_2",
|
||||||
|
"reagent_bottle_3",
|
||||||
|
"reagent_bottle_4",
|
||||||
|
"reagent_bottle_5",
|
||||||
|
"centrifuge_1",
|
||||||
|
"rotavap_1",
|
||||||
|
"main_reactor",
|
||||||
|
"heater_1",
|
||||||
|
"stirrer_1",
|
||||||
|
"stirrer_2",
|
||||||
|
"waste_bottle_1",
|
||||||
|
"waste_bottle_2",
|
||||||
|
"solenoid_valve_1",
|
||||||
|
"solenoid_valve_2",
|
||||||
|
"vacuum_pump_1",
|
||||||
|
"gas_source_1",
|
||||||
|
"filter_1",
|
||||||
|
"column_1",
|
||||||
|
"separator_1",
|
||||||
|
"collection_bottle_1",
|
||||||
|
"collection_bottle_2",
|
||||||
|
"collection_bottle_3"
|
||||||
|
],
|
||||||
|
"parent": null,
|
||||||
|
"type": "device",
|
||||||
|
"class": "workstation",
|
||||||
|
"position": {
|
||||||
|
"x": 600,
|
||||||
|
"y": 400,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"protocol_type": [
|
||||||
|
"AddProtocol",
|
||||||
|
"TransferProtocol",
|
||||||
|
"StartStirProtocol",
|
||||||
|
"StopStirProtocol",
|
||||||
|
"StirProtocol",
|
||||||
|
"RunColumnProtocol",
|
||||||
|
"CentrifugeProtocol",
|
||||||
|
"FilterProtocol",
|
||||||
|
"CleanVesselProtocol",
|
||||||
|
"DissolveProtocol",
|
||||||
|
"FilterThroughProtocol",
|
||||||
|
"WashSolidProtocol",
|
||||||
|
"SeparateProtocol",
|
||||||
|
"EvaporateProtocol",
|
||||||
|
"HeatChillProtocol",
|
||||||
|
"HeatChillStartProtocol",
|
||||||
|
"HeatChillStopProtocol",
|
||||||
|
"EvacuateAndRefillProtocol"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"data": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "multiway_valve_1",
|
||||||
|
"name": "八通阀门1",
|
||||||
|
"children": [],
|
||||||
|
"parent": "ComprehensiveProtocolStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_multiway_valve",
|
||||||
|
"position": {
|
||||||
|
"x": 400,
|
||||||
|
"y": 300,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"positions": 8
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"valve_state": "Ready",
|
||||||
|
"current_position": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "multiway_valve_2",
|
||||||
|
"name": "八通阀门2",
|
||||||
|
"children": [],
|
||||||
|
"parent": "ComprehensiveProtocolStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_multiway_valve",
|
||||||
|
"position": {
|
||||||
|
"x": 800,
|
||||||
|
"y": 300,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"positions": 8
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"valve_state": "Ready",
|
||||||
|
"current_position": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "transfer_pump_1",
|
||||||
|
"name": "转移泵1",
|
||||||
|
"children": [],
|
||||||
|
"parent": "ComprehensiveProtocolStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_transfer_pump",
|
||||||
|
"position": {
|
||||||
|
"x": 350,
|
||||||
|
"y": 250,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 25.0,
|
||||||
|
"transfer_rate": 10.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"status": "Idle",
|
||||||
|
"current_volume": 0.0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "transfer_pump_2",
|
||||||
|
"name": "转移泵2",
|
||||||
|
"children": [],
|
||||||
|
"parent": "ComprehensiveProtocolStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_transfer_pump",
|
||||||
|
"position": {
|
||||||
|
"x": 850,
|
||||||
|
"y": 250,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 25.0,
|
||||||
|
"transfer_rate": 10.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"status": "Idle",
|
||||||
|
"current_volume": 0.0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reagent_bottle_1",
|
||||||
|
"name": "试剂瓶1-DMF",
|
||||||
|
"children": [],
|
||||||
|
"parent": "ComprehensiveProtocolStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 200,
|
||||||
|
"y": 150,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"volume": 1000.0,
|
||||||
|
"reagent": "DMF"
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"current_volume": 1000.0,
|
||||||
|
"reagent_name": "DMF"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reagent_bottle_2",
|
||||||
|
"name": "试剂瓶2-乙酸乙酯",
|
||||||
|
"children": [],
|
||||||
|
"parent": "ComprehensiveProtocolStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 250,
|
||||||
|
"y": 150,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"volume": 1000.0,
|
||||||
|
"reagent": "ethyl_acetate"
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"current_volume": 1000.0,
|
||||||
|
"reagent_name": "ethyl_acetate"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reagent_bottle_3",
|
||||||
|
"name": "试剂瓶3-己烷",
|
||||||
|
"children": [],
|
||||||
|
"parent": "ComprehensiveProtocolStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 300,
|
||||||
|
"y": 150,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"volume": 1000.0,
|
||||||
|
"reagent": "hexane"
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"current_volume": 1000.0,
|
||||||
|
"reagent_name": "hexane"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reagent_bottle_4",
|
||||||
|
"name": "试剂瓶4-甲醇",
|
||||||
|
"children": [],
|
||||||
|
"parent": "ComprehensiveProtocolStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 900,
|
||||||
|
"y": 150,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"volume": 1000.0,
|
||||||
|
"reagent": "methanol"
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"current_volume": 1000.0,
|
||||||
|
"reagent_name": "methanol"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reagent_bottle_5",
|
||||||
|
"name": "试剂瓶5-水",
|
||||||
|
"children": [],
|
||||||
|
"parent": "ComprehensiveProtocolStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 950,
|
||||||
|
"y": 150,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"volume": 1000.0,
|
||||||
|
"reagent": "water"
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"current_volume": 1000.0,
|
||||||
|
"reagent_name": "water"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "centrifuge_1",
|
||||||
|
"name": "离心机",
|
||||||
|
"children": [],
|
||||||
|
"parent": "ComprehensiveProtocolStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_centrifuge",
|
||||||
|
"position": {
|
||||||
|
"x": 200,
|
||||||
|
"y": 400,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_speed": 15000.0,
|
||||||
|
"max_temp": 40.0,
|
||||||
|
"min_temp": 4.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"current_speed": 0.0,
|
||||||
|
"status": "Idle"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "rotavap_1",
|
||||||
|
"name": "旋转蒸发仪",
|
||||||
|
"children": [],
|
||||||
|
"parent": "ComprehensiveProtocolStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_rotavap",
|
||||||
|
"position": {
|
||||||
|
"x": 300,
|
||||||
|
"y": 400,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_temp": 180.0,
|
||||||
|
"max_rotation_speed": 280.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"status": "Idle",
|
||||||
|
"current_temp": 25.0,
|
||||||
|
"rotation_speed": 0.0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "main_reactor",
|
||||||
|
"name": "主反应器",
|
||||||
|
"children": [],
|
||||||
|
"parent": "ComprehensiveProtocolStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 400,
|
||||||
|
"y": 450,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"volume": 500.0,
|
||||||
|
"max_temp": 200.0,
|
||||||
|
"min_temp": -20.0,
|
||||||
|
"has_stirrer": true,
|
||||||
|
"has_heater": true
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"current_volume": 0.0,
|
||||||
|
"current_temp": 25.0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "heater_1",
|
||||||
|
"name": "加热器",
|
||||||
|
"children": [],
|
||||||
|
"parent": "ComprehensiveProtocolStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_heatchill",
|
||||||
|
"position": {
|
||||||
|
"x": 450,
|
||||||
|
"y": 450,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_temp": 200.0,
|
||||||
|
"min_temp": -20.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"status": "Idle",
|
||||||
|
"current_temp": 25.0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "stirrer_1",
|
||||||
|
"name": "搅拌器1",
|
||||||
|
"children": [],
|
||||||
|
"parent": "ComprehensiveProtocolStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_stirrer",
|
||||||
|
"position": {
|
||||||
|
"x": 350,
|
||||||
|
"y": 450,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_speed": 2000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"status": "Idle",
|
||||||
|
"current_speed": 0.0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "stirrer_2",
|
||||||
|
"name": "搅拌器2",
|
||||||
|
"children": [],
|
||||||
|
"parent": "ComprehensiveProtocolStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_stirrer",
|
||||||
|
"position": {
|
||||||
|
"x": 351,
|
||||||
|
"y": 451,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_speed": 2000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"status": "Idle",
|
||||||
|
"current_speed": 0.0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "waste_bottle_1",
|
||||||
|
"name": "废液瓶1",
|
||||||
|
"children": [],
|
||||||
|
"parent": "ComprehensiveProtocolStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 500,
|
||||||
|
"y": 400,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"volume": 2000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"current_volume": 0.0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "waste_bottle_2",
|
||||||
|
"name": "废液瓶2",
|
||||||
|
"children": [],
|
||||||
|
"parent": "ComprehensiveProtocolStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 1100,
|
||||||
|
"y": 500,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"volume": 2000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"current_volume": 0.0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "solenoid_valve_1",
|
||||||
|
"name": "电磁阀1",
|
||||||
|
"children": [],
|
||||||
|
"parent": "ComprehensiveProtocolStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_solenoid_valve",
|
||||||
|
"position": {
|
||||||
|
"x": 700,
|
||||||
|
"y": 200,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"voltage": 12.0,
|
||||||
|
"response_time": 0.1
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"valve_state": "Closed",
|
||||||
|
"is_open": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "solenoid_valve_2",
|
||||||
|
"name": "电磁阀2",
|
||||||
|
"children": [],
|
||||||
|
"parent": "ComprehensiveProtocolStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_solenoid_valve",
|
||||||
|
"position": {
|
||||||
|
"x": 700,
|
||||||
|
"y": 150,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"voltage": 12.0,
|
||||||
|
"response_time": 0.1
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"valve_state": "Closed",
|
||||||
|
"is_open": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "vacuum_pump_1",
|
||||||
|
"name": "真空泵",
|
||||||
|
"children": [],
|
||||||
|
"parent": "ComprehensiveProtocolStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_vacuum_pump",
|
||||||
|
"position": {
|
||||||
|
"x": 650,
|
||||||
|
"y": 200,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_vacuum": 0.1,
|
||||||
|
"pump_rate": 50.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"status": "Off",
|
||||||
|
"current_vacuum": 1.0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "gas_source_1",
|
||||||
|
"name": "气源",
|
||||||
|
"children": [],
|
||||||
|
"parent": "ComprehensiveProtocolStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_gas_source",
|
||||||
|
"position": {
|
||||||
|
"x": 650,
|
||||||
|
"y": 150,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {},
|
||||||
|
"data": {
|
||||||
|
"gas_type": "nitrogen",
|
||||||
|
"max_pressure": 5.0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "filter_1",
|
||||||
|
"name": "过滤器",
|
||||||
|
"children": [],
|
||||||
|
"parent": "ComprehensiveProtocolStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_filter",
|
||||||
|
"position": {
|
||||||
|
"x": 900,
|
||||||
|
"y": 400,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"filter_type": "membrane",
|
||||||
|
"max_pressure": 5.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"status": "Ready",
|
||||||
|
"pressure": 0.0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "column_1",
|
||||||
|
"name": "洗脱柱",
|
||||||
|
"children": [],
|
||||||
|
"parent": "ComprehensiveProtocolStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_column",
|
||||||
|
"position": {
|
||||||
|
"x": 950,
|
||||||
|
"y": 400,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"column_type": "silica_gel",
|
||||||
|
"length": 30.0,
|
||||||
|
"diameter": 2.5
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"status": "Ready",
|
||||||
|
"loaded": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "separator_1",
|
||||||
|
"name": "分液器",
|
||||||
|
"children": [],
|
||||||
|
"parent": "ComprehensiveProtocolStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_separator",
|
||||||
|
"position": {
|
||||||
|
"x": 1000,
|
||||||
|
"y": 450,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"volume": 250.0,
|
||||||
|
"has_phases": true
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"status": "Ready",
|
||||||
|
"phase_separation": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "collection_bottle_1",
|
||||||
|
"name": "接收瓶1",
|
||||||
|
"children": [],
|
||||||
|
"parent": "ComprehensiveProtocolStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 900,
|
||||||
|
"y": 500,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"volume": 250.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"current_volume": 0.0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "collection_bottle_2",
|
||||||
|
"name": "接收瓶2",
|
||||||
|
"children": [],
|
||||||
|
"parent": "ComprehensiveProtocolStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 950,
|
||||||
|
"y": 500,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"volume": 250.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"current_volume": 0.0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "collection_bottle_3",
|
||||||
|
"name": "接收瓶3",
|
||||||
|
"children": [],
|
||||||
|
"parent": "ComprehensiveProtocolStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 1050,
|
||||||
|
"y": 500,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"volume": 250.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"current_volume": 0.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"id": "link_valve1_pump1",
|
||||||
|
"source": "multiway_valve_1",
|
||||||
|
"target": "transfer_pump_1",
|
||||||
|
"source_port": "port_0",
|
||||||
|
"target_port": "inlet",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_1": "port_0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve1_reagent1",
|
||||||
|
"source": "multiway_valve_1",
|
||||||
|
"target": "reagent_bottle_1",
|
||||||
|
"source_port": "port_1",
|
||||||
|
"target_port": "outlet",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_1": "port_1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve1_reagent2",
|
||||||
|
"source": "multiway_valve_1",
|
||||||
|
"target": "reagent_bottle_2",
|
||||||
|
"source_port": "port_2",
|
||||||
|
"target_port": "outlet",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_1": "port_2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve1_reagent3",
|
||||||
|
"source": "multiway_valve_1",
|
||||||
|
"target": "reagent_bottle_3",
|
||||||
|
"source_port": "port_3",
|
||||||
|
"target_port": "outlet",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_1": "port_3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve1_centrifuge",
|
||||||
|
"source": "multiway_valve_1",
|
||||||
|
"target": "centrifuge_1",
|
||||||
|
"source_port": "port_4",
|
||||||
|
"target_port": "inlet",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_1": "port_4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve1_rotavap",
|
||||||
|
"source": "multiway_valve_1",
|
||||||
|
"target": "rotavap_1",
|
||||||
|
"source_port": "port_5",
|
||||||
|
"target_port": "inlet",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_1": "port_5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve1_reactor",
|
||||||
|
"source": "multiway_valve_1",
|
||||||
|
"target": "main_reactor",
|
||||||
|
"source_port": "port_6",
|
||||||
|
"target_port": "inlet",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_1": "port_6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve1_waste1",
|
||||||
|
"source": "multiway_valve_1",
|
||||||
|
"target": "waste_bottle_1",
|
||||||
|
"source_port": "port_7",
|
||||||
|
"target_port": "inlet",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_1": "port_7"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve1_valve2",
|
||||||
|
"source": "multiway_valve_1",
|
||||||
|
"target": "multiway_valve_2",
|
||||||
|
"source_port": "port_8",
|
||||||
|
"target_port": "port_1",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_1": "port_8",
|
||||||
|
"multiway_valve_2": "port_1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve2_pump2",
|
||||||
|
"source": "multiway_valve_2",
|
||||||
|
"target": "transfer_pump_2",
|
||||||
|
"source_port": "port_0",
|
||||||
|
"target_port": "inlet",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_2": "port_0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve2_solenoid1",
|
||||||
|
"source": "multiway_valve_2",
|
||||||
|
"target": "solenoid_valve_1",
|
||||||
|
"source_port": "port_2",
|
||||||
|
"target_port": "inlet",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_2": "port_2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_solenoid1_vacuum",
|
||||||
|
"source": "solenoid_valve_1",
|
||||||
|
"target": "vacuum_pump_1",
|
||||||
|
"source_port": "outlet",
|
||||||
|
"target_port": "inlet",
|
||||||
|
"type": "fluid"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve2_solenoid2",
|
||||||
|
"source": "multiway_valve_2",
|
||||||
|
"target": "solenoid_valve_2",
|
||||||
|
"source_port": "port_3",
|
||||||
|
"target_port": "inlet",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_2": "port_3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_solenoid2_gas",
|
||||||
|
"source": "solenoid_valve_2",
|
||||||
|
"target": "gas_source_1",
|
||||||
|
"source_port": "outlet",
|
||||||
|
"target_port": "outlet",
|
||||||
|
"type": "fluid"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve2_filter",
|
||||||
|
"source": "multiway_valve_2",
|
||||||
|
"target": "filter_1",
|
||||||
|
"source_port": "port_4",
|
||||||
|
"target_port": "inlet",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_2": "port_4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_filter_collection1",
|
||||||
|
"source": "filter_1",
|
||||||
|
"target": "collection_bottle_1",
|
||||||
|
"source_port": "filtrate_outlet",
|
||||||
|
"target_port": "inlet",
|
||||||
|
"type": "fluid"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve2_column",
|
||||||
|
"source": "multiway_valve_2",
|
||||||
|
"target": "column_1",
|
||||||
|
"source_port": "port_5",
|
||||||
|
"target_port": "inlet",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_2": "port_5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_column_collection2",
|
||||||
|
"source": "column_1",
|
||||||
|
"target": "collection_bottle_2",
|
||||||
|
"source_port": "outlet",
|
||||||
|
"target_port": "inlet",
|
||||||
|
"type": "fluid"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve2_separator",
|
||||||
|
"source": "multiway_valve_2",
|
||||||
|
"target": "separator_1",
|
||||||
|
"source_port": "port_6",
|
||||||
|
"target_port": "inlet",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_2": "port_6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_separator_collection3",
|
||||||
|
"source": "separator_1",
|
||||||
|
"target": "collection_bottle_3",
|
||||||
|
"source_port": "top_outlet",
|
||||||
|
"target_port": "inlet",
|
||||||
|
"type": "fluid"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_separator_stirrer_2",
|
||||||
|
"source": "separator_1",
|
||||||
|
"target": "stirrer_2",
|
||||||
|
"source_port": "top_outlet",
|
||||||
|
"target_port": "inlet",
|
||||||
|
"type": "fluid"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_separator_waste2",
|
||||||
|
"source": "separator_1",
|
||||||
|
"target": "waste_bottle_2",
|
||||||
|
"source_port": "bottom_outlet",
|
||||||
|
"target_port": "inlet",
|
||||||
|
"type": "fluid"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve2_reagent4",
|
||||||
|
"source": "multiway_valve_2",
|
||||||
|
"target": "reagent_bottle_4",
|
||||||
|
"source_port": "port_7",
|
||||||
|
"target_port": "outlet",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_2": "port_7"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve2_reagent5",
|
||||||
|
"source": "multiway_valve_2",
|
||||||
|
"target": "reagent_bottle_5",
|
||||||
|
"source_port": "port_8",
|
||||||
|
"target_port": "outlet",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_2": "port_8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "mech_stirrer_reactor",
|
||||||
|
"source": "stirrer_1",
|
||||||
|
"target": "main_reactor",
|
||||||
|
"type": "fluid"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "thermal_heater_reactor",
|
||||||
|
"source": "heater_1",
|
||||||
|
"target": "main_reactor",
|
||||||
|
"type": "fluid"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
314
test/experiments/mock_devices/mock_all.json
Normal file
314
test/experiments/mock_devices/mock_all.json
Normal file
@@ -0,0 +1,314 @@
|
|||||||
|
{
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"id": "MockChiller1",
|
||||||
|
"name": "模拟冷却器",
|
||||||
|
"children": [
|
||||||
|
"MockContainerForChiller1"
|
||||||
|
],
|
||||||
|
"parent": null,
|
||||||
|
"type": "device",
|
||||||
|
"class": "mock_chiller",
|
||||||
|
"position": {
|
||||||
|
"x": 620.6111111111111,
|
||||||
|
"y": 171,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "MOCK"
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"current_temperature": 25.0,
|
||||||
|
"target_temperature": 25.0,
|
||||||
|
"status": "Idle",
|
||||||
|
"is_cooling": false,
|
||||||
|
"is_heating": false,
|
||||||
|
"vessel": "",
|
||||||
|
"purpose": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "MockContainerForChiller1",
|
||||||
|
"name": "模拟容器",
|
||||||
|
"type": "container",
|
||||||
|
"parent": "MockChiller1",
|
||||||
|
"position": {
|
||||||
|
"x": 5,
|
||||||
|
"y": 0,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid_type": "CuCl2",
|
||||||
|
"liquid_volume": "100"
|
||||||
|
},
|
||||||
|
"children": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "MockFilter1",
|
||||||
|
"name": "模拟过滤器",
|
||||||
|
"children": [],
|
||||||
|
"parent": null,
|
||||||
|
"type": "device",
|
||||||
|
"class": "mock_filter",
|
||||||
|
"position": {
|
||||||
|
"x": 620.6111111111111,
|
||||||
|
"y": 171,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "MOCK"
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"status": "Idle",
|
||||||
|
"is_filtering": false,
|
||||||
|
"flow_rate": 0.0,
|
||||||
|
"filter_life": 100.0,
|
||||||
|
"vessel": "",
|
||||||
|
"filtrate_vessel": "",
|
||||||
|
"filtered_volume": 0.0,
|
||||||
|
"target_volume": 0.0,
|
||||||
|
"progress": 0.0,
|
||||||
|
"stir": false,
|
||||||
|
"stir_speed": 0.0,
|
||||||
|
"temperature": 25.0,
|
||||||
|
"continue_heatchill": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "MockHeater1",
|
||||||
|
"name": "模拟加热器",
|
||||||
|
"children": [],
|
||||||
|
"parent": null,
|
||||||
|
"type": "device",
|
||||||
|
"class": "mock_heater",
|
||||||
|
"position": {
|
||||||
|
"x": 620.6111111111111,
|
||||||
|
"y": 171,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "MOCK"
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"current_temperature": 25.0,
|
||||||
|
"target_temperature": 25.0,
|
||||||
|
"status": "Idle",
|
||||||
|
"is_heating": false,
|
||||||
|
"heating_power": 0.0,
|
||||||
|
"max_temperature": 300.0,
|
||||||
|
"vessel": "Unknown",
|
||||||
|
"purpose": "Unknown",
|
||||||
|
"stir": false,
|
||||||
|
"stir_speed": 0.0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "MockPump1",
|
||||||
|
"name": "模拟泵设备",
|
||||||
|
"children": [],
|
||||||
|
"parent": null,
|
||||||
|
"type": "device",
|
||||||
|
"class": "mock_pump",
|
||||||
|
"position": {
|
||||||
|
"x": 620.6111111111111,
|
||||||
|
"y": 171,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "MOCK"
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"status": "Idle",
|
||||||
|
"current_device": "MockPump1",
|
||||||
|
"pump_state": "Stopped",
|
||||||
|
"flow_rate": 0.0,
|
||||||
|
"target_flow_rate": 0.0,
|
||||||
|
"pressure": 0.0,
|
||||||
|
"total_volume": 0.0,
|
||||||
|
"max_flow_rate": 100.0,
|
||||||
|
"max_pressure": 10.0,
|
||||||
|
"from_vessel": "",
|
||||||
|
"to_vessel": "",
|
||||||
|
"transfer_volume": 0.0,
|
||||||
|
"amount": "",
|
||||||
|
"transfer_time": 0.0,
|
||||||
|
"is_viscous": false,
|
||||||
|
"rinsing_solvent": "",
|
||||||
|
"rinsing_volume": 0.0,
|
||||||
|
"rinsing_repeats": 0,
|
||||||
|
"is_solid": false,
|
||||||
|
"time_spent": 0.0,
|
||||||
|
"time_remaining": 0.0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "MockRotavap1",
|
||||||
|
"name": "模拟旋转蒸发器",
|
||||||
|
"children": [],
|
||||||
|
"parent": null,
|
||||||
|
"type": "device",
|
||||||
|
"class": "mock_rotavap",
|
||||||
|
"position": {
|
||||||
|
"x": 620.6111111111111,
|
||||||
|
"y": 171,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "MOCK"
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"status": "Idle",
|
||||||
|
"rotate_state": "Stopped",
|
||||||
|
"rotate_time": 0.0,
|
||||||
|
"rotate_speed": 0.0,
|
||||||
|
"pump_state": "Stopped",
|
||||||
|
"pump_time": 0.0,
|
||||||
|
"vacuum_level": 1013.25,
|
||||||
|
"temperature": 25.0,
|
||||||
|
"target_temperature": 25.0,
|
||||||
|
"success": "True"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "MockSeparator1",
|
||||||
|
"name": "模拟分离器",
|
||||||
|
"children": [],
|
||||||
|
"parent": null,
|
||||||
|
"type": "device",
|
||||||
|
"class": "mock_separator",
|
||||||
|
"position": {
|
||||||
|
"x": 620.6111111111111,
|
||||||
|
"y": 171,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "MOCK"
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"status": "Idle",
|
||||||
|
"settling_time": 0.0,
|
||||||
|
"valve_state": "Closed",
|
||||||
|
"shake_time": 0.0,
|
||||||
|
"shake_status": "Not Shaking",
|
||||||
|
"current_device": "MockSeparator1",
|
||||||
|
"purpose": "",
|
||||||
|
"product_phase": "",
|
||||||
|
"from_vessel": "",
|
||||||
|
"separation_vessel": "",
|
||||||
|
"to_vessel": "",
|
||||||
|
"waste_phase_to_vessel": "",
|
||||||
|
"solvent": "",
|
||||||
|
"solvent_volume": 0.0,
|
||||||
|
"through": "",
|
||||||
|
"repeats": 1,
|
||||||
|
"stir_time": 0.0,
|
||||||
|
"stir_speed": 0.0,
|
||||||
|
"time_spent": 0.0,
|
||||||
|
"time_remaining": 0.0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "MockSolenoidValve1",
|
||||||
|
"name": "模拟电磁阀",
|
||||||
|
"children": [],
|
||||||
|
"parent": null,
|
||||||
|
"type": "device",
|
||||||
|
"class": "mock_solenoid_valve",
|
||||||
|
"position": {
|
||||||
|
"x": 620.6111111111111,
|
||||||
|
"y": 171,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "MOCK"
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"status": "Idle",
|
||||||
|
"valve_status": "Closed"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "MockStirrer1NEW",
|
||||||
|
"name": "模拟搅拌器(new)",
|
||||||
|
"children": [],
|
||||||
|
"parent": null,
|
||||||
|
"type": "device",
|
||||||
|
"class": "mock_stirrer_new",
|
||||||
|
"position": {
|
||||||
|
"x": 620.6111111111111,
|
||||||
|
"y": 171,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "MOCK"
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"status": "Idle",
|
||||||
|
"vessel": "",
|
||||||
|
"purpose": "",
|
||||||
|
"stir_speed": 0.0,
|
||||||
|
"target_stir_speed": 0.0,
|
||||||
|
"stir_state": "Stopped",
|
||||||
|
"stir_time": 0.0,
|
||||||
|
"settling_time": 0.0,
|
||||||
|
"progress": 0.0,
|
||||||
|
"max_stir_speed": 2000.0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "MockStirrer1",
|
||||||
|
"name": "模拟搅拌器",
|
||||||
|
"children": [],
|
||||||
|
"parent": null,
|
||||||
|
"type": "device",
|
||||||
|
"class": "mock_stirrer",
|
||||||
|
"position": {
|
||||||
|
"x": 620.6111111111111,
|
||||||
|
"y": 171,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "MOCK"
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"status": "Idle",
|
||||||
|
"stir_speed": 0.0,
|
||||||
|
"target_stir_speed": 0.0,
|
||||||
|
"stir_state": "Stopped",
|
||||||
|
"temperature": 25.0,
|
||||||
|
"target_temperature": 25.0,
|
||||||
|
"heating_state": "Off",
|
||||||
|
"heating_power": 0.0,
|
||||||
|
"max_stir_speed": 2000.0,
|
||||||
|
"max_temperature": 300.0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "MockVacuum1",
|
||||||
|
"name": "模拟真空泵",
|
||||||
|
"children": [],
|
||||||
|
"parent": null,
|
||||||
|
"type": "device",
|
||||||
|
"class": "mock_vacuum",
|
||||||
|
"position": {
|
||||||
|
"x": 620.6111111111111,
|
||||||
|
"y": 171,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "MOCK"
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"status": "Idle",
|
||||||
|
"power_state": "Off",
|
||||||
|
"pump_state": "Stopped",
|
||||||
|
"vacuum_level": 1013.25,
|
||||||
|
"target_vacuum": 50.0,
|
||||||
|
"pump_speed": 0.0,
|
||||||
|
"pump_efficiency": 95.0,
|
||||||
|
"max_pump_speed": 100.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"links": []
|
||||||
|
}
|
||||||
30
test/experiments/mock_devices/mock_chiller.json
Normal file
30
test/experiments/mock_devices/mock_chiller.json
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"id": "MockChiller1",
|
||||||
|
"name": "模拟冷却器",
|
||||||
|
"children": [],
|
||||||
|
"parent": null,
|
||||||
|
"type": "device",
|
||||||
|
"class": "mock_chiller",
|
||||||
|
"position": {
|
||||||
|
"x": 620.6111111111111,
|
||||||
|
"y": 171,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "MOCK"
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"current_temperature": 25.0,
|
||||||
|
"target_temperature": 25.0,
|
||||||
|
"status": "Idle",
|
||||||
|
"is_cooling": false,
|
||||||
|
"is_heating": false,
|
||||||
|
"vessel": "",
|
||||||
|
"purpose": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"links": []
|
||||||
|
}
|
||||||
36
test/experiments/mock_devices/mock_filter.json
Normal file
36
test/experiments/mock_devices/mock_filter.json
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
{
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"id": "MockFilter1",
|
||||||
|
"name": "模拟过滤器",
|
||||||
|
"children": [],
|
||||||
|
"parent": null,
|
||||||
|
"type": "device",
|
||||||
|
"class": "mock_filter",
|
||||||
|
"position": {
|
||||||
|
"x": 620.6111111111111,
|
||||||
|
"y": 171,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "MOCK"
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"status": "Idle",
|
||||||
|
"is_filtering": false,
|
||||||
|
"flow_rate": 0.0,
|
||||||
|
"filter_life": 100.0,
|
||||||
|
"vessel": "",
|
||||||
|
"filtrate_vessel": "",
|
||||||
|
"filtered_volume": 0.0,
|
||||||
|
"target_volume": 0.0,
|
||||||
|
"progress": 0.0,
|
||||||
|
"stir": false,
|
||||||
|
"stir_speed": 0.0,
|
||||||
|
"temperature": 25.0,
|
||||||
|
"continue_heatchill": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"links": []
|
||||||
|
}
|
||||||
33
test/experiments/mock_devices/mock_heater.json
Normal file
33
test/experiments/mock_devices/mock_heater.json
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"id": "MockHeater1",
|
||||||
|
"name": "模拟加热器",
|
||||||
|
"children": [],
|
||||||
|
"parent": null,
|
||||||
|
"type": "device",
|
||||||
|
"class": "mock_heater",
|
||||||
|
"position": {
|
||||||
|
"x": 620.6111111111111,
|
||||||
|
"y": 171,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "MOCK"
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"current_temperature": 25.0,
|
||||||
|
"target_temperature": 25.0,
|
||||||
|
"status": "Idle",
|
||||||
|
"is_heating": false,
|
||||||
|
"heating_power": 0.0,
|
||||||
|
"max_temperature": 300.0,
|
||||||
|
"vessel": "Unknown",
|
||||||
|
"purpose": "Unknown",
|
||||||
|
"stir": false,
|
||||||
|
"stir_speed": 0.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"links": []
|
||||||
|
}
|
||||||
44
test/experiments/mock_devices/mock_pump.json
Normal file
44
test/experiments/mock_devices/mock_pump.json
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
{
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"id": "MockPump1",
|
||||||
|
"name": "模拟泵设备",
|
||||||
|
"children": [],
|
||||||
|
"parent": null,
|
||||||
|
"type": "device",
|
||||||
|
"class": "mock_pump",
|
||||||
|
"position": {
|
||||||
|
"x": 620.6111111111111,
|
||||||
|
"y": 171,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "MOCK"
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"status": "Idle",
|
||||||
|
"current_device": "MockPump1",
|
||||||
|
"pump_state": "Stopped",
|
||||||
|
"flow_rate": 0.0,
|
||||||
|
"target_flow_rate": 0.0,
|
||||||
|
"pressure": 0.0,
|
||||||
|
"total_volume": 0.0,
|
||||||
|
"max_flow_rate": 100.0,
|
||||||
|
"max_pressure": 10.0,
|
||||||
|
"from_vessel": "",
|
||||||
|
"to_vessel": "",
|
||||||
|
"transfer_volume": 0.0,
|
||||||
|
"amount": "",
|
||||||
|
"transfer_time": 0.0,
|
||||||
|
"is_viscous": false,
|
||||||
|
"rinsing_solvent": "",
|
||||||
|
"rinsing_volume": 0.0,
|
||||||
|
"rinsing_repeats": 0,
|
||||||
|
"is_solid": false,
|
||||||
|
"time_spent": 0.0,
|
||||||
|
"time_remaining": 0.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"links": []
|
||||||
|
}
|
||||||
33
test/experiments/mock_devices/mock_rotavap.json
Normal file
33
test/experiments/mock_devices/mock_rotavap.json
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"id": "MockRotavap1",
|
||||||
|
"name": "模拟旋转蒸发器",
|
||||||
|
"children": [],
|
||||||
|
"parent": null,
|
||||||
|
"type": "device",
|
||||||
|
"class": "mock_rotavap",
|
||||||
|
"position": {
|
||||||
|
"x": 620.6111111111111,
|
||||||
|
"y": 171,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "MOCK"
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"status": "Idle",
|
||||||
|
"rotate_state": "Stopped",
|
||||||
|
"rotate_time": 0.0,
|
||||||
|
"rotate_speed": 0.0,
|
||||||
|
"pump_state": "Stopped",
|
||||||
|
"pump_time": 0.0,
|
||||||
|
"vacuum_level": 1013.25,
|
||||||
|
"temperature": 25.0,
|
||||||
|
"target_temperature": 25.0,
|
||||||
|
"success": "True"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"links": []
|
||||||
|
}
|
||||||
43
test/experiments/mock_devices/mock_separator.json
Normal file
43
test/experiments/mock_devices/mock_separator.json
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
{
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"id": "MockSeparator1",
|
||||||
|
"name": "模拟分离器",
|
||||||
|
"children": [],
|
||||||
|
"parent": null,
|
||||||
|
"type": "device",
|
||||||
|
"class": "mock_separator",
|
||||||
|
"position": {
|
||||||
|
"x": 620.6111111111111,
|
||||||
|
"y": 171,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "MOCK"
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"status": "Idle",
|
||||||
|
"settling_time": 0.0,
|
||||||
|
"valve_state": "Closed",
|
||||||
|
"shake_time": 0.0,
|
||||||
|
"shake_status": "Not Shaking",
|
||||||
|
"current_device": "MockSeparator1",
|
||||||
|
"purpose": "",
|
||||||
|
"product_phase": "",
|
||||||
|
"from_vessel": "",
|
||||||
|
"separation_vessel": "",
|
||||||
|
"to_vessel": "",
|
||||||
|
"waste_phase_to_vessel": "",
|
||||||
|
"solvent": "",
|
||||||
|
"solvent_volume": 0.0,
|
||||||
|
"through": "",
|
||||||
|
"repeats": 1,
|
||||||
|
"stir_time": 0.0,
|
||||||
|
"stir_speed": 0.0,
|
||||||
|
"time_spent": 0.0,
|
||||||
|
"time_remaining": 0.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"links": []
|
||||||
|
}
|
||||||
25
test/experiments/mock_devices/mock_solenoid_valve.json
Normal file
25
test/experiments/mock_devices/mock_solenoid_valve.json
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"id": "MockSolenoidValve1",
|
||||||
|
"name": "模拟电磁阀",
|
||||||
|
"children": [],
|
||||||
|
"parent": null,
|
||||||
|
"type": "device",
|
||||||
|
"class": "mock_solenoid_valve",
|
||||||
|
"position": {
|
||||||
|
"x": 620.6111111111111,
|
||||||
|
"y": 171,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "MOCK"
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"status": "Idle",
|
||||||
|
"valve_status": "Closed"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"links": []
|
||||||
|
}
|
||||||
33
test/experiments/mock_devices/mock_stirrer.json
Normal file
33
test/experiments/mock_devices/mock_stirrer.json
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"id": "MockStirrer1",
|
||||||
|
"name": "模拟搅拌器",
|
||||||
|
"children": [],
|
||||||
|
"parent": null,
|
||||||
|
"type": "device",
|
||||||
|
"class": "mock_stirrer",
|
||||||
|
"position": {
|
||||||
|
"x": 620.6111111111111,
|
||||||
|
"y": 171,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "MOCK"
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"status": "Idle",
|
||||||
|
"stir_speed": 0.0,
|
||||||
|
"target_stir_speed": 0.0,
|
||||||
|
"stir_state": "Stopped",
|
||||||
|
"temperature": 25.0,
|
||||||
|
"target_temperature": 25.0,
|
||||||
|
"heating_state": "Off",
|
||||||
|
"heating_power": 0.0,
|
||||||
|
"max_stir_speed": 2000.0,
|
||||||
|
"max_temperature": 300.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"links": []
|
||||||
|
}
|
||||||
33
test/experiments/mock_devices/mock_stirrer_new.json
Normal file
33
test/experiments/mock_devices/mock_stirrer_new.json
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"id": "MockStirrer1COPY",
|
||||||
|
"name": "模拟搅拌器(Copy)",
|
||||||
|
"children": [],
|
||||||
|
"parent": null,
|
||||||
|
"type": "device",
|
||||||
|
"class": "mock_stirrer_new",
|
||||||
|
"position": {
|
||||||
|
"x": 620.6111111111111,
|
||||||
|
"y": 171,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "MOCK"
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"status": "Idle",
|
||||||
|
"vessel": "",
|
||||||
|
"purpose": "",
|
||||||
|
"stir_speed": 0.0,
|
||||||
|
"target_stir_speed": 0.0,
|
||||||
|
"stir_state": "Stopped",
|
||||||
|
"stir_time": 0.0,
|
||||||
|
"settling_time": 0.0,
|
||||||
|
"progress": 0.0,
|
||||||
|
"max_stir_speed": 2000.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"links": []
|
||||||
|
}
|
||||||
31
test/experiments/mock_devices/mock_vacuum.json
Normal file
31
test/experiments/mock_devices/mock_vacuum.json
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"id": "MockVacuum1",
|
||||||
|
"name": "模拟真空泵",
|
||||||
|
"children": [],
|
||||||
|
"parent": null,
|
||||||
|
"type": "device",
|
||||||
|
"class": "mock_vacuum",
|
||||||
|
"position": {
|
||||||
|
"x": 620.6111111111111,
|
||||||
|
"y": 171,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "MOCK"
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"status": "Idle",
|
||||||
|
"power_state": "Off",
|
||||||
|
"pump_state": "Stopped",
|
||||||
|
"vacuum_level": 1013.25,
|
||||||
|
"target_vacuum": 50.0,
|
||||||
|
"pump_speed": 0.0,
|
||||||
|
"pump_efficiency": 95.0,
|
||||||
|
"max_pump_speed": 100.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"links": []
|
||||||
|
}
|
||||||
376
test/experiments/mock_protocol/addteststation.json
Normal file
376
test/experiments/mock_protocol/addteststation.json
Normal file
@@ -0,0 +1,376 @@
|
|||||||
|
{
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"id": "AddTestStation",
|
||||||
|
"name": "添加试剂测试工作站",
|
||||||
|
"children": [
|
||||||
|
"transfer_pump",
|
||||||
|
"multiway_valve",
|
||||||
|
"stirrer",
|
||||||
|
"flask_reagent1",
|
||||||
|
"flask_reagent2",
|
||||||
|
"flask_reagent3",
|
||||||
|
"flask_reagent4",
|
||||||
|
"reactor",
|
||||||
|
"flask_waste",
|
||||||
|
"flask_rinsing",
|
||||||
|
"flask_buffer"
|
||||||
|
],
|
||||||
|
"parent": null,
|
||||||
|
"type": "device",
|
||||||
|
"class": "workstation",
|
||||||
|
"position": {
|
||||||
|
"x": 620,
|
||||||
|
"y": 171,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"protocol_type": ["AddProtocol", "TransferProtocol", "StartStirProtocol", "StopStirProtocol"]
|
||||||
|
},
|
||||||
|
"data": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "transfer_pump",
|
||||||
|
"name": "注射器泵",
|
||||||
|
"children": [],
|
||||||
|
"parent": "AddTestStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_transfer_pump",
|
||||||
|
"position": {
|
||||||
|
"x": 520,
|
||||||
|
"y": 300,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "VIRTUAL",
|
||||||
|
"max_volume": 50.0,
|
||||||
|
"transfer_rate": 5.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"status": "Idle"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "multiway_valve",
|
||||||
|
"name": "八通阀门",
|
||||||
|
"children": [],
|
||||||
|
"parent": "AddTestStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_multiway_valve",
|
||||||
|
"position": {
|
||||||
|
"x": 420,
|
||||||
|
"y": 300,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "VIRTUAL",
|
||||||
|
"positions": 8
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"status": "Idle",
|
||||||
|
"current_position": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "stirrer",
|
||||||
|
"name": "搅拌器",
|
||||||
|
"children": [],
|
||||||
|
"parent": "AddTestStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_stirrer",
|
||||||
|
"position": {
|
||||||
|
"x": 720,
|
||||||
|
"y": 450,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "VIRTUAL",
|
||||||
|
"max_temp": 100.0,
|
||||||
|
"max_speed": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"status": "Idle"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_reagent1",
|
||||||
|
"name": "试剂瓶1 (甲醇)",
|
||||||
|
"children": [],
|
||||||
|
"parent": "AddTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 100,
|
||||||
|
"y": 400,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": [
|
||||||
|
{
|
||||||
|
"name": "甲醇",
|
||||||
|
"volume": 800.0,
|
||||||
|
"concentration": "99.9%"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_reagent2",
|
||||||
|
"name": "试剂瓶2 (乙醇)",
|
||||||
|
"children": [],
|
||||||
|
"parent": "AddTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 180,
|
||||||
|
"y": 400,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": [
|
||||||
|
{
|
||||||
|
"name": "乙醇",
|
||||||
|
"volume": 750.0,
|
||||||
|
"concentration": "95%"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_reagent3",
|
||||||
|
"name": "试剂瓶3 (丙酮)",
|
||||||
|
"children": [],
|
||||||
|
"parent": "AddTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 260,
|
||||||
|
"y": 400,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": [
|
||||||
|
{
|
||||||
|
"name": "丙酮",
|
||||||
|
"volume": 900.0,
|
||||||
|
"concentration": "99.5%"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_reagent4",
|
||||||
|
"name": "试剂瓶4 (二氯甲烷)",
|
||||||
|
"children": [],
|
||||||
|
"parent": "AddTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 340,
|
||||||
|
"y": 400,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": [
|
||||||
|
{
|
||||||
|
"name": "二氯甲烷",
|
||||||
|
"volume": 850.0,
|
||||||
|
"concentration": "99.8%"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reactor",
|
||||||
|
"name": "反应器",
|
||||||
|
"children": [],
|
||||||
|
"parent": "AddTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 720,
|
||||||
|
"y": 400,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 2000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_waste",
|
||||||
|
"name": "废液瓶",
|
||||||
|
"children": [],
|
||||||
|
"parent": "AddTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 850,
|
||||||
|
"y": 400,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 3000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_rinsing",
|
||||||
|
"name": "冲洗液瓶",
|
||||||
|
"children": [],
|
||||||
|
"parent": "AddTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 950,
|
||||||
|
"y": 300,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": [
|
||||||
|
{
|
||||||
|
"name": "去离子水",
|
||||||
|
"volume": 800.0,
|
||||||
|
"concentration": "纯净"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_buffer",
|
||||||
|
"name": "缓冲液瓶",
|
||||||
|
"children": [],
|
||||||
|
"parent": "AddTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 950,
|
||||||
|
"y": 400,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": [
|
||||||
|
{
|
||||||
|
"name": "磷酸盐缓冲液",
|
||||||
|
"volume": 700.0,
|
||||||
|
"concentration": "0.1M, pH 7.4"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"source": "transfer_pump",
|
||||||
|
"target": "multiway_valve",
|
||||||
|
"type": "physical",
|
||||||
|
"port": {
|
||||||
|
"transfer_pump": "syringe-port",
|
||||||
|
"multiway_valve": "multiway-valve-inlet"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "multiway_valve",
|
||||||
|
"target": "flask_reagent1",
|
||||||
|
"type": "physical",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve": "multiway-valve-port-1",
|
||||||
|
"flask_reagent1": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "multiway_valve",
|
||||||
|
"target": "flask_reagent2",
|
||||||
|
"type": "physical",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve": "multiway-valve-port-2",
|
||||||
|
"flask_reagent2": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "multiway_valve",
|
||||||
|
"target": "flask_reagent3",
|
||||||
|
"type": "physical",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve": "multiway-valve-port-3",
|
||||||
|
"flask_reagent3": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "multiway_valve",
|
||||||
|
"target": "flask_reagent4",
|
||||||
|
"type": "physical",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve": "multiway-valve-port-4",
|
||||||
|
"flask_reagent4": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "multiway_valve",
|
||||||
|
"target": "reactor",
|
||||||
|
"type": "physical",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve": "multiway-valve-port-5",
|
||||||
|
"reactor": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "multiway_valve",
|
||||||
|
"target": "flask_waste",
|
||||||
|
"type": "physical",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve": "multiway-valve-port-6",
|
||||||
|
"flask_waste": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "multiway_valve",
|
||||||
|
"target": "flask_rinsing",
|
||||||
|
"type": "physical",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve": "multiway-valve-port-7",
|
||||||
|
"flask_rinsing": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "multiway_valve",
|
||||||
|
"target": "flask_buffer",
|
||||||
|
"type": "physical",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve": "multiway-valve-port-8",
|
||||||
|
"flask_buffer": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "stirrer",
|
||||||
|
"target": "reactor",
|
||||||
|
"type": "physical",
|
||||||
|
"port": {
|
||||||
|
"stirrer": "stirrer-vessel",
|
||||||
|
"reactor": "bottom"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
271
test/experiments/mock_protocol/centrifugeteststation.json
Normal file
271
test/experiments/mock_protocol/centrifugeteststation.json
Normal file
@@ -0,0 +1,271 @@
|
|||||||
|
{
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"id": "CentrifugeTestStation",
|
||||||
|
"name": "离心机测试工作站",
|
||||||
|
"children": [
|
||||||
|
"pump_add",
|
||||||
|
"flask_1",
|
||||||
|
"flask_2",
|
||||||
|
"flask_3",
|
||||||
|
"reactor",
|
||||||
|
"stirrer",
|
||||||
|
"centrifuge_1",
|
||||||
|
"flask_air"
|
||||||
|
],
|
||||||
|
"parent": null,
|
||||||
|
"type": "device",
|
||||||
|
"class": "workstation",
|
||||||
|
"position": {
|
||||||
|
"x": 620.6111111111111,
|
||||||
|
"y": 171,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"protocol_type": ["AddProtocol", "PumpTransferProtocol", "CleanProtocol", "CentrifugeProtocol"]
|
||||||
|
},
|
||||||
|
"data": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "pump_add",
|
||||||
|
"name": "pump_add",
|
||||||
|
"children": [],
|
||||||
|
"parent": "CentrifugeTestStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_pump",
|
||||||
|
"position": {
|
||||||
|
"x": 520.6111111111111,
|
||||||
|
"y": 300,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "VIRTUAL",
|
||||||
|
"max_volume": 25.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"status": "Idle"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "stirrer",
|
||||||
|
"name": "stirrer",
|
||||||
|
"children": [],
|
||||||
|
"parent": "CentrifugeTestStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_stirrer",
|
||||||
|
"position": {
|
||||||
|
"x": 650.1111111111111,
|
||||||
|
"y": 478,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "VIRTUAL",
|
||||||
|
"max_temp": 100.0,
|
||||||
|
"max_speed": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"status": "Idle"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "centrifuge_1",
|
||||||
|
"name": "离心机",
|
||||||
|
"children": [],
|
||||||
|
"parent": "CentrifugeTestStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_centrifuge",
|
||||||
|
"position": {
|
||||||
|
"x": 800,
|
||||||
|
"y": 300,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "VIRTUAL",
|
||||||
|
"max_speed": 15000.0,
|
||||||
|
"max_temp": 40.0,
|
||||||
|
"min_temp": 4.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"status": "Idle"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_1",
|
||||||
|
"name": "样品瓶1",
|
||||||
|
"children": [],
|
||||||
|
"parent": "CentrifugeTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 100,
|
||||||
|
"y": 428,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1500.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_2",
|
||||||
|
"name": "样品瓶2",
|
||||||
|
"children": [],
|
||||||
|
"parent": "CentrifugeTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 250,
|
||||||
|
"y": 428,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1500.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_3",
|
||||||
|
"name": "缓冲液瓶",
|
||||||
|
"children": [],
|
||||||
|
"parent": "CentrifugeTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 400,
|
||||||
|
"y": 428,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 2000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reactor",
|
||||||
|
"name": "反应器",
|
||||||
|
"children": [],
|
||||||
|
"parent": "CentrifugeTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 698.1111111111111,
|
||||||
|
"y": 428,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 5000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_air",
|
||||||
|
"name": "空气瓶",
|
||||||
|
"children": [],
|
||||||
|
"parent": "CentrifugeTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 950,
|
||||||
|
"y": 300,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 2000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"source": "stirrer",
|
||||||
|
"target": "reactor",
|
||||||
|
"type": "physical",
|
||||||
|
"port": {
|
||||||
|
"stirrer": "top",
|
||||||
|
"reactor": "bottom"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "pump_add",
|
||||||
|
"target": "flask_1",
|
||||||
|
"type": "physical",
|
||||||
|
"port": {
|
||||||
|
"pump_add": "outlet",
|
||||||
|
"flask_1": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "pump_add",
|
||||||
|
"target": "flask_2",
|
||||||
|
"type": "physical",
|
||||||
|
"port": {
|
||||||
|
"pump_add": "inlet",
|
||||||
|
"flask_2": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "pump_add",
|
||||||
|
"target": "flask_3",
|
||||||
|
"type": "physical",
|
||||||
|
"port": {
|
||||||
|
"pump_add": "inlet",
|
||||||
|
"flask_3": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "pump_add",
|
||||||
|
"target": "reactor",
|
||||||
|
"type": "physical",
|
||||||
|
"port": {
|
||||||
|
"pump_add": "outlet",
|
||||||
|
"reactor": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "pump_add",
|
||||||
|
"target": "flask_air",
|
||||||
|
"type": "physical",
|
||||||
|
"port": {
|
||||||
|
"pump_add": "inlet",
|
||||||
|
"flask_air": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "centrifuge_1",
|
||||||
|
"target": "reactor",
|
||||||
|
"type": "logical",
|
||||||
|
"port": {
|
||||||
|
"centrifuge_1": "chamber",
|
||||||
|
"reactor": "vessel"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "centrifuge_1",
|
||||||
|
"target": "flask_1",
|
||||||
|
"type": "logical",
|
||||||
|
"port": {
|
||||||
|
"centrifuge_1": "chamber",
|
||||||
|
"flask_1": "vessel"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "centrifuge_1",
|
||||||
|
"target": "flask_2",
|
||||||
|
"type": "logical",
|
||||||
|
"port": {
|
||||||
|
"centrifuge_1": "chamber",
|
||||||
|
"flask_2": "vessel"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
362
test/experiments/mock_protocol/cleanvesselteststation.json
Normal file
362
test/experiments/mock_protocol/cleanvesselteststation.json
Normal file
@@ -0,0 +1,362 @@
|
|||||||
|
{
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"id": "CleanVesselTestStation",
|
||||||
|
"name": "容器清洗测试工作站",
|
||||||
|
"children": [
|
||||||
|
"transfer_pump_cleaner",
|
||||||
|
"heatchill_1",
|
||||||
|
"flask_water",
|
||||||
|
"flask_ethanol",
|
||||||
|
"flask_acetone",
|
||||||
|
"flask_waste",
|
||||||
|
"reactor",
|
||||||
|
"flask_buffer",
|
||||||
|
"flask_sample",
|
||||||
|
"flask_air"
|
||||||
|
],
|
||||||
|
"parent": null,
|
||||||
|
"type": "device",
|
||||||
|
"class": "workstation",
|
||||||
|
"position": {
|
||||||
|
"x": 620.6111111111111,
|
||||||
|
"y": 171,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"protocol_type": ["CleanVesselProtocol", "TransferProtocol", "AddProtocol"]
|
||||||
|
},
|
||||||
|
"data": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "transfer_pump_cleaner",
|
||||||
|
"name": "清洗转移泵",
|
||||||
|
"children": [],
|
||||||
|
"parent": "CleanVesselTestStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_transfer_pump",
|
||||||
|
"position": {
|
||||||
|
"x": 520.6111111111111,
|
||||||
|
"y": 300,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "VIRTUAL",
|
||||||
|
"max_volume": 50.0,
|
||||||
|
"transfer_rate": 10.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"status": "Idle",
|
||||||
|
"current_volume": 0.0,
|
||||||
|
"max_volume": 50.0,
|
||||||
|
"transfer_rate": 10.0,
|
||||||
|
"from_vessel": "",
|
||||||
|
"to_vessel": "",
|
||||||
|
"progress": 0.0,
|
||||||
|
"transferred_volume": 0.0,
|
||||||
|
"current_status": "Ready"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "heatchill_1",
|
||||||
|
"name": "加热冷却器",
|
||||||
|
"children": [],
|
||||||
|
"parent": "CleanVesselTestStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_heatchill",
|
||||||
|
"position": {
|
||||||
|
"x": 650.1111111111111,
|
||||||
|
"y": 478,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "VIRTUAL",
|
||||||
|
"max_temp": 150.0,
|
||||||
|
"min_temp": -20.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"status": "Idle",
|
||||||
|
"current_temp": 25.0,
|
||||||
|
"target_temp": 25.0,
|
||||||
|
"vessel": "",
|
||||||
|
"purpose": "",
|
||||||
|
"progress": 0.0,
|
||||||
|
"current_status": "Ready"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_water",
|
||||||
|
"name": "水溶剂瓶",
|
||||||
|
"children": [],
|
||||||
|
"parent": "CleanVesselTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 100,
|
||||||
|
"y": 428,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 2000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": [
|
||||||
|
{
|
||||||
|
"name": "water",
|
||||||
|
"volume": 1500.0,
|
||||||
|
"concentration": 100.0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_ethanol",
|
||||||
|
"name": "乙醇溶剂瓶",
|
||||||
|
"children": [],
|
||||||
|
"parent": "CleanVesselTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 250,
|
||||||
|
"y": 428,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 2000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": [
|
||||||
|
{
|
||||||
|
"name": "ethanol",
|
||||||
|
"volume": 1500.0,
|
||||||
|
"concentration": 99.5
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_acetone",
|
||||||
|
"name": "丙酮溶剂瓶",
|
||||||
|
"children": [],
|
||||||
|
"parent": "CleanVesselTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 400,
|
||||||
|
"y": 428,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 2000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": [
|
||||||
|
{
|
||||||
|
"name": "acetone",
|
||||||
|
"volume": 1800.0,
|
||||||
|
"concentration": 99.9
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_waste",
|
||||||
|
"name": "废液瓶",
|
||||||
|
"children": [],
|
||||||
|
"parent": "CleanVesselTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 550,
|
||||||
|
"y": 428,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 5000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reactor",
|
||||||
|
"name": "反应器",
|
||||||
|
"children": [],
|
||||||
|
"parent": "CleanVesselTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 698.1111111111111,
|
||||||
|
"y": 428,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": [
|
||||||
|
{
|
||||||
|
"name": "residue",
|
||||||
|
"volume": 50.0,
|
||||||
|
"concentration": 100.0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_buffer",
|
||||||
|
"name": "缓冲液瓶",
|
||||||
|
"children": [],
|
||||||
|
"parent": "CleanVesselTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 850,
|
||||||
|
"y": 428,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 2000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": [
|
||||||
|
{
|
||||||
|
"name": "buffer",
|
||||||
|
"volume": 1000.0,
|
||||||
|
"concentration": 10.0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_sample",
|
||||||
|
"name": "样品瓶",
|
||||||
|
"children": [],
|
||||||
|
"parent": "CleanVesselTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 1000,
|
||||||
|
"y": 428,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 500.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_air",
|
||||||
|
"name": "空气瓶",
|
||||||
|
"children": [],
|
||||||
|
"parent": "CleanVesselTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 950,
|
||||||
|
"y": 300,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 2000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"source": "transfer_pump_cleaner",
|
||||||
|
"target": "flask_water",
|
||||||
|
"type": "physical",
|
||||||
|
"port": {
|
||||||
|
"transfer_pump_cleaner": "1",
|
||||||
|
"flask_water": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "transfer_pump_cleaner",
|
||||||
|
"target": "flask_ethanol",
|
||||||
|
"type": "physical",
|
||||||
|
"port": {
|
||||||
|
"transfer_pump_cleaner": "2",
|
||||||
|
"flask_ethanol": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "transfer_pump_cleaner",
|
||||||
|
"target": "flask_acetone",
|
||||||
|
"type": "physical",
|
||||||
|
"port": {
|
||||||
|
"transfer_pump_cleaner": "3",
|
||||||
|
"flask_acetone": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "transfer_pump_cleaner",
|
||||||
|
"target": "flask_waste",
|
||||||
|
"type": "physical",
|
||||||
|
"port": {
|
||||||
|
"transfer_pump_cleaner": "4",
|
||||||
|
"flask_waste": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "transfer_pump_cleaner",
|
||||||
|
"target": "reactor",
|
||||||
|
"type": "physical",
|
||||||
|
"port": {
|
||||||
|
"transfer_pump_cleaner": "5",
|
||||||
|
"reactor": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "transfer_pump_cleaner",
|
||||||
|
"target": "flask_buffer",
|
||||||
|
"type": "physical",
|
||||||
|
"port": {
|
||||||
|
"transfer_pump_cleaner": "6",
|
||||||
|
"flask_buffer": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "transfer_pump_cleaner",
|
||||||
|
"target": "flask_sample",
|
||||||
|
"type": "physical",
|
||||||
|
"port": {
|
||||||
|
"transfer_pump_cleaner": "7",
|
||||||
|
"flask_sample": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "transfer_pump_cleaner",
|
||||||
|
"target": "flask_air",
|
||||||
|
"type": "physical",
|
||||||
|
"port": {
|
||||||
|
"transfer_pump_cleaner": "8",
|
||||||
|
"flask_air": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "heatchill_1",
|
||||||
|
"target": "reactor",
|
||||||
|
"type": "physical",
|
||||||
|
"port": {
|
||||||
|
"heatchill_1": "heating_element",
|
||||||
|
"reactor": "bottom"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "heatchill_1",
|
||||||
|
"target": "flask_sample",
|
||||||
|
"type": "physical",
|
||||||
|
"port": {
|
||||||
|
"heatchill_1": "heating_element",
|
||||||
|
"flask_sample": "bottom"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
343
test/experiments/mock_protocol/dissolveteststation.json
Normal file
343
test/experiments/mock_protocol/dissolveteststation.json
Normal file
@@ -0,0 +1,343 @@
|
|||||||
|
{
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"id": "DissolveTestStation",
|
||||||
|
"name": "溶解测试工作站",
|
||||||
|
"children": [
|
||||||
|
"transfer_pump_1",
|
||||||
|
"heatchill_1",
|
||||||
|
"stirrer_1",
|
||||||
|
"flask_water",
|
||||||
|
"flask_ethanol",
|
||||||
|
"flask_dmso",
|
||||||
|
"reactor",
|
||||||
|
"flask_sample",
|
||||||
|
"flask_buffer"
|
||||||
|
],
|
||||||
|
"parent": null,
|
||||||
|
"type": "device",
|
||||||
|
"class": "workstation",
|
||||||
|
"position": {
|
||||||
|
"x": 620.6111111111111,
|
||||||
|
"y": 171,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"protocol_type": ["DissolveProtocol", "TransferProtocol", "HeatChillProtocol", "StirProtocol"]
|
||||||
|
},
|
||||||
|
"data": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "transfer_pump_1",
|
||||||
|
"name": "转移泵",
|
||||||
|
"children": [],
|
||||||
|
"parent": "DissolveTestStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_transfer_pump",
|
||||||
|
"position": {
|
||||||
|
"x": 520.6111111111111,
|
||||||
|
"y": 300,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "VIRTUAL",
|
||||||
|
"max_volume": 50.0,
|
||||||
|
"transfer_rate": 10.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"status": "Idle",
|
||||||
|
"current_volume": 0.0,
|
||||||
|
"max_volume": 50.0,
|
||||||
|
"transfer_rate": 10.0,
|
||||||
|
"from_vessel": "",
|
||||||
|
"to_vessel": "",
|
||||||
|
"progress": 0.0,
|
||||||
|
"transferred_volume": 0.0,
|
||||||
|
"current_status": "Ready"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "heatchill_1",
|
||||||
|
"name": "加热冷却器",
|
||||||
|
"children": [],
|
||||||
|
"parent": "DissolveTestStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_heatchill",
|
||||||
|
"position": {
|
||||||
|
"x": 650.1111111111111,
|
||||||
|
"y": 478,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "VIRTUAL",
|
||||||
|
"max_temp": 150.0,
|
||||||
|
"min_temp": -20.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"status": "Idle",
|
||||||
|
"current_temp": 25.0,
|
||||||
|
"target_temp": 25.0,
|
||||||
|
"vessel": "",
|
||||||
|
"purpose": "",
|
||||||
|
"progress": 0.0,
|
||||||
|
"current_status": "Ready"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "stirrer_1",
|
||||||
|
"name": "搅拌器",
|
||||||
|
"children": [],
|
||||||
|
"parent": "DissolveTestStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_stirrer",
|
||||||
|
"position": {
|
||||||
|
"x": 750.1111111111111,
|
||||||
|
"y": 300,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "VIRTUAL",
|
||||||
|
"max_speed": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"status": "Idle"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_water",
|
||||||
|
"name": "水溶剂瓶",
|
||||||
|
"children": [],
|
||||||
|
"parent": "DissolveTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 100,
|
||||||
|
"y": 428,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 2000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": [
|
||||||
|
{
|
||||||
|
"name": "water",
|
||||||
|
"volume": 1500.0,
|
||||||
|
"concentration": 100.0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_ethanol",
|
||||||
|
"name": "乙醇溶剂瓶",
|
||||||
|
"children": [],
|
||||||
|
"parent": "DissolveTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 250,
|
||||||
|
"y": 428,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 2000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": [
|
||||||
|
{
|
||||||
|
"name": "ethanol",
|
||||||
|
"volume": 1500.0,
|
||||||
|
"concentration": 99.5
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_dmso",
|
||||||
|
"name": "DMSO溶剂瓶",
|
||||||
|
"children": [],
|
||||||
|
"parent": "DissolveTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 400,
|
||||||
|
"y": 428,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": [
|
||||||
|
{
|
||||||
|
"name": "dmso",
|
||||||
|
"volume": 800.0,
|
||||||
|
"concentration": 99.9
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reactor",
|
||||||
|
"name": "反应器",
|
||||||
|
"children": [],
|
||||||
|
"parent": "DissolveTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 698.1111111111111,
|
||||||
|
"y": 428,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": [
|
||||||
|
{
|
||||||
|
"name": "solid_sample",
|
||||||
|
"volume": 10.0,
|
||||||
|
"concentration": 100.0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_sample",
|
||||||
|
"name": "样品瓶",
|
||||||
|
"children": [],
|
||||||
|
"parent": "DissolveTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 1000,
|
||||||
|
"y": 428,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 500.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_buffer",
|
||||||
|
"name": "缓冲液瓶",
|
||||||
|
"children": [],
|
||||||
|
"parent": "DissolveTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 850,
|
||||||
|
"y": 428,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 2000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": [
|
||||||
|
{
|
||||||
|
"name": "buffer",
|
||||||
|
"volume": 1000.0,
|
||||||
|
"concentration": 10.0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"source": "transfer_pump_1",
|
||||||
|
"target": "flask_water",
|
||||||
|
"type": "physical",
|
||||||
|
"port": {
|
||||||
|
"transfer_pump_1": "1",
|
||||||
|
"flask_water": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "transfer_pump_1",
|
||||||
|
"target": "flask_ethanol",
|
||||||
|
"type": "physical",
|
||||||
|
"port": {
|
||||||
|
"transfer_pump_1": "2",
|
||||||
|
"flask_ethanol": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "transfer_pump_1",
|
||||||
|
"target": "flask_dmso",
|
||||||
|
"type": "physical",
|
||||||
|
"port": {
|
||||||
|
"transfer_pump_1": "3",
|
||||||
|
"flask_dmso": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "transfer_pump_1",
|
||||||
|
"target": "reactor",
|
||||||
|
"type": "physical",
|
||||||
|
"port": {
|
||||||
|
"transfer_pump_1": "4",
|
||||||
|
"reactor": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "transfer_pump_1",
|
||||||
|
"target": "flask_sample",
|
||||||
|
"type": "physical",
|
||||||
|
"port": {
|
||||||
|
"transfer_pump_1": "5",
|
||||||
|
"flask_sample": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "transfer_pump_1",
|
||||||
|
"target": "flask_buffer",
|
||||||
|
"type": "physical",
|
||||||
|
"port": {
|
||||||
|
"transfer_pump_1": "6",
|
||||||
|
"flask_buffer": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "heatchill_1",
|
||||||
|
"target": "reactor",
|
||||||
|
"type": "physical",
|
||||||
|
"port": {
|
||||||
|
"heatchill_1": "heating_element",
|
||||||
|
"reactor": "bottom"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "heatchill_1",
|
||||||
|
"target": "flask_sample",
|
||||||
|
"type": "physical",
|
||||||
|
"port": {
|
||||||
|
"heatchill_1": "heating_element",
|
||||||
|
"flask_sample": "bottom"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "stirrer_1",
|
||||||
|
"target": "reactor",
|
||||||
|
"type": "physical",
|
||||||
|
"port": {
|
||||||
|
"stirrer_1": "stir_rod",
|
||||||
|
"reactor": "center"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "stirrer_1",
|
||||||
|
"target": "flask_sample",
|
||||||
|
"type": "physical",
|
||||||
|
"port": {
|
||||||
|
"stirrer_1": "stir_rod",
|
||||||
|
"flask_sample": "center"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
270
test/experiments/mock_protocol/filterteststation.json
Normal file
270
test/experiments/mock_protocol/filterteststation.json
Normal file
@@ -0,0 +1,270 @@
|
|||||||
|
{
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"id": "FilterTestStation",
|
||||||
|
"name": "过滤器测试工作站",
|
||||||
|
"children": [
|
||||||
|
"pump_add",
|
||||||
|
"flask_sample",
|
||||||
|
"flask_filtrate",
|
||||||
|
"flask_buffer",
|
||||||
|
"reactor",
|
||||||
|
"stirrer",
|
||||||
|
"filter_1",
|
||||||
|
"flask_air"
|
||||||
|
],
|
||||||
|
"parent": null,
|
||||||
|
"type": "device",
|
||||||
|
"class": "workstation",
|
||||||
|
"position": {
|
||||||
|
"x": 620.6111111111111,
|
||||||
|
"y": 171,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"protocol_type": ["AddProtocol", "PumpTransferProtocol", "CleanProtocol", "FilterProtocol"]
|
||||||
|
},
|
||||||
|
"data": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "pump_add",
|
||||||
|
"name": "pump_add",
|
||||||
|
"children": [],
|
||||||
|
"parent": "FilterTestStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_pump",
|
||||||
|
"position": {
|
||||||
|
"x": 520.6111111111111,
|
||||||
|
"y": 300,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "VIRTUAL",
|
||||||
|
"max_volume": 25.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"status": "Idle"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "stirrer",
|
||||||
|
"name": "stirrer",
|
||||||
|
"children": [],
|
||||||
|
"parent": "FilterTestStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_stirrer",
|
||||||
|
"position": {
|
||||||
|
"x": 650.1111111111111,
|
||||||
|
"y": 478,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "VIRTUAL",
|
||||||
|
"max_temp": 100.0,
|
||||||
|
"max_speed": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"status": "Idle"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "filter_1",
|
||||||
|
"name": "过滤器",
|
||||||
|
"children": [],
|
||||||
|
"parent": "FilterTestStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_filter",
|
||||||
|
"position": {
|
||||||
|
"x": 800,
|
||||||
|
"y": 300,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "VIRTUAL",
|
||||||
|
"max_temp": 100.0,
|
||||||
|
"max_stir_speed": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"status": "Idle"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_sample",
|
||||||
|
"name": "样品瓶",
|
||||||
|
"children": [],
|
||||||
|
"parent": "FilterTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 100,
|
||||||
|
"y": 428,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_filtrate",
|
||||||
|
"name": "滤液瓶",
|
||||||
|
"children": [],
|
||||||
|
"parent": "FilterTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 250,
|
||||||
|
"y": 428,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_buffer",
|
||||||
|
"name": "缓冲液瓶",
|
||||||
|
"children": [],
|
||||||
|
"parent": "FilterTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 400,
|
||||||
|
"y": 428,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 2000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reactor",
|
||||||
|
"name": "反应器",
|
||||||
|
"children": [],
|
||||||
|
"parent": "FilterTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 698.1111111111111,
|
||||||
|
"y": 428,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 5000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_air",
|
||||||
|
"name": "空气瓶",
|
||||||
|
"children": [],
|
||||||
|
"parent": "FilterTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 950,
|
||||||
|
"y": 300,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 2000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"source": "stirrer",
|
||||||
|
"target": "reactor",
|
||||||
|
"type": "physical",
|
||||||
|
"port": {
|
||||||
|
"stirrer": "top",
|
||||||
|
"reactor": "bottom"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "pump_add",
|
||||||
|
"target": "flask_sample",
|
||||||
|
"type": "physical",
|
||||||
|
"port": {
|
||||||
|
"pump_add": "outlet",
|
||||||
|
"flask_sample": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "pump_add",
|
||||||
|
"target": "flask_filtrate",
|
||||||
|
"type": "physical",
|
||||||
|
"port": {
|
||||||
|
"pump_add": "inlet",
|
||||||
|
"flask_filtrate": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "pump_add",
|
||||||
|
"target": "flask_buffer",
|
||||||
|
"type": "physical",
|
||||||
|
"port": {
|
||||||
|
"pump_add": "inlet",
|
||||||
|
"flask_buffer": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "pump_add",
|
||||||
|
"target": "reactor",
|
||||||
|
"type": "physical",
|
||||||
|
"port": {
|
||||||
|
"pump_add": "outlet",
|
||||||
|
"reactor": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "pump_add",
|
||||||
|
"target": "flask_air",
|
||||||
|
"type": "physical",
|
||||||
|
"port": {
|
||||||
|
"pump_add": "inlet",
|
||||||
|
"flask_air": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "filter_1",
|
||||||
|
"target": "reactor",
|
||||||
|
"type": "logical",
|
||||||
|
"port": {
|
||||||
|
"filter_1": "input",
|
||||||
|
"reactor": "vessel"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "filter_1",
|
||||||
|
"target": "flask_sample",
|
||||||
|
"type": "logical",
|
||||||
|
"port": {
|
||||||
|
"filter_1": "input",
|
||||||
|
"flask_sample": "vessel"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "filter_1",
|
||||||
|
"target": "flask_filtrate",
|
||||||
|
"type": "logical",
|
||||||
|
"port": {
|
||||||
|
"filter_1": "output",
|
||||||
|
"flask_filtrate": "vessel"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
388
test/experiments/mock_protocol/filterthroughteststation.json
Normal file
388
test/experiments/mock_protocol/filterthroughteststation.json
Normal file
@@ -0,0 +1,388 @@
|
|||||||
|
{
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"id": "FilterThroughTestStation",
|
||||||
|
"name": "过滤通过测试工作站",
|
||||||
|
"children": [
|
||||||
|
"transfer_pump_1",
|
||||||
|
"filter_1",
|
||||||
|
"flask_ethanol",
|
||||||
|
"flask_water",
|
||||||
|
"flask_methanol",
|
||||||
|
"reactor",
|
||||||
|
"collection_flask",
|
||||||
|
"waste_flask",
|
||||||
|
"flask_sample",
|
||||||
|
"flask_celite",
|
||||||
|
"flask_silica"
|
||||||
|
],
|
||||||
|
"parent": null,
|
||||||
|
"type": "device",
|
||||||
|
"class": "workstation",
|
||||||
|
"position": {
|
||||||
|
"x": 620.6111111111111,
|
||||||
|
"y": 171,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"protocol_type": ["FilterThroughProtocol", "TransferProtocol", "FilterProtocol"]
|
||||||
|
},
|
||||||
|
"data": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "transfer_pump_1",
|
||||||
|
"name": "转移泵",
|
||||||
|
"children": [],
|
||||||
|
"parent": "FilterThroughTestStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_transfer_pump",
|
||||||
|
"position": {
|
||||||
|
"x": 520.6111111111111,
|
||||||
|
"y": 300,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "VIRTUAL",
|
||||||
|
"max_volume": 50.0,
|
||||||
|
"transfer_rate": 10.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"status": "Idle",
|
||||||
|
"current_volume": 0.0,
|
||||||
|
"max_volume": 50.0,
|
||||||
|
"transfer_rate": 10.0,
|
||||||
|
"from_vessel": "",
|
||||||
|
"to_vessel": "",
|
||||||
|
"progress": 0.0,
|
||||||
|
"transferred_volume": 0.0,
|
||||||
|
"current_status": "Ready"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "filter_1",
|
||||||
|
"name": "过滤器",
|
||||||
|
"children": [],
|
||||||
|
"parent": "FilterThroughTestStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_filter",
|
||||||
|
"position": {
|
||||||
|
"x": 650.1111111111111,
|
||||||
|
"y": 478,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "VIRTUAL",
|
||||||
|
"max_temp": 100.0,
|
||||||
|
"max_stir_speed": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"status": "Idle",
|
||||||
|
"filter_state": "Ready",
|
||||||
|
"current_temp": 25.0,
|
||||||
|
"target_temp": 25.0,
|
||||||
|
"max_temp": 100.0,
|
||||||
|
"stir_speed": 0.0,
|
||||||
|
"max_stir_speed": 1000.0,
|
||||||
|
"filtered_volume": 0.0,
|
||||||
|
"progress": 0.0,
|
||||||
|
"message": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_ethanol",
|
||||||
|
"name": "乙醇溶剂瓶",
|
||||||
|
"children": [],
|
||||||
|
"parent": "FilterThroughTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 100,
|
||||||
|
"y": 428,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 2000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": [
|
||||||
|
{
|
||||||
|
"name": "ethanol",
|
||||||
|
"volume": 1500.0,
|
||||||
|
"concentration": 99.5
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_water",
|
||||||
|
"name": "水溶剂瓶",
|
||||||
|
"children": [],
|
||||||
|
"parent": "FilterThroughTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 250,
|
||||||
|
"y": 428,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 2000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": [
|
||||||
|
{
|
||||||
|
"name": "water",
|
||||||
|
"volume": 1800.0,
|
||||||
|
"concentration": 100.0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_methanol",
|
||||||
|
"name": "甲醇溶剂瓶",
|
||||||
|
"children": [],
|
||||||
|
"parent": "FilterThroughTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 400,
|
||||||
|
"y": 428,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": [
|
||||||
|
{
|
||||||
|
"name": "methanol",
|
||||||
|
"volume": 800.0,
|
||||||
|
"concentration": 99.9
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reactor",
|
||||||
|
"name": "反应器",
|
||||||
|
"children": [],
|
||||||
|
"parent": "FilterThroughTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 698.1111111111111,
|
||||||
|
"y": 428,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": [
|
||||||
|
{
|
||||||
|
"name": "crude_product",
|
||||||
|
"volume": 200.0,
|
||||||
|
"concentration": 80.0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "collection_flask",
|
||||||
|
"name": "收集瓶",
|
||||||
|
"children": [],
|
||||||
|
"parent": "FilterThroughTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 850,
|
||||||
|
"y": 428,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "waste_flask",
|
||||||
|
"name": "废液瓶",
|
||||||
|
"children": [],
|
||||||
|
"parent": "FilterThroughTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 1000,
|
||||||
|
"y": 428,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 2000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_sample",
|
||||||
|
"name": "样品瓶",
|
||||||
|
"children": [],
|
||||||
|
"parent": "FilterThroughTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 550,
|
||||||
|
"y": 300,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 500.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": [
|
||||||
|
{
|
||||||
|
"name": "sample_mixture",
|
||||||
|
"volume": 100.0,
|
||||||
|
"concentration": 50.0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_celite",
|
||||||
|
"name": "硅藻土容器",
|
||||||
|
"children": [],
|
||||||
|
"parent": "FilterThroughTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 150,
|
||||||
|
"y": 300,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 500.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": [
|
||||||
|
{
|
||||||
|
"name": "celite",
|
||||||
|
"volume": 50.0,
|
||||||
|
"concentration": 100.0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_silica",
|
||||||
|
"name": "硅胶容器",
|
||||||
|
"children": [],
|
||||||
|
"parent": "FilterThroughTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 300,
|
||||||
|
"y": 300,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 500.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": [
|
||||||
|
{
|
||||||
|
"name": "silica",
|
||||||
|
"volume": 30.0,
|
||||||
|
"concentration": 100.0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"source": "transfer_pump_1",
|
||||||
|
"target": "flask_ethanol",
|
||||||
|
"type": "physical",
|
||||||
|
"port": {
|
||||||
|
"transfer_pump_1": "1",
|
||||||
|
"flask_ethanol": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "transfer_pump_1",
|
||||||
|
"target": "flask_water",
|
||||||
|
"type": "physical",
|
||||||
|
"port": {
|
||||||
|
"transfer_pump_1": "2",
|
||||||
|
"flask_water": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "transfer_pump_1",
|
||||||
|
"target": "flask_methanol",
|
||||||
|
"type": "physical",
|
||||||
|
"port": {
|
||||||
|
"transfer_pump_1": "3",
|
||||||
|
"flask_methanol": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "transfer_pump_1",
|
||||||
|
"target": "reactor",
|
||||||
|
"type": "physical",
|
||||||
|
"port": {
|
||||||
|
"transfer_pump_1": "4",
|
||||||
|
"reactor": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "transfer_pump_1",
|
||||||
|
"target": "collection_flask",
|
||||||
|
"type": "physical",
|
||||||
|
"port": {
|
||||||
|
"transfer_pump_1": "5",
|
||||||
|
"collection_flask": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "transfer_pump_1",
|
||||||
|
"target": "waste_flask",
|
||||||
|
"type": "physical",
|
||||||
|
"port": {
|
||||||
|
"transfer_pump_1": "6",
|
||||||
|
"waste_flask": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "transfer_pump_1",
|
||||||
|
"target": "flask_sample",
|
||||||
|
"type": "physical",
|
||||||
|
"port": {
|
||||||
|
"transfer_pump_1": "7",
|
||||||
|
"flask_sample": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "filter_1",
|
||||||
|
"target": "collection_flask",
|
||||||
|
"type": "physical",
|
||||||
|
"port": {
|
||||||
|
"filter_1": "filter_element",
|
||||||
|
"collection_flask": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "filter_1",
|
||||||
|
"target": "reactor",
|
||||||
|
"type": "physical",
|
||||||
|
"port": {
|
||||||
|
"filter_1": "filter_element",
|
||||||
|
"reactor": "top"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
262
test/experiments/mock_protocol/heatchillteststation.json
Normal file
262
test/experiments/mock_protocol/heatchillteststation.json
Normal file
@@ -0,0 +1,262 @@
|
|||||||
|
{
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"id": "HeatChillTestStation",
|
||||||
|
"name": "加热冷却测试工作站",
|
||||||
|
"children": [
|
||||||
|
"pump_add",
|
||||||
|
"flask_sample",
|
||||||
|
"flask_buffer1",
|
||||||
|
"flask_buffer2",
|
||||||
|
"reactor",
|
||||||
|
"stirrer",
|
||||||
|
"heatchill_1",
|
||||||
|
"flask_air"
|
||||||
|
],
|
||||||
|
"parent": null,
|
||||||
|
"type": "device",
|
||||||
|
"class": "workstation",
|
||||||
|
"position": {
|
||||||
|
"x": 620.6111111111111,
|
||||||
|
"y": 171,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"protocol_type": ["AddProtocol", "PumpTransferProtocol", "CleanProtocol", "HeatChillProtocol", "HeatChillStartProtocol", "HeatChillStopProtocol"]
|
||||||
|
},
|
||||||
|
"data": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "pump_add",
|
||||||
|
"name": "pump_add",
|
||||||
|
"children": [],
|
||||||
|
"parent": "HeatChillTestStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_pump",
|
||||||
|
"position": {
|
||||||
|
"x": 520.6111111111111,
|
||||||
|
"y": 300,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "VIRTUAL",
|
||||||
|
"max_volume": 25.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"status": "Idle"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "stirrer",
|
||||||
|
"name": "stirrer",
|
||||||
|
"children": [],
|
||||||
|
"parent": "HeatChillTestStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_stirrer",
|
||||||
|
"position": {
|
||||||
|
"x": 650.1111111111111,
|
||||||
|
"y": 478,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "VIRTUAL",
|
||||||
|
"max_temp": 100.0,
|
||||||
|
"max_speed": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"status": "Idle"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "heatchill_1",
|
||||||
|
"name": "加热冷却器",
|
||||||
|
"children": [],
|
||||||
|
"parent": "HeatChillTestStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_heatchill",
|
||||||
|
"position": {
|
||||||
|
"x": 800,
|
||||||
|
"y": 300,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "VIRTUAL",
|
||||||
|
"max_temp": 200.0,
|
||||||
|
"min_temp": -80.0,
|
||||||
|
"max_stir_speed": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"status": "Idle"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_sample",
|
||||||
|
"name": "样品瓶",
|
||||||
|
"children": [],
|
||||||
|
"parent": "HeatChillTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 100,
|
||||||
|
"y": 428,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_buffer1",
|
||||||
|
"name": "缓冲液瓶1",
|
||||||
|
"children": [],
|
||||||
|
"parent": "HeatChillTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 250,
|
||||||
|
"y": 428,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 2000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_buffer2",
|
||||||
|
"name": "缓冲液瓶2",
|
||||||
|
"children": [],
|
||||||
|
"parent": "HeatChillTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 400,
|
||||||
|
"y": 428,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 2000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reactor",
|
||||||
|
"name": "反应器",
|
||||||
|
"children": [],
|
||||||
|
"parent": "HeatChillTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 698.1111111111111,
|
||||||
|
"y": 428,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 5000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_air",
|
||||||
|
"name": "空气瓶",
|
||||||
|
"children": [],
|
||||||
|
"parent": "HeatChillTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 950,
|
||||||
|
"y": 300,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 2000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"source": "stirrer",
|
||||||
|
"target": "reactor",
|
||||||
|
"type": "physical",
|
||||||
|
"port": {
|
||||||
|
"stirrer": "top",
|
||||||
|
"reactor": "bottom"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "pump_add",
|
||||||
|
"target": "flask_sample",
|
||||||
|
"type": "physical",
|
||||||
|
"port": {
|
||||||
|
"pump_add": "outlet",
|
||||||
|
"flask_sample": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "pump_add",
|
||||||
|
"target": "flask_buffer1",
|
||||||
|
"type": "physical",
|
||||||
|
"port": {
|
||||||
|
"pump_add": "inlet",
|
||||||
|
"flask_buffer1": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "pump_add",
|
||||||
|
"target": "flask_buffer2",
|
||||||
|
"type": "physical",
|
||||||
|
"port": {
|
||||||
|
"pump_add": "inlet",
|
||||||
|
"flask_buffer2": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "pump_add",
|
||||||
|
"target": "reactor",
|
||||||
|
"type": "physical",
|
||||||
|
"port": {
|
||||||
|
"pump_add": "outlet",
|
||||||
|
"reactor": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "pump_add",
|
||||||
|
"target": "flask_air",
|
||||||
|
"type": "physical",
|
||||||
|
"port": {
|
||||||
|
"pump_add": "inlet",
|
||||||
|
"flask_air": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "heatchill_1",
|
||||||
|
"target": "reactor",
|
||||||
|
"type": "logical",
|
||||||
|
"port": {
|
||||||
|
"heatchill_1": "heating_element",
|
||||||
|
"reactor": "vessel"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "heatchill_1",
|
||||||
|
"target": "flask_sample",
|
||||||
|
"type": "logical",
|
||||||
|
"port": {
|
||||||
|
"heatchill_1": "heating_element",
|
||||||
|
"flask_sample": "vessel"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
412
test/experiments/mock_protocol/runcolumnteststation.json
Normal file
412
test/experiments/mock_protocol/runcolumnteststation.json
Normal file
@@ -0,0 +1,412 @@
|
|||||||
|
{
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"id": "RunColumnTestStation",
|
||||||
|
"name": "柱层析测试工作站",
|
||||||
|
"children": [
|
||||||
|
"transfer_pump_1",
|
||||||
|
"column_1",
|
||||||
|
"flask_sample",
|
||||||
|
"flask_hexane",
|
||||||
|
"flask_ethyl_acetate",
|
||||||
|
"flask_methanol",
|
||||||
|
"collection_flask_1",
|
||||||
|
"collection_flask_2",
|
||||||
|
"collection_flask_3",
|
||||||
|
"waste_flask",
|
||||||
|
"reactor"
|
||||||
|
],
|
||||||
|
"parent": null,
|
||||||
|
"type": "device",
|
||||||
|
"class": "workstation",
|
||||||
|
"position": {
|
||||||
|
"x": 620.6111111111111,
|
||||||
|
"y": 171,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"protocol_type": ["RunColumnProtocol", "TransferProtocol"]
|
||||||
|
},
|
||||||
|
"data": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "transfer_pump_1",
|
||||||
|
"name": "转移泵",
|
||||||
|
"children": [],
|
||||||
|
"parent": "RunColumnTestStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_transfer_pump",
|
||||||
|
"position": {
|
||||||
|
"x": 520.6111111111111,
|
||||||
|
"y": 300,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "VIRTUAL",
|
||||||
|
"max_volume": 50.0,
|
||||||
|
"transfer_rate": 10.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"status": "Idle",
|
||||||
|
"current_volume": 0.0,
|
||||||
|
"max_volume": 50.0,
|
||||||
|
"transfer_rate": 10.0,
|
||||||
|
"from_vessel": "",
|
||||||
|
"to_vessel": "",
|
||||||
|
"progress": 0.0,
|
||||||
|
"transferred_volume": 0.0,
|
||||||
|
"current_status": "Ready"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "column_1",
|
||||||
|
"name": "柱层析设备",
|
||||||
|
"children": [],
|
||||||
|
"parent": "RunColumnTestStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_column",
|
||||||
|
"position": {
|
||||||
|
"x": 650.1111111111111,
|
||||||
|
"y": 478,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "VIRTUAL",
|
||||||
|
"max_flow_rate": 5.0,
|
||||||
|
"column_length": 30.0,
|
||||||
|
"column_diameter": 2.5
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"status": "Idle",
|
||||||
|
"column_state": "Ready",
|
||||||
|
"current_flow_rate": 0.0,
|
||||||
|
"max_flow_rate": 5.0,
|
||||||
|
"column_length": 30.0,
|
||||||
|
"column_diameter": 2.5,
|
||||||
|
"processed_volume": 0.0,
|
||||||
|
"progress": 0.0,
|
||||||
|
"current_status": "Ready"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_sample",
|
||||||
|
"name": "样品瓶",
|
||||||
|
"children": [],
|
||||||
|
"parent": "RunColumnTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 100,
|
||||||
|
"y": 428,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 500.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": [
|
||||||
|
{
|
||||||
|
"name": "crude_mixture",
|
||||||
|
"volume": 200.0,
|
||||||
|
"concentration": 70.0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_hexane",
|
||||||
|
"name": "正己烷洗脱剂",
|
||||||
|
"children": [],
|
||||||
|
"parent": "RunColumnTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 250,
|
||||||
|
"y": 428,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 2000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": [
|
||||||
|
{
|
||||||
|
"name": "hexane",
|
||||||
|
"volume": 1500.0,
|
||||||
|
"concentration": 99.8
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_ethyl_acetate",
|
||||||
|
"name": "乙酸乙酯洗脱剂",
|
||||||
|
"children": [],
|
||||||
|
"parent": "RunColumnTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 400,
|
||||||
|
"y": 428,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 2000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": [
|
||||||
|
{
|
||||||
|
"name": "ethyl_acetate",
|
||||||
|
"volume": 1500.0,
|
||||||
|
"concentration": 99.5
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_methanol",
|
||||||
|
"name": "甲醇洗脱剂",
|
||||||
|
"children": [],
|
||||||
|
"parent": "RunColumnTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 550,
|
||||||
|
"y": 428,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": [
|
||||||
|
{
|
||||||
|
"name": "methanol",
|
||||||
|
"volume": 800.0,
|
||||||
|
"concentration": 99.9
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "collection_flask_1",
|
||||||
|
"name": "收集瓶1",
|
||||||
|
"children": [],
|
||||||
|
"parent": "RunColumnTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 750,
|
||||||
|
"y": 428,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "collection_flask_2",
|
||||||
|
"name": "收集瓶2",
|
||||||
|
"children": [],
|
||||||
|
"parent": "RunColumnTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 900,
|
||||||
|
"y": 428,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "collection_flask_3",
|
||||||
|
"name": "收集瓶3",
|
||||||
|
"children": [],
|
||||||
|
"parent": "RunColumnTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 1050,
|
||||||
|
"y": 428,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "waste_flask",
|
||||||
|
"name": "废液瓶",
|
||||||
|
"children": [],
|
||||||
|
"parent": "RunColumnTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 1200,
|
||||||
|
"y": 428,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 2000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reactor",
|
||||||
|
"name": "反应器",
|
||||||
|
"children": [],
|
||||||
|
"parent": "RunColumnTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 698.1111111111111,
|
||||||
|
"y": 300,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": [
|
||||||
|
{
|
||||||
|
"name": "reaction_mixture",
|
||||||
|
"volume": 300.0,
|
||||||
|
"concentration": 85.0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"source": "transfer_pump_1",
|
||||||
|
"target": "flask_sample",
|
||||||
|
"type": "physical",
|
||||||
|
"port": {
|
||||||
|
"transfer_pump_1": "1",
|
||||||
|
"flask_sample": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "transfer_pump_1",
|
||||||
|
"target": "flask_hexane",
|
||||||
|
"type": "physical",
|
||||||
|
"port": {
|
||||||
|
"transfer_pump_1": "2",
|
||||||
|
"flask_hexane": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "transfer_pump_1",
|
||||||
|
"target": "flask_ethyl_acetate",
|
||||||
|
"type": "physical",
|
||||||
|
"port": {
|
||||||
|
"transfer_pump_1": "3",
|
||||||
|
"flask_ethyl_acetate": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "transfer_pump_1",
|
||||||
|
"target": "flask_methanol",
|
||||||
|
"type": "physical",
|
||||||
|
"port": {
|
||||||
|
"transfer_pump_1": "4",
|
||||||
|
"flask_methanol": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "transfer_pump_1",
|
||||||
|
"target": "column_1",
|
||||||
|
"type": "physical",
|
||||||
|
"port": {
|
||||||
|
"transfer_pump_1": "5",
|
||||||
|
"column_1": "inlet"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "transfer_pump_1",
|
||||||
|
"target": "collection_flask_1",
|
||||||
|
"type": "physical",
|
||||||
|
"port": {
|
||||||
|
"transfer_pump_1": "6",
|
||||||
|
"collection_flask_1": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "transfer_pump_1",
|
||||||
|
"target": "collection_flask_2",
|
||||||
|
"type": "physical",
|
||||||
|
"port": {
|
||||||
|
"transfer_pump_1": "7",
|
||||||
|
"collection_flask_2": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "transfer_pump_1",
|
||||||
|
"target": "collection_flask_3",
|
||||||
|
"type": "physical",
|
||||||
|
"port": {
|
||||||
|
"transfer_pump_1": "8",
|
||||||
|
"collection_flask_3": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "transfer_pump_1",
|
||||||
|
"target": "waste_flask",
|
||||||
|
"type": "physical",
|
||||||
|
"port": {
|
||||||
|
"transfer_pump_1": "9",
|
||||||
|
"waste_flask": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "transfer_pump_1",
|
||||||
|
"target": "reactor",
|
||||||
|
"type": "physical",
|
||||||
|
"port": {
|
||||||
|
"transfer_pump_1": "10",
|
||||||
|
"reactor": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "column_1",
|
||||||
|
"target": "collection_flask_1",
|
||||||
|
"type": "physical",
|
||||||
|
"port": {
|
||||||
|
"column_1": "outlet",
|
||||||
|
"collection_flask_1": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "column_1",
|
||||||
|
"target": "collection_flask_2",
|
||||||
|
"type": "physical",
|
||||||
|
"port": {
|
||||||
|
"column_1": "outlet",
|
||||||
|
"collection_flask_2": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "column_1",
|
||||||
|
"target": "collection_flask_3",
|
||||||
|
"type": "physical",
|
||||||
|
"port": {
|
||||||
|
"column_1": "outlet",
|
||||||
|
"collection_flask_3": "top"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
250
test/experiments/mock_protocol/stirteststation.json
Normal file
250
test/experiments/mock_protocol/stirteststation.json
Normal file
@@ -0,0 +1,250 @@
|
|||||||
|
{
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"id": "StirTestStation",
|
||||||
|
"name": "搅拌测试工作站",
|
||||||
|
"children": [
|
||||||
|
"pump_add",
|
||||||
|
"flask_sample",
|
||||||
|
"flask_buffer1",
|
||||||
|
"flask_buffer2",
|
||||||
|
"reactor",
|
||||||
|
"stirrer",
|
||||||
|
"flask_waste",
|
||||||
|
"flask_air"
|
||||||
|
],
|
||||||
|
"parent": null,
|
||||||
|
"type": "device",
|
||||||
|
"class": "workstation",
|
||||||
|
"position": {
|
||||||
|
"x": 620.6111111111111,
|
||||||
|
"y": 171,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"protocol_type": ["AddProtocol", "PumpTransferProtocol", "CleanProtocol", "StirProtocol", "StartStirProtocol", "StopStirProtocol"]
|
||||||
|
},
|
||||||
|
"data": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "pump_add",
|
||||||
|
"name": "添加泵",
|
||||||
|
"children": [],
|
||||||
|
"parent": "StirTestStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_pump",
|
||||||
|
"position": {
|
||||||
|
"x": 520.6111111111111,
|
||||||
|
"y": 300,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "VIRTUAL",
|
||||||
|
"max_volume": 25.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"status": "Idle"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "stirrer",
|
||||||
|
"name": "搅拌器",
|
||||||
|
"children": [],
|
||||||
|
"parent": "StirTestStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_stirrer",
|
||||||
|
"position": {
|
||||||
|
"x": 650.1111111111111,
|
||||||
|
"y": 478,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "VIRTUAL",
|
||||||
|
"max_temp": 100.0,
|
||||||
|
"max_speed": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"status": "Idle"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_sample",
|
||||||
|
"name": "样品瓶",
|
||||||
|
"children": [],
|
||||||
|
"parent": "StirTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 100,
|
||||||
|
"y": 428,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_buffer1",
|
||||||
|
"name": "缓冲液瓶1",
|
||||||
|
"children": [],
|
||||||
|
"parent": "StirTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 250,
|
||||||
|
"y": 428,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 2000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_buffer2",
|
||||||
|
"name": "缓冲液瓶2",
|
||||||
|
"children": [],
|
||||||
|
"parent": "StirTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 400,
|
||||||
|
"y": 428,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 2000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reactor",
|
||||||
|
"name": "反应器",
|
||||||
|
"children": [],
|
||||||
|
"parent": "StirTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 698.1111111111111,
|
||||||
|
"y": 428,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 5000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_waste",
|
||||||
|
"name": "废液瓶",
|
||||||
|
"children": [],
|
||||||
|
"parent": "StirTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 850,
|
||||||
|
"y": 428,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 3000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_air",
|
||||||
|
"name": "空气瓶",
|
||||||
|
"children": [],
|
||||||
|
"parent": "StirTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 950,
|
||||||
|
"y": 300,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 2000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"source": "stirrer",
|
||||||
|
"target": "reactor",
|
||||||
|
"type": "physical",
|
||||||
|
"port": {
|
||||||
|
"stirrer": "top",
|
||||||
|
"reactor": "bottom"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "pump_add",
|
||||||
|
"target": "flask_sample",
|
||||||
|
"type": "physical",
|
||||||
|
"port": {
|
||||||
|
"pump_add": "inlet",
|
||||||
|
"flask_sample": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "pump_add",
|
||||||
|
"target": "flask_buffer1",
|
||||||
|
"type": "physical",
|
||||||
|
"port": {
|
||||||
|
"pump_add": "inlet",
|
||||||
|
"flask_buffer1": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "pump_add",
|
||||||
|
"target": "flask_buffer2",
|
||||||
|
"type": "physical",
|
||||||
|
"port": {
|
||||||
|
"pump_add": "inlet",
|
||||||
|
"flask_buffer2": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "pump_add",
|
||||||
|
"target": "reactor",
|
||||||
|
"type": "physical",
|
||||||
|
"port": {
|
||||||
|
"pump_add": "outlet",
|
||||||
|
"reactor": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "pump_add",
|
||||||
|
"target": "flask_waste",
|
||||||
|
"type": "physical",
|
||||||
|
"port": {
|
||||||
|
"pump_add": "outlet",
|
||||||
|
"flask_waste": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "pump_add",
|
||||||
|
"target": "flask_air",
|
||||||
|
"type": "physical",
|
||||||
|
"port": {
|
||||||
|
"pump_add": "inlet",
|
||||||
|
"flask_air": "top"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
249
test/experiments/mock_protocol/transferteststation.json
Normal file
249
test/experiments/mock_protocol/transferteststation.json
Normal file
@@ -0,0 +1,249 @@
|
|||||||
|
{
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"id": "TransferTestStation",
|
||||||
|
"name": "液体转移测试工作站",
|
||||||
|
"children": [
|
||||||
|
"transfer_pump",
|
||||||
|
"flask_source1",
|
||||||
|
"flask_source2",
|
||||||
|
"flask_target1",
|
||||||
|
"flask_target2",
|
||||||
|
"reactor",
|
||||||
|
"flask_waste",
|
||||||
|
"flask_rinsing"
|
||||||
|
],
|
||||||
|
"parent": null,
|
||||||
|
"type": "device",
|
||||||
|
"class": "workstation",
|
||||||
|
"position": {
|
||||||
|
"x": 620.6111111111111,
|
||||||
|
"y": 171,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"protocol_type": ["TransferProtocol"]
|
||||||
|
},
|
||||||
|
"data": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "transfer_pump",
|
||||||
|
"name": "转移泵",
|
||||||
|
"children": [],
|
||||||
|
"parent": "TransferTestStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_transfer_pump",
|
||||||
|
"position": {
|
||||||
|
"x": 520.6111111111111,
|
||||||
|
"y": 300,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "VIRTUAL",
|
||||||
|
"max_volume": 50.0,
|
||||||
|
"transfer_rate": 5.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"status": "Idle"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_source1",
|
||||||
|
"name": "源容器1",
|
||||||
|
"children": [],
|
||||||
|
"parent": "TransferTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 100,
|
||||||
|
"y": 428,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_source2",
|
||||||
|
"name": "源容器2",
|
||||||
|
"children": [],
|
||||||
|
"parent": "TransferTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 250,
|
||||||
|
"y": 428,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_target1",
|
||||||
|
"name": "目标容器1",
|
||||||
|
"children": [],
|
||||||
|
"parent": "TransferTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 400,
|
||||||
|
"y": 428,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_target2",
|
||||||
|
"name": "目标容器2",
|
||||||
|
"children": [],
|
||||||
|
"parent": "TransferTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 550,
|
||||||
|
"y": 428,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reactor",
|
||||||
|
"name": "反应器",
|
||||||
|
"children": [],
|
||||||
|
"parent": "TransferTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 698.1111111111111,
|
||||||
|
"y": 428,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 2000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_waste",
|
||||||
|
"name": "废液瓶",
|
||||||
|
"children": [],
|
||||||
|
"parent": "TransferTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 850,
|
||||||
|
"y": 428,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 2000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_rinsing",
|
||||||
|
"name": "冲洗液瓶",
|
||||||
|
"children": [],
|
||||||
|
"parent": "TransferTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 950,
|
||||||
|
"y": 300,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"source": "transfer_pump",
|
||||||
|
"target": "flask_source1",
|
||||||
|
"type": "physical",
|
||||||
|
"port": {
|
||||||
|
"transfer_pump": "inlet",
|
||||||
|
"flask_source1": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "transfer_pump",
|
||||||
|
"target": "flask_source2",
|
||||||
|
"type": "physical",
|
||||||
|
"port": {
|
||||||
|
"transfer_pump": "inlet",
|
||||||
|
"flask_source2": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "transfer_pump",
|
||||||
|
"target": "flask_target1",
|
||||||
|
"type": "physical",
|
||||||
|
"port": {
|
||||||
|
"transfer_pump": "outlet",
|
||||||
|
"flask_target1": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "transfer_pump",
|
||||||
|
"target": "flask_target2",
|
||||||
|
"type": "physical",
|
||||||
|
"port": {
|
||||||
|
"transfer_pump": "outlet",
|
||||||
|
"flask_target2": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "transfer_pump",
|
||||||
|
"target": "reactor",
|
||||||
|
"type": "physical",
|
||||||
|
"port": {
|
||||||
|
"transfer_pump": "outlet",
|
||||||
|
"reactor": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "transfer_pump",
|
||||||
|
"target": "flask_waste",
|
||||||
|
"type": "physical",
|
||||||
|
"port": {
|
||||||
|
"transfer_pump": "outlet",
|
||||||
|
"flask_waste": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "transfer_pump",
|
||||||
|
"target": "flask_rinsing",
|
||||||
|
"type": "physical",
|
||||||
|
"port": {
|
||||||
|
"transfer_pump": "inlet",
|
||||||
|
"flask_rinsing": "top"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
494
test/experiments/mock_protocol/washsolidteststation.json
Normal file
494
test/experiments/mock_protocol/washsolidteststation.json
Normal file
@@ -0,0 +1,494 @@
|
|||||||
|
{
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"id": "WashSolidTestStation",
|
||||||
|
"name": "固体清洗测试工作站",
|
||||||
|
"children": [
|
||||||
|
"transfer_pump_1",
|
||||||
|
"heatchill_1",
|
||||||
|
"stirrer_1",
|
||||||
|
"filter_1",
|
||||||
|
"flask_ethanol",
|
||||||
|
"flask_water",
|
||||||
|
"flask_acetone",
|
||||||
|
"flask_methanol",
|
||||||
|
"reactor",
|
||||||
|
"collection_flask",
|
||||||
|
"waste_flask",
|
||||||
|
"flask_sample",
|
||||||
|
"filtrate_flask"
|
||||||
|
],
|
||||||
|
"parent": null,
|
||||||
|
"type": "device",
|
||||||
|
"class": "workstation",
|
||||||
|
"position": {
|
||||||
|
"x": 620.6111111111111,
|
||||||
|
"y": 171,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"protocol_type": ["WashSolidProtocol", "TransferProtocol", "FilterProtocol", "HeatChillProtocol", "StirProtocol"]
|
||||||
|
},
|
||||||
|
"data": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "transfer_pump_1",
|
||||||
|
"name": "转移泵",
|
||||||
|
"children": [],
|
||||||
|
"parent": "WashSolidTestStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_transfer_pump",
|
||||||
|
"position": {
|
||||||
|
"x": 520.6111111111111,
|
||||||
|
"y": 300,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "VIRTUAL",
|
||||||
|
"max_volume": 50.0,
|
||||||
|
"transfer_rate": 10.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"status": "Idle",
|
||||||
|
"current_volume": 0.0,
|
||||||
|
"max_volume": 50.0,
|
||||||
|
"transfer_rate": 10.0,
|
||||||
|
"from_vessel": "",
|
||||||
|
"to_vessel": "",
|
||||||
|
"progress": 0.0,
|
||||||
|
"transferred_volume": 0.0,
|
||||||
|
"current_status": "Ready"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "heatchill_1",
|
||||||
|
"name": "加热冷却器",
|
||||||
|
"children": [],
|
||||||
|
"parent": "WashSolidTestStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_heatchill",
|
||||||
|
"position": {
|
||||||
|
"x": 650.1111111111111,
|
||||||
|
"y": 478,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "VIRTUAL",
|
||||||
|
"max_temp": 150.0,
|
||||||
|
"min_temp": -20.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"status": "Idle",
|
||||||
|
"current_temp": 25.0,
|
||||||
|
"target_temp": 25.0,
|
||||||
|
"vessel": "",
|
||||||
|
"purpose": "",
|
||||||
|
"progress": 0.0,
|
||||||
|
"current_status": "Ready"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "stirrer_1",
|
||||||
|
"name": "搅拌器",
|
||||||
|
"children": [],
|
||||||
|
"parent": "WashSolidTestStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_stirrer",
|
||||||
|
"position": {
|
||||||
|
"x": 750.1111111111111,
|
||||||
|
"y": 300,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "VIRTUAL",
|
||||||
|
"max_speed": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"status": "Idle"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "filter_1",
|
||||||
|
"name": "过滤器",
|
||||||
|
"children": [],
|
||||||
|
"parent": "WashSolidTestStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_filter",
|
||||||
|
"position": {
|
||||||
|
"x": 850.1111111111111,
|
||||||
|
"y": 478,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "VIRTUAL",
|
||||||
|
"max_temp": 100.0,
|
||||||
|
"max_stir_speed": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"status": "Idle",
|
||||||
|
"filter_state": "Ready",
|
||||||
|
"current_temp": 25.0,
|
||||||
|
"target_temp": 25.0,
|
||||||
|
"max_temp": 100.0,
|
||||||
|
"stir_speed": 0.0,
|
||||||
|
"max_stir_speed": 1000.0,
|
||||||
|
"filtered_volume": 0.0,
|
||||||
|
"progress": 0.0,
|
||||||
|
"message": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_ethanol",
|
||||||
|
"name": "乙醇清洗剂",
|
||||||
|
"children": [],
|
||||||
|
"parent": "WashSolidTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 100,
|
||||||
|
"y": 428,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 2000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": [
|
||||||
|
{
|
||||||
|
"name": "ethanol",
|
||||||
|
"volume": 1500.0,
|
||||||
|
"concentration": 99.5
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_water",
|
||||||
|
"name": "水清洗剂",
|
||||||
|
"children": [],
|
||||||
|
"parent": "WashSolidTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 250,
|
||||||
|
"y": 428,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 2000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": [
|
||||||
|
{
|
||||||
|
"name": "water",
|
||||||
|
"volume": 1800.0,
|
||||||
|
"concentration": 100.0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_acetone",
|
||||||
|
"name": "丙酮清洗剂",
|
||||||
|
"children": [],
|
||||||
|
"parent": "WashSolidTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 400,
|
||||||
|
"y": 428,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": [
|
||||||
|
{
|
||||||
|
"name": "acetone",
|
||||||
|
"volume": 800.0,
|
||||||
|
"concentration": 99.8
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_methanol",
|
||||||
|
"name": "甲醇清洗剂",
|
||||||
|
"children": [],
|
||||||
|
"parent": "WashSolidTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 550,
|
||||||
|
"y": 428,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": [
|
||||||
|
{
|
||||||
|
"name": "methanol",
|
||||||
|
"volume": 800.0,
|
||||||
|
"concentration": 99.9
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reactor",
|
||||||
|
"name": "反应器",
|
||||||
|
"children": [],
|
||||||
|
"parent": "WashSolidTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 698.1111111111111,
|
||||||
|
"y": 428,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": [
|
||||||
|
{
|
||||||
|
"name": "solid_product",
|
||||||
|
"volume": 50.0,
|
||||||
|
"concentration": 100.0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "collection_flask",
|
||||||
|
"name": "收集瓶",
|
||||||
|
"children": [],
|
||||||
|
"parent": "WashSolidTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 850,
|
||||||
|
"y": 300,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "waste_flask",
|
||||||
|
"name": "废液瓶",
|
||||||
|
"children": [],
|
||||||
|
"parent": "WashSolidTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 1000,
|
||||||
|
"y": 428,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 2000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_sample",
|
||||||
|
"name": "样品瓶",
|
||||||
|
"children": [],
|
||||||
|
"parent": "WashSolidTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 1150,
|
||||||
|
"y": 300,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 500.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": [
|
||||||
|
{
|
||||||
|
"name": "crude_solid",
|
||||||
|
"volume": 30.0,
|
||||||
|
"concentration": 80.0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "filtrate_flask",
|
||||||
|
"name": "滤液收集瓶",
|
||||||
|
"children": [],
|
||||||
|
"parent": "WashSolidTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 1000,
|
||||||
|
"y": 300,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1500.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"source": "transfer_pump_1",
|
||||||
|
"target": "flask_ethanol",
|
||||||
|
"type": "physical",
|
||||||
|
"port": {
|
||||||
|
"transfer_pump_1": "1",
|
||||||
|
"flask_ethanol": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "transfer_pump_1",
|
||||||
|
"target": "flask_water",
|
||||||
|
"type": "physical",
|
||||||
|
"port": {
|
||||||
|
"transfer_pump_1": "2",
|
||||||
|
"flask_water": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "transfer_pump_1",
|
||||||
|
"target": "flask_acetone",
|
||||||
|
"type": "physical",
|
||||||
|
"port": {
|
||||||
|
"transfer_pump_1": "3",
|
||||||
|
"flask_acetone": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "transfer_pump_1",
|
||||||
|
"target": "flask_methanol",
|
||||||
|
"type": "physical",
|
||||||
|
"port": {
|
||||||
|
"transfer_pump_1": "4",
|
||||||
|
"flask_methanol": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "transfer_pump_1",
|
||||||
|
"target": "reactor",
|
||||||
|
"type": "physical",
|
||||||
|
"port": {
|
||||||
|
"transfer_pump_1": "5",
|
||||||
|
"reactor": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "transfer_pump_1",
|
||||||
|
"target": "collection_flask",
|
||||||
|
"type": "physical",
|
||||||
|
"port": {
|
||||||
|
"transfer_pump_1": "6",
|
||||||
|
"collection_flask": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "transfer_pump_1",
|
||||||
|
"target": "waste_flask",
|
||||||
|
"type": "physical",
|
||||||
|
"port": {
|
||||||
|
"transfer_pump_1": "7",
|
||||||
|
"waste_flask": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "transfer_pump_1",
|
||||||
|
"target": "flask_sample",
|
||||||
|
"type": "physical",
|
||||||
|
"port": {
|
||||||
|
"transfer_pump_1": "8",
|
||||||
|
"flask_sample": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "transfer_pump_1",
|
||||||
|
"target": "filtrate_flask",
|
||||||
|
"type": "physical",
|
||||||
|
"port": {
|
||||||
|
"transfer_pump_1": "9",
|
||||||
|
"filtrate_flask": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "heatchill_1",
|
||||||
|
"target": "reactor",
|
||||||
|
"type": "physical",
|
||||||
|
"port": {
|
||||||
|
"heatchill_1": "heating_element",
|
||||||
|
"reactor": "bottom"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "heatchill_1",
|
||||||
|
"target": "flask_sample",
|
||||||
|
"type": "physical",
|
||||||
|
"port": {
|
||||||
|
"heatchill_1": "heating_element",
|
||||||
|
"flask_sample": "bottom"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "stirrer_1",
|
||||||
|
"target": "reactor",
|
||||||
|
"type": "physical",
|
||||||
|
"port": {
|
||||||
|
"stirrer_1": "stir_rod",
|
||||||
|
"reactor": "center"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "stirrer_1",
|
||||||
|
"target": "flask_sample",
|
||||||
|
"type": "physical",
|
||||||
|
"port": {
|
||||||
|
"stirrer_1": "stir_rod",
|
||||||
|
"flask_sample": "center"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "filter_1",
|
||||||
|
"target": "reactor",
|
||||||
|
"type": "physical",
|
||||||
|
"port": {
|
||||||
|
"filter_1": "filter_element",
|
||||||
|
"reactor": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "filter_1",
|
||||||
|
"target": "flask_sample",
|
||||||
|
"type": "physical",
|
||||||
|
"port": {
|
||||||
|
"filter_1": "filter_element",
|
||||||
|
"flask_sample": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "filter_1",
|
||||||
|
"target": "filtrate_flask",
|
||||||
|
"type": "physical",
|
||||||
|
"port": {
|
||||||
|
"filter_1": "filter_element",
|
||||||
|
"filtrate_flask": "top"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -30,14 +30,17 @@
|
|||||||
"children": [],
|
"children": [],
|
||||||
"parent": "ReactorX",
|
"parent": "ReactorX",
|
||||||
"type": "container",
|
"type": "container",
|
||||||
"class": null,
|
"class": "container",
|
||||||
"position": {
|
"position": {
|
||||||
"x": 698.1111111111111,
|
"x": 698.1111111111111,
|
||||||
"y": 428,
|
"y": 428,
|
||||||
"z": 0
|
"z": 0
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"max_volume": 5000.0
|
"max_volume": 5000.0,
|
||||||
|
"size_x": 200.0,
|
||||||
|
"size_y": 200.0,
|
||||||
|
"size_z": 200.0
|
||||||
},
|
},
|
||||||
"data": {
|
"data": {
|
||||||
"liquid": [
|
"liquid": [
|
||||||
@@ -71,7 +74,7 @@
|
|||||||
"type": "device",
|
"type": "device",
|
||||||
"class": "solenoid_valve.mock",
|
"class": "solenoid_valve.mock",
|
||||||
"position": {
|
"position": {
|
||||||
"x": 620.6111111111111,
|
"x": 780,
|
||||||
"y": 171,
|
"y": 171,
|
||||||
"z": 0
|
"z": 0
|
||||||
},
|
},
|
||||||
@@ -89,7 +92,7 @@
|
|||||||
"type": "device",
|
"type": "device",
|
||||||
"class": "vacuum_pump.mock",
|
"class": "vacuum_pump.mock",
|
||||||
"position": {
|
"position": {
|
||||||
"x": 620.6111111111111,
|
"x": 500,
|
||||||
"y": 171,
|
"y": 171,
|
||||||
"z": 0
|
"z": 0
|
||||||
},
|
},
|
||||||
@@ -107,7 +110,7 @@
|
|||||||
"type": "device",
|
"type": "device",
|
||||||
"class": "gas_source.mock",
|
"class": "gas_source.mock",
|
||||||
"position": {
|
"position": {
|
||||||
"x": 620.6111111111111,
|
"x": 900,
|
||||||
"y": 171,
|
"y": 171,
|
||||||
"z": 0
|
"z": 0
|
||||||
},
|
},
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
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
35
test/experiments/test_moveit.json
Normal file
35
test/experiments/test_moveit.json
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
{
|
||||||
|
"nodes": [
|
||||||
|
|
||||||
|
{
|
||||||
|
"id": "benyao",
|
||||||
|
"name": "benyao",
|
||||||
|
"children": [
|
||||||
|
],
|
||||||
|
"parent": null,
|
||||||
|
"type": "device",
|
||||||
|
"class": "moveit.arm_slider",
|
||||||
|
"position": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"moveit_type": "arm_slider",
|
||||||
|
"joint_poses": {
|
||||||
|
"arm": {
|
||||||
|
"home": [0.0, 0.2, 0.0, 0.0, 0.0],
|
||||||
|
"pick": [1.2, 0.0, 0.0, 0.0, 0.0]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"device_config": {
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"links": [
|
||||||
|
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -8,6 +8,7 @@ def start_backend(
|
|||||||
backend: str,
|
backend: str,
|
||||||
devices_config: dict = {},
|
devices_config: dict = {},
|
||||||
resources_config: list = [],
|
resources_config: list = [],
|
||||||
|
resources_edge_config: list = [],
|
||||||
graph=None,
|
graph=None,
|
||||||
controllers_config: dict = {},
|
controllers_config: dict = {},
|
||||||
bridges=[],
|
bridges=[],
|
||||||
@@ -31,7 +32,7 @@ def start_backend(
|
|||||||
|
|
||||||
backend_thread = threading.Thread(
|
backend_thread = threading.Thread(
|
||||||
target=main if not without_host else slave,
|
target=main if not without_host else slave,
|
||||||
args=(devices_config, resources_config, graph, controllers_config, bridges, visual, resources_mesh_config),
|
args=(devices_config, resources_config, resources_edge_config, graph, controllers_config, bridges, visual, resources_mesh_config),
|
||||||
name="backend_thread",
|
name="backend_thread",
|
||||||
daemon=True,
|
daemon=True,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ from copy import deepcopy
|
|||||||
|
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
|
from unilabos.resources.graphio import tree_to_list, modify_to_backend_format
|
||||||
|
|
||||||
# 首先添加项目根目录到路径
|
# 首先添加项目根目录到路径
|
||||||
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))
|
||||||
@@ -134,29 +136,30 @@ def main():
|
|||||||
|
|
||||||
# 注册表
|
# 注册表
|
||||||
build_registry(args_dict["registry_path"])
|
build_registry(args_dict["registry_path"])
|
||||||
|
resource_edge_info = []
|
||||||
devices_and_resources = None
|
devices_and_resources = None
|
||||||
if args_dict["graph"] is not None:
|
if args_dict["graph"] is not None:
|
||||||
import unilabos.resources.graphio as graph_res
|
import unilabos.resources.graphio as graph_res
|
||||||
graph_res.physical_setup_graph = (
|
if args_dict["graph"].endswith(".json"):
|
||||||
read_node_link_json(args_dict["graph"])
|
graph, data = read_node_link_json(args_dict["graph"])
|
||||||
if args_dict["graph"].endswith(".json")
|
else:
|
||||||
else read_graphml(args_dict["graph"])
|
graph, data = read_graphml(args_dict["graph"])
|
||||||
)
|
graph_res.physical_setup_graph = graph
|
||||||
|
resource_edge_info = modify_to_backend_format(data["links"])
|
||||||
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"]:
|
||||||
@@ -183,6 +186,7 @@ def main():
|
|||||||
signal.signal(signal.SIGTERM, _exit)
|
signal.signal(signal.SIGTERM, _exit)
|
||||||
mqtt_client.start()
|
mqtt_client.start()
|
||||||
args_dict["resources_mesh_config"] = {}
|
args_dict["resources_mesh_config"] = {}
|
||||||
|
args_dict["resources_edge_config"] = resource_edge_info
|
||||||
# web visiualize 2D
|
# web visiualize 2D
|
||||||
if args_dict["visual"] != "disable":
|
if args_dict["visual"] != "disable":
|
||||||
enable_rviz = args_dict["visual"] == "rviz"
|
enable_rviz = args_dict["visual"] == "rviz"
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -161,12 +162,14 @@ class MQTTClient:
|
|||||||
status = {"data": device_status.get(device_id, {}), "device_id": device_id}
|
status = {"data": device_status.get(device_id, {}), "device_id": device_id}
|
||||||
address = f"labs/{MQConfig.lab_id}/devices/"
|
address = f"labs/{MQConfig.lab_id}/devices/"
|
||||||
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.debug(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,22 +30,44 @@ 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_edge_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/edge/batch_create/?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,
|
||||||
)
|
)
|
||||||
|
if response.status_code != 200:
|
||||||
|
logger.error(f"添加物料关系失败: {response.text}")
|
||||||
|
return response
|
||||||
|
|
||||||
|
def resource_add(self, resources: List[Dict[str, Any]], database_process_later: bool) -> requests.Response:
|
||||||
|
"""
|
||||||
|
添加资源
|
||||||
|
|
||||||
|
Args:
|
||||||
|
resources: 要添加的资源列表
|
||||||
|
database_process_later: 后台处理资源
|
||||||
|
Returns:
|
||||||
|
Response: API响应对象
|
||||||
|
"""
|
||||||
|
response = requests.post(
|
||||||
|
f"{self.remote_addr}/lab/resource/?database_process_later={1 if database_process_later else 0}",
|
||||||
|
json=resources,
|
||||||
|
headers={"Authorization": f"lab {self.auth}"},
|
||||||
|
timeout=5,
|
||||||
|
)
|
||||||
|
if response.status_code != 200:
|
||||||
|
logger.error(f"添加物料失败: {response.text}")
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def resource_get(self, id: str, with_children: bool = False) -> Dict[str, Any]:
|
def resource_get(self, id: str, with_children: bool = False) -> Dict[str, Any]:
|
||||||
@@ -60,7 +82,7 @@ class HTTPClient:
|
|||||||
Dict: 返回的资源数据
|
Dict: 返回的资源数据
|
||||||
"""
|
"""
|
||||||
response = requests.get(
|
response = requests.get(
|
||||||
f"{self.remote_addr}/lab/resource/",
|
f"{self.remote_addr}/lab/resource/?edge_format=1",
|
||||||
params={"id": id, "with_children": with_children},
|
params={"id": id, "with_children": with_children},
|
||||||
headers={"Authorization": f"lab {self.auth}"},
|
headers={"Authorization": f"lab {self.auth}"},
|
||||||
timeout=5,
|
timeout=5,
|
||||||
@@ -96,7 +118,7 @@ class HTTPClient:
|
|||||||
Response: API响应对象
|
Response: API响应对象
|
||||||
"""
|
"""
|
||||||
response = requests.patch(
|
response = requests.patch(
|
||||||
f"{self.remote_addr}/lab/resource/batch_update/",
|
f"{self.remote_addr}/lab/resource/batch_update/?edge_format=1",
|
||||||
json=resources,
|
json=resources,
|
||||||
headers={"Authorization": f"lab {self.auth}"},
|
headers={"Authorization": f"lab {self.auth}"},
|
||||||
timeout=5,
|
timeout=5,
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ from jinja2 import Environment, FileSystemLoader
|
|||||||
|
|
||||||
from unilabos.config.config import BasicConfig
|
from unilabos.config.config import BasicConfig
|
||||||
from unilabos.registry.registry import lab_registry
|
from unilabos.registry.registry import lab_registry
|
||||||
from unilabos.app.mq import mqtt_client
|
|
||||||
from unilabos.ros.msgs.message_converter import msg_converter_manager
|
from unilabos.ros.msgs.message_converter import msg_converter_manager
|
||||||
from unilabos.utils.log import error
|
from unilabos.utils.log import error
|
||||||
from unilabos.utils.type_check import TypeEncoder
|
from unilabos.utils.type_check import TypeEncoder
|
||||||
|
|||||||
@@ -5,6 +5,17 @@ from .separate_protocol import generate_separate_protocol
|
|||||||
from .evaporate_protocol import generate_evaporate_protocol
|
from .evaporate_protocol import generate_evaporate_protocol
|
||||||
from .evacuateandrefill_protocol import generate_evacuateandrefill_protocol
|
from .evacuateandrefill_protocol import generate_evacuateandrefill_protocol
|
||||||
from .agv_transfer_protocol import generate_agv_transfer_protocol
|
from .agv_transfer_protocol import generate_agv_transfer_protocol
|
||||||
|
from .add_protocol import generate_add_protocol
|
||||||
|
from .centrifuge_protocol import generate_centrifuge_protocol
|
||||||
|
from .filter_protocol import generate_filter_protocol
|
||||||
|
from .heatchill_protocol import generate_heat_chill_protocol, generate_heat_chill_start_protocol, generate_heat_chill_stop_protocol
|
||||||
|
from .stir_protocol import generate_stir_protocol, generate_start_stir_protocol, generate_stop_stir_protocol
|
||||||
|
from .transfer_protocol import generate_transfer_protocol
|
||||||
|
from .clean_vessel_protocol import generate_clean_vessel_protocol
|
||||||
|
from .dissolve_protocol import generate_dissolve_protocol
|
||||||
|
from .filter_through_protocol import generate_filter_through_protocol
|
||||||
|
from .run_column_protocol import generate_run_column_protocol
|
||||||
|
from .wash_solid_protocol import generate_wash_solid_protocol
|
||||||
|
|
||||||
|
|
||||||
# Define a dictionary of protocol generators.
|
# Define a dictionary of protocol generators.
|
||||||
@@ -15,5 +26,19 @@ action_protocol_generators = {
|
|||||||
EvaporateProtocol: generate_evaporate_protocol,
|
EvaporateProtocol: generate_evaporate_protocol,
|
||||||
EvacuateAndRefillProtocol: generate_evacuateandrefill_protocol,
|
EvacuateAndRefillProtocol: generate_evacuateandrefill_protocol,
|
||||||
AGVTransferProtocol: generate_agv_transfer_protocol,
|
AGVTransferProtocol: generate_agv_transfer_protocol,
|
||||||
|
CentrifugeProtocol: generate_centrifuge_protocol,
|
||||||
|
AddProtocol: generate_add_protocol,
|
||||||
|
FilterProtocol: generate_filter_protocol,
|
||||||
|
HeatChillProtocol: generate_heat_chill_protocol,
|
||||||
|
HeatChillStartProtocol: generate_heat_chill_start_protocol,
|
||||||
|
HeatChillStopProtocol: generate_heat_chill_stop_protocol,
|
||||||
|
StirProtocol: generate_stir_protocol,
|
||||||
|
StartStirProtocol: generate_start_stir_protocol,
|
||||||
|
StopStirProtocol: generate_stop_stir_protocol,
|
||||||
|
TransferProtocol: generate_transfer_protocol,
|
||||||
|
CleanVesselProtocol: generate_clean_vessel_protocol,
|
||||||
|
DissolveProtocol: generate_dissolve_protocol,
|
||||||
|
FilterThroughProtocol: generate_filter_through_protocol,
|
||||||
|
RunColumnProtocol: generate_run_column_protocol,
|
||||||
|
WashSolidProtocol: generate_wash_solid_protocol,
|
||||||
}
|
}
|
||||||
# End Protocols
|
|
||||||
|
|||||||
288
unilabos/compile/add_protocol.py
Normal file
288
unilabos/compile/add_protocol.py
Normal file
@@ -0,0 +1,288 @@
|
|||||||
|
import networkx as nx
|
||||||
|
from typing import List, Dict, Any
|
||||||
|
|
||||||
|
def generate_add_protocol(
|
||||||
|
G: nx.DiGraph,
|
||||||
|
vessel: str,
|
||||||
|
reagent: str,
|
||||||
|
volume: float,
|
||||||
|
mass: float,
|
||||||
|
amount: str,
|
||||||
|
time: float,
|
||||||
|
stir: bool,
|
||||||
|
stir_speed: float,
|
||||||
|
viscous: bool,
|
||||||
|
purpose: str
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
生成添加试剂的协议序列
|
||||||
|
|
||||||
|
流程:
|
||||||
|
1. 找到包含目标试剂的试剂瓶
|
||||||
|
2. 配置八通阀门到试剂瓶位置
|
||||||
|
3. 使用注射器泵吸取试剂
|
||||||
|
4. 配置八通阀门到反应器位置
|
||||||
|
5. 使用注射器泵推送试剂到反应器
|
||||||
|
6. 如果需要,启动搅拌
|
||||||
|
"""
|
||||||
|
action_sequence = []
|
||||||
|
|
||||||
|
# 验证目标容器存在
|
||||||
|
if vessel not in G.nodes():
|
||||||
|
raise ValueError(f"目标容器 {vessel} 不存在")
|
||||||
|
|
||||||
|
# 如果指定了体积,执行液体转移
|
||||||
|
if volume > 0:
|
||||||
|
# 1. 查找注射器泵 (transfer pump)
|
||||||
|
transfer_pump_nodes = [node for node in G.nodes()
|
||||||
|
if G.nodes[node].get('class') == 'virtual_transfer_pump']
|
||||||
|
|
||||||
|
if not transfer_pump_nodes:
|
||||||
|
raise ValueError("没有找到可用的注射器泵 (virtual_transfer_pump)")
|
||||||
|
|
||||||
|
transfer_pump_id = transfer_pump_nodes[0]
|
||||||
|
|
||||||
|
# 2. 查找八通阀门
|
||||||
|
multiway_valve_nodes = [node for node in G.nodes()
|
||||||
|
if G.nodes[node].get('class') == 'virtual_multiway_valve']
|
||||||
|
|
||||||
|
if not multiway_valve_nodes:
|
||||||
|
raise ValueError("没有找到可用的八通阀门 (virtual_multiway_valve)")
|
||||||
|
|
||||||
|
valve_id = multiway_valve_nodes[0]
|
||||||
|
|
||||||
|
# 3. 查找包含指定试剂的试剂瓶
|
||||||
|
reagent_vessel = None
|
||||||
|
available_flasks = [node for node in G.nodes()
|
||||||
|
if node.startswith('flask_')
|
||||||
|
and G.nodes[node].get('type') == 'container']
|
||||||
|
|
||||||
|
# 简化:使用第一个可用的试剂瓶,实际应该根据试剂名称匹配
|
||||||
|
if available_flasks:
|
||||||
|
reagent_vessel = available_flasks[0]
|
||||||
|
else:
|
||||||
|
raise ValueError("没有找到可用的试剂容器")
|
||||||
|
|
||||||
|
# 4. 获取试剂瓶和反应器对应的阀门位置
|
||||||
|
# 这需要根据实际连接图来确定,这里假设:
|
||||||
|
reagent_valve_position = 1 # 试剂瓶连接到阀门位置1
|
||||||
|
reactor_valve_position = 2 # 反应器连接到阀门位置2
|
||||||
|
|
||||||
|
# 5. 执行添加操作序列
|
||||||
|
|
||||||
|
# 5.1 设置阀门到试剂瓶位置
|
||||||
|
action_sequence.append({
|
||||||
|
"device_id": valve_id,
|
||||||
|
"action_name": "set_position",
|
||||||
|
"action_kwargs": {
|
||||||
|
"position": reagent_valve_position
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
# 5.2 使用注射器泵从试剂瓶吸取液体
|
||||||
|
action_sequence.append({
|
||||||
|
"device_id": transfer_pump_id,
|
||||||
|
"action_name": "transfer",
|
||||||
|
"action_kwargs": {
|
||||||
|
"from_vessel": reagent_vessel,
|
||||||
|
"to_vessel": transfer_pump_id, # 吸入到注射器
|
||||||
|
"volume": volume,
|
||||||
|
"amount": amount,
|
||||||
|
"time": time / 2, # 吸取时间为总时间的一半
|
||||||
|
"viscous": viscous,
|
||||||
|
"rinsing_solvent": "",
|
||||||
|
"rinsing_volume": 0.0,
|
||||||
|
"rinsing_repeats": 0,
|
||||||
|
"solid": False
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
# 5.3 设置阀门到反应器位置
|
||||||
|
action_sequence.append({
|
||||||
|
"device_id": valve_id,
|
||||||
|
"action_name": "set_position",
|
||||||
|
"action_kwargs": {
|
||||||
|
"position": reactor_valve_position
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
# 5.4 使用注射器泵将液体推送到反应器
|
||||||
|
action_sequence.append({
|
||||||
|
"device_id": transfer_pump_id,
|
||||||
|
"action_name": "transfer",
|
||||||
|
"action_kwargs": {
|
||||||
|
"from_vessel": transfer_pump_id, # 从注射器推出
|
||||||
|
"to_vessel": vessel,
|
||||||
|
"volume": volume,
|
||||||
|
"amount": amount,
|
||||||
|
"time": time / 2, # 推送时间为总时间的一半
|
||||||
|
"viscous": viscous,
|
||||||
|
"rinsing_solvent": "",
|
||||||
|
"rinsing_volume": 0.0,
|
||||||
|
"rinsing_repeats": 0,
|
||||||
|
"solid": False
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
# 6. 如果需要搅拌,启动搅拌器
|
||||||
|
if stir:
|
||||||
|
stirrer_nodes = [node for node in G.nodes()
|
||||||
|
if G.nodes[node].get('class') == 'virtual_stirrer']
|
||||||
|
|
||||||
|
if stirrer_nodes:
|
||||||
|
stirrer_id = stirrer_nodes[0]
|
||||||
|
action_sequence.append({
|
||||||
|
"device_id": stirrer_id,
|
||||||
|
"action_name": "start_stir",
|
||||||
|
"action_kwargs": {
|
||||||
|
"vessel": vessel,
|
||||||
|
"stir_speed": stir_speed,
|
||||||
|
"purpose": f"添加 {reagent} 后搅拌混合"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
print("警告:需要搅拌但未找到搅拌设备")
|
||||||
|
|
||||||
|
return action_sequence
|
||||||
|
|
||||||
|
|
||||||
|
def find_valve_position_for_vessel(G: nx.DiGraph, valve_id: str, vessel_id: str) -> int:
|
||||||
|
"""
|
||||||
|
根据连接图找到容器对应的阀门位置
|
||||||
|
|
||||||
|
Args:
|
||||||
|
G: 网络图
|
||||||
|
valve_id: 阀门设备ID
|
||||||
|
vessel_id: 容器ID
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
int: 阀门位置编号 (1-8)
|
||||||
|
"""
|
||||||
|
# 查找阀门到容器的连接
|
||||||
|
edges = G.edges(data=True)
|
||||||
|
|
||||||
|
for source, target, data in edges:
|
||||||
|
if source == valve_id and target == vessel_id:
|
||||||
|
# 从连接数据中提取端口信息
|
||||||
|
port_info = data.get('port', {})
|
||||||
|
valve_port = port_info.get(valve_id, '')
|
||||||
|
|
||||||
|
# 解析端口名称获取位置编号
|
||||||
|
if valve_port.startswith('multiway-valve-port-'):
|
||||||
|
position = valve_port.split('-')[-1]
|
||||||
|
return int(position)
|
||||||
|
|
||||||
|
# 默认返回位置1
|
||||||
|
return 1
|
||||||
|
|
||||||
|
|
||||||
|
def generate_add_with_autodiscovery(
|
||||||
|
G: nx.DiGraph,
|
||||||
|
vessel: str,
|
||||||
|
reagent: str,
|
||||||
|
volume: float,
|
||||||
|
**kwargs
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
智能添加协议生成器 - 自动发现设备连接关系
|
||||||
|
"""
|
||||||
|
action_sequence = []
|
||||||
|
|
||||||
|
# 查找必需的设备
|
||||||
|
devices = {
|
||||||
|
'transfer_pump': None,
|
||||||
|
'multiway_valve': None,
|
||||||
|
'stirrer': None
|
||||||
|
}
|
||||||
|
|
||||||
|
for node in G.nodes():
|
||||||
|
node_class = G.nodes[node].get('class')
|
||||||
|
if node_class == 'virtual_transfer_pump':
|
||||||
|
devices['transfer_pump'] = node
|
||||||
|
elif node_class == 'virtual_multiway_valve':
|
||||||
|
devices['multiway_valve'] = node
|
||||||
|
elif node_class == 'virtual_stirrer':
|
||||||
|
devices['stirrer'] = node
|
||||||
|
|
||||||
|
# 验证必需设备
|
||||||
|
if not devices['transfer_pump']:
|
||||||
|
raise ValueError("缺少注射器泵设备")
|
||||||
|
if not devices['multiway_valve']:
|
||||||
|
raise ValueError("缺少八通阀门设备")
|
||||||
|
|
||||||
|
# 查找试剂容器
|
||||||
|
reagent_vessels = [node for node in G.nodes()
|
||||||
|
if node.startswith('flask_')
|
||||||
|
and G.nodes[node].get('type') == 'container']
|
||||||
|
|
||||||
|
if not reagent_vessels:
|
||||||
|
raise ValueError("没有找到试剂容器")
|
||||||
|
|
||||||
|
# 执行添加流程
|
||||||
|
reagent_vessel = reagent_vessels[0]
|
||||||
|
reagent_pos = find_valve_position_for_vessel(G, devices['multiway_valve'], reagent_vessel)
|
||||||
|
reactor_pos = find_valve_position_for_vessel(G, devices['multiway_valve'], vessel)
|
||||||
|
|
||||||
|
# 生成操作序列
|
||||||
|
action_sequence.extend([
|
||||||
|
# 切换到试剂瓶
|
||||||
|
{
|
||||||
|
"device_id": devices['multiway_valve'],
|
||||||
|
"action_name": "set_position",
|
||||||
|
"action_kwargs": {"position": reagent_pos}
|
||||||
|
},
|
||||||
|
# 吸取试剂
|
||||||
|
{
|
||||||
|
"device_id": devices['transfer_pump'],
|
||||||
|
"action_name": "transfer",
|
||||||
|
"action_kwargs": {
|
||||||
|
"from_vessel": reagent_vessel,
|
||||||
|
"to_vessel": devices['transfer_pump'],
|
||||||
|
"volume": volume,
|
||||||
|
"amount": kwargs.get('amount', ''),
|
||||||
|
"time": kwargs.get('time', 10.0) / 2,
|
||||||
|
"viscous": kwargs.get('viscous', False),
|
||||||
|
"rinsing_solvent": "",
|
||||||
|
"rinsing_volume": 0.0,
|
||||||
|
"rinsing_repeats": 0,
|
||||||
|
"solid": False
|
||||||
|
}
|
||||||
|
},
|
||||||
|
# 切换到反应器
|
||||||
|
{
|
||||||
|
"device_id": devices['multiway_valve'],
|
||||||
|
"action_name": "set_position",
|
||||||
|
"action_kwargs": {"position": reactor_pos}
|
||||||
|
},
|
||||||
|
# 推送到反应器
|
||||||
|
{
|
||||||
|
"device_id": devices['transfer_pump'],
|
||||||
|
"action_name": "transfer",
|
||||||
|
"action_kwargs": {
|
||||||
|
"from_vessel": devices['transfer_pump'],
|
||||||
|
"to_vessel": vessel,
|
||||||
|
"volume": volume,
|
||||||
|
"amount": kwargs.get('amount', ''),
|
||||||
|
"time": kwargs.get('time', 10.0) / 2,
|
||||||
|
"viscous": kwargs.get('viscous', False),
|
||||||
|
"rinsing_solvent": "",
|
||||||
|
"rinsing_volume": 0.0,
|
||||||
|
"rinsing_repeats": 0,
|
||||||
|
"solid": False
|
||||||
|
}
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
# 如果需要搅拌
|
||||||
|
if kwargs.get('stir', False) and devices['stirrer']:
|
||||||
|
action_sequence.append({
|
||||||
|
"device_id": devices['stirrer'],
|
||||||
|
"action_name": "start_stir",
|
||||||
|
"action_kwargs": {
|
||||||
|
"vessel": vessel,
|
||||||
|
"stir_speed": kwargs.get('stir_speed', 300.0),
|
||||||
|
"purpose": f"添加 {reagent} 后混合"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return action_sequence
|
||||||
123
unilabos/compile/centrifuge_protocol.py
Normal file
123
unilabos/compile/centrifuge_protocol.py
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
from typing import List, Dict, Any
|
||||||
|
import networkx as nx
|
||||||
|
|
||||||
|
def generate_centrifuge_protocol(
|
||||||
|
G: nx.DiGraph,
|
||||||
|
vessel: str,
|
||||||
|
speed: float,
|
||||||
|
time: float,
|
||||||
|
temp: float = 25.0
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
生成离心操作的协议序列
|
||||||
|
|
||||||
|
Args:
|
||||||
|
G: 有向图,节点为设备和容器
|
||||||
|
vessel: 离心容器名称
|
||||||
|
speed: 离心速度 (rpm)
|
||||||
|
time: 离心时间 (秒)
|
||||||
|
temp: 温度 (摄氏度,可选)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List[Dict[str, Any]]: 离心操作的动作序列
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ValueError: 当找不到离心机设备时抛出异常
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
centrifuge_protocol = generate_centrifuge_protocol(G, "reactor", 5000, 300, 4.0)
|
||||||
|
"""
|
||||||
|
action_sequence = []
|
||||||
|
|
||||||
|
# 查找离心机设备
|
||||||
|
centrifuge_nodes = [node for node in G.nodes()
|
||||||
|
if G.nodes[node].get('class') == 'virtual_centrifuge']
|
||||||
|
|
||||||
|
if not centrifuge_nodes:
|
||||||
|
raise ValueError("没有找到可用的离心机设备")
|
||||||
|
|
||||||
|
# 使用第一个可用的离心机
|
||||||
|
centrifuge_id = centrifuge_nodes[0]
|
||||||
|
|
||||||
|
# 验证容器是否存在
|
||||||
|
if vessel not in G.nodes():
|
||||||
|
raise ValueError(f"容器 {vessel} 不存在于图中")
|
||||||
|
|
||||||
|
# 执行离心操作
|
||||||
|
action_sequence.append({
|
||||||
|
"device_id": centrifuge_id,
|
||||||
|
"action_name": "centrifuge",
|
||||||
|
"action_kwargs": {
|
||||||
|
"vessel": vessel,
|
||||||
|
"speed": speed,
|
||||||
|
"time": time,
|
||||||
|
"temp": temp
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return action_sequence
|
||||||
|
|
||||||
|
|
||||||
|
def generate_multi_step_centrifuge_protocol(
|
||||||
|
G: nx.DiGraph,
|
||||||
|
vessel: str,
|
||||||
|
steps: List[Dict[str, Any]]
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
生成多步骤离心操作的协议序列
|
||||||
|
|
||||||
|
Args:
|
||||||
|
G: 有向图,节点为设备和容器
|
||||||
|
vessel: 离心容器名称
|
||||||
|
steps: 离心步骤列表,每个步骤包含 speed, time, temp 参数
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List[Dict[str, Any]]: 多步骤离心操作的动作序列
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
steps = [
|
||||||
|
{"speed": 1000, "time": 60, "temp": 4.0}, # 低速预离心
|
||||||
|
{"speed": 12000, "time": 600, "temp": 4.0} # 高速离心
|
||||||
|
]
|
||||||
|
protocol = generate_multi_step_centrifuge_protocol(G, "reactor", steps)
|
||||||
|
"""
|
||||||
|
action_sequence = []
|
||||||
|
|
||||||
|
# 查找离心机设备
|
||||||
|
centrifuge_nodes = [node for node in G.nodes()
|
||||||
|
if G.nodes[node].get('class') == 'virtual_centrifuge']
|
||||||
|
|
||||||
|
if not centrifuge_nodes:
|
||||||
|
raise ValueError("没有找到可用的离心机设备")
|
||||||
|
|
||||||
|
centrifuge_id = centrifuge_nodes[0]
|
||||||
|
|
||||||
|
# 验证容器是否存在
|
||||||
|
if vessel not in G.nodes():
|
||||||
|
raise ValueError(f"容器 {vessel} 不存在于图中")
|
||||||
|
|
||||||
|
# 执行每个离心步骤
|
||||||
|
for i, step in enumerate(steps):
|
||||||
|
speed = step.get('speed', 5000)
|
||||||
|
time = step.get('time', 300)
|
||||||
|
temp = step.get('temp', 25.0)
|
||||||
|
|
||||||
|
action_sequence.append({
|
||||||
|
"device_id": centrifuge_id,
|
||||||
|
"action_name": "centrifuge",
|
||||||
|
"action_kwargs": {
|
||||||
|
"vessel": vessel,
|
||||||
|
"speed": speed,
|
||||||
|
"time": time,
|
||||||
|
"temp": temp
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
# 步骤间等待时间(除了最后一步)
|
||||||
|
if i < len(steps) - 1:
|
||||||
|
action_sequence.append({
|
||||||
|
"action_name": "wait",
|
||||||
|
"action_kwargs": {"time": 3}
|
||||||
|
})
|
||||||
|
|
||||||
|
return action_sequence
|
||||||
126
unilabos/compile/clean_vessel_protocol.py
Normal file
126
unilabos/compile/clean_vessel_protocol.py
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
from typing import List, Dict, Any
|
||||||
|
import networkx as nx
|
||||||
|
|
||||||
|
def generate_clean_vessel_protocol(
|
||||||
|
G: nx.DiGraph,
|
||||||
|
vessel: str,
|
||||||
|
solvent: str,
|
||||||
|
volume: float,
|
||||||
|
temp: float,
|
||||||
|
repeats: int = 1
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
生成容器清洗操作的协议序列,使用transfer操作实现清洗
|
||||||
|
|
||||||
|
Args:
|
||||||
|
G: 有向图,节点为设备和容器
|
||||||
|
vessel: 要清洗的容器名称
|
||||||
|
solvent: 用于清洗容器的溶剂名称
|
||||||
|
volume: 清洗溶剂的体积
|
||||||
|
temp: 清洗时的温度
|
||||||
|
repeats: 清洗操作的重复次数,默认为 1
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List[Dict[str, Any]]: 容器清洗操作的动作序列
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ValueError: 当找不到必要的设备时抛出异常
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
clean_vessel_protocol = generate_clean_vessel_protocol(G, "reactor", "water", 50.0, 25.0, 2)
|
||||||
|
"""
|
||||||
|
action_sequence = []
|
||||||
|
|
||||||
|
# 查找虚拟转移泵设备进行清洗操作
|
||||||
|
pump_nodes = [node for node in G.nodes()
|
||||||
|
if G.nodes[node].get('class') == 'virtual_transfer_pump']
|
||||||
|
|
||||||
|
if not pump_nodes:
|
||||||
|
raise ValueError("没有找到可用的转移泵设备进行容器清洗")
|
||||||
|
|
||||||
|
pump_id = pump_nodes[0]
|
||||||
|
|
||||||
|
# 验证容器是否存在
|
||||||
|
if vessel not in G.nodes():
|
||||||
|
raise ValueError(f"容器 {vessel} 不存在于图中")
|
||||||
|
|
||||||
|
# 查找溶剂容器
|
||||||
|
solvent_vessel = f"flask_{solvent}"
|
||||||
|
if solvent_vessel not in G.nodes():
|
||||||
|
raise ValueError(f"溶剂容器 {solvent_vessel} 不存在于图中")
|
||||||
|
|
||||||
|
# 查找废液容器
|
||||||
|
waste_vessel = "flask_waste"
|
||||||
|
if waste_vessel not in G.nodes():
|
||||||
|
raise ValueError(f"废液容器 {waste_vessel} 不存在于图中")
|
||||||
|
|
||||||
|
# 查找加热设备(如果需要加热)
|
||||||
|
heatchill_nodes = [node for node in G.nodes()
|
||||||
|
if G.nodes[node].get('class') == 'virtual_heatchill']
|
||||||
|
|
||||||
|
heatchill_id = heatchill_nodes[0] if heatchill_nodes else None
|
||||||
|
|
||||||
|
# 执行清洗操作序列
|
||||||
|
for repeat in range(repeats):
|
||||||
|
# 1. 如果需要加热,先设置温度
|
||||||
|
if temp > 25.0 and heatchill_id:
|
||||||
|
action_sequence.append({
|
||||||
|
"device_id": heatchill_id,
|
||||||
|
"action_name": "heat_chill_start",
|
||||||
|
"action_kwargs": {
|
||||||
|
"vessel": vessel,
|
||||||
|
"temp": temp,
|
||||||
|
"purpose": "cleaning"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
# 2. 使用transfer操作:从溶剂容器转移清洗溶剂到目标容器
|
||||||
|
action_sequence.append({
|
||||||
|
"device_id": pump_id,
|
||||||
|
"action_name": "transfer",
|
||||||
|
"action_kwargs": {
|
||||||
|
"from_vessel": solvent_vessel,
|
||||||
|
"to_vessel": vessel,
|
||||||
|
"volume": volume,
|
||||||
|
"amount": f"cleaning with {solvent} - cycle {repeat + 1}",
|
||||||
|
"time": 0.0,
|
||||||
|
"viscous": False,
|
||||||
|
"rinsing_solvent": "",
|
||||||
|
"rinsing_volume": 0.0,
|
||||||
|
"rinsing_repeats": 0,
|
||||||
|
"solid": False
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
# 3. 等待清洗作用时间(可选,可以添加wait操作)
|
||||||
|
# 这里省略wait操作,直接进行下一步
|
||||||
|
|
||||||
|
# 4. 将清洗后的溶剂转移到废液容器
|
||||||
|
action_sequence.append({
|
||||||
|
"device_id": pump_id,
|
||||||
|
"action_name": "transfer",
|
||||||
|
"action_kwargs": {
|
||||||
|
"from_vessel": vessel,
|
||||||
|
"to_vessel": waste_vessel,
|
||||||
|
"volume": volume,
|
||||||
|
"amount": f"waste from cleaning {vessel} - cycle {repeat + 1}",
|
||||||
|
"time": 0.0,
|
||||||
|
"viscous": False,
|
||||||
|
"rinsing_solvent": "",
|
||||||
|
"rinsing_volume": 0.0,
|
||||||
|
"rinsing_repeats": 0,
|
||||||
|
"solid": False
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
# 5. 如果加热了,停止加热
|
||||||
|
if temp > 25.0 and heatchill_id:
|
||||||
|
action_sequence.append({
|
||||||
|
"device_id": heatchill_id,
|
||||||
|
"action_name": "heat_chill_stop",
|
||||||
|
"action_kwargs": {
|
||||||
|
"vessel": vessel
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return action_sequence
|
||||||
162
unilabos/compile/dissolve_protocol.py
Normal file
162
unilabos/compile/dissolve_protocol.py
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
from typing import List, Dict, Any
|
||||||
|
import networkx as nx
|
||||||
|
|
||||||
|
def generate_dissolve_protocol(
|
||||||
|
G: nx.DiGraph,
|
||||||
|
vessel: str,
|
||||||
|
solvent: str,
|
||||||
|
volume: float,
|
||||||
|
amount: str = "",
|
||||||
|
temp: float = 25.0,
|
||||||
|
time: float = 0.0,
|
||||||
|
stir_speed: float = 0.0
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
生成溶解操作的协议序列
|
||||||
|
|
||||||
|
Args:
|
||||||
|
G: 有向图,节点为设备和容器
|
||||||
|
vessel: 装有要溶解物质的容器名称
|
||||||
|
solvent: 用于溶解物质的溶剂名称
|
||||||
|
volume: 溶剂的体积,可选参数
|
||||||
|
amount: 要溶解物质的量,可选参数
|
||||||
|
temp: 溶解时的温度,可选参数
|
||||||
|
time: 溶解的时间,可选参数
|
||||||
|
stir_speed: 搅拌速度,可选参数
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List[Dict[str, Any]]: 溶解操作的动作序列
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ValueError: 当找不到必要的设备时抛出异常
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
dissolve_protocol = generate_dissolve_protocol(G, "reactor", "water", 100.0, "NaCl 5g", 60.0, 300.0, 500.0)
|
||||||
|
"""
|
||||||
|
action_sequence = []
|
||||||
|
|
||||||
|
# 验证容器是否存在
|
||||||
|
if vessel not in G.nodes():
|
||||||
|
raise ValueError(f"容器 {vessel} 不存在于图中")
|
||||||
|
|
||||||
|
# 查找溶剂容器
|
||||||
|
solvent_vessel = f"flask_{solvent}"
|
||||||
|
if solvent_vessel not in G.nodes():
|
||||||
|
# 如果没有找到特定溶剂容器,查找可用的源容器
|
||||||
|
available_vessels = [node for node in G.nodes()
|
||||||
|
if node.startswith('flask_') and
|
||||||
|
G.nodes[node].get('type') == 'container']
|
||||||
|
if available_vessels:
|
||||||
|
solvent_vessel = available_vessels[0]
|
||||||
|
else:
|
||||||
|
raise ValueError(f"没有找到溶剂容器 {solvent}")
|
||||||
|
|
||||||
|
# 查找转移泵设备
|
||||||
|
pump_nodes = [node for node in G.nodes()
|
||||||
|
if G.nodes[node].get('class') == 'virtual_transfer_pump']
|
||||||
|
|
||||||
|
if not pump_nodes:
|
||||||
|
raise ValueError("没有找到可用的转移泵设备")
|
||||||
|
|
||||||
|
pump_id = pump_nodes[0]
|
||||||
|
|
||||||
|
# 查找加热设备(如果需要加热)
|
||||||
|
heatchill_nodes = [node for node in G.nodes()
|
||||||
|
if G.nodes[node].get('class') == 'virtual_heatchill']
|
||||||
|
|
||||||
|
heatchill_id = heatchill_nodes[0] if heatchill_nodes else None
|
||||||
|
|
||||||
|
# 查找搅拌设备(如果需要搅拌)
|
||||||
|
stirrer_nodes = [node for node in G.nodes()
|
||||||
|
if G.nodes[node].get('class') == 'virtual_stirrer']
|
||||||
|
|
||||||
|
stirrer_id = stirrer_nodes[0] if stirrer_nodes else None
|
||||||
|
|
||||||
|
# 步骤1:如果需要加热,先设置温度
|
||||||
|
if temp > 25.0 and heatchill_id:
|
||||||
|
action_sequence.append({
|
||||||
|
"device_id": heatchill_id,
|
||||||
|
"action_name": "heat_chill_start",
|
||||||
|
"action_kwargs": {
|
||||||
|
"vessel": vessel,
|
||||||
|
"temp": temp,
|
||||||
|
"purpose": "dissolution"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
# 步骤2:添加溶剂到容器中
|
||||||
|
if volume > 0:
|
||||||
|
action_sequence.append({
|
||||||
|
"device_id": pump_id,
|
||||||
|
"action_name": "transfer",
|
||||||
|
"action_kwargs": {
|
||||||
|
"from_vessel": solvent_vessel,
|
||||||
|
"to_vessel": vessel,
|
||||||
|
"volume": volume,
|
||||||
|
"amount": f"solvent {solvent} for dissolving {amount}",
|
||||||
|
"time": 0.0,
|
||||||
|
"viscous": False,
|
||||||
|
"rinsing_solvent": "",
|
||||||
|
"rinsing_volume": 0.0,
|
||||||
|
"rinsing_repeats": 0,
|
||||||
|
"solid": False
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
# 步骤3:如果需要搅拌,开始搅拌
|
||||||
|
if stir_speed > 0 and stirrer_id:
|
||||||
|
action_sequence.append({
|
||||||
|
"device_id": stirrer_id,
|
||||||
|
"action_name": "start_stir",
|
||||||
|
"action_kwargs": {
|
||||||
|
"vessel": vessel,
|
||||||
|
"stir_speed": stir_speed,
|
||||||
|
"purpose": f"dissolving {amount} in {solvent}"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
# 步骤4:如果指定了溶解时间,等待溶解完成
|
||||||
|
if time > 0:
|
||||||
|
# 这里可以添加等待操作,或者使用搅拌操作来模拟溶解时间
|
||||||
|
if stirrer_id and stir_speed > 0:
|
||||||
|
# 停止之前的搅拌,使用定时搅拌
|
||||||
|
action_sequence.append({
|
||||||
|
"device_id": stirrer_id,
|
||||||
|
"action_name": "stop_stir",
|
||||||
|
"action_kwargs": {
|
||||||
|
"vessel": vessel
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
# 开始定时搅拌
|
||||||
|
action_sequence.append({
|
||||||
|
"device_id": stirrer_id,
|
||||||
|
"action_name": "stir",
|
||||||
|
"action_kwargs": {
|
||||||
|
"stir_time": time,
|
||||||
|
"stir_speed": stir_speed,
|
||||||
|
"settling_time": 10.0 # 搅拌后静置10秒
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
# 步骤5:如果加热了,停止加热
|
||||||
|
if temp > 25.0 and heatchill_id:
|
||||||
|
action_sequence.append({
|
||||||
|
"device_id": heatchill_id,
|
||||||
|
"action_name": "heat_chill_stop",
|
||||||
|
"action_kwargs": {
|
||||||
|
"vessel": vessel
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
# 步骤6:如果还在搅拌,停止搅拌(除非已经用定时搅拌)
|
||||||
|
if stir_speed > 0 and stirrer_id and time == 0:
|
||||||
|
action_sequence.append({
|
||||||
|
"device_id": stirrer_id,
|
||||||
|
"action_name": "stop_stir",
|
||||||
|
"action_kwargs": {
|
||||||
|
"vessel": vessel
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return action_sequence
|
||||||
@@ -69,14 +69,14 @@ def generate_evacuateandrefill_protocol(
|
|||||||
"device_id": vacuum_backbone["pump"],
|
"device_id": vacuum_backbone["pump"],
|
||||||
"action_name": "set_status",
|
"action_name": "set_status",
|
||||||
"action_kwargs": {
|
"action_kwargs": {
|
||||||
"command": "ON"
|
"string": "ON"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"device_id": vacuum_backbone["gas"],
|
"device_id": vacuum_backbone["gas"],
|
||||||
"action_name": "set_status",
|
"action_name": "set_status",
|
||||||
"action_kwargs": {
|
"action_kwargs": {
|
||||||
"command": "OFF"
|
"string": "OFF"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
@@ -106,14 +106,14 @@ def generate_evacuateandrefill_protocol(
|
|||||||
"device_id": vacuum_backbone["pump"],
|
"device_id": vacuum_backbone["pump"],
|
||||||
"action_name": "set_status",
|
"action_name": "set_status",
|
||||||
"action_kwargs": {
|
"action_kwargs": {
|
||||||
"command": "OFF"
|
"string": "OFF"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"device_id": vacuum_backbone["gas"],
|
"device_id": vacuum_backbone["gas"],
|
||||||
"action_name": "set_status",
|
"action_name": "set_status",
|
||||||
"action_kwargs": {
|
"action_kwargs": {
|
||||||
"command": "ON"
|
"string": "ON"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
@@ -125,7 +125,7 @@ def generate_evacuateandrefill_protocol(
|
|||||||
"device_id": vacuum_backbone["gas"],
|
"device_id": vacuum_backbone["gas"],
|
||||||
"action_name": "set_status",
|
"action_name": "set_status",
|
||||||
"action_kwargs": {
|
"action_kwargs": {
|
||||||
"command": "OFF"
|
"string": "OFF"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
70
unilabos/compile/filter_protocol.py
Normal file
70
unilabos/compile/filter_protocol.py
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
from typing import List, Dict, Any
|
||||||
|
import networkx as nx
|
||||||
|
|
||||||
|
def generate_filter_protocol(
|
||||||
|
G: nx.DiGraph,
|
||||||
|
vessel: str,
|
||||||
|
filtrate_vessel: str = "",
|
||||||
|
stir: bool = False,
|
||||||
|
stir_speed: float = 300.0,
|
||||||
|
temp: float = 25.0,
|
||||||
|
continue_heatchill: bool = False,
|
||||||
|
volume: float = 0.0
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
生成过滤操作的协议序列
|
||||||
|
|
||||||
|
Args:
|
||||||
|
G: 有向图,节点为设备和容器
|
||||||
|
vessel: 过滤容器
|
||||||
|
filtrate_vessel: 滤液容器(可选)
|
||||||
|
stir: 是否搅拌
|
||||||
|
stir_speed: 搅拌速度(可选)
|
||||||
|
temp: 温度(可选,摄氏度)
|
||||||
|
continue_heatchill: 是否继续加热冷却
|
||||||
|
volume: 过滤体积(可选)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List[Dict[str, Any]]: 过滤操作的动作序列
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ValueError: 当找不到过滤设备时抛出异常
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
filter_protocol = generate_filter_protocol(G, "reactor", "filtrate_vessel", stir=True, volume=100.0)
|
||||||
|
"""
|
||||||
|
action_sequence = []
|
||||||
|
|
||||||
|
# 查找过滤设备
|
||||||
|
filter_nodes = [node for node in G.nodes()
|
||||||
|
if G.nodes[node].get('class') == 'virtual_filter']
|
||||||
|
|
||||||
|
if not filter_nodes:
|
||||||
|
raise ValueError("没有找到可用的过滤设备")
|
||||||
|
|
||||||
|
# 使用第一个可用的过滤器
|
||||||
|
filter_id = filter_nodes[0]
|
||||||
|
|
||||||
|
# 验证容器是否存在
|
||||||
|
if vessel not in G.nodes():
|
||||||
|
raise ValueError(f"过滤容器 {vessel} 不存在于图中")
|
||||||
|
|
||||||
|
if filtrate_vessel and filtrate_vessel not in G.nodes():
|
||||||
|
raise ValueError(f"滤液容器 {filtrate_vessel} 不存在于图中")
|
||||||
|
|
||||||
|
# 执行过滤操作
|
||||||
|
action_sequence.append({
|
||||||
|
"device_id": filter_id,
|
||||||
|
"action_name": "filter_sample",
|
||||||
|
"action_kwargs": {
|
||||||
|
"vessel": vessel,
|
||||||
|
"filtrate_vessel": filtrate_vessel,
|
||||||
|
"stir": stir,
|
||||||
|
"stir_speed": stir_speed,
|
||||||
|
"temp": temp,
|
||||||
|
"continue_heatchill": continue_heatchill,
|
||||||
|
"volume": volume
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return action_sequence
|
||||||
150
unilabos/compile/filter_through_protocol.py
Normal file
150
unilabos/compile/filter_through_protocol.py
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
from typing import List, Dict, Any
|
||||||
|
import networkx as nx
|
||||||
|
|
||||||
|
def generate_filter_through_protocol(
|
||||||
|
G: nx.DiGraph,
|
||||||
|
from_vessel: str,
|
||||||
|
to_vessel: str,
|
||||||
|
filter_through: str,
|
||||||
|
eluting_solvent: str = "",
|
||||||
|
eluting_volume: float = 0.0,
|
||||||
|
eluting_repeats: int = 0,
|
||||||
|
residence_time: float = 0.0
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
生成通过过滤介质过滤的协议序列
|
||||||
|
|
||||||
|
Args:
|
||||||
|
G: 有向图,节点为设备和容器
|
||||||
|
from_vessel: 源容器的名称,即物质起始所在的容器
|
||||||
|
to_vessel: 目标容器的名称,物质过滤后要到达的容器
|
||||||
|
filter_through: 过滤时所通过的介质,如滤纸、柱子等
|
||||||
|
eluting_solvent: 洗脱溶剂的名称,可选参数
|
||||||
|
eluting_volume: 洗脱溶剂的体积,可选参数
|
||||||
|
eluting_repeats: 洗脱操作的重复次数,默认为 0
|
||||||
|
residence_time: 物质在过滤介质中的停留时间,可选参数
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List[Dict[str, Any]]: 过滤操作的动作序列
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ValueError: 当找不到必要的设备时抛出异常
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
filter_through_protocol = generate_filter_through_protocol(
|
||||||
|
G, "reactor", "collection_flask", "celite", "ethanol", 50.0, 2, 60.0
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
action_sequence = []
|
||||||
|
|
||||||
|
# 验证容器是否存在
|
||||||
|
if from_vessel not in G.nodes():
|
||||||
|
raise ValueError(f"源容器 {from_vessel} 不存在于图中")
|
||||||
|
|
||||||
|
if to_vessel not in G.nodes():
|
||||||
|
raise ValueError(f"目标容器 {to_vessel} 不存在于图中")
|
||||||
|
|
||||||
|
# 查找转移泵设备(用于液体转移)
|
||||||
|
pump_nodes = [node for node in G.nodes()
|
||||||
|
if G.nodes[node].get('class') == 'virtual_transfer_pump']
|
||||||
|
|
||||||
|
if not pump_nodes:
|
||||||
|
raise ValueError("没有找到可用的转移泵设备")
|
||||||
|
|
||||||
|
pump_id = pump_nodes[0]
|
||||||
|
|
||||||
|
# 查找过滤设备(可选,如果有专门的过滤设备)
|
||||||
|
filter_nodes = [node for node in G.nodes()
|
||||||
|
if G.nodes[node].get('class') == 'virtual_filter']
|
||||||
|
|
||||||
|
filter_id = filter_nodes[0] if filter_nodes else None
|
||||||
|
|
||||||
|
# 查找洗脱溶剂容器(如果需要洗脱)
|
||||||
|
eluting_vessel = None
|
||||||
|
if eluting_solvent and eluting_volume > 0:
|
||||||
|
eluting_vessel = f"flask_{eluting_solvent}"
|
||||||
|
if eluting_vessel not in G.nodes():
|
||||||
|
# 查找可用的溶剂容器
|
||||||
|
available_vessels = [node for node in G.nodes()
|
||||||
|
if node.startswith('flask_') and
|
||||||
|
G.nodes[node].get('type') == 'container']
|
||||||
|
if available_vessels:
|
||||||
|
eluting_vessel = available_vessels[0]
|
||||||
|
else:
|
||||||
|
raise ValueError(f"没有找到洗脱溶剂容器 {eluting_solvent}")
|
||||||
|
|
||||||
|
# 步骤1:将样品从源容器转移到过滤装置(模拟通过过滤介质)
|
||||||
|
# 这里我们将过滤过程分解为多个转移步骤来模拟通过介质的过程
|
||||||
|
|
||||||
|
# 首先转移样品(模拟样品通过过滤介质)
|
||||||
|
action_sequence.append({
|
||||||
|
"device_id": pump_id,
|
||||||
|
"action_name": "transfer",
|
||||||
|
"action_kwargs": {
|
||||||
|
"from_vessel": from_vessel,
|
||||||
|
"to_vessel": to_vessel,
|
||||||
|
"volume": 0.0, # 转移所有液体,体积由系统确定
|
||||||
|
"amount": f"通过 {filter_through} 过滤",
|
||||||
|
"time": residence_time if residence_time > 0 else 0.0,
|
||||||
|
"viscous": False,
|
||||||
|
"rinsing_solvent": "",
|
||||||
|
"rinsing_volume": 0.0,
|
||||||
|
"rinsing_repeats": 0,
|
||||||
|
"solid": True # 通过过滤介质可能涉及固体分离
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
# 步骤2:如果有专门的过滤设备,使用过滤设备处理
|
||||||
|
if filter_id:
|
||||||
|
action_sequence.append({
|
||||||
|
"device_id": filter_id,
|
||||||
|
"action_name": "filter_sample",
|
||||||
|
"action_kwargs": {
|
||||||
|
"vessel": to_vessel,
|
||||||
|
"filtrate_vessel": to_vessel,
|
||||||
|
"stir": False,
|
||||||
|
"stir_speed": 0.0,
|
||||||
|
"temp": 25.0,
|
||||||
|
"continue_heatchill": False,
|
||||||
|
"volume": 0.0
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
# 步骤3:洗脱操作(如果指定了洗脱溶剂和重复次数)
|
||||||
|
if eluting_solvent and eluting_volume > 0 and eluting_repeats > 0 and eluting_vessel:
|
||||||
|
for repeat in range(eluting_repeats):
|
||||||
|
# 添加洗脱溶剂
|
||||||
|
action_sequence.append({
|
||||||
|
"device_id": pump_id,
|
||||||
|
"action_name": "transfer",
|
||||||
|
"action_kwargs": {
|
||||||
|
"from_vessel": eluting_vessel,
|
||||||
|
"to_vessel": to_vessel,
|
||||||
|
"volume": eluting_volume,
|
||||||
|
"amount": f"洗脱溶剂 {eluting_solvent} - 第 {repeat + 1} 次",
|
||||||
|
"time": 0.0,
|
||||||
|
"viscous": False,
|
||||||
|
"rinsing_solvent": "",
|
||||||
|
"rinsing_volume": 0.0,
|
||||||
|
"rinsing_repeats": 0,
|
||||||
|
"solid": False
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
# 如果有过滤设备,再次过滤洗脱液
|
||||||
|
if filter_id:
|
||||||
|
action_sequence.append({
|
||||||
|
"device_id": filter_id,
|
||||||
|
"action_name": "filter_sample",
|
||||||
|
"action_kwargs": {
|
||||||
|
"vessel": to_vessel,
|
||||||
|
"filtrate_vessel": to_vessel,
|
||||||
|
"stir": False,
|
||||||
|
"stir_speed": 0.0,
|
||||||
|
"temp": 25.0,
|
||||||
|
"continue_heatchill": False,
|
||||||
|
"volume": eluting_volume
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return action_sequence
|
||||||
117
unilabos/compile/heatchill_protocol.py
Normal file
117
unilabos/compile/heatchill_protocol.py
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
from typing import List, Dict, Any
|
||||||
|
import networkx as nx
|
||||||
|
|
||||||
|
def generate_heat_chill_protocol(
|
||||||
|
G: nx.DiGraph,
|
||||||
|
vessel: str,
|
||||||
|
temp: float,
|
||||||
|
time: float,
|
||||||
|
stir: bool,
|
||||||
|
stir_speed: float,
|
||||||
|
purpose: str
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
生成加热/冷却操作的协议序列 - 严格按照 HeatChill.action
|
||||||
|
"""
|
||||||
|
action_sequence = []
|
||||||
|
|
||||||
|
# 查找加热/冷却设备
|
||||||
|
heatchill_nodes = [node for node in G.nodes()
|
||||||
|
if G.nodes[node].get('class') == 'virtual_heatchill']
|
||||||
|
|
||||||
|
if not heatchill_nodes:
|
||||||
|
raise ValueError("没有找到可用的加热/冷却设备")
|
||||||
|
|
||||||
|
heatchill_id = heatchill_nodes[0]
|
||||||
|
|
||||||
|
if vessel not in G.nodes():
|
||||||
|
raise ValueError(f"容器 {vessel} 不存在于图中")
|
||||||
|
|
||||||
|
action_sequence.append({
|
||||||
|
"device_id": heatchill_id,
|
||||||
|
"action_name": "heat_chill",
|
||||||
|
"action_kwargs": {
|
||||||
|
"vessel": vessel,
|
||||||
|
"temp": temp,
|
||||||
|
"time": time,
|
||||||
|
"stir": stir,
|
||||||
|
"stir_speed": stir_speed,
|
||||||
|
"purpose": purpose
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return action_sequence
|
||||||
|
|
||||||
|
|
||||||
|
def generate_heat_chill_start_protocol(
|
||||||
|
G: nx.DiGraph,
|
||||||
|
vessel: str,
|
||||||
|
temp: float,
|
||||||
|
purpose: str
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
生成开始加热/冷却操作的协议序列 - 严格按照 HeatChillStart.action
|
||||||
|
"""
|
||||||
|
action_sequence = []
|
||||||
|
|
||||||
|
heatchill_nodes = [node for node in G.nodes()
|
||||||
|
if G.nodes[node].get('class') == 'virtual_heatchill']
|
||||||
|
|
||||||
|
if not heatchill_nodes:
|
||||||
|
raise ValueError("没有找到可用的加热/冷却设备")
|
||||||
|
|
||||||
|
heatchill_id = heatchill_nodes[0]
|
||||||
|
|
||||||
|
if vessel not in G.nodes():
|
||||||
|
raise ValueError(f"容器 {vessel} 不存在于图中")
|
||||||
|
|
||||||
|
action_sequence.append({
|
||||||
|
"device_id": heatchill_id,
|
||||||
|
"action_name": "heat_chill_start",
|
||||||
|
"action_kwargs": {
|
||||||
|
"vessel": vessel,
|
||||||
|
"temp": temp,
|
||||||
|
"purpose": purpose
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return action_sequence
|
||||||
|
|
||||||
|
|
||||||
|
def generate_heat_chill_stop_protocol(
|
||||||
|
G: nx.DiGraph,
|
||||||
|
vessel: str
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
生成停止加热/冷却操作的协议序列
|
||||||
|
|
||||||
|
Args:
|
||||||
|
G: 有向图,节点为设备和容器
|
||||||
|
vessel: 容器名称
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List[Dict[str, Any]]: 停止加热/冷却操作的动作序列
|
||||||
|
"""
|
||||||
|
action_sequence = []
|
||||||
|
|
||||||
|
# 查找加热/冷却设备
|
||||||
|
heatchill_nodes = [node for node in G.nodes()
|
||||||
|
if G.nodes[node].get('class') == 'virtual_heatchill']
|
||||||
|
|
||||||
|
if not heatchill_nodes:
|
||||||
|
raise ValueError("没有找到可用的加热/冷却设备")
|
||||||
|
|
||||||
|
heatchill_id = heatchill_nodes[0]
|
||||||
|
|
||||||
|
if vessel not in G.nodes():
|
||||||
|
raise ValueError(f"容器 {vessel} 不存在于图中")
|
||||||
|
|
||||||
|
action_sequence.append({
|
||||||
|
"device_id": heatchill_id,
|
||||||
|
"action_name": "heat_chill_stop",
|
||||||
|
"action_kwargs": {
|
||||||
|
"vessel": vessel
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return action_sequence
|
||||||
@@ -2,6 +2,31 @@ import numpy as np
|
|||||||
import networkx as nx
|
import networkx as nx
|
||||||
|
|
||||||
|
|
||||||
|
def is_integrated_pump(node_name):
|
||||||
|
return "pump" in node_name and "valve" in node_name
|
||||||
|
|
||||||
|
|
||||||
|
def find_connected_pump(G, valve_node):
|
||||||
|
for neighbor in G.neighbors(valve_node):
|
||||||
|
if "pump" in G.nodes[neighbor]["class"]:
|
||||||
|
return neighbor
|
||||||
|
raise ValueError(f"未找到与阀 {valve_node} 唯一相连的泵节点")
|
||||||
|
|
||||||
|
|
||||||
|
def build_pump_valve_maps(G, pump_backbone):
|
||||||
|
pumps_from_node = {}
|
||||||
|
valve_from_node = {}
|
||||||
|
for node in pump_backbone:
|
||||||
|
if is_integrated_pump(node):
|
||||||
|
pumps_from_node[node] = node
|
||||||
|
valve_from_node[node] = node
|
||||||
|
else:
|
||||||
|
pump_node = find_connected_pump(G, node)
|
||||||
|
pumps_from_node[node] = pump_node
|
||||||
|
valve_from_node[node] = node
|
||||||
|
return pumps_from_node, valve_from_node
|
||||||
|
|
||||||
|
|
||||||
def generate_pump_protocol(
|
def generate_pump_protocol(
|
||||||
G: nx.DiGraph,
|
G: nx.DiGraph,
|
||||||
from_vessel: str,
|
from_vessel: str,
|
||||||
@@ -38,7 +63,9 @@ def generate_pump_protocol(
|
|||||||
if transfer_flowrate == 0:
|
if transfer_flowrate == 0:
|
||||||
transfer_flowrate = flowrate
|
transfer_flowrate = flowrate
|
||||||
|
|
||||||
min_transfer_volume = min([nodes[pump]["max_volume"] for pump in pump_backbone])
|
pumps_from_node, valve_from_node = build_pump_valve_maps(G, pump_backbone)
|
||||||
|
|
||||||
|
min_transfer_volume = min([nodes[pumps_from_node[node]]["config"]["max_volume"] for node in pump_backbone])
|
||||||
repeats = int(np.ceil(volume / min_transfer_volume))
|
repeats = int(np.ceil(volume / min_transfer_volume))
|
||||||
if repeats > 1 and (from_vessel.startswith("pump") or to_vessel.startswith("pump")):
|
if repeats > 1 and (from_vessel.startswith("pump") or to_vessel.startswith("pump")):
|
||||||
raise ValueError("Cannot transfer volume larger than min_transfer_volume between two pumps.")
|
raise ValueError("Cannot transfer volume larger than min_transfer_volume between two pumps.")
|
||||||
@@ -51,14 +78,14 @@ def generate_pump_protocol(
|
|||||||
if not from_vessel.startswith("pump"):
|
if not from_vessel.startswith("pump"):
|
||||||
pump_action_sequence.extend([
|
pump_action_sequence.extend([
|
||||||
{
|
{
|
||||||
"device_id": pump_backbone[0],
|
"device_id": valve_from_node[pump_backbone[0]],
|
||||||
"action_name": "set_valve_position",
|
"action_name": "set_valve_position",
|
||||||
"action_kwargs": {
|
"action_kwargs": {
|
||||||
"command": G.get_edge_data(pump_backbone[0], from_vessel)["port"][pump_backbone[0]]
|
"command": G.get_edge_data(pump_backbone[0], from_vessel)["port"][pump_backbone[0]]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"device_id": pump_backbone[0],
|
"device_id": pumps_from_node[pump_backbone[0]],
|
||||||
"action_name": "set_position",
|
"action_name": "set_position",
|
||||||
"action_kwargs": {
|
"action_kwargs": {
|
||||||
"position": float(min(volume_left, min_transfer_volume)),
|
"position": float(min(volume_left, min_transfer_volume)),
|
||||||
@@ -67,28 +94,28 @@ def generate_pump_protocol(
|
|||||||
}
|
}
|
||||||
])
|
])
|
||||||
pump_action_sequence.append({"action_name": "wait", "action_kwargs": {"time": 5}})
|
pump_action_sequence.append({"action_name": "wait", "action_kwargs": {"time": 5}})
|
||||||
for pumpA, pumpB in zip(pump_backbone[:-1], pump_backbone[1:]):
|
for nodeA, nodeB in zip(pump_backbone[:-1], pump_backbone[1:]):
|
||||||
# 相邻两泵同时切换阀门至连通位置
|
# 相邻两泵同时切换阀门至连通位置
|
||||||
pump_action_sequence.append([
|
pump_action_sequence.append([
|
||||||
{
|
{
|
||||||
"device_id": pumpA,
|
"device_id": valve_from_node[nodeA],
|
||||||
"action_name": "set_valve_position",
|
"action_name": "set_valve_position",
|
||||||
"action_kwargs": {
|
"action_kwargs": {
|
||||||
"command": G.get_edge_data(pumpA, pumpB)["port"][pumpA]
|
"command": G.get_edge_data(nodeA, nodeB)["port"][nodeA]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"device_id": pumpB,
|
"device_id": valve_from_node[nodeB],
|
||||||
"action_name": "set_valve_position",
|
"action_name": "set_valve_position",
|
||||||
"action_kwargs": {
|
"action_kwargs": {
|
||||||
"command": G.get_edge_data(pumpB, pumpA)["port"][pumpB],
|
"command": G.get_edge_data(nodeB, nodeA)["port"][nodeB],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
# 相邻两泵液体转移:泵A排出液体,泵B吸入液体
|
# 相邻两泵液体转移:泵A排出液体,泵B吸入液体
|
||||||
pump_action_sequence.append([
|
pump_action_sequence.append([
|
||||||
{
|
{
|
||||||
"device_id": pumpA,
|
"device_id": pumps_from_node[nodeA],
|
||||||
"action_name": "set_position",
|
"action_name": "set_position",
|
||||||
"action_kwargs": {
|
"action_kwargs": {
|
||||||
"position": 0.0,
|
"position": 0.0,
|
||||||
@@ -96,7 +123,7 @@ def generate_pump_protocol(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"device_id": pumpB,
|
"device_id": pumps_from_node[nodeB],
|
||||||
"action_name": "set_position",
|
"action_name": "set_position",
|
||||||
"action_kwargs": {
|
"action_kwargs": {
|
||||||
"position": float(min(volume_left, min_transfer_volume)),
|
"position": float(min(volume_left, min_transfer_volume)),
|
||||||
@@ -110,14 +137,14 @@ def generate_pump_protocol(
|
|||||||
# 单泵依次执行阀指令、活塞指令,将最后一台泵液体缓慢加入容器B
|
# 单泵依次执行阀指令、活塞指令,将最后一台泵液体缓慢加入容器B
|
||||||
pump_action_sequence.extend([
|
pump_action_sequence.extend([
|
||||||
{
|
{
|
||||||
"device_id": pump_backbone[-1],
|
"device_id": valve_from_node[pump_backbone[-1]],
|
||||||
"action_name": "set_valve_position",
|
"action_name": "set_valve_position",
|
||||||
"action_kwargs": {
|
"action_kwargs": {
|
||||||
"command": G.get_edge_data(pump_backbone[-1], to_vessel)["port"][pump_backbone[-1]]
|
"command": G.get_edge_data(pump_backbone[-1], to_vessel)["port"][pump_backbone[-1]]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"device_id": pump_backbone[-1],
|
"device_id": pumps_from_node[pump_backbone[-1]],
|
||||||
"action_name": "set_position",
|
"action_name": "set_position",
|
||||||
"action_kwargs": {
|
"action_kwargs": {
|
||||||
"position": 0.0,
|
"position": 0.0,
|
||||||
@@ -180,15 +207,19 @@ def generate_pump_protocol_with_rinsing(
|
|||||||
shortest_path = nx.shortest_path(G, source=from_vessel, target=to_vessel)
|
shortest_path = nx.shortest_path(G, source=from_vessel, target=to_vessel)
|
||||||
pump_backbone = shortest_path[1: -1]
|
pump_backbone = shortest_path[1: -1]
|
||||||
nodes = G.nodes(data=True)
|
nodes = G.nodes(data=True)
|
||||||
min_transfer_volume = float(min([nodes[pump]["max_volume"] for pump in pump_backbone]))
|
|
||||||
|
pumps_from_node, valve_from_node = build_pump_valve_maps(G, pump_backbone)
|
||||||
|
|
||||||
|
min_transfer_volume = min([nodes[pumps_from_node[node]]["config"]["max_volume"] for node in pump_backbone])
|
||||||
if time != 0:
|
if time != 0:
|
||||||
flowrate = transfer_flowrate = volume / time
|
flowrate = transfer_flowrate = volume / time
|
||||||
|
|
||||||
pump_action_sequence = generate_pump_protocol(G, from_vessel, to_vessel, float(volume), flowrate, transfer_flowrate)
|
pump_action_sequence = generate_pump_protocol(G, from_vessel, to_vessel, float(volume), flowrate, transfer_flowrate)
|
||||||
if rinsing_solvent != "air":
|
if rinsing_solvent != "air" and rinsing_solvent != "":
|
||||||
if "," in rinsing_solvent:
|
if "," in rinsing_solvent:
|
||||||
rinsing_solvents = rinsing_solvent.split(",")
|
rinsing_solvents = rinsing_solvent.split(",")
|
||||||
assert len(rinsing_solvents) == rinsing_repeats, "Number of rinsing solvents must match number of rinsing repeats."
|
assert len(
|
||||||
|
rinsing_solvents) == rinsing_repeats, "Number of rinsing solvents must match number of rinsing repeats."
|
||||||
else:
|
else:
|
||||||
rinsing_solvents = [rinsing_solvent] * rinsing_repeats
|
rinsing_solvents = [rinsing_solvent] * rinsing_repeats
|
||||||
|
|
||||||
@@ -196,18 +227,28 @@ def generate_pump_protocol_with_rinsing(
|
|||||||
solvent_vessel = f"flask_{rinsing_solvent}"
|
solvent_vessel = f"flask_{rinsing_solvent}"
|
||||||
# 清洗泵
|
# 清洗泵
|
||||||
pump_action_sequence.extend(
|
pump_action_sequence.extend(
|
||||||
generate_pump_protocol(G, solvent_vessel, pump_backbone[0], min_transfer_volume, flowrate, transfer_flowrate) +
|
generate_pump_protocol(G, solvent_vessel, pump_backbone[0], min_transfer_volume, flowrate,
|
||||||
generate_pump_protocol(G, pump_backbone[0], pump_backbone[-1], min_transfer_volume, flowrate, transfer_flowrate) +
|
transfer_flowrate) +
|
||||||
generate_pump_protocol(G, pump_backbone[-1], waste_vessel, min_transfer_volume, flowrate, transfer_flowrate)
|
generate_pump_protocol(G, pump_backbone[0], pump_backbone[-1], min_transfer_volume, flowrate,
|
||||||
|
transfer_flowrate) +
|
||||||
|
generate_pump_protocol(G, pump_backbone[-1], waste_vessel, min_transfer_volume, flowrate,
|
||||||
|
transfer_flowrate)
|
||||||
)
|
)
|
||||||
# 如果转移的是溶液,第一种冲洗溶剂请选用溶液的溶剂,稀释泵内、转移管道内的溶液。后续冲洗溶剂不需要此操作。
|
# 如果转移的是溶液,第一种冲洗溶剂请选用溶液的溶剂,稀释泵内、转移管道内的溶液。后续冲洗溶剂不需要此操作。
|
||||||
if rinsing_solvent == rinsing_solvents[0]:
|
if rinsing_solvent == rinsing_solvents[0]:
|
||||||
pump_action_sequence.extend(generate_pump_protocol(G, solvent_vessel, from_vessel, rinsing_volume, flowrate, transfer_flowrate))
|
pump_action_sequence.extend(
|
||||||
pump_action_sequence.extend(generate_pump_protocol(G, solvent_vessel, to_vessel, rinsing_volume, flowrate, transfer_flowrate))
|
generate_pump_protocol(G, solvent_vessel, from_vessel, rinsing_volume, flowrate, transfer_flowrate))
|
||||||
pump_action_sequence.extend(generate_pump_protocol(G, air_vessel, solvent_vessel, rinsing_volume, flowrate, transfer_flowrate))
|
pump_action_sequence.extend(
|
||||||
pump_action_sequence.extend(generate_pump_protocol(G, air_vessel, waste_vessel, rinsing_volume, flowrate, transfer_flowrate))
|
generate_pump_protocol(G, solvent_vessel, to_vessel, rinsing_volume, flowrate, transfer_flowrate))
|
||||||
pump_action_sequence.extend(generate_pump_protocol(G, air_vessel, from_vessel, rinsing_volume, flowrate, transfer_flowrate) * 2)
|
pump_action_sequence.extend(
|
||||||
pump_action_sequence.extend(generate_pump_protocol(G, air_vessel, to_vessel, rinsing_volume, flowrate, transfer_flowrate) * 2)
|
generate_pump_protocol(G, air_vessel, solvent_vessel, rinsing_volume, flowrate, transfer_flowrate))
|
||||||
|
pump_action_sequence.extend(
|
||||||
|
generate_pump_protocol(G, air_vessel, waste_vessel, rinsing_volume, flowrate, transfer_flowrate))
|
||||||
|
if rinsing_solvent != "":
|
||||||
|
pump_action_sequence.extend(
|
||||||
|
generate_pump_protocol(G, air_vessel, from_vessel, rinsing_volume, flowrate, transfer_flowrate) * 2)
|
||||||
|
pump_action_sequence.extend(
|
||||||
|
generate_pump_protocol(G, air_vessel, to_vessel, rinsing_volume, flowrate, transfer_flowrate) * 2)
|
||||||
|
|
||||||
return pump_action_sequence
|
return pump_action_sequence
|
||||||
# End Protocols
|
# End Protocols
|
||||||
|
|||||||
102
unilabos/compile/run_column_protocol.py
Normal file
102
unilabos/compile/run_column_protocol.py
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
from typing import List, Dict, Any
|
||||||
|
import networkx as nx
|
||||||
|
|
||||||
|
def generate_run_column_protocol(
|
||||||
|
G: nx.DiGraph,
|
||||||
|
from_vessel: str,
|
||||||
|
to_vessel: str,
|
||||||
|
column: str
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
生成柱层析分离的协议序列
|
||||||
|
|
||||||
|
Args:
|
||||||
|
G: 有向图,节点为设备和容器
|
||||||
|
from_vessel: 源容器的名称,即样品起始所在的容器
|
||||||
|
to_vessel: 目标容器的名称,分离后的样品要到达的容器
|
||||||
|
column: 所使用的柱子的名称
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List[Dict[str, Any]]: 柱层析分离操作的动作序列
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ValueError: 当找不到必要的设备时抛出异常
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
run_column_protocol = generate_run_column_protocol(G, "reactor", "collection_flask", "silica_column")
|
||||||
|
"""
|
||||||
|
action_sequence = []
|
||||||
|
|
||||||
|
# 验证容器是否存在
|
||||||
|
if from_vessel not in G.nodes():
|
||||||
|
raise ValueError(f"源容器 {from_vessel} 不存在于图中")
|
||||||
|
|
||||||
|
if to_vessel not in G.nodes():
|
||||||
|
raise ValueError(f"目标容器 {to_vessel} 不存在于图中")
|
||||||
|
|
||||||
|
# 查找转移泵设备(用于样品转移)
|
||||||
|
pump_nodes = [node for node in G.nodes()
|
||||||
|
if G.nodes[node].get('class') == 'virtual_transfer_pump']
|
||||||
|
|
||||||
|
if not pump_nodes:
|
||||||
|
raise ValueError("没有找到可用的转移泵设备")
|
||||||
|
|
||||||
|
pump_id = pump_nodes[0]
|
||||||
|
|
||||||
|
# 查找柱层析设备
|
||||||
|
column_nodes = [node for node in G.nodes()
|
||||||
|
if G.nodes[node].get('class') == 'virtual_column']
|
||||||
|
|
||||||
|
if not column_nodes:
|
||||||
|
raise ValueError("没有找到可用的柱层析设备")
|
||||||
|
|
||||||
|
column_id = column_nodes[0]
|
||||||
|
|
||||||
|
# 步骤1:将样品从源容器转移到柱子上
|
||||||
|
action_sequence.append({
|
||||||
|
"device_id": pump_id,
|
||||||
|
"action_name": "transfer",
|
||||||
|
"action_kwargs": {
|
||||||
|
"from_vessel": from_vessel,
|
||||||
|
"to_vessel": column_id, # 将样品转移到柱子设备
|
||||||
|
"volume": 0.0, # 转移所有液体,体积由系统确定
|
||||||
|
"amount": f"样品上柱 - 使用 {column}",
|
||||||
|
"time": 0.0,
|
||||||
|
"viscous": False,
|
||||||
|
"rinsing_solvent": "",
|
||||||
|
"rinsing_volume": 0.0,
|
||||||
|
"rinsing_repeats": 0,
|
||||||
|
"solid": False
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
# 步骤2:运行柱层析分离
|
||||||
|
action_sequence.append({
|
||||||
|
"device_id": column_id,
|
||||||
|
"action_name": "run_column",
|
||||||
|
"action_kwargs": {
|
||||||
|
"from_vessel": from_vessel,
|
||||||
|
"to_vessel": to_vessel,
|
||||||
|
"column": column
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
# 步骤3:将分离后的产物从柱子转移到目标容器
|
||||||
|
action_sequence.append({
|
||||||
|
"device_id": pump_id,
|
||||||
|
"action_name": "transfer",
|
||||||
|
"action_kwargs": {
|
||||||
|
"from_vessel": column_id, # 从柱子设备转移
|
||||||
|
"to_vessel": to_vessel,
|
||||||
|
"volume": 0.0, # 转移所有液体,体积由系统确定
|
||||||
|
"amount": f"收集分离产物 - 来自 {column}",
|
||||||
|
"time": 0.0,
|
||||||
|
"viscous": False,
|
||||||
|
"rinsing_solvent": "",
|
||||||
|
"rinsing_volume": 0.0,
|
||||||
|
"rinsing_repeats": 0,
|
||||||
|
"solid": False
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return action_sequence
|
||||||
137
unilabos/compile/stir_protocol.py
Normal file
137
unilabos/compile/stir_protocol.py
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
from typing import List, Dict, Any
|
||||||
|
import networkx as nx
|
||||||
|
|
||||||
|
def generate_stir_protocol(
|
||||||
|
G: nx.DiGraph,
|
||||||
|
stir_time: float,
|
||||||
|
stir_speed: float,
|
||||||
|
settling_time: float
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
生成搅拌操作的协议序列
|
||||||
|
|
||||||
|
Args:
|
||||||
|
G: 有向图,节点为设备和容器
|
||||||
|
stir_time: 搅拌时间 (秒)
|
||||||
|
stir_speed: 搅拌速度 (rpm)
|
||||||
|
settling_time: 沉降时间 (秒)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List[Dict[str, Any]]: 搅拌操作的动作序列
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ValueError: 当找不到搅拌设备时抛出异常
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
stir_protocol = generate_stir_protocol(G, 300.0, 500.0, 60.0)
|
||||||
|
"""
|
||||||
|
action_sequence = []
|
||||||
|
|
||||||
|
# 查找搅拌设备
|
||||||
|
stirrer_nodes = [node for node in G.nodes()
|
||||||
|
if G.nodes[node].get('class') == 'virtual_stirrer']
|
||||||
|
|
||||||
|
if not stirrer_nodes:
|
||||||
|
raise ValueError("没有找到可用的搅拌设备")
|
||||||
|
|
||||||
|
# 使用第一个可用的搅拌器
|
||||||
|
stirrer_id = stirrer_nodes[0]
|
||||||
|
|
||||||
|
# 执行搅拌操作
|
||||||
|
action_sequence.append({
|
||||||
|
"device_id": stirrer_id,
|
||||||
|
"action_name": "stir",
|
||||||
|
"action_kwargs": {
|
||||||
|
"stir_time": stir_time,
|
||||||
|
"stir_speed": stir_speed,
|
||||||
|
"settling_time": settling_time
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return action_sequence
|
||||||
|
|
||||||
|
|
||||||
|
def generate_start_stir_protocol(
|
||||||
|
G: nx.DiGraph,
|
||||||
|
vessel: str,
|
||||||
|
stir_speed: float,
|
||||||
|
purpose: str
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
生成开始搅拌操作的协议序列
|
||||||
|
|
||||||
|
Args:
|
||||||
|
G: 有向图,节点为设备和容器
|
||||||
|
vessel: 搅拌容器
|
||||||
|
stir_speed: 搅拌速度 (rpm)
|
||||||
|
purpose: 搅拌目的
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List[Dict[str, Any]]: 开始搅拌操作的动作序列
|
||||||
|
"""
|
||||||
|
action_sequence = []
|
||||||
|
|
||||||
|
# 查找搅拌设备
|
||||||
|
stirrer_nodes = [node for node in G.nodes()
|
||||||
|
if G.nodes[node].get('class') == 'virtual_stirrer']
|
||||||
|
|
||||||
|
if not stirrer_nodes:
|
||||||
|
raise ValueError("没有找到可用的搅拌设备")
|
||||||
|
|
||||||
|
stirrer_id = stirrer_nodes[0]
|
||||||
|
|
||||||
|
# 验证容器是否存在
|
||||||
|
if vessel not in G.nodes():
|
||||||
|
raise ValueError(f"容器 {vessel} 不存在于图中")
|
||||||
|
|
||||||
|
action_sequence.append({
|
||||||
|
"device_id": stirrer_id,
|
||||||
|
"action_name": "start_stir",
|
||||||
|
"action_kwargs": {
|
||||||
|
"vessel": vessel,
|
||||||
|
"stir_speed": stir_speed,
|
||||||
|
"purpose": purpose
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return action_sequence
|
||||||
|
|
||||||
|
|
||||||
|
def generate_stop_stir_protocol(
|
||||||
|
G: nx.DiGraph,
|
||||||
|
vessel: str
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
生成停止搅拌操作的协议序列
|
||||||
|
|
||||||
|
Args:
|
||||||
|
G: 有向图,节点为设备和容器
|
||||||
|
vessel: 搅拌容器
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List[Dict[str, Any]]: 停止搅拌操作的动作序列
|
||||||
|
"""
|
||||||
|
action_sequence = []
|
||||||
|
|
||||||
|
# 查找搅拌设备
|
||||||
|
stirrer_nodes = [node for node in G.nodes()
|
||||||
|
if G.nodes[node].get('class') == 'virtual_stirrer']
|
||||||
|
|
||||||
|
if not stirrer_nodes:
|
||||||
|
raise ValueError("没有找到可用的搅拌设备")
|
||||||
|
|
||||||
|
stirrer_id = stirrer_nodes[0]
|
||||||
|
|
||||||
|
# 验证容器是否存在
|
||||||
|
if vessel not in G.nodes():
|
||||||
|
raise ValueError(f"容器 {vessel} 不存在于图中")
|
||||||
|
|
||||||
|
action_sequence.append({
|
||||||
|
"device_id": stirrer_id,
|
||||||
|
"action_name": "stop_stir",
|
||||||
|
"action_kwargs": {
|
||||||
|
"vessel": vessel
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return action_sequence
|
||||||
79
unilabos/compile/transfer_protocol.py
Normal file
79
unilabos/compile/transfer_protocol.py
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
from typing import List, Dict, Any
|
||||||
|
import networkx as nx
|
||||||
|
|
||||||
|
def generate_transfer_protocol(
|
||||||
|
G: nx.DiGraph,
|
||||||
|
from_vessel: str,
|
||||||
|
to_vessel: str,
|
||||||
|
volume: float,
|
||||||
|
amount: str = "",
|
||||||
|
time: float = 0,
|
||||||
|
viscous: bool = False,
|
||||||
|
rinsing_solvent: str = "",
|
||||||
|
rinsing_volume: float = 0.0,
|
||||||
|
rinsing_repeats: int = 0,
|
||||||
|
solid: bool = False
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
生成液体转移操作的协议序列
|
||||||
|
|
||||||
|
Args:
|
||||||
|
G: 有向图,节点为设备和容器
|
||||||
|
from_vessel: 源容器
|
||||||
|
to_vessel: 目标容器
|
||||||
|
volume: 转移体积 (mL)
|
||||||
|
amount: 数量描述 (可选)
|
||||||
|
time: 转移时间 (秒,可选)
|
||||||
|
viscous: 是否为粘性液体
|
||||||
|
rinsing_solvent: 冲洗溶剂 (可选)
|
||||||
|
rinsing_volume: 冲洗体积 (mL,可选)
|
||||||
|
rinsing_repeats: 冲洗重复次数
|
||||||
|
solid: 是否涉及固体
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List[Dict[str, Any]]: 转移操作的动作序列
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ValueError: 当找不到合适的转移设备时抛出异常
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
transfer_protocol = generate_transfer_protocol(G, "flask_1", "reactor", 10.0)
|
||||||
|
"""
|
||||||
|
action_sequence = []
|
||||||
|
|
||||||
|
# 查找虚拟转移泵设备用于液体转移 - 修复:应该查找 virtual_transfer_pump
|
||||||
|
pump_nodes = [node for node in G.nodes()
|
||||||
|
if G.nodes[node].get('class') == 'virtual_transfer_pump']
|
||||||
|
|
||||||
|
if not pump_nodes:
|
||||||
|
raise ValueError("没有找到可用的转移泵设备进行液体转移")
|
||||||
|
|
||||||
|
# 使用第一个可用的泵
|
||||||
|
pump_id = pump_nodes[0]
|
||||||
|
|
||||||
|
# 验证容器是否存在
|
||||||
|
if from_vessel not in G.nodes():
|
||||||
|
raise ValueError(f"源容器 {from_vessel} 不存在于图中")
|
||||||
|
|
||||||
|
if to_vessel not in G.nodes():
|
||||||
|
raise ValueError(f"目标容器 {to_vessel} 不存在于图中")
|
||||||
|
|
||||||
|
# 执行液体转移操作 - 参数完全匹配Transfer.action
|
||||||
|
action_sequence.append({
|
||||||
|
"device_id": pump_id,
|
||||||
|
"action_name": "transfer",
|
||||||
|
"action_kwargs": {
|
||||||
|
"from_vessel": from_vessel,
|
||||||
|
"to_vessel": to_vessel,
|
||||||
|
"volume": volume,
|
||||||
|
"amount": amount,
|
||||||
|
"time": time,
|
||||||
|
"viscous": viscous,
|
||||||
|
"rinsing_solvent": rinsing_solvent,
|
||||||
|
"rinsing_volume": rinsing_volume,
|
||||||
|
"rinsing_repeats": rinsing_repeats,
|
||||||
|
"solid": solid
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return action_sequence
|
||||||
216
unilabos/compile/wash_solid_protocol.py
Normal file
216
unilabos/compile/wash_solid_protocol.py
Normal file
@@ -0,0 +1,216 @@
|
|||||||
|
from typing import List, Dict, Any
|
||||||
|
import networkx as nx
|
||||||
|
|
||||||
|
def generate_wash_solid_protocol(
|
||||||
|
G: nx.DiGraph,
|
||||||
|
vessel: str,
|
||||||
|
solvent: str,
|
||||||
|
volume: float,
|
||||||
|
filtrate_vessel: str = "",
|
||||||
|
temp: float = 25.0,
|
||||||
|
stir: bool = False,
|
||||||
|
stir_speed: float = 0.0,
|
||||||
|
time: float = 0.0,
|
||||||
|
repeats: int = 1
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
生成固体清洗的协议序列
|
||||||
|
|
||||||
|
Args:
|
||||||
|
G: 有向图,节点为设备和容器
|
||||||
|
vessel: 装有固体物质的容器名称
|
||||||
|
solvent: 用于清洗固体的溶剂名称
|
||||||
|
volume: 清洗溶剂的体积
|
||||||
|
filtrate_vessel: 滤液要收集到的容器名称,可选参数
|
||||||
|
temp: 清洗时的温度,可选参数
|
||||||
|
stir: 是否在清洗过程中搅拌,默认为 False
|
||||||
|
stir_speed: 搅拌速度,可选参数
|
||||||
|
time: 清洗的时间,可选参数
|
||||||
|
repeats: 清洗操作的重复次数,默认为 1
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List[Dict[str, Any]]: 固体清洗操作的动作序列
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ValueError: 当找不到必要的设备时抛出异常
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
wash_solid_protocol = generate_wash_solid_protocol(
|
||||||
|
G, "reactor", "ethanol", 100.0, "waste_flask", 60.0, True, 300.0, 600.0, 3
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
action_sequence = []
|
||||||
|
|
||||||
|
# 验证容器是否存在
|
||||||
|
if vessel not in G.nodes():
|
||||||
|
raise ValueError(f"固体容器 {vessel} 不存在于图中")
|
||||||
|
|
||||||
|
if filtrate_vessel and filtrate_vessel not in G.nodes():
|
||||||
|
raise ValueError(f"滤液容器 {filtrate_vessel} 不存在于图中")
|
||||||
|
|
||||||
|
# 查找转移泵设备(用于添加溶剂和转移滤液)
|
||||||
|
pump_nodes = [node for node in G.nodes()
|
||||||
|
if G.nodes[node].get('class') == 'virtual_transfer_pump']
|
||||||
|
|
||||||
|
if not pump_nodes:
|
||||||
|
raise ValueError("没有找到可用的转移泵设备")
|
||||||
|
|
||||||
|
pump_id = pump_nodes[0]
|
||||||
|
|
||||||
|
# 查找加热设备(如果需要加热)
|
||||||
|
heatchill_nodes = [node for node in G.nodes()
|
||||||
|
if G.nodes[node].get('class') == 'virtual_heatchill']
|
||||||
|
|
||||||
|
heatchill_id = heatchill_nodes[0] if heatchill_nodes else None
|
||||||
|
|
||||||
|
# 查找搅拌设备(如果需要搅拌)
|
||||||
|
stirrer_nodes = [node for node in G.nodes()
|
||||||
|
if G.nodes[node].get('class') == 'virtual_stirrer']
|
||||||
|
|
||||||
|
stirrer_id = stirrer_nodes[0] if stirrer_nodes else None
|
||||||
|
|
||||||
|
# 查找过滤设备(用于分离固体和滤液)
|
||||||
|
filter_nodes = [node for node in G.nodes()
|
||||||
|
if G.nodes[node].get('class') == 'virtual_filter']
|
||||||
|
|
||||||
|
filter_id = filter_nodes[0] if filter_nodes else None
|
||||||
|
|
||||||
|
# 查找溶剂容器
|
||||||
|
solvent_vessel = f"flask_{solvent}"
|
||||||
|
if solvent_vessel not in G.nodes():
|
||||||
|
# 如果没有找到特定溶剂容器,查找可用的源容器
|
||||||
|
available_vessels = [node for node in G.nodes()
|
||||||
|
if node.startswith('flask_') and
|
||||||
|
G.nodes[node].get('type') == 'container']
|
||||||
|
if available_vessels:
|
||||||
|
solvent_vessel = available_vessels[0]
|
||||||
|
else:
|
||||||
|
raise ValueError(f"没有找到溶剂容器 {solvent}")
|
||||||
|
|
||||||
|
# 如果没有指定滤液容器,使用废液容器
|
||||||
|
if not filtrate_vessel:
|
||||||
|
waste_vessels = [node for node in G.nodes()
|
||||||
|
if 'waste' in node.lower() and
|
||||||
|
G.nodes[node].get('type') == 'container']
|
||||||
|
filtrate_vessel = waste_vessels[0] if waste_vessels else "waste_flask"
|
||||||
|
|
||||||
|
# 重复清洗操作
|
||||||
|
for repeat in range(repeats):
|
||||||
|
repeat_num = repeat + 1
|
||||||
|
|
||||||
|
# 步骤1:如果需要加热,先设置温度
|
||||||
|
if temp > 25.0 and heatchill_id:
|
||||||
|
action_sequence.append({
|
||||||
|
"device_id": heatchill_id,
|
||||||
|
"action_name": "heat_chill_start",
|
||||||
|
"action_kwargs": {
|
||||||
|
"vessel": vessel,
|
||||||
|
"temp": temp,
|
||||||
|
"purpose": f"固体清洗 - 第 {repeat_num} 次"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
# 步骤2:添加清洗溶剂到固体容器
|
||||||
|
action_sequence.append({
|
||||||
|
"device_id": pump_id,
|
||||||
|
"action_name": "transfer",
|
||||||
|
"action_kwargs": {
|
||||||
|
"from_vessel": solvent_vessel,
|
||||||
|
"to_vessel": vessel,
|
||||||
|
"volume": volume,
|
||||||
|
"amount": f"清洗溶剂 {solvent} - 第 {repeat_num} 次",
|
||||||
|
"time": 0.0,
|
||||||
|
"viscous": False,
|
||||||
|
"rinsing_solvent": "",
|
||||||
|
"rinsing_volume": 0.0,
|
||||||
|
"rinsing_repeats": 0,
|
||||||
|
"solid": False
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
# 步骤3:如果需要搅拌,开始搅拌
|
||||||
|
if stir and stir_speed > 0 and stirrer_id:
|
||||||
|
if time > 0:
|
||||||
|
# 定时搅拌
|
||||||
|
action_sequence.append({
|
||||||
|
"device_id": stirrer_id,
|
||||||
|
"action_name": "stir",
|
||||||
|
"action_kwargs": {
|
||||||
|
"stir_time": time,
|
||||||
|
"stir_speed": stir_speed,
|
||||||
|
"settling_time": 30.0 # 搅拌后静置30秒
|
||||||
|
}
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
# 开始搅拌(需要手动停止)
|
||||||
|
action_sequence.append({
|
||||||
|
"device_id": stirrer_id,
|
||||||
|
"action_name": "start_stir",
|
||||||
|
"action_kwargs": {
|
||||||
|
"vessel": vessel,
|
||||||
|
"stir_speed": stir_speed,
|
||||||
|
"purpose": f"固体清洗搅拌 - 第 {repeat_num} 次"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
# 步骤4:如果指定了清洗时间但没有搅拌,等待清洗时间
|
||||||
|
if time > 0 and (not stir or stir_speed == 0):
|
||||||
|
# 这里可以添加等待操作,暂时跳过
|
||||||
|
pass
|
||||||
|
|
||||||
|
# 步骤5:如果有搅拌且没有定时,停止搅拌
|
||||||
|
if stir and stir_speed > 0 and time == 0 and stirrer_id:
|
||||||
|
action_sequence.append({
|
||||||
|
"device_id": stirrer_id,
|
||||||
|
"action_name": "stop_stir",
|
||||||
|
"action_kwargs": {
|
||||||
|
"vessel": vessel
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
# 步骤6:过滤分离固体和滤液
|
||||||
|
if filter_id:
|
||||||
|
action_sequence.append({
|
||||||
|
"device_id": filter_id,
|
||||||
|
"action_name": "filter_sample",
|
||||||
|
"action_kwargs": {
|
||||||
|
"vessel": vessel,
|
||||||
|
"filtrate_vessel": filtrate_vessel,
|
||||||
|
"stir": False,
|
||||||
|
"stir_speed": 0.0,
|
||||||
|
"temp": temp,
|
||||||
|
"continue_heatchill": temp > 25.0,
|
||||||
|
"volume": volume
|
||||||
|
}
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
# 没有专门的过滤设备,使用转移泵模拟过滤过程
|
||||||
|
# 将滤液转移到滤液容器
|
||||||
|
action_sequence.append({
|
||||||
|
"device_id": pump_id,
|
||||||
|
"action_name": "transfer",
|
||||||
|
"action_kwargs": {
|
||||||
|
"from_vessel": vessel,
|
||||||
|
"to_vessel": filtrate_vessel,
|
||||||
|
"volume": volume,
|
||||||
|
"amount": f"转移滤液 - 第 {repeat_num} 次清洗",
|
||||||
|
"time": 0.0,
|
||||||
|
"viscous": False,
|
||||||
|
"rinsing_solvent": "",
|
||||||
|
"rinsing_volume": 0.0,
|
||||||
|
"rinsing_repeats": 0,
|
||||||
|
"solid": False
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
# 步骤7:如果加热了,停止加热(在最后一次清洗后)
|
||||||
|
if temp > 25.0 and heatchill_id and repeat_num == repeats:
|
||||||
|
action_sequence.append({
|
||||||
|
"device_id": heatchill_id,
|
||||||
|
"action_name": "heat_chill_stop",
|
||||||
|
"action_kwargs": {
|
||||||
|
"vessel": vessel
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return action_sequence
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
# Default initial positions for full_dev's ros2_control fake system
|
||||||
|
|
||||||
|
initial_positions:
|
||||||
|
arm_base_joint: 0
|
||||||
|
arm_link_1_joint: 0
|
||||||
|
arm_link_2_joint: 0
|
||||||
|
arm_link_3_joint: 0
|
||||||
|
gripper_base_joint: 0
|
||||||
|
gripper_right_joint: 0.03
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
# joint_limits.yaml allows the dynamics properties specified in the URDF to be overwritten or augmented as needed
|
||||||
|
|
||||||
|
# For beginners, we downscale velocity and acceleration limits.
|
||||||
|
# You can always specify higher scaling factors (<= 1.0) in your motion requests. # Increase the values below to 1.0 to always move at maximum speed.
|
||||||
|
default_velocity_scaling_factor: 0.1
|
||||||
|
default_acceleration_scaling_factor: 0.1
|
||||||
|
|
||||||
|
# Specific joint properties can be changed with the keys [max_position, min_position, max_velocity, max_acceleration]
|
||||||
|
# Joint limits can be turned off with [has_velocity_limits, has_acceleration_limits]
|
||||||
|
joint_limits:
|
||||||
|
arm_base_joint:
|
||||||
|
has_velocity_limits: true
|
||||||
|
max_velocity: 0
|
||||||
|
has_acceleration_limits: false
|
||||||
|
max_acceleration: 0
|
||||||
|
arm_link_1_joint:
|
||||||
|
has_velocity_limits: true
|
||||||
|
max_velocity: 0
|
||||||
|
has_acceleration_limits: false
|
||||||
|
max_acceleration: 0
|
||||||
|
arm_link_2_joint:
|
||||||
|
has_velocity_limits: true
|
||||||
|
max_velocity: 0
|
||||||
|
has_acceleration_limits: false
|
||||||
|
max_acceleration: 0
|
||||||
|
arm_link_3_joint:
|
||||||
|
has_velocity_limits: true
|
||||||
|
max_velocity: 0
|
||||||
|
has_acceleration_limits: false
|
||||||
|
max_acceleration: 0
|
||||||
|
gripper_base_joint:
|
||||||
|
has_velocity_limits: true
|
||||||
|
max_velocity: 0
|
||||||
|
has_acceleration_limits: false
|
||||||
|
max_acceleration: 0
|
||||||
|
gripper_right_joint:
|
||||||
|
has_velocity_limits: true
|
||||||
|
max_velocity: 0
|
||||||
|
has_acceleration_limits: false
|
||||||
|
max_acceleration: 0
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
arm:
|
||||||
|
kinematics_solver: lma_kinematics_plugin/LMAKinematicsPlugin
|
||||||
|
kinematics_solver_search_resolution: 0.0050000000000000001
|
||||||
|
kinematics_solver_timeout: 0.0050000000000000001
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<robot xmlns:xacro="http://www.ros.org/wiki/xacro">
|
||||||
|
<xacro:macro name="arm_slider_ros2_control" params="device_name mesh_path">
|
||||||
|
<xacro:property name="initial_positions" value="${load_yaml(mesh_path + '/devices/arm_slider/config/initial_positions.yaml')['initial_positions']}"/>
|
||||||
|
|
||||||
|
<ros2_control name="${device_name}arm_slider" type="system">
|
||||||
|
<hardware>
|
||||||
|
<!-- By default, set up controllers for simulation. This won't work on real hardware -->
|
||||||
|
<plugin>mock_components/GenericSystem</plugin>
|
||||||
|
</hardware>
|
||||||
|
<joint name="${device_name}arm_base_joint">
|
||||||
|
<command_interface name="position"/>
|
||||||
|
<state_interface name="position">
|
||||||
|
<param name="initial_value">${initial_positions['arm_base_joint']}</param>
|
||||||
|
</state_interface>
|
||||||
|
<state_interface name="velocity"/>
|
||||||
|
</joint>
|
||||||
|
<joint name="${device_name}arm_link_1_joint">
|
||||||
|
<command_interface name="position"/>
|
||||||
|
<state_interface name="position">
|
||||||
|
<param name="initial_value">${initial_positions['arm_link_1_joint']}</param>
|
||||||
|
</state_interface>
|
||||||
|
<state_interface name="velocity"/>
|
||||||
|
</joint>
|
||||||
|
<joint name="${device_name}arm_link_2_joint">
|
||||||
|
<command_interface name="position"/>
|
||||||
|
<state_interface name="position">
|
||||||
|
<param name="initial_value">${initial_positions['arm_link_2_joint']}</param>
|
||||||
|
</state_interface>
|
||||||
|
<state_interface name="velocity"/>
|
||||||
|
</joint>
|
||||||
|
<joint name="${device_name}arm_link_3_joint">
|
||||||
|
<command_interface name="position"/>
|
||||||
|
<state_interface name="position">
|
||||||
|
<param name="initial_value">${initial_positions['arm_link_3_joint']}</param>
|
||||||
|
</state_interface>
|
||||||
|
<state_interface name="velocity"/>
|
||||||
|
</joint>
|
||||||
|
<joint name="${device_name}gripper_base_joint">
|
||||||
|
<command_interface name="position"/>
|
||||||
|
<state_interface name="position">
|
||||||
|
<param name="initial_value">${initial_positions['gripper_base_joint']}</param>
|
||||||
|
</state_interface>
|
||||||
|
<state_interface name="velocity"/>
|
||||||
|
</joint>
|
||||||
|
<joint name="${device_name}gripper_right_joint">
|
||||||
|
<command_interface name="position"/>
|
||||||
|
<state_interface name="position">
|
||||||
|
<param name="initial_value">${initial_positions['gripper_right_joint']}</param>
|
||||||
|
</state_interface>
|
||||||
|
<state_interface name="velocity"/>
|
||||||
|
</joint>
|
||||||
|
|
||||||
|
</ros2_control>
|
||||||
|
</xacro:macro>
|
||||||
|
</robot>
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!--This does not replace URDF, and is not an extension of URDF.
|
||||||
|
This is a format for representing semantic information about the robot structure.
|
||||||
|
A URDF file must exist for this robot as well, where the joints and the links that are referenced are defined
|
||||||
|
-->
|
||||||
|
<robot xmlns:xacro="http://ros.org/wiki/xacro">
|
||||||
|
<xacro:macro name="arm_slider_srdf" params="device_name">
|
||||||
|
<!--GROUPS: Representation of a set of joints and links. This can be useful for specifying DOF to plan for, defining arms, end effectors, etc-->
|
||||||
|
<!--LINKS: When a link is specified, the parent joint of that link (if it exists) is automatically included-->
|
||||||
|
<!--JOINTS: When a joint is specified, the child link of that joint (which will always exist) is automatically included-->
|
||||||
|
<!--CHAINS: When a chain is specified, all the links along the chain (including endpoints) are included in the group. Additionally, all the joints that are parents to included links are also included. This means that joints along the chain and the parent joint of the base link are included in the group-->
|
||||||
|
<!--SUBGROUPS: Groups can also be formed by referencing to already defined group names-->
|
||||||
|
<group name="${device_name}arm">
|
||||||
|
<chain base_link="${device_name}arm_slideway" tip_link="${device_name}gripper_base"/>
|
||||||
|
</group>
|
||||||
|
<group name="${device_name}arm_gripper">
|
||||||
|
<joint name="${device_name}gripper_right_joint"/>
|
||||||
|
</group>
|
||||||
|
<!--DISABLE COLLISIONS: By default it is assumed that any link of the robot could potentially come into collision with any other link in the robot. This tag disables collision checking between a specified pair of links. -->
|
||||||
|
<disable_collisions link1="${device_name}arm_base" link2="${device_name}arm_link_2" reason="Adjacent"/>
|
||||||
|
<disable_collisions link1="${device_name}arm_base" link2="${device_name}arm_link_1" reason="Adjacent"/>
|
||||||
|
<disable_collisions link1="${device_name}arm_base" link2="${device_name}arm_link_3" reason="Never"/>
|
||||||
|
<disable_collisions link1="${device_name}arm_base" link2="${device_name}arm_slideway" reason="Adjacent"/>
|
||||||
|
<disable_collisions link1="${device_name}arm_link_1" link2="${device_name}arm_link_2" reason="Adjacent"/>
|
||||||
|
<disable_collisions link1="${device_name}arm_link_1" link2="${device_name}arm_link_3" reason="Never"/>
|
||||||
|
<disable_collisions link1="${device_name}arm_link_1" link2="${device_name}arm_slideway" reason="Never"/>
|
||||||
|
<disable_collisions link1="${device_name}arm_link_1" link2="${device_name}gripper_base" reason="Never"/>
|
||||||
|
<disable_collisions link1="${device_name}arm_link_1" link2="${device_name}gripper_left" reason="Never"/>
|
||||||
|
<disable_collisions link1="${device_name}arm_link_1" link2="${device_name}gripper_right" reason="Never"/>
|
||||||
|
<disable_collisions link1="${device_name}arm_link_2" link2="${device_name}arm_link_3" reason="Adjacent"/>
|
||||||
|
<disable_collisions link1="${device_name}arm_link_2" link2="${device_name}arm_slideway" reason="Never"/>
|
||||||
|
<disable_collisions link1="${device_name}arm_link_2" link2="${device_name}gripper_base" reason="Never"/>
|
||||||
|
<disable_collisions link1="${device_name}arm_link_2" link2="${device_name}gripper_left" reason="Never"/>
|
||||||
|
<disable_collisions link1="${device_name}arm_link_2" link2="${device_name}gripper_right" reason="Never"/>
|
||||||
|
<disable_collisions link1="${device_name}arm_link_3" link2="${device_name}arm_slideway" reason="Never"/>
|
||||||
|
<disable_collisions link1="${device_name}arm_link_3" link2="${device_name}gripper_base" reason="Adjacent"/>
|
||||||
|
<disable_collisions link1="${device_name}arm_link_3" link2="${device_name}gripper_left" reason="Never"/>
|
||||||
|
<disable_collisions link1="${device_name}arm_link_3" link2="${device_name}gripper_right" reason="Never"/>
|
||||||
|
<disable_collisions link1="${device_name}arm_slideway" link2="${device_name}gripper_base" reason="Never"/>
|
||||||
|
<disable_collisions link1="${device_name}arm_slideway" link2="${device_name}gripper_left" reason="Never"/>
|
||||||
|
<disable_collisions link1="${device_name}arm_slideway" link2="${device_name}gripper_right" reason="Never"/>
|
||||||
|
<disable_collisions link1="${device_name}gripper_base" link2="${device_name}gripper_left" reason="Adjacent"/>
|
||||||
|
<disable_collisions link1="${device_name}gripper_base" link2="${device_name}gripper_right" reason="Adjacent"/>
|
||||||
|
<disable_collisions link1="${device_name}gripper_left" link2="${device_name}gripper_right" reason="Never"/>
|
||||||
|
</xacro:macro>
|
||||||
|
</robot>
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"arm":
|
||||||
|
{
|
||||||
|
"joint_names": [
|
||||||
|
"arm_base_joint",
|
||||||
|
"arm_link_1_joint",
|
||||||
|
"arm_link_2_joint",
|
||||||
|
"arm_link_3_joint",
|
||||||
|
"gripper_base_joint"
|
||||||
|
],
|
||||||
|
"base_link_name": "device_link",
|
||||||
|
"end_effector_name": "gripper_base"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
# MoveIt uses this configuration for controller management
|
||||||
|
|
||||||
|
moveit_controller_manager: moveit_simple_controller_manager/MoveItSimpleControllerManager
|
||||||
|
|
||||||
|
moveit_simple_controller_manager:
|
||||||
|
controller_names:
|
||||||
|
- arm_controller
|
||||||
|
- gripper_controller
|
||||||
|
|
||||||
|
arm_controller:
|
||||||
|
type: FollowJointTrajectory
|
||||||
|
action_ns: follow_joint_trajectory
|
||||||
|
default: true
|
||||||
|
joints:
|
||||||
|
- arm_base_joint
|
||||||
|
- arm_link_1_joint
|
||||||
|
- arm_link_2_joint
|
||||||
|
- arm_link_3_joint
|
||||||
|
- gripper_base_joint
|
||||||
|
action_ns: follow_joint_trajectory
|
||||||
|
default: true
|
||||||
|
gripper_controller:
|
||||||
|
type: FollowJointTrajectory
|
||||||
|
action_ns: follow_joint_trajectory
|
||||||
|
default: true
|
||||||
|
joints:
|
||||||
|
- gripper_right_joint
|
||||||
|
action_ns: follow_joint_trajectory
|
||||||
|
default: true
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
planner_configs:
|
||||||
|
- ompl_interface/OMPLPlanner
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
# Limits for the Pilz planner
|
||||||
|
cartesian_limits:
|
||||||
|
max_trans_vel: 1.0
|
||||||
|
max_trans_acc: 2.25
|
||||||
|
max_trans_dec: -5.0
|
||||||
|
max_rot_vel: 1.57
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
# This config file is used by ros2_control
|
||||||
|
controller_manager:
|
||||||
|
ros__parameters:
|
||||||
|
update_rate: 100 # Hz
|
||||||
|
|
||||||
|
arm_controller:
|
||||||
|
type: joint_trajectory_controller/JointTrajectoryController
|
||||||
|
|
||||||
|
|
||||||
|
gripper_controller:
|
||||||
|
type: joint_trajectory_controller/JointTrajectoryController
|
||||||
|
|
||||||
|
|
||||||
|
joint_state_broadcaster:
|
||||||
|
type: joint_state_broadcaster/JointStateBroadcaster
|
||||||
|
|
||||||
|
arm_controller:
|
||||||
|
ros__parameters:
|
||||||
|
joints:
|
||||||
|
- arm_base_joint
|
||||||
|
- arm_link_1_joint
|
||||||
|
- arm_link_2_joint
|
||||||
|
- arm_link_3_joint
|
||||||
|
- gripper_base_joint
|
||||||
|
command_interfaces:
|
||||||
|
- position
|
||||||
|
state_interfaces:
|
||||||
|
- position
|
||||||
|
- velocity
|
||||||
|
|
||||||
|
gripper_controller:
|
||||||
|
ros__parameters:
|
||||||
|
joints:
|
||||||
|
- gripper_right_joint
|
||||||
|
command_interfaces:
|
||||||
|
- position
|
||||||
|
state_interfaces:
|
||||||
|
- position
|
||||||
|
- velocity
|
||||||
44
unilabos/device_mesh/devices/arm_slider/joint_limit.yaml
Normal file
44
unilabos/device_mesh/devices/arm_slider/joint_limit.yaml
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
joint_limits:
|
||||||
|
|
||||||
|
arm_base_joint:
|
||||||
|
effort: 50
|
||||||
|
velocity: 1.0
|
||||||
|
lower: 0
|
||||||
|
upper: 1.5
|
||||||
|
|
||||||
|
arm_link_1_joint:
|
||||||
|
effort: 50
|
||||||
|
velocity: 1.0
|
||||||
|
lower: 0
|
||||||
|
upper: 0.6
|
||||||
|
|
||||||
|
arm_link_2_joint:
|
||||||
|
effort: 50
|
||||||
|
velocity: 1.0
|
||||||
|
lower: !degrees -95
|
||||||
|
upper: !degrees 95
|
||||||
|
|
||||||
|
arm_link_3_joint:
|
||||||
|
effort: 50
|
||||||
|
velocity: 1.0
|
||||||
|
lower: !degrees -195
|
||||||
|
upper: !degrees 195
|
||||||
|
|
||||||
|
gripper_base_joint:
|
||||||
|
effort: 50
|
||||||
|
velocity: 1.0
|
||||||
|
lower: !degrees -95
|
||||||
|
upper: !degrees 95
|
||||||
|
|
||||||
|
|
||||||
|
gripper_right_joint:
|
||||||
|
effort: 50
|
||||||
|
velocity: 1.0
|
||||||
|
lower: 0
|
||||||
|
upper: 0.03
|
||||||
|
|
||||||
|
gripper_left_joint:
|
||||||
|
effort: 50
|
||||||
|
velocity: 1.0
|
||||||
|
lower: 0
|
||||||
|
upper: 0.03
|
||||||
293
unilabos/device_mesh/devices/arm_slider/macro_device.xacro
Normal file
293
unilabos/device_mesh/devices/arm_slider/macro_device.xacro
Normal file
@@ -0,0 +1,293 @@
|
|||||||
|
<?xml version="1.0" ?>
|
||||||
|
<robot xmlns:xacro="http://ros.org/wiki/xacro" name="arm_slider">
|
||||||
|
|
||||||
|
<xacro:macro name="arm_slider" params="mesh_path:='' parent_link:='' station_name:='' device_name:='' x:=0 y:=0 z:=0 rx:=0 ry:=0 r:=0">
|
||||||
|
<!-- Read .yaml files from disk, load content into properties -->
|
||||||
|
<xacro:property name= "joint_limit_parameters" value="${xacro.load_yaml(mesh_path + '/devices/arm_slider/joint_limit.yaml')}"/>
|
||||||
|
|
||||||
|
<!-- Extract subsections from yaml dictionaries -->
|
||||||
|
<xacro:property name= "sec_limits" value="${joint_limit_parameters['joint_limits']}"/>
|
||||||
|
|
||||||
|
<joint name="${station_name}${device_name}base_link_joint" type="fixed">
|
||||||
|
<origin xyz="${x} ${y} ${z}" rpy="${rx} ${ry} ${r}" />
|
||||||
|
<parent link="${parent_link}"/>
|
||||||
|
<child link="${station_name}${device_name}device_link"/>
|
||||||
|
<axis xyz="0 0 0"/>
|
||||||
|
</joint>
|
||||||
|
|
||||||
|
<link name="${station_name}${device_name}device_link"/>
|
||||||
|
<joint name="${station_name}${device_name}device_link_joint" type="fixed">
|
||||||
|
<origin xyz="0 0 0" rpy="0 0 0" />
|
||||||
|
<parent link="${station_name}${device_name}device_link"/>
|
||||||
|
<child link="${station_name}${device_name}arm_slideway"/>
|
||||||
|
<axis xyz="0 0 0"/>
|
||||||
|
</joint>
|
||||||
|
|
||||||
|
<!-- JOINTS LIMIT PARAMETERS -->
|
||||||
|
<xacro:property name="limit_arm_base_joint" value="${sec_limits['arm_base_joint']}" />
|
||||||
|
<xacro:property name="limit_arm_link_1_joint" value="${sec_limits['arm_link_1_joint']}" />
|
||||||
|
<xacro:property name="limit_arm_link_2_joint" value="${sec_limits['arm_link_2_joint']}" />
|
||||||
|
<xacro:property name="limit_arm_link_3_joint" value="${sec_limits['arm_link_3_joint']}" />
|
||||||
|
<xacro:property name="limit_gripper_base_joint" value="${sec_limits['gripper_base_joint']}" />
|
||||||
|
<xacro:property name="limit_gripper_right_joint" value="${sec_limits['gripper_right_joint']}"/>
|
||||||
|
<xacro:property name="limit_gripper_left_joint" value="${sec_limits['gripper_left_joint']}" />
|
||||||
|
<link name="${station_name}${device_name}arm_slideway">
|
||||||
|
<inertial>
|
||||||
|
<origin rpy="0 0 0" xyz="-0.913122246354019 -0.00141851388483838 0.0416079172839272"/>
|
||||||
|
<mass value="13.6578107753627"/>
|
||||||
|
<inertia ixx="0.0507627640890578" ixy="0.0245166532634714" ixz="-0.0112656803168519" iyy="5.2550852314372" iyz="0.000302974193920367" izz="5.26892263696439"/>
|
||||||
|
</inertial>
|
||||||
|
<visual>
|
||||||
|
<origin rpy="0 0 0" xyz="0 0 0"/>
|
||||||
|
<geometry>
|
||||||
|
<mesh filename="file://${mesh_path}/devices/arm_slider/meshes/arm_slideway.STL"/>
|
||||||
|
</geometry>
|
||||||
|
<material name="">
|
||||||
|
<color rgba="0.752941176470588 0.752941176470588 0.752941176470588 1"/>
|
||||||
|
</material>
|
||||||
|
</visual>
|
||||||
|
<collision>
|
||||||
|
<origin rpy="0 0 0" xyz="0 0 0"/>
|
||||||
|
<geometry>
|
||||||
|
<mesh filename="file://${mesh_path}/devices/arm_slider/meshes/arm_slideway.STL"/>
|
||||||
|
</geometry>
|
||||||
|
</collision>
|
||||||
|
</link>
|
||||||
|
|
||||||
|
<joint name="${station_name}${device_name}arm_base_joint" type="prismatic">
|
||||||
|
<origin rpy="0 0 0" xyz="0.307 0 0.1225"/>
|
||||||
|
<parent link="${station_name}${device_name}arm_slideway"/>
|
||||||
|
<child link="${station_name}${device_name}arm_base"/>
|
||||||
|
<axis xyz="1 0 0"/>
|
||||||
|
<limit
|
||||||
|
effort="${limit_arm_base_joint['effort']}"
|
||||||
|
lower="${limit_arm_base_joint['lower']}"
|
||||||
|
upper="${limit_arm_base_joint['upper']}"
|
||||||
|
velocity="${limit_arm_base_joint['velocity']}"/>
|
||||||
|
</joint>
|
||||||
|
|
||||||
|
<link name="${station_name}${device_name}arm_base">
|
||||||
|
<inertial>
|
||||||
|
<origin rpy="0 0 0" xyz="1.48458338655733E-06 -0.00831873687136486 0.351728466012153"/>
|
||||||
|
<mass value="16.1341586205194"/>
|
||||||
|
<inertia ixx="0.54871651759045" ixy="7.65476367433116E-07" ixz="2.0515139488158E-07" iyy="0.55113098995396" iyz="-5.13261457726806E-07" izz="0.0619081867727048"/>
|
||||||
|
</inertial>
|
||||||
|
<visual>
|
||||||
|
<origin rpy="0 0 0" xyz="0 0 0"/>
|
||||||
|
<geometry>
|
||||||
|
<mesh filename="file://${mesh_path}/devices/arm_slider/meshes/arm_base.STL"/>
|
||||||
|
</geometry>
|
||||||
|
<material name="">
|
||||||
|
<color rgba="1 1 1 1"/>
|
||||||
|
</material>
|
||||||
|
</visual>
|
||||||
|
<collision>
|
||||||
|
<origin rpy="0 0 0" xyz="0 0 0"/>
|
||||||
|
<geometry>
|
||||||
|
<mesh filename="file://${mesh_path}/devices/arm_slider/meshes/arm_base.STL"/>
|
||||||
|
</geometry>
|
||||||
|
</collision>
|
||||||
|
</link>
|
||||||
|
|
||||||
|
<link name="${station_name}${device_name}arm_link_1">
|
||||||
|
<inertial>
|
||||||
|
<origin rpy="0 0 0" xyz="0 -0.0102223856758559 0.0348505130779933"/>
|
||||||
|
<mass value="0.828629227096429"/>
|
||||||
|
<inertia ixx="0.00119703598787112" ixy="-2.46083048832131E-19" ixz="1.43864352731199E-19" iyy="0.00108355785790042" iyz="1.88092240278693E-06" izz="0.00160914803816438"/>
|
||||||
|
</inertial>
|
||||||
|
<visual>
|
||||||
|
<origin rpy="0 0 0" xyz="0 0 0"/>
|
||||||
|
<geometry>
|
||||||
|
<mesh filename="file://${mesh_path}/devices/arm_slider/meshes/arm_link_1.STL"/>
|
||||||
|
</geometry>
|
||||||
|
<material name="">
|
||||||
|
<color rgba="1 1 1 1"/>
|
||||||
|
</material>
|
||||||
|
</visual>
|
||||||
|
<collision>
|
||||||
|
<origin rpy="0 0 0" xyz="0 0 0"/>
|
||||||
|
<geometry>
|
||||||
|
<mesh filename="file://${mesh_path}/devices/arm_slider/meshes/arm_link_1.STL"/>
|
||||||
|
</geometry>
|
||||||
|
</collision>
|
||||||
|
</link>
|
||||||
|
<joint name="${station_name}${device_name}arm_link_1_joint" type="prismatic">
|
||||||
|
<origin rpy="0 0 0" xyz="0 0.1249 0.15"/>
|
||||||
|
<parent link="${station_name}${device_name}arm_base"/>
|
||||||
|
<child link="${station_name}${device_name}arm_link_1"/>
|
||||||
|
<axis xyz="0 0 1"/>
|
||||||
|
<limit
|
||||||
|
effort="${limit_arm_link_1_joint['effort']}"
|
||||||
|
lower="${limit_arm_link_1_joint['lower']}"
|
||||||
|
upper="${limit_arm_link_1_joint['upper']}"
|
||||||
|
velocity="${limit_arm_link_1_joint['velocity']}"/>
|
||||||
|
</joint>
|
||||||
|
<link name="${station_name}${device_name}arm_link_2">
|
||||||
|
<inertial>
|
||||||
|
<origin rpy="0 0 0" xyz="-3.33066907387547E-16 0.100000000000003 -0.0325000000000004"/>
|
||||||
|
<mass value="2.04764861029349"/>
|
||||||
|
<inertia ixx="0.0150150059448827" ixy="-1.28113733272213E-17" ixz="6.7561418872754E-19" iyy="0.00262980501315445" iyz="7.44451536320152E-18" izz="0.0162030186138787"/>
|
||||||
|
</inertial>
|
||||||
|
<visual>
|
||||||
|
<origin rpy="0 0 0" xyz="0 0 0"/>
|
||||||
|
<geometry>
|
||||||
|
<mesh filename="file://${mesh_path}/devices/arm_slider/meshes/arm_link_2.STL"/>
|
||||||
|
</geometry>
|
||||||
|
<material name="">
|
||||||
|
<color rgba="1 1 1 1"/>
|
||||||
|
</material>
|
||||||
|
</visual>
|
||||||
|
<collision>
|
||||||
|
<origin rpy="0 0 0" xyz="0 0 0"/>
|
||||||
|
<geometry>
|
||||||
|
<mesh filename="file://${mesh_path}/devices/arm_slider/meshes/arm_link_2.STL"/>
|
||||||
|
</geometry>
|
||||||
|
</collision>
|
||||||
|
</link>
|
||||||
|
<joint name="${station_name}${device_name}arm_link_2_joint" type="revolute">
|
||||||
|
<origin rpy="0 0 0" xyz="0 0 0"/>
|
||||||
|
<parent link="${station_name}${device_name}arm_link_1"/>
|
||||||
|
<child link="${station_name}${device_name}arm_link_2"/>
|
||||||
|
<axis xyz="0 0 1"/>
|
||||||
|
<limit
|
||||||
|
effort="${limit_arm_link_2_joint['effort']}"
|
||||||
|
lower="${limit_arm_link_2_joint['lower']}"
|
||||||
|
upper="${limit_arm_link_2_joint['upper']}"
|
||||||
|
velocity="${limit_arm_link_2_joint['velocity']}"/>
|
||||||
|
</joint>
|
||||||
|
<link name="${station_name}${device_name}arm_link_3">
|
||||||
|
<inertial>
|
||||||
|
<origin rpy="0 0 0" xyz="4.77395900588817E-15 0.0861257730831348 -0.0227999999999999"/>
|
||||||
|
<mass value="1.19870202871083"/>
|
||||||
|
<inertia ixx="0.00780783223764428" ixy="7.26567379579506E-18" ixz="1.02766851352053E-18" iyy="0.00109642607170081" iyz="-9.73775385060067E-18" izz="0.0084997384510058"/>
|
||||||
|
</inertial>
|
||||||
|
<visual>
|
||||||
|
<origin rpy="0 0 0" xyz="0 0 0"/>
|
||||||
|
<geometry>
|
||||||
|
<mesh filename="file://${mesh_path}/devices/arm_slider/meshes/arm_link_3.STL"/>
|
||||||
|
</geometry>
|
||||||
|
<material name="">
|
||||||
|
<color rgba="1 1 1 1"/>
|
||||||
|
</material>
|
||||||
|
</visual>
|
||||||
|
<collision>
|
||||||
|
<origin rpy="0 0 0" xyz="0 0 0"/>
|
||||||
|
<geometry>
|
||||||
|
<mesh filename="file://${mesh_path}/devices/arm_slider/meshes/arm_link_3.STL"/>
|
||||||
|
</geometry>
|
||||||
|
</collision>
|
||||||
|
</link>
|
||||||
|
<joint name="${station_name}${device_name}arm_link_3_joint" type="revolute">
|
||||||
|
<origin rpy="0 0 0" xyz="0 0.2 -0.0647"/>
|
||||||
|
<parent link="${station_name}${device_name}arm_link_2"/>
|
||||||
|
<child link="${station_name}${device_name}arm_link_3"/>
|
||||||
|
<axis xyz="0 0 1"/>
|
||||||
|
<limit
|
||||||
|
effort="${limit_arm_link_3_joint['effort']}"
|
||||||
|
lower="${limit_arm_link_3_joint['lower']}"
|
||||||
|
upper="${limit_arm_link_3_joint['upper']}"
|
||||||
|
velocity="${limit_arm_link_3_joint['velocity']}"/>
|
||||||
|
</joint>
|
||||||
|
<link name="${station_name}${device_name}gripper_base">
|
||||||
|
<inertial>
|
||||||
|
<origin rpy="0 0 0" xyz="-6.05365748571618E-05 0.0373027483464434 -0.0264392017534612"/>
|
||||||
|
<mass value="0.511925198394943"/>
|
||||||
|
<inertia ixx="0.000640463815051467" ixy="1.08132229596356E-06" ixz="7.165124649009E-07" iyy="0.000552164156414554" iyz="9.80000237347941E-06" izz="0.00103553457812823"/>
|
||||||
|
</inertial>
|
||||||
|
<visual>
|
||||||
|
<origin rpy="0 0 0" xyz="0 0 0"/>
|
||||||
|
<geometry>
|
||||||
|
<mesh filename="file://${mesh_path}/devices/arm_slider/meshes/gripper_base.STL"/>
|
||||||
|
</geometry>
|
||||||
|
<material name="">
|
||||||
|
<color rgba="1 1 1 1"/>
|
||||||
|
</material>
|
||||||
|
</visual>
|
||||||
|
<collision>
|
||||||
|
<origin rpy="0 0 0" xyz="0 0 0"/>
|
||||||
|
<geometry>
|
||||||
|
<mesh filename="file://${mesh_path}/devices/arm_slider/meshes/gripper_base.STL"/>
|
||||||
|
</geometry>
|
||||||
|
</collision>
|
||||||
|
</link>
|
||||||
|
<joint name="${station_name}${device_name}gripper_base_joint" type="revolute">
|
||||||
|
<origin rpy="0 0 0" xyz="0 0.2 -0.045"/>
|
||||||
|
<parent link="${station_name}${device_name}arm_link_3"/>
|
||||||
|
<child link="${station_name}${device_name}gripper_base"/>
|
||||||
|
<axis xyz="0 0 1"/>
|
||||||
|
<limit
|
||||||
|
effort="${limit_gripper_base_joint['effort']}"
|
||||||
|
lower="${limit_gripper_base_joint['lower']}"
|
||||||
|
upper="${limit_gripper_base_joint['upper']}"
|
||||||
|
velocity="${limit_gripper_base_joint['velocity']}"/>
|
||||||
|
</joint>
|
||||||
|
<link name="${station_name}${device_name}gripper_right">
|
||||||
|
<inertial>
|
||||||
|
<origin rpy="0 0 0" xyz="0.0340005471193899 0.0339655085140826 -0.0325252119823062"/>
|
||||||
|
<mass value="0.013337481136229"/>
|
||||||
|
<inertia ixx="2.02427962974094E-05" ixy="1.78442722292145E-06" ixz="-4.36485961300289E-07" iyy="1.4816483393622E-06" iyz="2.60539468115799E-06" izz="1.96629693098755E-05"/>
|
||||||
|
</inertial>
|
||||||
|
<visual>
|
||||||
|
<origin rpy="0 0 0" xyz="0 0 0"/>
|
||||||
|
<geometry>
|
||||||
|
<mesh filename="file://${mesh_path}/devices/arm_slider/meshes/gripper_right.STL"/>
|
||||||
|
</geometry>
|
||||||
|
<material name="">
|
||||||
|
<color rgba="1 1 1 1"/>
|
||||||
|
</material>
|
||||||
|
</visual>
|
||||||
|
<collision>
|
||||||
|
<origin rpy="0 0 0" xyz="0 0 0"/>
|
||||||
|
<geometry>
|
||||||
|
<mesh filename="file://${mesh_path}/devices/arm_slider/meshes/gripper_right.STL"/>
|
||||||
|
</geometry>
|
||||||
|
</collision>
|
||||||
|
</link>
|
||||||
|
<joint name="${station_name}${device_name}gripper_right_joint" type="prismatic">
|
||||||
|
<origin rpy="0 0 0" xyz="0 0.0942 -0.022277"/>
|
||||||
|
<parent link="${station_name}${device_name}gripper_base"/>
|
||||||
|
<child link="${station_name}${device_name}gripper_right"/>
|
||||||
|
<axis xyz="1 0 0"/>
|
||||||
|
<limit
|
||||||
|
effort="${limit_gripper_right_joint['effort']}"
|
||||||
|
lower="${limit_gripper_right_joint['lower']}"
|
||||||
|
upper="${limit_gripper_right_joint['upper']}"
|
||||||
|
velocity="${limit_gripper_right_joint['velocity']}"/>
|
||||||
|
</joint>
|
||||||
|
<link name="${station_name}${device_name}gripper_left">
|
||||||
|
<inertial>
|
||||||
|
<origin rpy="0 3.1416 0" xyz="-0.0340005471193521 0.0339655081029604 -0.0325252119827364"/>
|
||||||
|
<mass value="0.0133374811362292"/>
|
||||||
|
<inertia ixx="2.02427962974094E-05" ixy="-1.78442720812615E-06" ixz="4.36485961300305E-07" iyy="1.48164833936224E-06" iyz="2.6053946859901E-06" izz="1.96629693098755E-05"/>
|
||||||
|
</inertial>
|
||||||
|
<visual>
|
||||||
|
<origin rpy="0 3.1416 0" xyz="0 0 0"/>
|
||||||
|
<geometry>
|
||||||
|
<mesh filename="file://${mesh_path}/devices/arm_slider/meshes/gripper_left.STL"/>
|
||||||
|
</geometry>
|
||||||
|
<material name="">
|
||||||
|
<color rgba="1 1 1 1"/>
|
||||||
|
</material>
|
||||||
|
</visual>
|
||||||
|
<collision>
|
||||||
|
<origin rpy="0 3.1416 0" xyz="0 0 0"/>
|
||||||
|
<geometry>
|
||||||
|
<mesh filename="file://${mesh_path}/devices/arm_slider/meshes/gripper_left.STL"/>
|
||||||
|
</geometry>
|
||||||
|
</collision>
|
||||||
|
</link>
|
||||||
|
<joint name="${station_name}${device_name}gripper_left_joint" type="prismatic">
|
||||||
|
<origin rpy="0 3.1416 0" xyz="0 0.0942 -0.022277"/>
|
||||||
|
<parent link="${station_name}${device_name}gripper_base"/>
|
||||||
|
<child link="${station_name}${device_name}gripper_left"/>
|
||||||
|
<axis xyz="1 0 0"/>
|
||||||
|
<limit
|
||||||
|
effort="${limit_gripper_left_joint['effort']}"
|
||||||
|
lower="${limit_gripper_left_joint['lower']}"
|
||||||
|
upper="${limit_gripper_left_joint['upper']}"
|
||||||
|
velocity="${limit_gripper_left_joint['velocity']}"/>
|
||||||
|
<mimic joint="${station_name}${device_name}gripper_right_joint" multiplier="1" />
|
||||||
|
</joint>
|
||||||
|
|
||||||
|
</xacro:macro>
|
||||||
|
</robot>
|
||||||
BIN
unilabos/device_mesh/devices/arm_slider/meshes/arm_base.STL
Normal file
BIN
unilabos/device_mesh/devices/arm_slider/meshes/arm_base.STL
Normal file
Binary file not shown.
BIN
unilabos/device_mesh/devices/arm_slider/meshes/arm_link_1.STL
Normal file
BIN
unilabos/device_mesh/devices/arm_slider/meshes/arm_link_1.STL
Normal file
Binary file not shown.
BIN
unilabos/device_mesh/devices/arm_slider/meshes/arm_link_2.STL
Normal file
BIN
unilabos/device_mesh/devices/arm_slider/meshes/arm_link_2.STL
Normal file
Binary file not shown.
BIN
unilabos/device_mesh/devices/arm_slider/meshes/arm_link_3.STL
Normal file
BIN
unilabos/device_mesh/devices/arm_slider/meshes/arm_link_3.STL
Normal file
Binary file not shown.
BIN
unilabos/device_mesh/devices/arm_slider/meshes/arm_slideway.STL
Normal file
BIN
unilabos/device_mesh/devices/arm_slider/meshes/arm_slideway.STL
Normal file
Binary file not shown.
BIN
unilabos/device_mesh/devices/arm_slider/meshes/gripper_base.STL
Normal file
BIN
unilabos/device_mesh/devices/arm_slider/meshes/gripper_base.STL
Normal file
Binary file not shown.
BIN
unilabos/device_mesh/devices/arm_slider/meshes/gripper_left.STL
Normal file
BIN
unilabos/device_mesh/devices/arm_slider/meshes/gripper_left.STL
Normal file
Binary file not shown.
BIN
unilabos/device_mesh/devices/arm_slider/meshes/gripper_right.STL
Normal file
BIN
unilabos/device_mesh/devices/arm_slider/meshes/gripper_right.STL
Normal file
Binary file not shown.
@@ -0,0 +1,9 @@
|
|||||||
|
# Default initial positions for full_dev's ros2_control fake system
|
||||||
|
|
||||||
|
initial_positions:
|
||||||
|
arm_base_joint: 0
|
||||||
|
arm_link_1_joint: 0
|
||||||
|
arm_link_2_joint: 0
|
||||||
|
arm_link_3_joint: 0
|
||||||
|
gripper_base_joint: 0
|
||||||
|
gripper_right_joint: 0.03
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
# joint_limits.yaml allows the dynamics properties specified in the URDF to be overwritten or augmented as needed
|
||||||
|
|
||||||
|
# For beginners, we downscale velocity and acceleration limits.
|
||||||
|
# You can always specify higher scaling factors (<= 1.0) in your motion requests. # Increase the values below to 1.0 to always move at maximum speed.
|
||||||
|
default_velocity_scaling_factor: 0.1
|
||||||
|
default_acceleration_scaling_factor: 0.1
|
||||||
|
|
||||||
|
# Specific joint properties can be changed with the keys [max_position, min_position, max_velocity, max_acceleration]
|
||||||
|
# Joint limits can be turned off with [has_velocity_limits, has_acceleration_limits]
|
||||||
|
joint_limits:
|
||||||
|
arm_base_joint:
|
||||||
|
has_velocity_limits: true
|
||||||
|
max_velocity: 0
|
||||||
|
has_acceleration_limits: false
|
||||||
|
max_acceleration: 0
|
||||||
|
arm_link_1_joint:
|
||||||
|
has_velocity_limits: true
|
||||||
|
max_velocity: 0
|
||||||
|
has_acceleration_limits: false
|
||||||
|
max_acceleration: 0
|
||||||
|
arm_link_2_joint:
|
||||||
|
has_velocity_limits: true
|
||||||
|
max_velocity: 0
|
||||||
|
has_acceleration_limits: false
|
||||||
|
max_acceleration: 0
|
||||||
|
arm_link_3_joint:
|
||||||
|
has_velocity_limits: true
|
||||||
|
max_velocity: 0
|
||||||
|
has_acceleration_limits: false
|
||||||
|
max_acceleration: 0
|
||||||
|
gripper_base_joint:
|
||||||
|
has_velocity_limits: true
|
||||||
|
max_velocity: 0
|
||||||
|
has_acceleration_limits: false
|
||||||
|
max_acceleration: 0
|
||||||
|
gripper_right_joint:
|
||||||
|
has_velocity_limits: true
|
||||||
|
max_velocity: 0
|
||||||
|
has_acceleration_limits: false
|
||||||
|
max_acceleration: 0
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
arm:
|
||||||
|
kinematics_solver: lma_kinematics_plugin/LMAKinematicsPlugin
|
||||||
|
kinematics_solver_search_resolution: 0.0050000000000000001
|
||||||
|
kinematics_solver_timeout: 0.0050000000000000001
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<robot xmlns:xacro="http://www.ros.org/wiki/xacro">
|
||||||
|
<xacro:macro name="benyao_arm_ros2_control" params="device_name mesh_path">
|
||||||
|
<xacro:property name="initial_positions" value="${load_yaml(mesh_path + '/devices/benyao_arm/config/initial_positions.yaml')['initial_positions']}"/>
|
||||||
|
|
||||||
|
<ros2_control name="${device_name}benyao_arm" type="system">
|
||||||
|
<hardware>
|
||||||
|
<!-- By default, set up controllers for simulation. This won't work on real hardware -->
|
||||||
|
<plugin>mock_components/GenericSystem</plugin>
|
||||||
|
</hardware>
|
||||||
|
<joint name="${device_name}arm_base_joint">
|
||||||
|
<command_interface name="position"/>
|
||||||
|
<state_interface name="position">
|
||||||
|
<param name="initial_value">${initial_positions['arm_base_joint']}</param>
|
||||||
|
</state_interface>
|
||||||
|
<state_interface name="velocity"/>
|
||||||
|
</joint>
|
||||||
|
<joint name="${device_name}arm_link_1_joint">
|
||||||
|
<command_interface name="position"/>
|
||||||
|
<state_interface name="position">
|
||||||
|
<param name="initial_value">${initial_positions['arm_link_1_joint']}</param>
|
||||||
|
</state_interface>
|
||||||
|
<state_interface name="velocity"/>
|
||||||
|
</joint>
|
||||||
|
<joint name="${device_name}arm_link_2_joint">
|
||||||
|
<command_interface name="position"/>
|
||||||
|
<state_interface name="position">
|
||||||
|
<param name="initial_value">${initial_positions['arm_link_2_joint']}</param>
|
||||||
|
</state_interface>
|
||||||
|
<state_interface name="velocity"/>
|
||||||
|
</joint>
|
||||||
|
<joint name="${device_name}arm_link_3_joint">
|
||||||
|
<command_interface name="position"/>
|
||||||
|
<state_interface name="position">
|
||||||
|
<param name="initial_value">${initial_positions['arm_link_3_joint']}</param>
|
||||||
|
</state_interface>
|
||||||
|
<state_interface name="velocity"/>
|
||||||
|
</joint>
|
||||||
|
<joint name="${device_name}gripper_base_joint">
|
||||||
|
<command_interface name="position"/>
|
||||||
|
<state_interface name="position">
|
||||||
|
<param name="initial_value">${initial_positions['gripper_base_joint']}</param>
|
||||||
|
</state_interface>
|
||||||
|
<state_interface name="velocity"/>
|
||||||
|
</joint>
|
||||||
|
<joint name="${device_name}gripper_right_joint">
|
||||||
|
<command_interface name="position"/>
|
||||||
|
<state_interface name="position">
|
||||||
|
<param name="initial_value">${initial_positions['gripper_right_joint']}</param>
|
||||||
|
</state_interface>
|
||||||
|
<state_interface name="velocity"/>
|
||||||
|
</joint>
|
||||||
|
|
||||||
|
</ros2_control>
|
||||||
|
</xacro:macro>
|
||||||
|
</robot>
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!--This does not replace URDF, and is not an extension of URDF.
|
||||||
|
This is a format for representing semantic information about the robot structure.
|
||||||
|
A URDF file must exist for this robot as well, where the joints and the links that are referenced are defined
|
||||||
|
-->
|
||||||
|
<robot xmlns:xacro="http://ros.org/wiki/xacro">
|
||||||
|
<xacro:macro name="benyao_arm_srdf" params="device_name">
|
||||||
|
<!--GROUPS: Representation of a set of joints and links. This can be useful for specifying DOF to plan for, defining arms, end effectors, etc-->
|
||||||
|
<!--LINKS: When a link is specified, the parent joint of that link (if it exists) is automatically included-->
|
||||||
|
<!--JOINTS: When a joint is specified, the child link of that joint (which will always exist) is automatically included-->
|
||||||
|
<!--CHAINS: When a chain is specified, all the links along the chain (including endpoints) are included in the group. Additionally, all the joints that are parents to included links are also included. This means that joints along the chain and the parent joint of the base link are included in the group-->
|
||||||
|
<!--SUBGROUPS: Groups can also be formed by referencing to already defined group names-->
|
||||||
|
<group name="${device_name}arm">
|
||||||
|
<chain base_link="${device_name}arm_slideway" tip_link="${device_name}gripper_base"/>
|
||||||
|
</group>
|
||||||
|
<group name="${device_name}arm_gripper">
|
||||||
|
<joint name="${device_name}gripper_right_joint"/>
|
||||||
|
</group>
|
||||||
|
<!--DISABLE COLLISIONS: By default it is assumed that any link of the robot could potentially come into collision with any other link in the robot. This tag disables collision checking between a specified pair of links. -->
|
||||||
|
<disable_collisions link1="${device_name}arm_base" link2="${device_name}arm_link_2" reason="Adjacent"/>
|
||||||
|
<disable_collisions link1="${device_name}arm_base" link2="${device_name}arm_link_1" reason="Adjacent"/>
|
||||||
|
<disable_collisions link1="${device_name}arm_base" link2="${device_name}arm_link_3" reason="Never"/>
|
||||||
|
<disable_collisions link1="${device_name}arm_base" link2="${device_name}arm_slideway" reason="Adjacent"/>
|
||||||
|
<disable_collisions link1="${device_name}arm_link_1" link2="${device_name}arm_link_2" reason="Adjacent"/>
|
||||||
|
<disable_collisions link1="${device_name}arm_link_1" link2="${device_name}arm_link_3" reason="Never"/>
|
||||||
|
<disable_collisions link1="${device_name}arm_link_1" link2="${device_name}arm_slideway" reason="Never"/>
|
||||||
|
<disable_collisions link1="${device_name}arm_link_1" link2="${device_name}gripper_base" reason="Never"/>
|
||||||
|
<disable_collisions link1="${device_name}arm_link_1" link2="${device_name}gripper_left" reason="Never"/>
|
||||||
|
<disable_collisions link1="${device_name}arm_link_1" link2="${device_name}gripper_right" reason="Never"/>
|
||||||
|
<disable_collisions link1="${device_name}arm_link_2" link2="${device_name}arm_link_3" reason="Adjacent"/>
|
||||||
|
<disable_collisions link1="${device_name}arm_link_2" link2="${device_name}arm_slideway" reason="Never"/>
|
||||||
|
<disable_collisions link1="${device_name}arm_link_2" link2="${device_name}gripper_base" reason="Never"/>
|
||||||
|
<disable_collisions link1="${device_name}arm_link_2" link2="${device_name}gripper_left" reason="Never"/>
|
||||||
|
<disable_collisions link1="${device_name}arm_link_2" link2="${device_name}gripper_right" reason="Never"/>
|
||||||
|
<disable_collisions link1="${device_name}arm_link_3" link2="${device_name}arm_slideway" reason="Never"/>
|
||||||
|
<disable_collisions link1="${device_name}arm_link_3" link2="${device_name}gripper_base" reason="Adjacent"/>
|
||||||
|
<disable_collisions link1="${device_name}arm_link_3" link2="${device_name}gripper_left" reason="Never"/>
|
||||||
|
<disable_collisions link1="${device_name}arm_link_3" link2="${device_name}gripper_right" reason="Never"/>
|
||||||
|
<disable_collisions link1="${device_name}arm_slideway" link2="${device_name}gripper_base" reason="Never"/>
|
||||||
|
<disable_collisions link1="${device_name}arm_slideway" link2="${device_name}gripper_left" reason="Never"/>
|
||||||
|
<disable_collisions link1="${device_name}arm_slideway" link2="${device_name}gripper_right" reason="Never"/>
|
||||||
|
<disable_collisions link1="${device_name}gripper_base" link2="${device_name}gripper_left" reason="Adjacent"/>
|
||||||
|
<disable_collisions link1="${device_name}gripper_base" link2="${device_name}gripper_right" reason="Adjacent"/>
|
||||||
|
<disable_collisions link1="${device_name}gripper_left" link2="${device_name}gripper_right" reason="Never"/>
|
||||||
|
</xacro:macro>
|
||||||
|
</robot>
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"arm":
|
||||||
|
{
|
||||||
|
"joint_names": [
|
||||||
|
"arm_base_joint",
|
||||||
|
"arm_link_1_joint",
|
||||||
|
"arm_link_2_joint",
|
||||||
|
"arm_link_3_joint",
|
||||||
|
"gripper_base_joint"
|
||||||
|
],
|
||||||
|
"base_link_name": "device_link",
|
||||||
|
"end_effector_name": "gripper_base"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
# MoveIt uses this configuration for controller management
|
||||||
|
|
||||||
|
moveit_controller_manager: moveit_simple_controller_manager/MoveItSimpleControllerManager
|
||||||
|
|
||||||
|
moveit_simple_controller_manager:
|
||||||
|
controller_names:
|
||||||
|
- arm_controller
|
||||||
|
- gripper_controller
|
||||||
|
|
||||||
|
arm_controller:
|
||||||
|
type: FollowJointTrajectory
|
||||||
|
action_ns: follow_joint_trajectory
|
||||||
|
default: true
|
||||||
|
joints:
|
||||||
|
- arm_base_joint
|
||||||
|
- arm_link_1_joint
|
||||||
|
- arm_link_2_joint
|
||||||
|
- arm_link_3_joint
|
||||||
|
- gripper_base_joint
|
||||||
|
action_ns: follow_joint_trajectory
|
||||||
|
default: true
|
||||||
|
gripper_controller:
|
||||||
|
type: FollowJointTrajectory
|
||||||
|
action_ns: follow_joint_trajectory
|
||||||
|
default: true
|
||||||
|
joints:
|
||||||
|
- gripper_right_joint
|
||||||
|
action_ns: follow_joint_trajectory
|
||||||
|
default: true
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
planner_configs:
|
||||||
|
- ompl_interface/OMPLPlanner
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
# Limits for the Pilz planner
|
||||||
|
cartesian_limits:
|
||||||
|
max_trans_vel: 1.0
|
||||||
|
max_trans_acc: 2.25
|
||||||
|
max_trans_dec: -5.0
|
||||||
|
max_rot_vel: 1.57
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
# This config file is used by ros2_control
|
||||||
|
controller_manager:
|
||||||
|
ros__parameters:
|
||||||
|
update_rate: 100 # Hz
|
||||||
|
|
||||||
|
arm_controller:
|
||||||
|
type: joint_trajectory_controller/JointTrajectoryController
|
||||||
|
|
||||||
|
|
||||||
|
gripper_controller:
|
||||||
|
type: joint_trajectory_controller/JointTrajectoryController
|
||||||
|
|
||||||
|
|
||||||
|
joint_state_broadcaster:
|
||||||
|
type: joint_state_broadcaster/JointStateBroadcaster
|
||||||
|
|
||||||
|
arm_controller:
|
||||||
|
ros__parameters:
|
||||||
|
joints:
|
||||||
|
- arm_base_joint
|
||||||
|
- arm_link_1_joint
|
||||||
|
- arm_link_2_joint
|
||||||
|
- arm_link_3_joint
|
||||||
|
- gripper_base_joint
|
||||||
|
command_interfaces:
|
||||||
|
- position
|
||||||
|
state_interfaces:
|
||||||
|
- position
|
||||||
|
- velocity
|
||||||
|
|
||||||
|
gripper_controller:
|
||||||
|
ros__parameters:
|
||||||
|
joints:
|
||||||
|
- gripper_right_joint
|
||||||
|
command_interfaces:
|
||||||
|
- position
|
||||||
|
state_interfaces:
|
||||||
|
- position
|
||||||
|
- velocity
|
||||||
44
unilabos/device_mesh/devices/benyao_arm/joint_limit.yaml
Normal file
44
unilabos/device_mesh/devices/benyao_arm/joint_limit.yaml
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
joint_limits:
|
||||||
|
|
||||||
|
arm_base_joint:
|
||||||
|
effort: 50
|
||||||
|
velocity: 1.0
|
||||||
|
lower: 0
|
||||||
|
upper: 1.5
|
||||||
|
|
||||||
|
arm_link_1_joint:
|
||||||
|
effort: 50
|
||||||
|
velocity: 1.0
|
||||||
|
lower: 0
|
||||||
|
upper: 0.6
|
||||||
|
|
||||||
|
arm_link_2_joint:
|
||||||
|
effort: 50
|
||||||
|
velocity: 1.0
|
||||||
|
lower: !degrees -95
|
||||||
|
upper: !degrees 95
|
||||||
|
|
||||||
|
arm_link_3_joint:
|
||||||
|
effort: 50
|
||||||
|
velocity: 1.0
|
||||||
|
lower: !degrees -195
|
||||||
|
upper: !degrees 195
|
||||||
|
|
||||||
|
gripper_base_joint:
|
||||||
|
effort: 50
|
||||||
|
velocity: 1.0
|
||||||
|
lower: !degrees -95
|
||||||
|
upper: !degrees 95
|
||||||
|
|
||||||
|
|
||||||
|
gripper_right_joint:
|
||||||
|
effort: 50
|
||||||
|
velocity: 1.0
|
||||||
|
lower: 0
|
||||||
|
upper: 0.03
|
||||||
|
|
||||||
|
gripper_left_joint:
|
||||||
|
effort: 50
|
||||||
|
velocity: 1.0
|
||||||
|
lower: 0
|
||||||
|
upper: 0.03
|
||||||
293
unilabos/device_mesh/devices/benyao_arm/macro_device.xacro
Normal file
293
unilabos/device_mesh/devices/benyao_arm/macro_device.xacro
Normal file
@@ -0,0 +1,293 @@
|
|||||||
|
<?xml version="1.0" ?>
|
||||||
|
<robot xmlns:xacro="http://ros.org/wiki/xacro" name="benyao_arm">
|
||||||
|
|
||||||
|
<xacro:macro name="benyao_arm" params="mesh_path:='' parent_link:='' station_name:='' device_name:='' x:=0 y:=0 z:=0 rx:=0 ry:=0 r:=0">
|
||||||
|
<!-- Read .yaml files from disk, load content into properties -->
|
||||||
|
<xacro:property name= "joint_limit_parameters" value="${xacro.load_yaml(mesh_path + '/devices/benyao_arm/joint_limit.yaml')}"/>
|
||||||
|
|
||||||
|
<!-- Extract subsections from yaml dictionaries -->
|
||||||
|
<xacro:property name= "sec_limits" value="${joint_limit_parameters['joint_limits']}"/>
|
||||||
|
|
||||||
|
<joint name="${station_name}${device_name}base_link_joint" type="fixed">
|
||||||
|
<origin xyz="${x} ${y} ${z}" rpy="${rx} ${ry} ${r}" />
|
||||||
|
<parent link="${parent_link}"/>
|
||||||
|
<child link="${station_name}${device_name}device_link"/>
|
||||||
|
<axis xyz="0 0 0"/>
|
||||||
|
</joint>
|
||||||
|
|
||||||
|
<link name="${station_name}${device_name}device_link"/>
|
||||||
|
<joint name="${station_name}${device_name}device_link_joint" type="fixed">
|
||||||
|
<origin xyz="0 0 0" rpy="0 0 0" />
|
||||||
|
<parent link="${station_name}${device_name}device_link"/>
|
||||||
|
<child link="${station_name}${device_name}arm_slideway"/>
|
||||||
|
<axis xyz="0 0 0"/>
|
||||||
|
</joint>
|
||||||
|
|
||||||
|
<!-- JOINTS LIMIT PARAMETERS -->
|
||||||
|
<xacro:property name="limit_arm_base_joint" value="${sec_limits['arm_base_joint']}" />
|
||||||
|
<xacro:property name="limit_arm_link_1_joint" value="${sec_limits['arm_link_1_joint']}" />
|
||||||
|
<xacro:property name="limit_arm_link_2_joint" value="${sec_limits['arm_link_2_joint']}" />
|
||||||
|
<xacro:property name="limit_arm_link_3_joint" value="${sec_limits['arm_link_3_joint']}" />
|
||||||
|
<xacro:property name="limit_gripper_base_joint" value="${sec_limits['gripper_base_joint']}" />
|
||||||
|
<xacro:property name="limit_gripper_right_joint" value="${sec_limits['gripper_right_joint']}"/>
|
||||||
|
<xacro:property name="limit_gripper_left_joint" value="${sec_limits['gripper_left_joint']}" />
|
||||||
|
<link name="${station_name}${device_name}arm_slideway">
|
||||||
|
<inertial>
|
||||||
|
<origin rpy="0 0 0" xyz="-0.913122246354019 -0.00141851388483838 0.0416079172839272"/>
|
||||||
|
<mass value="13.6578107753627"/>
|
||||||
|
<inertia ixx="0.0507627640890578" ixy="0.0245166532634714" ixz="-0.0112656803168519" iyy="5.2550852314372" iyz="0.000302974193920367" izz="5.26892263696439"/>
|
||||||
|
</inertial>
|
||||||
|
<visual>
|
||||||
|
<origin rpy="0 0 0" xyz="0 0 0"/>
|
||||||
|
<geometry>
|
||||||
|
<mesh filename="file://${mesh_path}/devices/benyao_arm/meshes/arm_slideway.STL"/>
|
||||||
|
</geometry>
|
||||||
|
<material name="">
|
||||||
|
<color rgba="0.752941176470588 0.752941176470588 0.752941176470588 1"/>
|
||||||
|
</material>
|
||||||
|
</visual>
|
||||||
|
<collision>
|
||||||
|
<origin rpy="0 0 0" xyz="0 0 0"/>
|
||||||
|
<geometry>
|
||||||
|
<mesh filename="file://${mesh_path}/devices/benyao_arm/meshes/arm_slideway.STL"/>
|
||||||
|
</geometry>
|
||||||
|
</collision>
|
||||||
|
</link>
|
||||||
|
|
||||||
|
<joint name="${station_name}${device_name}arm_base_joint" type="prismatic">
|
||||||
|
<origin rpy="0 0 0" xyz="0.307 0 0.1225"/>
|
||||||
|
<parent link="${station_name}${device_name}arm_slideway"/>
|
||||||
|
<child link="${station_name}${device_name}arm_base"/>
|
||||||
|
<axis xyz="1 0 0"/>
|
||||||
|
<limit
|
||||||
|
effort="${limit_arm_base_joint['effort']}"
|
||||||
|
lower="${limit_arm_base_joint['lower']}"
|
||||||
|
upper="${limit_arm_base_joint['upper']}"
|
||||||
|
velocity="${limit_arm_base_joint['velocity']}"/>
|
||||||
|
</joint>
|
||||||
|
|
||||||
|
<link name="${station_name}${device_name}arm_base">
|
||||||
|
<inertial>
|
||||||
|
<origin rpy="0 0 0" xyz="1.48458338655733E-06 -0.00831873687136486 0.351728466012153"/>
|
||||||
|
<mass value="16.1341586205194"/>
|
||||||
|
<inertia ixx="0.54871651759045" ixy="7.65476367433116E-07" ixz="2.0515139488158E-07" iyy="0.55113098995396" iyz="-5.13261457726806E-07" izz="0.0619081867727048"/>
|
||||||
|
</inertial>
|
||||||
|
<visual>
|
||||||
|
<origin rpy="0 0 0" xyz="0 0 0"/>
|
||||||
|
<geometry>
|
||||||
|
<mesh filename="file://${mesh_path}/devices/benyao_arm/meshes/arm_base.STL"/>
|
||||||
|
</geometry>
|
||||||
|
<material name="">
|
||||||
|
<color rgba="1 1 1 1"/>
|
||||||
|
</material>
|
||||||
|
</visual>
|
||||||
|
<collision>
|
||||||
|
<origin rpy="0 0 0" xyz="0 0 0"/>
|
||||||
|
<geometry>
|
||||||
|
<mesh filename="file://${mesh_path}/devices/benyao_arm/meshes/arm_base.STL"/>
|
||||||
|
</geometry>
|
||||||
|
</collision>
|
||||||
|
</link>
|
||||||
|
|
||||||
|
<link name="${station_name}${device_name}arm_link_1">
|
||||||
|
<inertial>
|
||||||
|
<origin rpy="0 0 0" xyz="0 -0.0102223856758559 0.0348505130779933"/>
|
||||||
|
<mass value="0.828629227096429"/>
|
||||||
|
<inertia ixx="0.00119703598787112" ixy="-2.46083048832131E-19" ixz="1.43864352731199E-19" iyy="0.00108355785790042" iyz="1.88092240278693E-06" izz="0.00160914803816438"/>
|
||||||
|
</inertial>
|
||||||
|
<visual>
|
||||||
|
<origin rpy="0 0 0" xyz="0 0 0"/>
|
||||||
|
<geometry>
|
||||||
|
<mesh filename="file://${mesh_path}/devices/benyao_arm/meshes/arm_link_1.STL"/>
|
||||||
|
</geometry>
|
||||||
|
<material name="">
|
||||||
|
<color rgba="1 1 1 1"/>
|
||||||
|
</material>
|
||||||
|
</visual>
|
||||||
|
<collision>
|
||||||
|
<origin rpy="0 0 0" xyz="0 0 0"/>
|
||||||
|
<geometry>
|
||||||
|
<mesh filename="file://${mesh_path}/devices/benyao_arm/meshes/arm_link_1.STL"/>
|
||||||
|
</geometry>
|
||||||
|
</collision>
|
||||||
|
</link>
|
||||||
|
<joint name="${station_name}${device_name}arm_link_1_joint" type="prismatic">
|
||||||
|
<origin rpy="0 0 0" xyz="0 0.1249 0.15"/>
|
||||||
|
<parent link="${station_name}${device_name}arm_base"/>
|
||||||
|
<child link="${station_name}${device_name}arm_link_1"/>
|
||||||
|
<axis xyz="0 0 1"/>
|
||||||
|
<limit
|
||||||
|
effort="${limit_arm_link_1_joint['effort']}"
|
||||||
|
lower="${limit_arm_link_1_joint['lower']}"
|
||||||
|
upper="${limit_arm_link_1_joint['upper']}"
|
||||||
|
velocity="${limit_arm_link_1_joint['velocity']}"/>
|
||||||
|
</joint>
|
||||||
|
<link name="${station_name}${device_name}arm_link_2">
|
||||||
|
<inertial>
|
||||||
|
<origin rpy="0 0 0" xyz="-3.33066907387547E-16 0.100000000000003 -0.0325000000000004"/>
|
||||||
|
<mass value="2.04764861029349"/>
|
||||||
|
<inertia ixx="0.0150150059448827" ixy="-1.28113733272213E-17" ixz="6.7561418872754E-19" iyy="0.00262980501315445" iyz="7.44451536320152E-18" izz="0.0162030186138787"/>
|
||||||
|
</inertial>
|
||||||
|
<visual>
|
||||||
|
<origin rpy="0 0 0" xyz="0 0 0"/>
|
||||||
|
<geometry>
|
||||||
|
<mesh filename="file://${mesh_path}/devices/benyao_arm/meshes/arm_link_2.STL"/>
|
||||||
|
</geometry>
|
||||||
|
<material name="">
|
||||||
|
<color rgba="1 1 1 1"/>
|
||||||
|
</material>
|
||||||
|
</visual>
|
||||||
|
<collision>
|
||||||
|
<origin rpy="0 0 0" xyz="0 0 0"/>
|
||||||
|
<geometry>
|
||||||
|
<mesh filename="file://${mesh_path}/devices/benyao_arm/meshes/arm_link_2.STL"/>
|
||||||
|
</geometry>
|
||||||
|
</collision>
|
||||||
|
</link>
|
||||||
|
<joint name="${station_name}${device_name}arm_link_2_joint" type="revolute">
|
||||||
|
<origin rpy="0 0 0" xyz="0 0 0"/>
|
||||||
|
<parent link="${station_name}${device_name}arm_link_1"/>
|
||||||
|
<child link="${station_name}${device_name}arm_link_2"/>
|
||||||
|
<axis xyz="0 0 1"/>
|
||||||
|
<limit
|
||||||
|
effort="${limit_arm_link_2_joint['effort']}"
|
||||||
|
lower="${limit_arm_link_2_joint['lower']}"
|
||||||
|
upper="${limit_arm_link_2_joint['upper']}"
|
||||||
|
velocity="${limit_arm_link_2_joint['velocity']}"/>
|
||||||
|
</joint>
|
||||||
|
<link name="${station_name}${device_name}arm_link_3">
|
||||||
|
<inertial>
|
||||||
|
<origin rpy="0 0 0" xyz="4.77395900588817E-15 0.0861257730831348 -0.0227999999999999"/>
|
||||||
|
<mass value="1.19870202871083"/>
|
||||||
|
<inertia ixx="0.00780783223764428" ixy="7.26567379579506E-18" ixz="1.02766851352053E-18" iyy="0.00109642607170081" iyz="-9.73775385060067E-18" izz="0.0084997384510058"/>
|
||||||
|
</inertial>
|
||||||
|
<visual>
|
||||||
|
<origin rpy="0 0 0" xyz="0 0 0"/>
|
||||||
|
<geometry>
|
||||||
|
<mesh filename="file://${mesh_path}/devices/benyao_arm/meshes/arm_link_3.STL"/>
|
||||||
|
</geometry>
|
||||||
|
<material name="">
|
||||||
|
<color rgba="1 1 1 1"/>
|
||||||
|
</material>
|
||||||
|
</visual>
|
||||||
|
<collision>
|
||||||
|
<origin rpy="0 0 0" xyz="0 0 0"/>
|
||||||
|
<geometry>
|
||||||
|
<mesh filename="file://${mesh_path}/devices/benyao_arm/meshes/arm_link_3.STL"/>
|
||||||
|
</geometry>
|
||||||
|
</collision>
|
||||||
|
</link>
|
||||||
|
<joint name="${station_name}${device_name}arm_link_3_joint" type="revolute">
|
||||||
|
<origin rpy="0 0 0" xyz="0 0.2 -0.0647"/>
|
||||||
|
<parent link="${station_name}${device_name}arm_link_2"/>
|
||||||
|
<child link="${station_name}${device_name}arm_link_3"/>
|
||||||
|
<axis xyz="0 0 1"/>
|
||||||
|
<limit
|
||||||
|
effort="${limit_arm_link_3_joint['effort']}"
|
||||||
|
lower="${limit_arm_link_3_joint['lower']}"
|
||||||
|
upper="${limit_arm_link_3_joint['upper']}"
|
||||||
|
velocity="${limit_arm_link_3_joint['velocity']}"/>
|
||||||
|
</joint>
|
||||||
|
<link name="${station_name}${device_name}gripper_base">
|
||||||
|
<inertial>
|
||||||
|
<origin rpy="0 0 0" xyz="-6.05365748571618E-05 0.0373027483464434 -0.0264392017534612"/>
|
||||||
|
<mass value="0.511925198394943"/>
|
||||||
|
<inertia ixx="0.000640463815051467" ixy="1.08132229596356E-06" ixz="7.165124649009E-07" iyy="0.000552164156414554" iyz="9.80000237347941E-06" izz="0.00103553457812823"/>
|
||||||
|
</inertial>
|
||||||
|
<visual>
|
||||||
|
<origin rpy="0 0 0" xyz="0 0 0"/>
|
||||||
|
<geometry>
|
||||||
|
<mesh filename="file://${mesh_path}/devices/benyao_arm/meshes/gripper_base.STL"/>
|
||||||
|
</geometry>
|
||||||
|
<material name="">
|
||||||
|
<color rgba="1 1 1 1"/>
|
||||||
|
</material>
|
||||||
|
</visual>
|
||||||
|
<collision>
|
||||||
|
<origin rpy="0 0 0" xyz="0 0 0"/>
|
||||||
|
<geometry>
|
||||||
|
<mesh filename="file://${mesh_path}/devices/benyao_arm/meshes/gripper_base.STL"/>
|
||||||
|
</geometry>
|
||||||
|
</collision>
|
||||||
|
</link>
|
||||||
|
<joint name="${station_name}${device_name}gripper_base_joint" type="revolute">
|
||||||
|
<origin rpy="0 0 0" xyz="0 0.2 -0.045"/>
|
||||||
|
<parent link="${station_name}${device_name}arm_link_3"/>
|
||||||
|
<child link="${station_name}${device_name}gripper_base"/>
|
||||||
|
<axis xyz="0 0 1"/>
|
||||||
|
<limit
|
||||||
|
effort="${limit_gripper_base_joint['effort']}"
|
||||||
|
lower="${limit_gripper_base_joint['lower']}"
|
||||||
|
upper="${limit_gripper_base_joint['upper']}"
|
||||||
|
velocity="${limit_gripper_base_joint['velocity']}"/>
|
||||||
|
</joint>
|
||||||
|
<link name="${station_name}${device_name}gripper_right">
|
||||||
|
<inertial>
|
||||||
|
<origin rpy="0 0 0" xyz="0.0340005471193899 0.0339655085140826 -0.0325252119823062"/>
|
||||||
|
<mass value="0.013337481136229"/>
|
||||||
|
<inertia ixx="2.02427962974094E-05" ixy="1.78442722292145E-06" ixz="-4.36485961300289E-07" iyy="1.4816483393622E-06" iyz="2.60539468115799E-06" izz="1.96629693098755E-05"/>
|
||||||
|
</inertial>
|
||||||
|
<visual>
|
||||||
|
<origin rpy="0 0 0" xyz="0 0 0"/>
|
||||||
|
<geometry>
|
||||||
|
<mesh filename="file://${mesh_path}/devices/benyao_arm/meshes/gripper_right.STL"/>
|
||||||
|
</geometry>
|
||||||
|
<material name="">
|
||||||
|
<color rgba="1 1 1 1"/>
|
||||||
|
</material>
|
||||||
|
</visual>
|
||||||
|
<collision>
|
||||||
|
<origin rpy="0 0 0" xyz="0 0 0"/>
|
||||||
|
<geometry>
|
||||||
|
<mesh filename="file://${mesh_path}/devices/benyao_arm/meshes/gripper_right.STL"/>
|
||||||
|
</geometry>
|
||||||
|
</collision>
|
||||||
|
</link>
|
||||||
|
<joint name="${station_name}${device_name}gripper_right_joint" type="prismatic">
|
||||||
|
<origin rpy="0 0 0" xyz="0 0.0942 -0.022277"/>
|
||||||
|
<parent link="${station_name}${device_name}gripper_base"/>
|
||||||
|
<child link="${station_name}${device_name}gripper_right"/>
|
||||||
|
<axis xyz="1 0 0"/>
|
||||||
|
<limit
|
||||||
|
effort="${limit_gripper_right_joint['effort']}"
|
||||||
|
lower="${limit_gripper_right_joint['lower']}"
|
||||||
|
upper="${limit_gripper_right_joint['upper']}"
|
||||||
|
velocity="${limit_gripper_right_joint['velocity']}"/>
|
||||||
|
</joint>
|
||||||
|
<link name="${station_name}${device_name}gripper_left">
|
||||||
|
<inertial>
|
||||||
|
<origin rpy="0 3.1416 0" xyz="-0.0340005471193521 0.0339655081029604 -0.0325252119827364"/>
|
||||||
|
<mass value="0.0133374811362292"/>
|
||||||
|
<inertia ixx="2.02427962974094E-05" ixy="-1.78442720812615E-06" ixz="4.36485961300305E-07" iyy="1.48164833936224E-06" iyz="2.6053946859901E-06" izz="1.96629693098755E-05"/>
|
||||||
|
</inertial>
|
||||||
|
<visual>
|
||||||
|
<origin rpy="0 3.1416 0" xyz="0 0 0"/>
|
||||||
|
<geometry>
|
||||||
|
<mesh filename="file://${mesh_path}/devices/benyao_arm/meshes/gripper_left.STL"/>
|
||||||
|
</geometry>
|
||||||
|
<material name="">
|
||||||
|
<color rgba="1 1 1 1"/>
|
||||||
|
</material>
|
||||||
|
</visual>
|
||||||
|
<collision>
|
||||||
|
<origin rpy="0 3.1416 0" xyz="0 0 0"/>
|
||||||
|
<geometry>
|
||||||
|
<mesh filename="file://${mesh_path}/devices/benyao_arm/meshes/gripper_left.STL"/>
|
||||||
|
</geometry>
|
||||||
|
</collision>
|
||||||
|
</link>
|
||||||
|
<joint name="${station_name}${device_name}gripper_left_joint" type="prismatic">
|
||||||
|
<origin rpy="0 3.1416 0" xyz="0 0.0942 -0.022277"/>
|
||||||
|
<parent link="${station_name}${device_name}gripper_base"/>
|
||||||
|
<child link="${station_name}${device_name}gripper_left"/>
|
||||||
|
<axis xyz="1 0 0"/>
|
||||||
|
<limit
|
||||||
|
effort="${limit_gripper_left_joint['effort']}"
|
||||||
|
lower="${limit_gripper_left_joint['lower']}"
|
||||||
|
upper="${limit_gripper_left_joint['upper']}"
|
||||||
|
velocity="${limit_gripper_left_joint['velocity']}"/>
|
||||||
|
<mimic joint="${station_name}${device_name}gripper_right_joint" multiplier="1" />
|
||||||
|
</joint>
|
||||||
|
|
||||||
|
</xacro:macro>
|
||||||
|
</robot>
|
||||||
BIN
unilabos/device_mesh/devices/benyao_arm/meshes/arm_base.STL
Normal file
BIN
unilabos/device_mesh/devices/benyao_arm/meshes/arm_base.STL
Normal file
Binary file not shown.
BIN
unilabos/device_mesh/devices/benyao_arm/meshes/arm_link_1.STL
Normal file
BIN
unilabos/device_mesh/devices/benyao_arm/meshes/arm_link_1.STL
Normal file
Binary file not shown.
BIN
unilabos/device_mesh/devices/benyao_arm/meshes/arm_link_2.STL
Normal file
BIN
unilabos/device_mesh/devices/benyao_arm/meshes/arm_link_2.STL
Normal file
Binary file not shown.
BIN
unilabos/device_mesh/devices/benyao_arm/meshes/arm_link_3.STL
Normal file
BIN
unilabos/device_mesh/devices/benyao_arm/meshes/arm_link_3.STL
Normal file
Binary file not shown.
BIN
unilabos/device_mesh/devices/benyao_arm/meshes/arm_slideway.STL
Normal file
BIN
unilabos/device_mesh/devices/benyao_arm/meshes/arm_slideway.STL
Normal file
Binary file not shown.
BIN
unilabos/device_mesh/devices/benyao_arm/meshes/gripper_base.STL
Normal file
BIN
unilabos/device_mesh/devices/benyao_arm/meshes/gripper_base.STL
Normal file
Binary file not shown.
BIN
unilabos/device_mesh/devices/benyao_arm/meshes/gripper_left.STL
Normal file
BIN
unilabos/device_mesh/devices/benyao_arm/meshes/gripper_left.STL
Normal file
Binary file not shown.
BIN
unilabos/device_mesh/devices/benyao_arm/meshes/gripper_right.STL
Normal file
BIN
unilabos/device_mesh/devices/benyao_arm/meshes/gripper_right.STL
Normal file
Binary file not shown.
@@ -3,11 +3,10 @@
|
|||||||
<robot xmlns:xacro="http://ros.org/wiki/xacro">
|
<robot xmlns:xacro="http://ros.org/wiki/xacro">
|
||||||
|
|
||||||
<xacro:macro name="opentrons_liquid_handler"
|
<xacro:macro name="opentrons_liquid_handler"
|
||||||
params="parent_link:='' station_name:='' device_name:='' x:=0 y:=0 z:=0 r:=0 mesh_path:=''">
|
params="parent_link:='' station_name:='' device_name:='' x:=0 y:=0 z:=0 rx:=0 ry:=0 r:=0 mesh_path:=''">
|
||||||
|
|
||||||
|
|
||||||
<joint name="${station_name}${device_name}base_link_joint" type="fixed">
|
<joint name="${station_name}${device_name}base_link_joint" type="fixed">
|
||||||
<origin xyz="${x} ${y} ${z}" rpy="0 0 ${r}" />
|
<origin xyz="${x} ${y} ${z}" rpy="${rx} ${ry} ${r}" />
|
||||||
<parent link="${parent_link}"/>
|
<parent link="${parent_link}"/>
|
||||||
<child link="${station_name}${device_name}device_link"/>
|
<child link="${station_name}${device_name}device_link"/>
|
||||||
<axis xyz="0 0 0"/>
|
<axis xyz="0 0 0"/>
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user