mirror of
https://github.com/dptech-corp/Uni-Lab-OS.git
synced 2025-12-19 05:51:17 +00:00
Compare commits
12 Commits
7b8638aa03
...
v0.9.7
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4139e079f4 | ||
|
|
efc0a9fbbc | ||
|
|
7db3123547 | ||
|
|
6da7a20a7a | ||
|
|
aa1c67de29 | ||
|
|
3470a1cb69 | ||
|
|
6f69df440c | ||
|
|
b420d1fa8e | ||
|
|
767e0fcdee | ||
|
|
84944396e9 | ||
|
|
bfcb214b53 | ||
|
|
ec4e6c6cfd |
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
|
||||||
8
.gitignore
vendored
8
.gitignore
vendored
@@ -6,6 +6,7 @@ __pycache__/
|
|||||||
.vscode
|
.vscode
|
||||||
*.py[cod]
|
*.py[cod]
|
||||||
*$py.class
|
*$py.class
|
||||||
|
service
|
||||||
|
|
||||||
# C extensions
|
# C extensions
|
||||||
*.so
|
*.so
|
||||||
@@ -231,4 +232,9 @@ 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 *
|
||||||
|
|||||||
73
README.md
73
README.md
@@ -4,83 +4,90 @@
|
|||||||
|
|
||||||
# Uni-Lab-OS
|
# Uni-Lab-OS
|
||||||
|
|
||||||
|
<!-- Language switcher -->
|
||||||
|
**English** | [中文](README_zh.md)
|
||||||
|
|
||||||
[](https://github.com/dptech-corp/Uni-Lab-OS/stargazers)
|
[](https://github.com/dptech-corp/Uni-Lab-OS/stargazers)
|
||||||
[](https://github.com/dptech-corp/Uni-Lab-OS/network/members)
|
[](https://github.com/dptech-corp/Uni-Lab-OS/network/members)
|
||||||
[](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 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
|
||||||
|
|
||||||
详细文档可在以下位置找到:
|
- Multi-device integration management
|
||||||
|
- Automated experimental workflows
|
||||||
|
- Cloud connectivity capabilities
|
||||||
|
- Flexible configuration system
|
||||||
|
- Support for multiple experimental protocols
|
||||||
|
|
||||||
- [在线文档](https://readthedocs.dp.tech/Uni-Lab/v0.8.0/)
|
## Documentation
|
||||||
|
|
||||||
## 快速开始
|
Detailed documentation can be found at:
|
||||||
|
|
||||||
1. 配置Conda环境
|
- [Online Documentation](https://readthedocs.dp.tech/Uni-Lab/v0.8.0/)
|
||||||
|
|
||||||
Uni-Lab-OS 建议使用 `mamba` 管理环境。根据您的操作系统选择适当的环境文件:
|
## Quick Start
|
||||||
|
|
||||||
|
1. Configure Conda Environment
|
||||||
|
|
||||||
|
Uni-Lab-OS recommends using `mamba` for environment management. Choose the appropriate environment file for your operating system:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 创建新环境
|
# Create new environment
|
||||||
mamba env create -f unilabos-[YOUR_OS].yaml
|
mamba env create -f unilabos-[YOUR_OS].yaml
|
||||||
mamba activate unilab
|
mamba activate unilab
|
||||||
|
|
||||||
# 或更新现有环境
|
# Or update existing environment
|
||||||
# 其中 `[YOUR_OS]` 可以是 `win64`, `linux-64`, `osx-64`, 或 `osx-arm64`。
|
# Where `[YOUR_OS]` can be `win64`, `linux-64`, `osx-64`, or `osx-arm64`.
|
||||||
conda env update --file unilabos-[YOUR_OS].yml -n 环境名
|
conda env update --file unilabos-[YOUR_OS].yml -n environment_name
|
||||||
|
|
||||||
# 现阶段,需要安装 `unilabos_msgs` 包
|
# Currently, you need to install the `unilabos_msgs` package
|
||||||
# 可以前往 Release 页面下载系统对应的包进行安装
|
# You can download the system-specific package from the Release page
|
||||||
conda install ros-humble-unilabos-msgs-0.9.0-xxxxx.tar.bz2
|
conda install ros-humble-unilabos-msgs-0.9.7-xxxxx.tar.bz2
|
||||||
|
|
||||||
# 安装PyLabRobot等前置
|
# Install PyLabRobot and other prerequisites
|
||||||
git clone https://github.com/PyLabRobot/pylabrobot plr_repo
|
git clone https://github.com/PyLabRobot/pylabrobot plr_repo
|
||||||
cd plr_repo
|
cd plr_repo
|
||||||
pip install .[opentrons]
|
pip install .[opentrons]
|
||||||
```
|
```
|
||||||
|
|
||||||
2. 安装 Uni-Lab-OS:
|
2. Install Uni-Lab-OS:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 克隆仓库
|
# Clone the repository
|
||||||
git clone https://github.com/dptech-corp/Uni-Lab-OS.git
|
git clone https://github.com/dptech-corp/Uni-Lab-OS.git
|
||||||
cd Uni-Lab-OS
|
cd Uni-Lab-OS
|
||||||
|
|
||||||
# 安装 Uni-Lab-OS
|
# Install Uni-Lab-OS
|
||||||
pip install .
|
pip install .
|
||||||
```
|
```
|
||||||
|
|
||||||
3. 启动 Uni-Lab 系统:
|
3. Start Uni-Lab System:
|
||||||
|
|
||||||
请见[文档-启动样例](https://readthedocs.dp.tech/Uni-Lab/v0.8.0/boot_examples/index.html)
|
Please refer to [Documentation - Boot Examples](https://readthedocs.dp.tech/Uni-Lab/v0.8.0/boot_examples/index.html)
|
||||||
|
|
||||||
## 消息格式
|
## Message Format
|
||||||
|
|
||||||
Uni-Lab-OS 使用预构建的 `unilabos_msgs` 进行系统通信。您可以在 [GitHub Releases](https://github.com/dptech-corp/Uni-Lab-OS/releases) 页面找到已构建的版本。
|
Uni-Lab-OS uses pre-built `unilabos_msgs` for system communication. You can find the built versions on the [GitHub Releases](https://github.com/dptech-corp/Uni-Lab-OS/releases) page.
|
||||||
|
|
||||||
## 许可证
|
## License
|
||||||
|
|
||||||
此项目采用 GPL-3.0 许可 - 详情请参阅 [LICENSE](LICENSE) 文件。
|
This project is licensed under GPL-3.0 - see the [LICENSE](LICENSE) file for details.
|
||||||
|
|
||||||
## 项目统计
|
## Project Statistics
|
||||||
|
|
||||||
### Stars 趋势
|
### Stars Trend
|
||||||
|
|
||||||
<a href="https://star-history.com/#dptech-corp/Uni-Lab-OS&Date">
|
<a href="https://star-history.com/#dptech-corp/Uni-Lab-OS&Date">
|
||||||
<img src="https://api.star-history.com/svg?repos=dptech-corp/Uni-Lab-OS&type=Date" alt="Star History Chart" width="600">
|
<img src="https://api.star-history.com/svg?repos=dptech-corp/Uni-Lab-OS&type=Date" alt="Star History Chart" width="600">
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
## 联系我们
|
## Contact Us
|
||||||
|
|
||||||
- GitHub Issues: [https://github.com/dptech-corp/Uni-Lab-OS/issues](https://github.com/dptech-corp/Uni-Lab-OS/issues)
|
- GitHub Issues: [https://github.com/dptech-corp/Uni-Lab-OS/issues](https://github.com/dptech-corp/Uni-Lab-OS/issues)
|
||||||
93
README_zh.md
Normal file
93
README_zh.md
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
<div align="center">
|
||||||
|
<img src="docs/logo.png" alt="Uni-Lab Logo" width="200"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
# Uni-Lab-OS
|
||||||
|
|
||||||
|
<!-- Language switcher -->
|
||||||
|
[English](README.md) | **中文**
|
||||||
|
|
||||||
|
[](https://github.com/dptech-corp/Uni-Lab-OS/stargazers)
|
||||||
|
[](https://github.com/dptech-corp/Uni-Lab-OS/network/members)
|
||||||
|
[](https://github.com/dptech-corp/Uni-Lab-OS/issues)
|
||||||
|
[](https://github.com/dptech-corp/Uni-Lab-OS/blob/main/LICENSE)
|
||||||
|
|
||||||
|
Uni-Lab-OS是一个用于实验室自动化的综合平台,旨在连接和控制各种实验设备,实现实验流程的自动化和标准化。
|
||||||
|
|
||||||
|
## 🏆 比赛
|
||||||
|
|
||||||
|
欢迎参加[有机化学合成智能实验大赛](https://bohrium.dp.tech/competitions/1451645258),使用 Uni-Lab-OS 探索自动化合成!
|
||||||
|
|
||||||
|
## 核心特点
|
||||||
|
|
||||||
|
- 多设备集成管理
|
||||||
|
- 自动化实验流程
|
||||||
|
- 云端连接能力
|
||||||
|
- 灵活的配置系统
|
||||||
|
- 支持多种实验协议
|
||||||
|
|
||||||
|
## 文档
|
||||||
|
|
||||||
|
详细文档可在以下位置找到:
|
||||||
|
|
||||||
|
- [在线文档](https://readthedocs.dp.tech/Uni-Lab/v0.8.0/)
|
||||||
|
|
||||||
|
## 快速开始
|
||||||
|
|
||||||
|
1. 配置Conda环境
|
||||||
|
|
||||||
|
Uni-Lab-OS 建议使用 `mamba` 管理环境。根据您的操作系统选择适当的环境文件:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 创建新环境
|
||||||
|
mamba env create -f unilabos-[YOUR_OS].yaml
|
||||||
|
mamba activate unilab
|
||||||
|
|
||||||
|
# 或更新现有环境
|
||||||
|
# 其中 `[YOUR_OS]` 可以是 `win64`, `linux-64`, `osx-64`, 或 `osx-arm64`。
|
||||||
|
conda env update --file unilabos-[YOUR_OS].yml -n 环境名
|
||||||
|
|
||||||
|
# 现阶段,需要安装 `unilabos_msgs` 包
|
||||||
|
# 可以前往 Release 页面下载系统对应的包进行安装
|
||||||
|
conda install ros-humble-unilabos-msgs-0.9.7-xxxxx.tar.bz2
|
||||||
|
|
||||||
|
# 安装PyLabRobot等前置
|
||||||
|
git clone https://github.com/PyLabRobot/pylabrobot plr_repo
|
||||||
|
cd plr_repo
|
||||||
|
pip install .[opentrons]
|
||||||
|
```
|
||||||
|
|
||||||
|
2. 安装 Uni-Lab-OS:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 克隆仓库
|
||||||
|
git clone https://github.com/dptech-corp/Uni-Lab-OS.git
|
||||||
|
cd Uni-Lab-OS
|
||||||
|
|
||||||
|
# 安装 Uni-Lab-OS
|
||||||
|
pip install .
|
||||||
|
```
|
||||||
|
|
||||||
|
3. 启动 Uni-Lab 系统:
|
||||||
|
|
||||||
|
请见[文档-启动样例](https://readthedocs.dp.tech/Uni-Lab/v0.8.0/boot_examples/index.html)
|
||||||
|
|
||||||
|
## 消息格式
|
||||||
|
|
||||||
|
Uni-Lab-OS 使用预构建的 `unilabos_msgs` 进行系统通信。您可以在 [GitHub Releases](https://github.com/dptech-corp/Uni-Lab-OS/releases) 页面找到已构建的版本。
|
||||||
|
|
||||||
|
## 许可证
|
||||||
|
|
||||||
|
此项目采用 GPL-3.0 许可 - 详情请参阅 [LICENSE](LICENSE) 文件。
|
||||||
|
|
||||||
|
## 项目统计
|
||||||
|
|
||||||
|
### Stars 趋势
|
||||||
|
|
||||||
|
<a href="https://star-history.com/#dptech-corp/Uni-Lab-OS&Date">
|
||||||
|
<img src="https://api.star-history.com/svg?repos=dptech-corp/Uni-Lab-OS&type=Date" alt="Star History Chart" width="600">
|
||||||
|
</a>
|
||||||
|
|
||||||
|
## 联系我们
|
||||||
|
|
||||||
|
- GitHub Issues: [https://github.com/dptech-corp/Uni-Lab-OS/issues](https://github.com/dptech-corp/Uni-Lab-OS/issues)
|
||||||
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.0
|
version: 0.9.7
|
||||||
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.0"
|
version: "0.9.7"
|
||||||
|
|
||||||
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
|
||||||
|
|||||||
3
setup.py
3
setup.py
@@ -4,7 +4,7 @@ package_name = 'unilabos'
|
|||||||
|
|
||||||
setup(
|
setup(
|
||||||
name=package_name,
|
name=package_name,
|
||||||
version='0.9.0',
|
version='0.9.7',
|
||||||
packages=find_packages(),
|
packages=find_packages(),
|
||||||
include_package_data=True,
|
include_package_data=True,
|
||||||
install_requires=['setuptools'],
|
install_requires=['setuptools'],
|
||||||
@@ -17,6 +17,7 @@ setup(
|
|||||||
entry_points={
|
entry_points={
|
||||||
'console_scripts': [
|
'console_scripts': [
|
||||||
"unilab = unilabos.app.main:main",
|
"unilab = unilabos.app.main:main",
|
||||||
|
"unilab-register = unilabos.app.register:main"
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
使用plr_test.json启动,将Well加入Plate中
|
使用plr_test.json启动,将Well加入Plate中
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
ros2 action send_goal /devices/host_node/add_resource_from_outer 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': '' }"
|
||||||
```
|
```
|
||||||
@@ -0,0 +1,563 @@
|
|||||||
|
{
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"id": "AddProtocolTestStation",
|
||||||
|
"name": "添加协议测试站",
|
||||||
|
"children": [
|
||||||
|
"transfer_pump_1",
|
||||||
|
"transfer_pump_2",
|
||||||
|
"multiway_valve_1",
|
||||||
|
"multiway_valve_2",
|
||||||
|
"stirrer_1",
|
||||||
|
"stirrer_2",
|
||||||
|
"flask_DMF",
|
||||||
|
"flask_ethyl_acetate",
|
||||||
|
"flask_methanol",
|
||||||
|
"flask_acetone",
|
||||||
|
"flask_water",
|
||||||
|
"flask_air",
|
||||||
|
"main_reactor",
|
||||||
|
"secondary_reactor",
|
||||||
|
"waste_workup",
|
||||||
|
"collection_bottle_1",
|
||||||
|
"collection_bottle_2"
|
||||||
|
],
|
||||||
|
"parent": null,
|
||||||
|
"type": "device",
|
||||||
|
"class": "workstation",
|
||||||
|
"position": {
|
||||||
|
"x": 500,
|
||||||
|
"y": 200,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"protocol_type": ["PumpTransferProtocol", "AddProtocol"]
|
||||||
|
},
|
||||||
|
"data": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "transfer_pump_1",
|
||||||
|
"name": "转移泵1",
|
||||||
|
"children": [],
|
||||||
|
"parent": "AddProtocolTestStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_transfer_pump",
|
||||||
|
"position": {
|
||||||
|
"x": 250,
|
||||||
|
"y": 300,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "VIRTUAL_PUMP1",
|
||||||
|
"max_volume": 25.0,
|
||||||
|
"transfer_rate": 5.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"position": 0.0,
|
||||||
|
"status": "Idle"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "transfer_pump_2",
|
||||||
|
"name": "转移泵2",
|
||||||
|
"children": [],
|
||||||
|
"parent": "AddProtocolTestStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_transfer_pump",
|
||||||
|
"position": {
|
||||||
|
"x": 750,
|
||||||
|
"y": 300,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "VIRTUAL_PUMP2",
|
||||||
|
"max_volume": 25.0,
|
||||||
|
"transfer_rate": 5.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"position": 0.0,
|
||||||
|
"status": "Idle"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "multiway_valve_1",
|
||||||
|
"name": "试剂分配阀",
|
||||||
|
"children": [],
|
||||||
|
"parent": "AddProtocolTestStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_multiway_valve",
|
||||||
|
"position": {
|
||||||
|
"x": 250,
|
||||||
|
"y": 400,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "VIRTUAL_VALVE1",
|
||||||
|
"positions": 8
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"current_position": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "multiway_valve_2",
|
||||||
|
"name": "反应器分配阀",
|
||||||
|
"children": [],
|
||||||
|
"parent": "AddProtocolTestStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_multiway_valve",
|
||||||
|
"position": {
|
||||||
|
"x": 750,
|
||||||
|
"y": 400,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "VIRTUAL_VALVE2",
|
||||||
|
"positions": 8
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"current_position": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "stirrer_1",
|
||||||
|
"name": "主反应器搅拌器",
|
||||||
|
"children": [],
|
||||||
|
"parent": "AddProtocolTestStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_stirrer",
|
||||||
|
"position": {
|
||||||
|
"x": 600,
|
||||||
|
"y": 450,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "VIRTUAL_STIRRER1",
|
||||||
|
"max_speed": 1500.0,
|
||||||
|
"default_speed": 300.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"speed": 0.0,
|
||||||
|
"status": "Stopped"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "stirrer_2",
|
||||||
|
"name": "副反应器搅拌器",
|
||||||
|
"children": [],
|
||||||
|
"parent": "AddProtocolTestStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_stirrer",
|
||||||
|
"position": {
|
||||||
|
"x": 900,
|
||||||
|
"y": 450,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "VIRTUAL_STIRRER2",
|
||||||
|
"max_speed": 1500.0,
|
||||||
|
"default_speed": 300.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"speed": 0.0,
|
||||||
|
"status": "Stopped"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_DMF",
|
||||||
|
"name": "DMF试剂瓶",
|
||||||
|
"children": [],
|
||||||
|
"parent": "AddProtocolTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 50,
|
||||||
|
"y": 550,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": [
|
||||||
|
{
|
||||||
|
"liquid_type": "DMF",
|
||||||
|
"liquid_volume": 800.0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_ethyl_acetate",
|
||||||
|
"name": "乙酸乙酯试剂瓶",
|
||||||
|
"children": [],
|
||||||
|
"parent": "AddProtocolTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 150,
|
||||||
|
"y": 550,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": [
|
||||||
|
{
|
||||||
|
"liquid_type": "ethyl_acetate",
|
||||||
|
"liquid_volume": 800.0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_methanol",
|
||||||
|
"name": "甲醇试剂瓶",
|
||||||
|
"children": [],
|
||||||
|
"parent": "AddProtocolTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 250,
|
||||||
|
"y": 550,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": [
|
||||||
|
{
|
||||||
|
"liquid_type": "methanol",
|
||||||
|
"liquid_volume": 800.0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_acetone",
|
||||||
|
"name": "丙酮试剂瓶",
|
||||||
|
"children": [],
|
||||||
|
"parent": "AddProtocolTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 350,
|
||||||
|
"y": 550,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": [
|
||||||
|
{
|
||||||
|
"liquid_type": "acetone",
|
||||||
|
"liquid_volume": 800.0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_water",
|
||||||
|
"name": "蒸馏水瓶",
|
||||||
|
"children": [],
|
||||||
|
"parent": "AddProtocolTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 450,
|
||||||
|
"y": 550,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": [
|
||||||
|
{
|
||||||
|
"liquid_type": "water",
|
||||||
|
"liquid_volume": 800.0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_air",
|
||||||
|
"name": "空气瓶",
|
||||||
|
"children": [],
|
||||||
|
"parent": "AddProtocolTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 550,
|
||||||
|
"y": 550,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "main_reactor",
|
||||||
|
"name": "主反应器",
|
||||||
|
"children": [],
|
||||||
|
"parent": "AddProtocolTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 600,
|
||||||
|
"y": 500,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 2000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "secondary_reactor",
|
||||||
|
"name": "副反应器",
|
||||||
|
"children": [],
|
||||||
|
"parent": "AddProtocolTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 900,
|
||||||
|
"y": 500,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "waste_workup",
|
||||||
|
"name": "废液处理瓶",
|
||||||
|
"children": [],
|
||||||
|
"parent": "AddProtocolTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 700,
|
||||||
|
"y": 600,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 2000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "collection_bottle_1",
|
||||||
|
"name": "收集瓶1",
|
||||||
|
"children": [],
|
||||||
|
"parent": "AddProtocolTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 800,
|
||||||
|
"y": 600,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "collection_bottle_2",
|
||||||
|
"name": "收集瓶2",
|
||||||
|
"children": [],
|
||||||
|
"parent": "AddProtocolTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 900,
|
||||||
|
"y": 600,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"id": "link_pump1_valve1",
|
||||||
|
"source": "transfer_pump_1",
|
||||||
|
"target": "multiway_valve_1",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"transfer_pump_1": "transferpump",
|
||||||
|
"multiway_valve_1": "transferpump"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_pump2_valve2",
|
||||||
|
"source": "transfer_pump_2",
|
||||||
|
"target": "multiway_valve_2",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"transfer_pump_2": "transferpump",
|
||||||
|
"multiway_valve_2": "transferpump"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve1_valve2",
|
||||||
|
"source": "multiway_valve_1",
|
||||||
|
"target": "multiway_valve_2",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_1": "8",
|
||||||
|
"multiway_valve_2": "1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve1_DMF",
|
||||||
|
"source": "multiway_valve_1",
|
||||||
|
"target": "flask_DMF",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_1": "1",
|
||||||
|
"flask_DMF": "outlet"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve1_ethyl_acetate",
|
||||||
|
"source": "multiway_valve_1",
|
||||||
|
"target": "flask_ethyl_acetate",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_1": "2",
|
||||||
|
"flask_ethyl_acetate": "outlet"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve1_methanol",
|
||||||
|
"source": "multiway_valve_1",
|
||||||
|
"target": "flask_methanol",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_1": "3",
|
||||||
|
"flask_methanol": "outlet"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve1_acetone",
|
||||||
|
"source": "multiway_valve_1",
|
||||||
|
"target": "flask_acetone",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_1": "4",
|
||||||
|
"flask_acetone": "outlet"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve1_water",
|
||||||
|
"source": "multiway_valve_1",
|
||||||
|
"target": "flask_water",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_1": "5",
|
||||||
|
"flask_water": "outlet"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve1_air",
|
||||||
|
"source": "multiway_valve_1",
|
||||||
|
"target": "flask_air",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_1": "6",
|
||||||
|
"flask_air": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve2_main_reactor",
|
||||||
|
"source": "multiway_valve_2",
|
||||||
|
"target": "main_reactor",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_2": "2",
|
||||||
|
"main_reactor": "inlet"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve2_secondary_reactor",
|
||||||
|
"source": "multiway_valve_2",
|
||||||
|
"target": "secondary_reactor",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_2": "3",
|
||||||
|
"secondary_reactor": "inlet"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve2_waste",
|
||||||
|
"source": "multiway_valve_2",
|
||||||
|
"target": "waste_workup",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_2": "6",
|
||||||
|
"waste_workup": "inlet"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve2_collection1",
|
||||||
|
"source": "multiway_valve_2",
|
||||||
|
"target": "collection_bottle_1",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_2": "7",
|
||||||
|
"collection_bottle_1": "inlet"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve2_collection2",
|
||||||
|
"source": "multiway_valve_2",
|
||||||
|
"target": "collection_bottle_2",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_2": "8",
|
||||||
|
"collection_bottle_2": "inlet"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_stirrer1_main_reactor",
|
||||||
|
"source": "stirrer_1",
|
||||||
|
"target": "main_reactor",
|
||||||
|
"type": "mechanical",
|
||||||
|
"port": {
|
||||||
|
"stirrer_1": "stirrer_head",
|
||||||
|
"main_reactor": "stirrer_port"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_stirrer2_secondary_reactor",
|
||||||
|
"source": "stirrer_2",
|
||||||
|
"target": "secondary_reactor",
|
||||||
|
"type": "mechanical",
|
||||||
|
"port": {
|
||||||
|
"stirrer_2": "stirrer_head",
|
||||||
|
"secondary_reactor": "stirrer_port"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,438 @@
|
|||||||
|
{
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"id": "CentrifugeProtocolTestStation",
|
||||||
|
"name": "离心协议测试站",
|
||||||
|
"children": [
|
||||||
|
"transfer_pump_1",
|
||||||
|
"transfer_pump_2",
|
||||||
|
"multiway_valve_1",
|
||||||
|
"multiway_valve_2",
|
||||||
|
"centrifuge_1",
|
||||||
|
"reaction_mixture",
|
||||||
|
"centrifuge_tube",
|
||||||
|
"collection_bottle_1",
|
||||||
|
"flask_water",
|
||||||
|
"flask_ethanol",
|
||||||
|
"flask_acetone",
|
||||||
|
"flask_air",
|
||||||
|
"waste_workup"
|
||||||
|
],
|
||||||
|
"parent": null,
|
||||||
|
"type": "device",
|
||||||
|
"class": "workstation",
|
||||||
|
"position": {
|
||||||
|
"x": 500,
|
||||||
|
"y": 200,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"protocol_type": [
|
||||||
|
"CentrifugeProtocol",
|
||||||
|
"PumpTransferProtocol"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"data": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "transfer_pump_1",
|
||||||
|
"name": "主转移泵",
|
||||||
|
"children": [],
|
||||||
|
"parent": "CentrifugeProtocolTestStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_transfer_pump",
|
||||||
|
"position": {
|
||||||
|
"x": 200,
|
||||||
|
"y": 300,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "VIRTUAL_PUMP1",
|
||||||
|
"max_volume": 25.0,
|
||||||
|
"transfer_rate": 2.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"position": 0.0,
|
||||||
|
"status": "Idle"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "transfer_pump_2",
|
||||||
|
"name": "副转移泵",
|
||||||
|
"children": [],
|
||||||
|
"parent": "CentrifugeProtocolTestStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_transfer_pump",
|
||||||
|
"position": {
|
||||||
|
"x": 400,
|
||||||
|
"y": 300,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "VIRTUAL_PUMP2",
|
||||||
|
"max_volume": 25.0,
|
||||||
|
"transfer_rate": 2.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"position": 0.0,
|
||||||
|
"status": "Idle"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "multiway_valve_1",
|
||||||
|
"name": "溶剂分配阀",
|
||||||
|
"children": [],
|
||||||
|
"parent": "CentrifugeProtocolTestStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_multiway_valve",
|
||||||
|
"position": {
|
||||||
|
"x": 200,
|
||||||
|
"y": 400,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "VIRTUAL_VALVE1",
|
||||||
|
"positions": 8
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"current_position": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "multiway_valve_2",
|
||||||
|
"name": "样品分配阀",
|
||||||
|
"children": [],
|
||||||
|
"parent": "CentrifugeProtocolTestStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_multiway_valve",
|
||||||
|
"position": {
|
||||||
|
"x": 400,
|
||||||
|
"y": 400,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "VIRTUAL_VALVE2",
|
||||||
|
"positions": 8
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"current_position": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "centrifuge_1",
|
||||||
|
"name": "离心机",
|
||||||
|
"children": [],
|
||||||
|
"parent": "CentrifugeProtocolTestStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_centrifuge",
|
||||||
|
"position": {
|
||||||
|
"x": 600,
|
||||||
|
"y": 350,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "VIRTUAL_CENTRIFUGE1",
|
||||||
|
"max_speed": 15000.0,
|
||||||
|
"max_temp": 40.0,
|
||||||
|
"min_temp": 4.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"status": "Idle"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reaction_mixture",
|
||||||
|
"name": "反应混合物",
|
||||||
|
"children": [],
|
||||||
|
"parent": "CentrifugeProtocolTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 100,
|
||||||
|
"y": 500,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 500.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": [
|
||||||
|
{
|
||||||
|
"liquid_type": "cell_suspension",
|
||||||
|
"liquid_volume": 200.0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "centrifuge_tube",
|
||||||
|
"name": "离心管",
|
||||||
|
"children": [],
|
||||||
|
"parent": "CentrifugeProtocolTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 600,
|
||||||
|
"y": 450,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 15.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "collection_bottle_1",
|
||||||
|
"name": "上清液收集瓶",
|
||||||
|
"children": [],
|
||||||
|
"parent": "CentrifugeProtocolTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 700,
|
||||||
|
"y": 500,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 500.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_water",
|
||||||
|
"name": "蒸馏水瓶",
|
||||||
|
"children": [],
|
||||||
|
"parent": "CentrifugeProtocolTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 200,
|
||||||
|
"y": 600,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": [
|
||||||
|
{
|
||||||
|
"liquid_type": "water",
|
||||||
|
"liquid_volume": 900.0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_ethanol",
|
||||||
|
"name": "乙醇清洗瓶",
|
||||||
|
"children": [],
|
||||||
|
"parent": "CentrifugeProtocolTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 300,
|
||||||
|
"y": 600,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": [
|
||||||
|
{
|
||||||
|
"liquid_type": "ethanol",
|
||||||
|
"liquid_volume": 800.0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_acetone",
|
||||||
|
"name": "丙酮清洗瓶",
|
||||||
|
"children": [],
|
||||||
|
"parent": "CentrifugeProtocolTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 400,
|
||||||
|
"y": 600,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": [
|
||||||
|
{
|
||||||
|
"liquid_type": "acetone",
|
||||||
|
"liquid_volume": 800.0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_air",
|
||||||
|
"name": "空气瓶",
|
||||||
|
"children": [],
|
||||||
|
"parent": "CentrifugeProtocolTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 100,
|
||||||
|
"y": 600,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "waste_workup",
|
||||||
|
"name": "废液瓶",
|
||||||
|
"children": [],
|
||||||
|
"parent": "CentrifugeProtocolTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 800,
|
||||||
|
"y": 550,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 2000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"id": "link_pump1_valve1",
|
||||||
|
"source": "transfer_pump_1",
|
||||||
|
"target": "multiway_valve_1",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"transfer_pump_1": "transferpump",
|
||||||
|
"multiway_valve_1": "transferpump"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_pump2_valve2",
|
||||||
|
"source": "transfer_pump_2",
|
||||||
|
"target": "multiway_valve_2",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"transfer_pump_2": "transferpump",
|
||||||
|
"multiway_valve_2": "transferpump"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve1_air",
|
||||||
|
"source": "multiway_valve_1",
|
||||||
|
"target": "flask_air",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_1": "1",
|
||||||
|
"flask_air": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve1_water",
|
||||||
|
"source": "multiway_valve_1",
|
||||||
|
"target": "flask_water",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_1": "2",
|
||||||
|
"flask_water": "outlet"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve1_ethanol",
|
||||||
|
"source": "multiway_valve_1",
|
||||||
|
"target": "flask_ethanol",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_1": "3",
|
||||||
|
"flask_ethanol": "outlet"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve1_acetone",
|
||||||
|
"source": "multiway_valve_1",
|
||||||
|
"target": "flask_acetone",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_1": "4",
|
||||||
|
"flask_acetone": "outlet"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve1_valve2",
|
||||||
|
"source": "multiway_valve_1",
|
||||||
|
"target": "multiway_valve_2",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_1": "5",
|
||||||
|
"multiway_valve_2": "1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve2_reaction_mixture",
|
||||||
|
"source": "multiway_valve_2",
|
||||||
|
"target": "reaction_mixture",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_2": "2",
|
||||||
|
"reaction_mixture": "inlet"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve2_centrifuge_tube",
|
||||||
|
"source": "multiway_valve_2",
|
||||||
|
"target": "centrifuge_tube",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_2": "3",
|
||||||
|
"centrifuge_tube": "inlet"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve2_collection",
|
||||||
|
"source": "multiway_valve_2",
|
||||||
|
"target": "collection_bottle_1",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_2": "4",
|
||||||
|
"collection_bottle_1": "inlet"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve2_waste",
|
||||||
|
"source": "multiway_valve_2",
|
||||||
|
"target": "waste_workup",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_2": "5",
|
||||||
|
"waste_workup": "inlet"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_centrifuge1_centrifuge_tube",
|
||||||
|
"source": "centrifuge_1",
|
||||||
|
"target": "centrifuge_tube",
|
||||||
|
"type": "transport",
|
||||||
|
"port": {
|
||||||
|
"centrifuge_1": "centrifuge",
|
||||||
|
"centrifuge_tube": "centrifuge_port"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,446 @@
|
|||||||
|
{
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"id": "CleanVesselProtocolTestStation",
|
||||||
|
"name": "容器清洗协议测试站",
|
||||||
|
"children": [
|
||||||
|
"transfer_pump_1",
|
||||||
|
"transfer_pump_2",
|
||||||
|
"multiway_valve_1",
|
||||||
|
"multiway_valve_2",
|
||||||
|
"heatchill_1",
|
||||||
|
"flask_water",
|
||||||
|
"flask_acetone",
|
||||||
|
"flask_ethanol",
|
||||||
|
"flask_air",
|
||||||
|
"main_reactor",
|
||||||
|
"secondary_reactor",
|
||||||
|
"waste_workup"
|
||||||
|
],
|
||||||
|
"parent": null,
|
||||||
|
"type": "device",
|
||||||
|
"class": "workstation",
|
||||||
|
"position": {
|
||||||
|
"x": 500,
|
||||||
|
"y": 200,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"protocol_type": [
|
||||||
|
"CleanVesselProtocol",
|
||||||
|
"PumpTransferProtocol",
|
||||||
|
"HeatChillProtocol",
|
||||||
|
"HeatChillStartProtocol",
|
||||||
|
"HeatChillStopProtocol"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"data": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "transfer_pump_1",
|
||||||
|
"name": "主清洗泵",
|
||||||
|
"children": [],
|
||||||
|
"parent": "CleanVesselProtocolTestStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_transfer_pump",
|
||||||
|
"position": {
|
||||||
|
"x": 250,
|
||||||
|
"y": 300,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "VIRTUAL_PUMP1",
|
||||||
|
"max_volume": 25.0,
|
||||||
|
"transfer_rate": 2.5
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"position": 0.0,
|
||||||
|
"status": "Idle"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "transfer_pump_2",
|
||||||
|
"name": "副清洗泵",
|
||||||
|
"children": [],
|
||||||
|
"parent": "CleanVesselProtocolTestStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_transfer_pump",
|
||||||
|
"position": {
|
||||||
|
"x": 450,
|
||||||
|
"y": 300,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "VIRTUAL_PUMP2",
|
||||||
|
"max_volume": 25.0,
|
||||||
|
"transfer_rate": 2.5
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"position": 0.0,
|
||||||
|
"status": "Idle"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "multiway_valve_1",
|
||||||
|
"name": "溶剂分配阀",
|
||||||
|
"children": [],
|
||||||
|
"parent": "CleanVesselProtocolTestStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_multiway_valve",
|
||||||
|
"position": {
|
||||||
|
"x": 250,
|
||||||
|
"y": 400,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "VIRTUAL_VALVE1",
|
||||||
|
"positions": 8
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"current_position": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "multiway_valve_2",
|
||||||
|
"name": "容器分配阀",
|
||||||
|
"children": [],
|
||||||
|
"parent": "CleanVesselProtocolTestStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_multiway_valve",
|
||||||
|
"position": {
|
||||||
|
"x": 450,
|
||||||
|
"y": 400,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "VIRTUAL_VALVE2",
|
||||||
|
"positions": 8
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"current_position": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "heatchill_1",
|
||||||
|
"name": "加热清洗器",
|
||||||
|
"children": [],
|
||||||
|
"parent": "CleanVesselProtocolTestStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_heatchill",
|
||||||
|
"position": {
|
||||||
|
"x": 600,
|
||||||
|
"y": 350,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "VIRTUAL_HEATCHILL1",
|
||||||
|
"max_temp": 100.0,
|
||||||
|
"min_temp": 10.0,
|
||||||
|
"max_stir_speed": 500.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"status": "Idle"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_water",
|
||||||
|
"name": "蒸馏水瓶",
|
||||||
|
"children": [],
|
||||||
|
"parent": "CleanVesselProtocolTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": "container",
|
||||||
|
"position": {
|
||||||
|
"x": 50,
|
||||||
|
"y": 500,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": [
|
||||||
|
{
|
||||||
|
"liquid_type": "water",
|
||||||
|
"liquid_volume": 900.0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_acetone",
|
||||||
|
"name": "丙酮清洗瓶",
|
||||||
|
"children": [],
|
||||||
|
"parent": "CleanVesselProtocolTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": "container",
|
||||||
|
"position": {
|
||||||
|
"x": 150,
|
||||||
|
"y": 500,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": [
|
||||||
|
{
|
||||||
|
"liquid_type": "acetone",
|
||||||
|
"liquid_volume": 800.0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_ethanol",
|
||||||
|
"name": "乙醇清洗瓶",
|
||||||
|
"children": [],
|
||||||
|
"parent": "CleanVesselProtocolTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": "container",
|
||||||
|
"position": {
|
||||||
|
"x": 250,
|
||||||
|
"y": 500,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": [
|
||||||
|
{
|
||||||
|
"liquid_type": "ethanol",
|
||||||
|
"liquid_volume": 800.0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_air",
|
||||||
|
"name": "空气瓶",
|
||||||
|
"children": [],
|
||||||
|
"parent": "CleanVesselProtocolTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": "container",
|
||||||
|
"position": {
|
||||||
|
"x": 350,
|
||||||
|
"y": 500,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "main_reactor",
|
||||||
|
"name": "主反应器",
|
||||||
|
"children": [],
|
||||||
|
"parent": "CleanVesselProtocolTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": "container",
|
||||||
|
"position": {
|
||||||
|
"x": 600,
|
||||||
|
"y": 450,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 2000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": [
|
||||||
|
{
|
||||||
|
"liquid_type": "residue",
|
||||||
|
"liquid_volume": 50.0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "secondary_reactor",
|
||||||
|
"name": "副反应器",
|
||||||
|
"children": [],
|
||||||
|
"parent": "CleanVesselProtocolTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": "container",
|
||||||
|
"position": {
|
||||||
|
"x": 800,
|
||||||
|
"y": 450,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": [
|
||||||
|
{
|
||||||
|
"liquid_type": "organic_residue",
|
||||||
|
"liquid_volume": 30.0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "waste_workup",
|
||||||
|
"name": "清洗废液瓶",
|
||||||
|
"children": [],
|
||||||
|
"parent": "CleanVesselProtocolTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": "container",
|
||||||
|
"position": {
|
||||||
|
"x": 700,
|
||||||
|
"y": 550,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 3000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"id": "link_pump1_to_valve1",
|
||||||
|
"source": "transfer_pump_1",
|
||||||
|
"target": "multiway_valve_1",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"transfer_pump_1": "transferpump",
|
||||||
|
"multiway_valve_1": "transferpump"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_pump2_to_valve2",
|
||||||
|
"source": "transfer_pump_2",
|
||||||
|
"target": "multiway_valve_2",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"transfer_pump_2": "transferpump",
|
||||||
|
"multiway_valve_2": "transferpump"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve1_to_water",
|
||||||
|
"source": "multiway_valve_1",
|
||||||
|
"target": "flask_water",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_1": "2",
|
||||||
|
"flask_water": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve1_to_acetone",
|
||||||
|
"source": "multiway_valve_1",
|
||||||
|
"target": "flask_acetone",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_1": "3",
|
||||||
|
"flask_acetone": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve1_to_ethanol",
|
||||||
|
"source": "multiway_valve_1",
|
||||||
|
"target": "flask_ethanol",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_1": "4",
|
||||||
|
"flask_ethanol": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve1_to_air",
|
||||||
|
"source": "multiway_valve_1",
|
||||||
|
"target": "flask_air",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_1": "6",
|
||||||
|
"flask_air": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve1_to_valve2_for_cleaning",
|
||||||
|
"source": "multiway_valve_1",
|
||||||
|
"target": "multiway_valve_2",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_1": "1",
|
||||||
|
"multiway_valve_2": "8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve2_to_main_reactor_in",
|
||||||
|
"source": "multiway_valve_2",
|
||||||
|
"target": "main_reactor",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_2": "2",
|
||||||
|
"main_reactor": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve2_to_secondary_reactor_in",
|
||||||
|
"source": "multiway_valve_2",
|
||||||
|
"target": "secondary_reactor",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_2": "3",
|
||||||
|
"secondary_reactor": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_main_reactor_out_to_valve2",
|
||||||
|
"source": "main_reactor",
|
||||||
|
"target": "multiway_valve_2",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"main_reactor": "bottom",
|
||||||
|
"multiway_valve_2": "6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_secondary_reactor_out_to_valve2",
|
||||||
|
"source": "secondary_reactor",
|
||||||
|
"target": "multiway_valve_2",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"secondary_reactor": "bottom",
|
||||||
|
"multiway_valve_2": "7"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve2_to_waste",
|
||||||
|
"source": "multiway_valve_2",
|
||||||
|
"target": "waste_workup",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_2": "4",
|
||||||
|
"waste_workup": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_heatchill1_to_main_reactor",
|
||||||
|
"source": "heatchill_1",
|
||||||
|
"target": "main_reactor",
|
||||||
|
"type": "mechanical",
|
||||||
|
"port": {
|
||||||
|
"heatchill_1": "heatchill",
|
||||||
|
"main_reactor": "bind"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_heatchill1_to_secondary_reactor",
|
||||||
|
"source": "heatchill_1",
|
||||||
|
"target": "secondary_reactor",
|
||||||
|
"type": "mechanical",
|
||||||
|
"port": {
|
||||||
|
"heatchill_1": "heatchill",
|
||||||
|
"secondary_reactor": "bind"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,367 @@
|
|||||||
|
{
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"id": "DualValvePumpStation",
|
||||||
|
"name": "双阀门泵站",
|
||||||
|
"children": [
|
||||||
|
"transfer_pump_1",
|
||||||
|
"transfer_pump_2",
|
||||||
|
"multiway_valve_1",
|
||||||
|
"multiway_valve_2",
|
||||||
|
"flask_DMF",
|
||||||
|
"flask_ethyl_acetate",
|
||||||
|
"flask_methanol",
|
||||||
|
"flask_air",
|
||||||
|
"main_reactor",
|
||||||
|
"waste_workup",
|
||||||
|
"collection_bottle_1"
|
||||||
|
],
|
||||||
|
"parent": null,
|
||||||
|
"type": "device",
|
||||||
|
"class": "workstation",
|
||||||
|
"position": {
|
||||||
|
"x": 500,
|
||||||
|
"y": 200,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"protocol_type": ["PumpTransferProtocol"]
|
||||||
|
},
|
||||||
|
"data": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "transfer_pump_1",
|
||||||
|
"name": "转移泵1",
|
||||||
|
"children": [],
|
||||||
|
"parent": "DualValvePumpStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_transfer_pump",
|
||||||
|
"position": {
|
||||||
|
"x": 300,
|
||||||
|
"y": 300,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "VIRTUAL_PUMP1",
|
||||||
|
"max_volume": 25.0,
|
||||||
|
"transfer_rate": 5.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"position": 0.0,
|
||||||
|
"status": "Idle"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "transfer_pump_2",
|
||||||
|
"name": "转移泵2",
|
||||||
|
"children": [],
|
||||||
|
"parent": "DualValvePumpStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_transfer_pump",
|
||||||
|
"position": {
|
||||||
|
"x": 700,
|
||||||
|
"y": 300,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "VIRTUAL_PUMP2",
|
||||||
|
"max_volume": 25.0,
|
||||||
|
"transfer_rate": 5.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"position": 0.0,
|
||||||
|
"status": "Idle"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "multiway_valve_1",
|
||||||
|
"name": "第一个八通阀",
|
||||||
|
"children": [],
|
||||||
|
"parent": "DualValvePumpStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_multiway_valve",
|
||||||
|
"position": {
|
||||||
|
"x": 300,
|
||||||
|
"y": 400,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "VIRTUAL_VALVE1",
|
||||||
|
"positions": 8
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"current_position": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "multiway_valve_2",
|
||||||
|
"name": "第二个八通阀",
|
||||||
|
"children": [],
|
||||||
|
"parent": "DualValvePumpStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_multiway_valve",
|
||||||
|
"position": {
|
||||||
|
"x": 700,
|
||||||
|
"y": 400,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "VIRTUAL_VALVE2",
|
||||||
|
"positions": 8
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"current_position": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_DMF",
|
||||||
|
"name": "DMF试剂瓶",
|
||||||
|
"children": [],
|
||||||
|
"parent": "DualValvePumpStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 100,
|
||||||
|
"y": 500,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": [
|
||||||
|
{
|
||||||
|
"liquid_type": "DMF",
|
||||||
|
"liquid_volume": 800.0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_ethyl_acetate",
|
||||||
|
"name": "乙酸乙酯试剂瓶",
|
||||||
|
"children": [],
|
||||||
|
"parent": "DualValvePumpStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 200,
|
||||||
|
"y": 500,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": [
|
||||||
|
{
|
||||||
|
"liquid_type": "ethyl_acetate",
|
||||||
|
"liquid_volume": 800.0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_methanol",
|
||||||
|
"name": "甲醇试剂瓶",
|
||||||
|
"children": [],
|
||||||
|
"parent": "DualValvePumpStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 300,
|
||||||
|
"y": 500,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": [
|
||||||
|
{
|
||||||
|
"liquid_type": "methanol",
|
||||||
|
"liquid_volume": 800.0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_air",
|
||||||
|
"name": "空气瓶",
|
||||||
|
"children": [],
|
||||||
|
"parent": "DualValvePumpStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 400,
|
||||||
|
"y": 500,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "main_reactor",
|
||||||
|
"name": "主反应器",
|
||||||
|
"children": [],
|
||||||
|
"parent": "DualValvePumpStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 600,
|
||||||
|
"y": 500,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 2000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "waste_workup",
|
||||||
|
"name": "废液处理瓶",
|
||||||
|
"children": [],
|
||||||
|
"parent": "DualValvePumpStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 700,
|
||||||
|
"y": 500,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 2000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "collection_bottle_1",
|
||||||
|
"name": "收集瓶1",
|
||||||
|
"children": [],
|
||||||
|
"parent": "DualValvePumpStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 800,
|
||||||
|
"y": 500,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"id": "link_pump1_valve1",
|
||||||
|
"source": "transfer_pump_1",
|
||||||
|
"target": "multiway_valve_1",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"transfer_pump_1": "transferpump",
|
||||||
|
"multiway_valve_1": "transferpump"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_pump2_valve2",
|
||||||
|
"source": "transfer_pump_2",
|
||||||
|
"target": "multiway_valve_2",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"transfer_pump_2": "transferpump",
|
||||||
|
"multiway_valve_2": "transferpump"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve1_valve2",
|
||||||
|
"source": "multiway_valve_1",
|
||||||
|
"target": "multiway_valve_2",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_1": "8",
|
||||||
|
"multiway_valve_2": "1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve1_air",
|
||||||
|
"source": "multiway_valve_1",
|
||||||
|
"target": "flask_air",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_1": "1",
|
||||||
|
"flask_air": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve1_DMF",
|
||||||
|
"source": "multiway_valve_1",
|
||||||
|
"target": "flask_DMF",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_1": "2",
|
||||||
|
"flask_DMF": "outlet"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve1_ethyl_acetate",
|
||||||
|
"source": "multiway_valve_1",
|
||||||
|
"target": "flask_ethyl_acetate",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_1": "3",
|
||||||
|
"flask_ethyl_acetate": "outlet"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve1_methanol",
|
||||||
|
"source": "multiway_valve_1",
|
||||||
|
"target": "flask_methanol",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_1": "4",
|
||||||
|
"flask_methanol": "outlet"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve2_reactor",
|
||||||
|
"source": "multiway_valve_2",
|
||||||
|
"target": "main_reactor",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_2": "5",
|
||||||
|
"main_reactor": "inlet"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve2_waste",
|
||||||
|
"source": "multiway_valve_2",
|
||||||
|
"target": "waste_workup",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_2": "6",
|
||||||
|
"waste_workup": "inlet"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve2_collection",
|
||||||
|
"source": "multiway_valve_2",
|
||||||
|
"target": "collection_bottle_1",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_2": "7",
|
||||||
|
"collection_bottle_1": "inlet"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,557 @@
|
|||||||
|
{
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"id": "EvacuateRefillTestStation",
|
||||||
|
"name": "抽真空充气测试站",
|
||||||
|
"children": [
|
||||||
|
"transfer_pump_1",
|
||||||
|
"transfer_pump_2",
|
||||||
|
"multiway_valve_1",
|
||||||
|
"multiway_valve_2",
|
||||||
|
"flask_DMF",
|
||||||
|
"flask_ethyl_acetate",
|
||||||
|
"flask_methanol",
|
||||||
|
"flask_air",
|
||||||
|
"vacuum_pump_1",
|
||||||
|
"gas_source_nitrogen",
|
||||||
|
"gas_source_air",
|
||||||
|
"solenoid_valve_vacuum",
|
||||||
|
"solenoid_valve_gas",
|
||||||
|
"main_reactor",
|
||||||
|
"stirrer_1",
|
||||||
|
"waste_workup",
|
||||||
|
"collection_bottle_1"
|
||||||
|
],
|
||||||
|
"parent": null,
|
||||||
|
"type": "device",
|
||||||
|
"class": "workstation",
|
||||||
|
"position": {
|
||||||
|
"x": 500,
|
||||||
|
"y": 200,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"protocol_type": ["PumpTransferProtocol", "EvacuateAndRefillProtocol"]
|
||||||
|
},
|
||||||
|
"data": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "transfer_pump_1",
|
||||||
|
"name": "转移泵1",
|
||||||
|
"children": [],
|
||||||
|
"parent": "EvacuateRefillTestStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_transfer_pump",
|
||||||
|
"position": {
|
||||||
|
"x": 300,
|
||||||
|
"y": 300,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "VIRTUAL_PUMP1",
|
||||||
|
"max_volume": 25.0,
|
||||||
|
"transfer_rate": 5.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"position": 0.0,
|
||||||
|
"status": "Idle"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "transfer_pump_2",
|
||||||
|
"name": "转移泵2",
|
||||||
|
"children": [],
|
||||||
|
"parent": "EvacuateRefillTestStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_transfer_pump",
|
||||||
|
"position": {
|
||||||
|
"x": 700,
|
||||||
|
"y": 300,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "VIRTUAL_PUMP2",
|
||||||
|
"max_volume": 25.0,
|
||||||
|
"transfer_rate": 5.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"position": 0.0,
|
||||||
|
"status": "Idle"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "multiway_valve_1",
|
||||||
|
"name": "第一个八通阀",
|
||||||
|
"children": [],
|
||||||
|
"parent": "EvacuateRefillTestStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_multiway_valve",
|
||||||
|
"position": {
|
||||||
|
"x": 300,
|
||||||
|
"y": 400,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "VIRTUAL_VALVE1",
|
||||||
|
"positions": 8
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"current_position": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "multiway_valve_2",
|
||||||
|
"name": "第二个八通阀",
|
||||||
|
"children": [],
|
||||||
|
"parent": "EvacuateRefillTestStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_multiway_valve",
|
||||||
|
"position": {
|
||||||
|
"x": 700,
|
||||||
|
"y": 400,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "VIRTUAL_VALVE2",
|
||||||
|
"positions": 8
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"current_position": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "vacuum_pump_1",
|
||||||
|
"name": "真空泵1",
|
||||||
|
"children": [],
|
||||||
|
"parent": "EvacuateRefillTestStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_vacuum_pump",
|
||||||
|
"position": {
|
||||||
|
"x": 150,
|
||||||
|
"y": 200,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "VIRTUAL_VACUUM1",
|
||||||
|
"max_pressure": -0.9
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"status": "OFF",
|
||||||
|
"pressure": 0.0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "gas_source_nitrogen",
|
||||||
|
"name": "氮气源",
|
||||||
|
"children": [],
|
||||||
|
"parent": "EvacuateRefillTestStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_gas_source",
|
||||||
|
"position": {
|
||||||
|
"x": 850,
|
||||||
|
"y": 200,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "VIRTUAL_GAS_N2",
|
||||||
|
"gas_type": "nitrogen",
|
||||||
|
"max_pressure": 5.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"status": "OFF",
|
||||||
|
"flow_rate": 0.0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "gas_source_air",
|
||||||
|
"name": "空气源",
|
||||||
|
"children": [],
|
||||||
|
"parent": "EvacuateRefillTestStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_gas_source",
|
||||||
|
"position": {
|
||||||
|
"x": 950,
|
||||||
|
"y": 200,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "VIRTUAL_GAS_AIR",
|
||||||
|
"gas_type": "air",
|
||||||
|
"max_pressure": 3.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"status": "OFF",
|
||||||
|
"flow_rate": 0.0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "solenoid_valve_vacuum",
|
||||||
|
"name": "真空电磁阀",
|
||||||
|
"children": [],
|
||||||
|
"parent": "EvacuateRefillTestStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_solenoid_valve",
|
||||||
|
"position": {
|
||||||
|
"x": 225,
|
||||||
|
"y": 300,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "VIRTUAL_SOLENOID_VACUUM"
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"valve_position": "CLOSED"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "solenoid_valve_gas",
|
||||||
|
"name": "气源电磁阀",
|
||||||
|
"children": [],
|
||||||
|
"parent": "EvacuateRefillTestStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_solenoid_valve",
|
||||||
|
"position": {
|
||||||
|
"x": 775,
|
||||||
|
"y": 300,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "VIRTUAL_SOLENOID_GAS"
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"valve_position": "CLOSED"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_DMF",
|
||||||
|
"name": "DMF试剂瓶",
|
||||||
|
"children": [],
|
||||||
|
"parent": "EvacuateRefillTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 100,
|
||||||
|
"y": 500,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": [
|
||||||
|
{
|
||||||
|
"liquid_type": "DMF",
|
||||||
|
"liquid_volume": 800.0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_ethyl_acetate",
|
||||||
|
"name": "乙酸乙酯试剂瓶",
|
||||||
|
"children": [],
|
||||||
|
"parent": "EvacuateRefillTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 200,
|
||||||
|
"y": 500,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": [
|
||||||
|
{
|
||||||
|
"liquid_type": "ethyl_acetate",
|
||||||
|
"liquid_volume": 800.0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_methanol",
|
||||||
|
"name": "甲醇试剂瓶",
|
||||||
|
"children": [],
|
||||||
|
"parent": "EvacuateRefillTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 300,
|
||||||
|
"y": 500,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": [
|
||||||
|
{
|
||||||
|
"liquid_type": "methanol",
|
||||||
|
"liquid_volume": 800.0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_air",
|
||||||
|
"name": "空气瓶",
|
||||||
|
"children": [],
|
||||||
|
"parent": "EvacuateRefillTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 400,
|
||||||
|
"y": 500,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "main_reactor",
|
||||||
|
"name": "主反应器",
|
||||||
|
"children": [],
|
||||||
|
"parent": "EvacuateRefillTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 600,
|
||||||
|
"y": 500,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 2000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "stirrer_1",
|
||||||
|
"name": "搅拌器1",
|
||||||
|
"children": [],
|
||||||
|
"parent": "EvacuateRefillTestStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_stirrer",
|
||||||
|
"position": {
|
||||||
|
"x": 600,
|
||||||
|
"y": 450,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "VIRTUAL_STIRRER1",
|
||||||
|
"max_speed": 1500.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"speed": 0.0,
|
||||||
|
"status": "OFF"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "waste_workup",
|
||||||
|
"name": "废液处理瓶",
|
||||||
|
"children": [],
|
||||||
|
"parent": "EvacuateRefillTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 700,
|
||||||
|
"y": 500,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 2000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "collection_bottle_1",
|
||||||
|
"name": "收集瓶1",
|
||||||
|
"children": [],
|
||||||
|
"parent": "EvacuateRefillTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 800,
|
||||||
|
"y": 500,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"id": "link_pump1_valve1",
|
||||||
|
"source": "transfer_pump_1",
|
||||||
|
"target": "multiway_valve_1",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"transfer_pump_1": "transferpump",
|
||||||
|
"multiway_valve_1": "transferpump"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_pump2_valve2",
|
||||||
|
"source": "transfer_pump_2",
|
||||||
|
"target": "multiway_valve_2",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"transfer_pump_2": "transferpump",
|
||||||
|
"multiway_valve_2": "transferpump"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve1_valve2",
|
||||||
|
"source": "multiway_valve_1",
|
||||||
|
"target": "multiway_valve_2",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_1": "8",
|
||||||
|
"multiway_valve_2": "1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_vacuum_solenoid",
|
||||||
|
"source": "vacuum_pump_1",
|
||||||
|
"target": "solenoid_valve_vacuum",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"vacuum_pump_1": "outlet",
|
||||||
|
"solenoid_valve_vacuum": "inlet"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_solenoid_vacuum_valve1",
|
||||||
|
"source": "solenoid_valve_vacuum",
|
||||||
|
"target": "multiway_valve_1",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"solenoid_valve_vacuum": "outlet",
|
||||||
|
"multiway_valve_1": "7"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_gas_solenoid",
|
||||||
|
"source": "gas_source_nitrogen",
|
||||||
|
"target": "solenoid_valve_gas",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"gas_source_nitrogen": "outlet",
|
||||||
|
"solenoid_valve_gas": "inlet"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_solenoid_gas_valve2",
|
||||||
|
"source": "solenoid_valve_gas",
|
||||||
|
"target": "multiway_valve_2",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"solenoid_valve_gas": "outlet",
|
||||||
|
"multiway_valve_2": "8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_air_source_valve2",
|
||||||
|
"source": "gas_source_air",
|
||||||
|
"target": "multiway_valve_2",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"gas_source_air": "outlet",
|
||||||
|
"multiway_valve_2": "2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve1_air",
|
||||||
|
"source": "multiway_valve_1",
|
||||||
|
"target": "flask_air",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_1": "1",
|
||||||
|
"flask_air": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve1_DMF",
|
||||||
|
"source": "multiway_valve_1",
|
||||||
|
"target": "flask_DMF",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_1": "2",
|
||||||
|
"flask_DMF": "outlet"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve1_ethyl_acetate",
|
||||||
|
"source": "multiway_valve_1",
|
||||||
|
"target": "flask_ethyl_acetate",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_1": "3",
|
||||||
|
"flask_ethyl_acetate": "outlet"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve1_methanol",
|
||||||
|
"source": "multiway_valve_1",
|
||||||
|
"target": "flask_methanol",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_1": "4",
|
||||||
|
"flask_methanol": "outlet"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve2_reactor",
|
||||||
|
"source": "multiway_valve_2",
|
||||||
|
"target": "main_reactor",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_2": "5",
|
||||||
|
"main_reactor": "inlet"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve2_waste",
|
||||||
|
"source": "multiway_valve_2",
|
||||||
|
"target": "waste_workup",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_2": "6",
|
||||||
|
"waste_workup": "inlet"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve2_collection",
|
||||||
|
"source": "multiway_valve_2",
|
||||||
|
"target": "collection_bottle_1",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_2": "7",
|
||||||
|
"collection_bottle_1": "inlet"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_stirrer_reactor",
|
||||||
|
"source": "stirrer_1",
|
||||||
|
"target": "main_reactor",
|
||||||
|
"type": "mechanical",
|
||||||
|
"port": {
|
||||||
|
"stirrer_1": "stirrer",
|
||||||
|
"main_reactor": "stirrer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,503 @@
|
|||||||
|
{
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"id": "EvaporateProtocolTestStation",
|
||||||
|
"name": "蒸发协议测试站",
|
||||||
|
"children": [
|
||||||
|
"transfer_pump_1",
|
||||||
|
"transfer_pump_2",
|
||||||
|
"multiway_valve_1",
|
||||||
|
"multiway_valve_2",
|
||||||
|
"rotavap_1",
|
||||||
|
"heatchill_1",
|
||||||
|
"reaction_mixture",
|
||||||
|
"rotavap_flask",
|
||||||
|
"rotavap_condenser",
|
||||||
|
"flask_distillate",
|
||||||
|
"flask_ethanol",
|
||||||
|
"flask_acetone",
|
||||||
|
"flask_water",
|
||||||
|
"flask_air",
|
||||||
|
"waste_workup"
|
||||||
|
],
|
||||||
|
"parent": null,
|
||||||
|
"type": "device",
|
||||||
|
"class": "workstation",
|
||||||
|
"position": {
|
||||||
|
"x": 500,
|
||||||
|
"y": 200,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"protocol_type": [
|
||||||
|
"EvaporateProtocol",
|
||||||
|
"PumpTransferProtocol",
|
||||||
|
"HeatChillProtocol",
|
||||||
|
"HeatChillStartProtocol",
|
||||||
|
"HeatChillStopProtocol"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"data": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "transfer_pump_1",
|
||||||
|
"name": "主转移泵",
|
||||||
|
"children": [],
|
||||||
|
"parent": "EvaporateProtocolTestStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_transfer_pump",
|
||||||
|
"position": {
|
||||||
|
"x": 200,
|
||||||
|
"y": 300,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "VIRTUAL_PUMP1",
|
||||||
|
"max_volume": 25.0,
|
||||||
|
"transfer_rate": 2.5
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"position": 0.0,
|
||||||
|
"status": "Idle"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "transfer_pump_2",
|
||||||
|
"name": "副转移泵",
|
||||||
|
"children": [],
|
||||||
|
"parent": "EvaporateProtocolTestStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_transfer_pump",
|
||||||
|
"position": {
|
||||||
|
"x": 400,
|
||||||
|
"y": 300,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "VIRTUAL_PUMP2",
|
||||||
|
"max_volume": 25.0,
|
||||||
|
"transfer_rate": 2.5
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"position": 0.0,
|
||||||
|
"status": "Idle"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "multiway_valve_1",
|
||||||
|
"name": "溶剂分配阀",
|
||||||
|
"children": [],
|
||||||
|
"parent": "EvaporateProtocolTestStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_multiway_valve",
|
||||||
|
"position": {
|
||||||
|
"x": 200,
|
||||||
|
"y": 400,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "VIRTUAL_VALVE1",
|
||||||
|
"positions": 8
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"current_position": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "multiway_valve_2",
|
||||||
|
"name": "容器分配阀",
|
||||||
|
"children": [],
|
||||||
|
"parent": "EvaporateProtocolTestStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_multiway_valve",
|
||||||
|
"position": {
|
||||||
|
"x": 400,
|
||||||
|
"y": 400,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "VIRTUAL_VALVE2",
|
||||||
|
"positions": 8
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"current_position": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "rotavap_1",
|
||||||
|
"name": "旋转蒸发仪",
|
||||||
|
"children": [],
|
||||||
|
"parent": "EvaporateProtocolTestStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_rotavap",
|
||||||
|
"position": {
|
||||||
|
"x": 700,
|
||||||
|
"y": 350,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "VIRTUAL_ROTAVAP1",
|
||||||
|
"max_temp": 180.0,
|
||||||
|
"max_rotation_speed": 280.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"status": "Ready"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "heatchill_1",
|
||||||
|
"name": "预加热器",
|
||||||
|
"children": [],
|
||||||
|
"parent": "EvaporateProtocolTestStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_heatchill",
|
||||||
|
"position": {
|
||||||
|
"x": 100,
|
||||||
|
"y": 550,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "VIRTUAL_HEATCHILL1",
|
||||||
|
"max_temp": 100.0,
|
||||||
|
"min_temp": 10.0,
|
||||||
|
"max_stir_speed": 500.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"status": "Idle"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reaction_mixture",
|
||||||
|
"name": "反应混合物",
|
||||||
|
"children": [],
|
||||||
|
"parent": "EvaporateProtocolTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 100,
|
||||||
|
"y": 450,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": [
|
||||||
|
{
|
||||||
|
"liquid_type": "reaction_mixture",
|
||||||
|
"liquid_volume": 600.0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "rotavap_flask",
|
||||||
|
"name": "旋蒸样品瓶",
|
||||||
|
"children": [],
|
||||||
|
"parent": "EvaporateProtocolTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 700,
|
||||||
|
"y": 450,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 500.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "rotavap_condenser",
|
||||||
|
"name": "旋蒸冷凝器",
|
||||||
|
"children": [],
|
||||||
|
"parent": "EvaporateProtocolTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 800,
|
||||||
|
"y": 350,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 500.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_distillate",
|
||||||
|
"name": "溶剂回收瓶",
|
||||||
|
"children": [],
|
||||||
|
"parent": "EvaporateProtocolTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 800,
|
||||||
|
"y": 450,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_ethanol",
|
||||||
|
"name": "乙醇清洗瓶",
|
||||||
|
"children": [],
|
||||||
|
"parent": "EvaporateProtocolTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 50,
|
||||||
|
"y": 600,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": [
|
||||||
|
{
|
||||||
|
"liquid_type": "ethanol",
|
||||||
|
"liquid_volume": 800.0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_acetone",
|
||||||
|
"name": "丙酮清洗瓶",
|
||||||
|
"children": [],
|
||||||
|
"parent": "EvaporateProtocolTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 150,
|
||||||
|
"y": 600,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": [
|
||||||
|
{
|
||||||
|
"liquid_type": "acetone",
|
||||||
|
"liquid_volume": 800.0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_water",
|
||||||
|
"name": "蒸馏水瓶",
|
||||||
|
"children": [],
|
||||||
|
"parent": "EvaporateProtocolTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 250,
|
||||||
|
"y": 600,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": [
|
||||||
|
{
|
||||||
|
"liquid_type": "water",
|
||||||
|
"liquid_volume": 900.0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_air",
|
||||||
|
"name": "空气瓶",
|
||||||
|
"children": [],
|
||||||
|
"parent": "EvaporateProtocolTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 350,
|
||||||
|
"y": 600,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "waste_workup",
|
||||||
|
"name": "废液瓶",
|
||||||
|
"children": [],
|
||||||
|
"parent": "EvaporateProtocolTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 600,
|
||||||
|
"y": 550,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 3000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"id": "link_pump1_valve1",
|
||||||
|
"source": "transfer_pump_1",
|
||||||
|
"target": "multiway_valve_1",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"transfer_pump_1": "transferpump",
|
||||||
|
"multiway_valve_1": "transferpump"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_pump2_valve2",
|
||||||
|
"source": "transfer_pump_2",
|
||||||
|
"target": "multiway_valve_2",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"transfer_pump_2": "transferpump",
|
||||||
|
"multiway_valve_2": "transferpump"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve1_air",
|
||||||
|
"source": "multiway_valve_1",
|
||||||
|
"target": "flask_air",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_1": "1",
|
||||||
|
"flask_air": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve1_ethanol",
|
||||||
|
"source": "multiway_valve_1",
|
||||||
|
"target": "flask_ethanol",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_1": "2",
|
||||||
|
"flask_ethanol": "outlet"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve1_acetone",
|
||||||
|
"source": "multiway_valve_1",
|
||||||
|
"target": "flask_acetone",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_1": "3",
|
||||||
|
"flask_acetone": "outlet"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve1_water",
|
||||||
|
"source": "multiway_valve_1",
|
||||||
|
"target": "flask_water",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_1": "4",
|
||||||
|
"flask_water": "outlet"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve1_valve2",
|
||||||
|
"source": "multiway_valve_1",
|
||||||
|
"target": "multiway_valve_2",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_1": "5",
|
||||||
|
"multiway_valve_2": "1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve2_reaction_mixture",
|
||||||
|
"source": "multiway_valve_2",
|
||||||
|
"target": "reaction_mixture",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_2": "2",
|
||||||
|
"reaction_mixture": "inlet"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve2_rotavap_flask",
|
||||||
|
"source": "multiway_valve_2",
|
||||||
|
"target": "rotavap_flask",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_2": "3",
|
||||||
|
"rotavap_flask": "inlet"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve2_rotavap_condenser",
|
||||||
|
"source": "multiway_valve_2",
|
||||||
|
"target": "rotavap_condenser",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_2": "4",
|
||||||
|
"rotavap_condenser": "inlet"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve2_distillate",
|
||||||
|
"source": "multiway_valve_2",
|
||||||
|
"target": "flask_distillate",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_2": "5",
|
||||||
|
"flask_distillate": "inlet"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve2_waste",
|
||||||
|
"source": "multiway_valve_2",
|
||||||
|
"target": "waste_workup",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_2": "6",
|
||||||
|
"waste_workup": "inlet"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_rotavap1_rotavap_flask",
|
||||||
|
"source": "rotavap_1",
|
||||||
|
"target": "rotavap_flask",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"rotavap_1": "rotavap-sample",
|
||||||
|
"rotavap_flask": "rotavap_port"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_heatchill1_reaction_mixture",
|
||||||
|
"source": "heatchill_1",
|
||||||
|
"target": "reaction_mixture",
|
||||||
|
"type": "mechanical",
|
||||||
|
"port": {
|
||||||
|
"heatchill_1": "heatchill",
|
||||||
|
"reaction_mixture": "heating_jacket"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,534 @@
|
|||||||
|
{
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"id": "FilterProtocolTestStation",
|
||||||
|
"name": "过滤协议测试站",
|
||||||
|
"children": [
|
||||||
|
"transfer_pump_1",
|
||||||
|
"transfer_pump_2",
|
||||||
|
"multiway_valve_1",
|
||||||
|
"multiway_valve_2",
|
||||||
|
"filter_1",
|
||||||
|
"heatchill_1",
|
||||||
|
"reaction_mixture",
|
||||||
|
"filter_vessel",
|
||||||
|
"filtrate_vessel",
|
||||||
|
"collection_bottle_1",
|
||||||
|
"collection_bottle_2",
|
||||||
|
"flask_water",
|
||||||
|
"flask_ethanol",
|
||||||
|
"flask_acetone",
|
||||||
|
"flask_air",
|
||||||
|
"waste_workup"
|
||||||
|
],
|
||||||
|
"parent": null,
|
||||||
|
"type": "device",
|
||||||
|
"class": "workstation",
|
||||||
|
"position": {
|
||||||
|
"x": 500,
|
||||||
|
"y": 200,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"protocol_type": [
|
||||||
|
"FilterProtocol",
|
||||||
|
"PumpTransferProtocol",
|
||||||
|
"HeatChillProtocol",
|
||||||
|
"HeatChillStartProtocol",
|
||||||
|
"HeatChillStopProtocol"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"data": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "transfer_pump_1",
|
||||||
|
"name": "主转移泵",
|
||||||
|
"children": [],
|
||||||
|
"parent": "FilterProtocolTestStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_transfer_pump",
|
||||||
|
"position": {
|
||||||
|
"x": 200,
|
||||||
|
"y": 300,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "VIRTUAL_PUMP1",
|
||||||
|
"max_volume": 25.0,
|
||||||
|
"transfer_rate": 2.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"position": 0.0,
|
||||||
|
"status": "Idle"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "transfer_pump_2",
|
||||||
|
"name": "副转移泵",
|
||||||
|
"children": [],
|
||||||
|
"parent": "FilterProtocolTestStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_transfer_pump",
|
||||||
|
"position": {
|
||||||
|
"x": 400,
|
||||||
|
"y": 300,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "VIRTUAL_PUMP2",
|
||||||
|
"max_volume": 25.0,
|
||||||
|
"transfer_rate": 2.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"position": 0.0,
|
||||||
|
"status": "Idle"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "multiway_valve_1",
|
||||||
|
"name": "溶剂分配阀",
|
||||||
|
"children": [],
|
||||||
|
"parent": "FilterProtocolTestStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_multiway_valve",
|
||||||
|
"position": {
|
||||||
|
"x": 200,
|
||||||
|
"y": 400,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "VIRTUAL_VALVE1",
|
||||||
|
"positions": 8
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"current_position": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "multiway_valve_2",
|
||||||
|
"name": "样品分配阀",
|
||||||
|
"children": [],
|
||||||
|
"parent": "FilterProtocolTestStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_multiway_valve",
|
||||||
|
"position": {
|
||||||
|
"x": 400,
|
||||||
|
"y": 400,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "VIRTUAL_VALVE2",
|
||||||
|
"positions": 8
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"current_position": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "filter_1",
|
||||||
|
"name": "过滤器",
|
||||||
|
"children": [],
|
||||||
|
"parent": "FilterProtocolTestStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_filter",
|
||||||
|
"position": {
|
||||||
|
"x": 600,
|
||||||
|
"y": 350,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "VIRTUAL_FILTER1",
|
||||||
|
"max_temp": 100.0,
|
||||||
|
"max_stir_speed": 1000.0,
|
||||||
|
"max_volume": 500.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"status": "Idle"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "heatchill_1",
|
||||||
|
"name": "加热搅拌器",
|
||||||
|
"children": [],
|
||||||
|
"parent": "FilterProtocolTestStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_heatchill",
|
||||||
|
"position": {
|
||||||
|
"x": 600,
|
||||||
|
"y": 450,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "VIRTUAL_HEATCHILL1",
|
||||||
|
"max_temp": 100.0,
|
||||||
|
"min_temp": 4.0,
|
||||||
|
"max_stir_speed": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"status": "Idle"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reaction_mixture",
|
||||||
|
"name": "反应混合物",
|
||||||
|
"children": [],
|
||||||
|
"parent": "FilterProtocolTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 100,
|
||||||
|
"y": 500,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": [
|
||||||
|
{
|
||||||
|
"liquid_type": "cell_suspension",
|
||||||
|
"liquid_volume": 200.0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "filter_vessel",
|
||||||
|
"name": "过滤器容器",
|
||||||
|
"children": [],
|
||||||
|
"parent": "FilterProtocolTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 600,
|
||||||
|
"y": 550,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 500.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "filtrate_vessel",
|
||||||
|
"name": "滤液收集容器",
|
||||||
|
"children": [],
|
||||||
|
"parent": "FilterProtocolTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 700,
|
||||||
|
"y": 500,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 500.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "collection_bottle_1",
|
||||||
|
"name": "收集瓶1",
|
||||||
|
"children": [],
|
||||||
|
"parent": "FilterProtocolTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 800,
|
||||||
|
"y": 500,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "collection_bottle_2",
|
||||||
|
"name": "收集瓶2",
|
||||||
|
"children": [],
|
||||||
|
"parent": "FilterProtocolTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 900,
|
||||||
|
"y": 500,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_water",
|
||||||
|
"name": "蒸馏水瓶",
|
||||||
|
"children": [],
|
||||||
|
"parent": "FilterProtocolTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 200,
|
||||||
|
"y": 600,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": [
|
||||||
|
{
|
||||||
|
"liquid_type": "water",
|
||||||
|
"liquid_volume": 900.0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_ethanol",
|
||||||
|
"name": "乙醇清洗瓶",
|
||||||
|
"children": [],
|
||||||
|
"parent": "FilterProtocolTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 300,
|
||||||
|
"y": 600,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": [
|
||||||
|
{
|
||||||
|
"liquid_type": "ethanol",
|
||||||
|
"liquid_volume": 800.0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_acetone",
|
||||||
|
"name": "丙酮清洗瓶",
|
||||||
|
"children": [],
|
||||||
|
"parent": "FilterProtocolTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 400,
|
||||||
|
"y": 600,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": [
|
||||||
|
{
|
||||||
|
"liquid_type": "acetone",
|
||||||
|
"liquid_volume": 800.0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_air",
|
||||||
|
"name": "空气瓶",
|
||||||
|
"children": [],
|
||||||
|
"parent": "FilterProtocolTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 100,
|
||||||
|
"y": 600,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "waste_workup",
|
||||||
|
"name": "废液瓶",
|
||||||
|
"children": [],
|
||||||
|
"parent": "FilterProtocolTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 800,
|
||||||
|
"y": 600,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 2000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"id": "link_pump1_valve1",
|
||||||
|
"source": "transfer_pump_1",
|
||||||
|
"target": "multiway_valve_1",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"transfer_pump_1": "transferpump",
|
||||||
|
"multiway_valve_1": "transferpump"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_pump2_valve2",
|
||||||
|
"source": "transfer_pump_2",
|
||||||
|
"target": "multiway_valve_2",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"transfer_pump_2": "transferpump",
|
||||||
|
"multiway_valve_2": "transferpump"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve1_air",
|
||||||
|
"source": "multiway_valve_1",
|
||||||
|
"target": "flask_air",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_1": "1",
|
||||||
|
"flask_air": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve1_water",
|
||||||
|
"source": "multiway_valve_1",
|
||||||
|
"target": "flask_water",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_1": "2",
|
||||||
|
"flask_water": "outlet"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve1_ethanol",
|
||||||
|
"source": "multiway_valve_1",
|
||||||
|
"target": "flask_ethanol",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_1": "3",
|
||||||
|
"flask_ethanol": "outlet"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve1_acetone",
|
||||||
|
"source": "multiway_valve_1",
|
||||||
|
"target": "flask_acetone",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_1": "4",
|
||||||
|
"flask_acetone": "outlet"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve1_valve2",
|
||||||
|
"source": "multiway_valve_1",
|
||||||
|
"target": "multiway_valve_2",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_1": "5",
|
||||||
|
"multiway_valve_2": "1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve2_reaction_mixture",
|
||||||
|
"source": "multiway_valve_2",
|
||||||
|
"target": "reaction_mixture",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_2": "2",
|
||||||
|
"reaction_mixture": "inlet"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve2_filter_vessel",
|
||||||
|
"source": "multiway_valve_2",
|
||||||
|
"target": "filter_vessel",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_2": "3",
|
||||||
|
"filter_vessel": "inlet"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve2_filtrate_vessel",
|
||||||
|
"source": "multiway_valve_2",
|
||||||
|
"target": "filtrate_vessel",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_2": "4",
|
||||||
|
"filtrate_vessel": "inlet"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve2_collection1",
|
||||||
|
"source": "multiway_valve_2",
|
||||||
|
"target": "collection_bottle_1",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_2": "5",
|
||||||
|
"collection_bottle_1": "inlet"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve2_collection2",
|
||||||
|
"source": "multiway_valve_2",
|
||||||
|
"target": "collection_bottle_2",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_2": "6",
|
||||||
|
"collection_bottle_2": "inlet"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve2_waste",
|
||||||
|
"source": "multiway_valve_2",
|
||||||
|
"target": "waste_workup",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_2": "7",
|
||||||
|
"waste_workup": "inlet"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_filter1_filter_vessel",
|
||||||
|
"source": "filter_1",
|
||||||
|
"target": "filter_vessel",
|
||||||
|
"type": "transport",
|
||||||
|
"port": {
|
||||||
|
"filter_1": "filter",
|
||||||
|
"filter_vessel": "filter_port"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_heatchill1_filter_vessel",
|
||||||
|
"source": "heatchill_1",
|
||||||
|
"target": "filter_vessel",
|
||||||
|
"type": "mechanical",
|
||||||
|
"port": {
|
||||||
|
"heatchill_1": "heatchill",
|
||||||
|
"filter_vessel": "heating_jacket"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,671 @@
|
|||||||
|
{
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"id": "HeatChillProtocolTestStation",
|
||||||
|
"name": "加热冷却协议测试站",
|
||||||
|
"children": [
|
||||||
|
"transfer_pump_1",
|
||||||
|
"transfer_pump_2",
|
||||||
|
"multiway_valve_1",
|
||||||
|
"multiway_valve_2",
|
||||||
|
"stirrer_1",
|
||||||
|
"stirrer_2",
|
||||||
|
"heatchill_1",
|
||||||
|
"heatchill_2",
|
||||||
|
"flask_DMF",
|
||||||
|
"flask_ethyl_acetate",
|
||||||
|
"flask_methanol",
|
||||||
|
"flask_acetone",
|
||||||
|
"flask_water",
|
||||||
|
"flask_ethanol",
|
||||||
|
"flask_air",
|
||||||
|
"main_reactor",
|
||||||
|
"secondary_reactor",
|
||||||
|
"waste_workup",
|
||||||
|
"collection_bottle_1",
|
||||||
|
"collection_bottle_2"
|
||||||
|
],
|
||||||
|
"parent": null,
|
||||||
|
"type": "device",
|
||||||
|
"class": "workstation",
|
||||||
|
"position": {
|
||||||
|
"x": 500,
|
||||||
|
"y": 200,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"protocol_type": [
|
||||||
|
"PumpTransferProtocol",
|
||||||
|
"AddProtocol",
|
||||||
|
"HeatChillProtocol",
|
||||||
|
"HeatChillStartProtocol",
|
||||||
|
"HeatChillStopProtocol",
|
||||||
|
"DissolveProtocol"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"data": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "transfer_pump_1",
|
||||||
|
"name": "转移泵1",
|
||||||
|
"children": [],
|
||||||
|
"parent": "HeatChillProtocolTestStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_transfer_pump",
|
||||||
|
"position": {
|
||||||
|
"x": 250,
|
||||||
|
"y": 300,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "VIRTUAL_PUMP1",
|
||||||
|
"max_volume": 25.0,
|
||||||
|
"transfer_rate": 5.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"position": 0.0,
|
||||||
|
"status": "Idle"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "transfer_pump_2",
|
||||||
|
"name": "转移泵2",
|
||||||
|
"children": [],
|
||||||
|
"parent": "HeatChillProtocolTestStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_transfer_pump",
|
||||||
|
"position": {
|
||||||
|
"x": 750,
|
||||||
|
"y": 300,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "VIRTUAL_PUMP2",
|
||||||
|
"max_volume": 25.0,
|
||||||
|
"transfer_rate": 5.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"position": 0.0,
|
||||||
|
"status": "Idle"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "multiway_valve_1",
|
||||||
|
"name": "试剂分配阀",
|
||||||
|
"children": [],
|
||||||
|
"parent": "HeatChillProtocolTestStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_multiway_valve",
|
||||||
|
"position": {
|
||||||
|
"x": 250,
|
||||||
|
"y": 400,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "VIRTUAL_VALVE1",
|
||||||
|
"positions": 8
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"current_position": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "multiway_valve_2",
|
||||||
|
"name": "反应器分配阀",
|
||||||
|
"children": [],
|
||||||
|
"parent": "HeatChillProtocolTestStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_multiway_valve",
|
||||||
|
"position": {
|
||||||
|
"x": 750,
|
||||||
|
"y": 400,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "VIRTUAL_VALVE2",
|
||||||
|
"positions": 8
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"current_position": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "stirrer_1",
|
||||||
|
"name": "主反应器搅拌器",
|
||||||
|
"children": [],
|
||||||
|
"parent": "HeatChillProtocolTestStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_stirrer",
|
||||||
|
"position": {
|
||||||
|
"x": 600,
|
||||||
|
"y": 450,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "VIRTUAL_STIRRER1",
|
||||||
|
"max_speed": 1500.0,
|
||||||
|
"default_speed": 300.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"speed": 0.0,
|
||||||
|
"status": "Stopped"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "stirrer_2",
|
||||||
|
"name": "副反应器搅拌器",
|
||||||
|
"children": [],
|
||||||
|
"parent": "HeatChillProtocolTestStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_stirrer",
|
||||||
|
"position": {
|
||||||
|
"x": 900,
|
||||||
|
"y": 450,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "VIRTUAL_STIRRER2",
|
||||||
|
"max_speed": 1500.0,
|
||||||
|
"default_speed": 300.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"speed": 0.0,
|
||||||
|
"status": "Stopped"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "heatchill_1",
|
||||||
|
"name": "主反应器加热冷却器",
|
||||||
|
"children": [],
|
||||||
|
"parent": "HeatChillProtocolTestStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_heatchill",
|
||||||
|
"position": {
|
||||||
|
"x": 550,
|
||||||
|
"y": 400,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "VIRTUAL_HEATCHILL1",
|
||||||
|
"max_temp": 200.0,
|
||||||
|
"min_temp": -80.0,
|
||||||
|
"max_stir_speed": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"status": "Idle"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "heatchill_2",
|
||||||
|
"name": "副反应器加热冷却器",
|
||||||
|
"children": [],
|
||||||
|
"parent": "HeatChillProtocolTestStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_heatchill",
|
||||||
|
"position": {
|
||||||
|
"x": 850,
|
||||||
|
"y": 400,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "VIRTUAL_HEATCHILL2",
|
||||||
|
"max_temp": 200.0,
|
||||||
|
"min_temp": -80.0,
|
||||||
|
"max_stir_speed": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"status": "Idle"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_DMF",
|
||||||
|
"name": "DMF试剂瓶",
|
||||||
|
"children": [],
|
||||||
|
"parent": "HeatChillProtocolTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 50,
|
||||||
|
"y": 550,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": [
|
||||||
|
{
|
||||||
|
"liquid_type": "DMF",
|
||||||
|
"liquid_volume": 800.0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_ethyl_acetate",
|
||||||
|
"name": "乙酸乙酯试剂瓶",
|
||||||
|
"children": [],
|
||||||
|
"parent": "HeatChillProtocolTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 150,
|
||||||
|
"y": 550,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": [
|
||||||
|
{
|
||||||
|
"liquid_type": "ethyl_acetate",
|
||||||
|
"liquid_volume": 800.0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_methanol",
|
||||||
|
"name": "甲醇试剂瓶",
|
||||||
|
"children": [],
|
||||||
|
"parent": "HeatChillProtocolTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 250,
|
||||||
|
"y": 550,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": [
|
||||||
|
{
|
||||||
|
"liquid_type": "methanol",
|
||||||
|
"liquid_volume": 800.0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_ethanol",
|
||||||
|
"name": "乙醇试剂瓶",
|
||||||
|
"children": [],
|
||||||
|
"parent": "HeatChillProtocolTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 650,
|
||||||
|
"y": 550,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": [
|
||||||
|
{
|
||||||
|
"liquid_type": "ethanol",
|
||||||
|
"liquid_volume": 800.0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_acetone",
|
||||||
|
"name": "丙酮试剂瓶",
|
||||||
|
"children": [],
|
||||||
|
"parent": "HeatChillProtocolTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 350,
|
||||||
|
"y": 550,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": [
|
||||||
|
{
|
||||||
|
"liquid_type": "acetone",
|
||||||
|
"liquid_volume": 800.0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_water",
|
||||||
|
"name": "蒸馏水瓶",
|
||||||
|
"children": [],
|
||||||
|
"parent": "HeatChillProtocolTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 450,
|
||||||
|
"y": 550,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": [
|
||||||
|
{
|
||||||
|
"liquid_type": "water",
|
||||||
|
"liquid_volume": 800.0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_air",
|
||||||
|
"name": "空气瓶",
|
||||||
|
"children": [],
|
||||||
|
"parent": "HeatChillProtocolTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 550,
|
||||||
|
"y": 550,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "main_reactor",
|
||||||
|
"name": "主反应器",
|
||||||
|
"children": [],
|
||||||
|
"parent": "HeatChillProtocolTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 600,
|
||||||
|
"y": 500,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 2000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "secondary_reactor",
|
||||||
|
"name": "副反应器",
|
||||||
|
"children": [],
|
||||||
|
"parent": "HeatChillProtocolTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 900,
|
||||||
|
"y": 500,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "waste_workup",
|
||||||
|
"name": "废液处理瓶",
|
||||||
|
"children": [],
|
||||||
|
"parent": "HeatChillProtocolTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 700,
|
||||||
|
"y": 600,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 2000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "collection_bottle_1",
|
||||||
|
"name": "收集瓶1",
|
||||||
|
"children": [],
|
||||||
|
"parent": "HeatChillProtocolTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 800,
|
||||||
|
"y": 600,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "collection_bottle_2",
|
||||||
|
"name": "收集瓶2",
|
||||||
|
"children": [],
|
||||||
|
"parent": "HeatChillProtocolTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 900,
|
||||||
|
"y": 600,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"id": "link_pump1_valve1",
|
||||||
|
"source": "transfer_pump_1",
|
||||||
|
"target": "multiway_valve_1",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"transfer_pump_1": "transferpump",
|
||||||
|
"multiway_valve_1": "transferpump"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_pump2_valve2",
|
||||||
|
"source": "transfer_pump_2",
|
||||||
|
"target": "multiway_valve_2",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"transfer_pump_2": "transferpump",
|
||||||
|
"multiway_valve_2": "transferpump"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve1_valve2",
|
||||||
|
"source": "multiway_valve_1",
|
||||||
|
"target": "multiway_valve_2",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_1": "8",
|
||||||
|
"multiway_valve_2": "1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve1_DMF",
|
||||||
|
"source": "multiway_valve_1",
|
||||||
|
"target": "flask_DMF",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_1": "1",
|
||||||
|
"flask_DMF": "outlet"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve1_ethyl_acetate",
|
||||||
|
"source": "multiway_valve_1",
|
||||||
|
"target": "flask_ethyl_acetate",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_1": "2",
|
||||||
|
"flask_ethyl_acetate": "outlet"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve1_methanol",
|
||||||
|
"source": "multiway_valve_1",
|
||||||
|
"target": "flask_methanol",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_1": "3",
|
||||||
|
"flask_methanol": "outlet"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve1_acetone",
|
||||||
|
"source": "multiway_valve_1",
|
||||||
|
"target": "flask_acetone",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_1": "4",
|
||||||
|
"flask_acetone": "outlet"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve1_water",
|
||||||
|
"source": "multiway_valve_1",
|
||||||
|
"target": "flask_water",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_1": "5",
|
||||||
|
"flask_water": "outlet"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve1_air",
|
||||||
|
"source": "multiway_valve_1",
|
||||||
|
"target": "flask_air",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_1": "6",
|
||||||
|
"flask_air": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve2_main_reactor",
|
||||||
|
"source": "multiway_valve_2",
|
||||||
|
"target": "main_reactor",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_2": "2",
|
||||||
|
"main_reactor": "inlet"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve2_secondary_reactor",
|
||||||
|
"source": "multiway_valve_2",
|
||||||
|
"target": "secondary_reactor",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_2": "3",
|
||||||
|
"secondary_reactor": "inlet"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve2_waste",
|
||||||
|
"source": "multiway_valve_2",
|
||||||
|
"target": "waste_workup",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_2": "6",
|
||||||
|
"waste_workup": "inlet"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve2_collection1",
|
||||||
|
"source": "multiway_valve_2",
|
||||||
|
"target": "collection_bottle_1",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_2": "7",
|
||||||
|
"collection_bottle_1": "inlet"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve2_collection2",
|
||||||
|
"source": "multiway_valve_2",
|
||||||
|
"target": "collection_bottle_2",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_2": "8",
|
||||||
|
"collection_bottle_2": "inlet"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_stirrer1_main_reactor",
|
||||||
|
"source": "stirrer_1",
|
||||||
|
"target": "main_reactor",
|
||||||
|
"type": "mechanical",
|
||||||
|
"port": {
|
||||||
|
"stirrer_1": "stirrer_head",
|
||||||
|
"main_reactor": "stirrer_port"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_stirrer2_secondary_reactor",
|
||||||
|
"source": "stirrer_2",
|
||||||
|
"target": "secondary_reactor",
|
||||||
|
"type": "mechanical",
|
||||||
|
"port": {
|
||||||
|
"stirrer_2": "stirrer_head",
|
||||||
|
"secondary_reactor": "stirrer_port"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_heatchill1_main_reactor",
|
||||||
|
"source": "heatchill_1",
|
||||||
|
"target": "main_reactor",
|
||||||
|
"type": "thermal",
|
||||||
|
"port": {
|
||||||
|
"heatchill_1": "heating_surface",
|
||||||
|
"main_reactor": "heating_jacket"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_heatchill2_secondary_reactor",
|
||||||
|
"source": "heatchill_2",
|
||||||
|
"target": "secondary_reactor",
|
||||||
|
"type": "thermal",
|
||||||
|
"port": {
|
||||||
|
"heatchill_2": "heating_surface",
|
||||||
|
"secondary_reactor": "heating_jacket"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve1_ethanol",
|
||||||
|
"source": "multiway_valve_1",
|
||||||
|
"target": "flask_ethanol",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_1": "7",
|
||||||
|
"flask_ethanol": "outlet"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,778 @@
|
|||||||
|
{
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"id": "PumpTransferFilterThroughTestStation",
|
||||||
|
"name": "泵转移+过滤介质测试站",
|
||||||
|
"children": [
|
||||||
|
"transfer_pump_1",
|
||||||
|
"transfer_pump_2",
|
||||||
|
"multiway_valve_1",
|
||||||
|
"multiway_valve_2",
|
||||||
|
"reaction_mixture",
|
||||||
|
"crude_product",
|
||||||
|
"filter_celite",
|
||||||
|
"column_silica_gel",
|
||||||
|
"filter_C18",
|
||||||
|
"pure_product",
|
||||||
|
"collection_bottle_1",
|
||||||
|
"collection_bottle_2",
|
||||||
|
"collection_bottle_3",
|
||||||
|
"intermediate_vessel_1",
|
||||||
|
"intermediate_vessel_2",
|
||||||
|
"flask_water",
|
||||||
|
"flask_ethanol",
|
||||||
|
"flask_methanol",
|
||||||
|
"flask_ethyl_acetate",
|
||||||
|
"flask_acetone",
|
||||||
|
"flask_hexane",
|
||||||
|
"flask_air",
|
||||||
|
"waste_workup"
|
||||||
|
],
|
||||||
|
"parent": null,
|
||||||
|
"type": "device",
|
||||||
|
"class": "workstation",
|
||||||
|
"position": {
|
||||||
|
"x": 500,
|
||||||
|
"y": 200,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"protocol_type": [
|
||||||
|
"PumpTransferProtocol",
|
||||||
|
"FilterThroughProtocol"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"data": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "transfer_pump_1",
|
||||||
|
"name": "主转移泵",
|
||||||
|
"children": [],
|
||||||
|
"parent": "PumpTransferFilterThroughTestStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_transfer_pump",
|
||||||
|
"position": {
|
||||||
|
"x": 200,
|
||||||
|
"y": 300,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "VIRTUAL_PUMP1",
|
||||||
|
"max_volume": 25.0,
|
||||||
|
"transfer_rate": 2.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"position": 0.0,
|
||||||
|
"status": "Idle"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "transfer_pump_2",
|
||||||
|
"name": "副转移泵",
|
||||||
|
"children": [],
|
||||||
|
"parent": "PumpTransferFilterThroughTestStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_transfer_pump",
|
||||||
|
"position": {
|
||||||
|
"x": 400,
|
||||||
|
"y": 300,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "VIRTUAL_PUMP2",
|
||||||
|
"max_volume": 25.0,
|
||||||
|
"transfer_rate": 2.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"position": 0.0,
|
||||||
|
"status": "Idle"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "multiway_valve_1",
|
||||||
|
"name": "溶剂分配阀",
|
||||||
|
"children": [],
|
||||||
|
"parent": "PumpTransferFilterThroughTestStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_multiway_valve",
|
||||||
|
"position": {
|
||||||
|
"x": 200,
|
||||||
|
"y": 400,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "VIRTUAL_VALVE1",
|
||||||
|
"positions": 8
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"current_position": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "multiway_valve_2",
|
||||||
|
"name": "样品分配阀",
|
||||||
|
"children": [],
|
||||||
|
"parent": "PumpTransferFilterThroughTestStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_multiway_valve",
|
||||||
|
"position": {
|
||||||
|
"x": 400,
|
||||||
|
"y": 400,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "VIRTUAL_VALVE2",
|
||||||
|
"positions": 8
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"current_position": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reaction_mixture",
|
||||||
|
"name": "反应混合物",
|
||||||
|
"children": [],
|
||||||
|
"parent": "PumpTransferFilterThroughTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 100,
|
||||||
|
"y": 500,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": [
|
||||||
|
{
|
||||||
|
"liquid_type": "organic_reaction_mixture",
|
||||||
|
"liquid_volume": 250.0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "crude_product",
|
||||||
|
"name": "粗产品",
|
||||||
|
"children": [],
|
||||||
|
"parent": "PumpTransferFilterThroughTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 200,
|
||||||
|
"y": 500,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": [
|
||||||
|
{
|
||||||
|
"liquid_type": "crude_organic_compound",
|
||||||
|
"liquid_volume": 150.0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "filter_celite",
|
||||||
|
"name": "硅藻土过滤器",
|
||||||
|
"children": [],
|
||||||
|
"parent": "PumpTransferFilterThroughTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 600,
|
||||||
|
"y": 450,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 300.0,
|
||||||
|
"filter_type": "celite_pad"
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "column_silica_gel",
|
||||||
|
"name": "硅胶柱",
|
||||||
|
"children": [],
|
||||||
|
"parent": "PumpTransferFilterThroughTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 700,
|
||||||
|
"y": 450,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 200.0,
|
||||||
|
"filter_type": "silica_gel_column"
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "filter_C18",
|
||||||
|
"name": "C18固相萃取柱",
|
||||||
|
"children": [],
|
||||||
|
"parent": "PumpTransferFilterThroughTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 800,
|
||||||
|
"y": 450,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 100.0,
|
||||||
|
"filter_type": "C18_cartridge"
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "pure_product",
|
||||||
|
"name": "纯产品",
|
||||||
|
"children": [],
|
||||||
|
"parent": "PumpTransferFilterThroughTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 900,
|
||||||
|
"y": 500,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "collection_bottle_1",
|
||||||
|
"name": "收集瓶1",
|
||||||
|
"children": [],
|
||||||
|
"parent": "PumpTransferFilterThroughTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 600,
|
||||||
|
"y": 550,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "collection_bottle_2",
|
||||||
|
"name": "收集瓶2",
|
||||||
|
"children": [],
|
||||||
|
"parent": "PumpTransferFilterThroughTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 700,
|
||||||
|
"y": 550,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "collection_bottle_3",
|
||||||
|
"name": "收集瓶3",
|
||||||
|
"children": [],
|
||||||
|
"parent": "PumpTransferFilterThroughTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 800,
|
||||||
|
"y": 550,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "intermediate_vessel_1",
|
||||||
|
"name": "中间容器1",
|
||||||
|
"children": [],
|
||||||
|
"parent": "PumpTransferFilterThroughTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 300,
|
||||||
|
"y": 500,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "intermediate_vessel_2",
|
||||||
|
"name": "中间容器2",
|
||||||
|
"children": [],
|
||||||
|
"parent": "PumpTransferFilterThroughTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 400,
|
||||||
|
"y": 500,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_water",
|
||||||
|
"name": "蒸馏水瓶",
|
||||||
|
"children": [],
|
||||||
|
"parent": "PumpTransferFilterThroughTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 100,
|
||||||
|
"y": 600,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": [
|
||||||
|
{
|
||||||
|
"liquid_type": "water",
|
||||||
|
"liquid_volume": 900.0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_ethanol",
|
||||||
|
"name": "乙醇瓶",
|
||||||
|
"children": [],
|
||||||
|
"parent": "PumpTransferFilterThroughTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 200,
|
||||||
|
"y": 600,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": [
|
||||||
|
{
|
||||||
|
"liquid_type": "ethanol",
|
||||||
|
"liquid_volume": 800.0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_methanol",
|
||||||
|
"name": "甲醇瓶",
|
||||||
|
"children": [],
|
||||||
|
"parent": "PumpTransferFilterThroughTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 300,
|
||||||
|
"y": 600,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": [
|
||||||
|
{
|
||||||
|
"liquid_type": "methanol",
|
||||||
|
"liquid_volume": 800.0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_ethyl_acetate",
|
||||||
|
"name": "乙酸乙酯瓶",
|
||||||
|
"children": [],
|
||||||
|
"parent": "PumpTransferFilterThroughTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 400,
|
||||||
|
"y": 600,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": [
|
||||||
|
{
|
||||||
|
"liquid_type": "ethyl_acetate",
|
||||||
|
"liquid_volume": 800.0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_acetone",
|
||||||
|
"name": "丙酮瓶",
|
||||||
|
"children": [],
|
||||||
|
"parent": "PumpTransferFilterThroughTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 500,
|
||||||
|
"y": 600,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": [
|
||||||
|
{
|
||||||
|
"liquid_type": "acetone",
|
||||||
|
"liquid_volume": 800.0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_hexane",
|
||||||
|
"name": "正己烷瓶",
|
||||||
|
"children": [],
|
||||||
|
"parent": "PumpTransferFilterThroughTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 600,
|
||||||
|
"y": 600,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": [
|
||||||
|
{
|
||||||
|
"liquid_type": "hexane",
|
||||||
|
"liquid_volume": 800.0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_air",
|
||||||
|
"name": "空气瓶",
|
||||||
|
"children": [],
|
||||||
|
"parent": "PumpTransferFilterThroughTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 700,
|
||||||
|
"y": 600,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "waste_workup",
|
||||||
|
"name": "废液瓶",
|
||||||
|
"children": [],
|
||||||
|
"parent": "PumpTransferFilterThroughTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 800,
|
||||||
|
"y": 600,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 2000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"id": "link_pump1_valve1",
|
||||||
|
"source": "transfer_pump_1",
|
||||||
|
"target": "multiway_valve_1",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"transfer_pump_1": "transferpump",
|
||||||
|
"multiway_valve_1": "transferpump"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_pump2_valve2",
|
||||||
|
"source": "transfer_pump_2",
|
||||||
|
"target": "multiway_valve_2",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"transfer_pump_2": "transferpump",
|
||||||
|
"multiway_valve_2": "transferpump"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve1_air",
|
||||||
|
"source": "multiway_valve_1",
|
||||||
|
"target": "flask_air",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_1": "1",
|
||||||
|
"flask_air": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve1_water",
|
||||||
|
"source": "multiway_valve_1",
|
||||||
|
"target": "flask_water",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_1": "2",
|
||||||
|
"flask_water": "outlet"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve1_ethanol",
|
||||||
|
"source": "multiway_valve_1",
|
||||||
|
"target": "flask_ethanol",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_1": "3",
|
||||||
|
"flask_ethanol": "outlet"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve1_methanol",
|
||||||
|
"source": "multiway_valve_1",
|
||||||
|
"target": "flask_methanol",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_1": "4",
|
||||||
|
"flask_methanol": "outlet"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve1_ethyl_acetate",
|
||||||
|
"source": "multiway_valve_1",
|
||||||
|
"target": "flask_ethyl_acetate",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_1": "5",
|
||||||
|
"flask_ethyl_acetate": "outlet"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve1_acetone",
|
||||||
|
"source": "multiway_valve_1",
|
||||||
|
"target": "flask_acetone",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_1": "6",
|
||||||
|
"flask_acetone": "outlet"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve1_hexane",
|
||||||
|
"source": "multiway_valve_1",
|
||||||
|
"target": "flask_hexane",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_1": "7",
|
||||||
|
"flask_hexane": "outlet"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve1_valve2",
|
||||||
|
"source": "multiway_valve_1",
|
||||||
|
"target": "multiway_valve_2",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_1": "8",
|
||||||
|
"multiway_valve_2": "1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve2_reaction_mixture",
|
||||||
|
"source": "multiway_valve_2",
|
||||||
|
"target": "reaction_mixture",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_2": "2",
|
||||||
|
"reaction_mixture": "inlet"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve2_crude_product",
|
||||||
|
"source": "multiway_valve_2",
|
||||||
|
"target": "crude_product",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_2": "3",
|
||||||
|
"crude_product": "inlet"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve2_intermediate1",
|
||||||
|
"source": "multiway_valve_2",
|
||||||
|
"target": "intermediate_vessel_1",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_2": "4",
|
||||||
|
"intermediate_vessel_1": "inlet"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve2_intermediate2",
|
||||||
|
"source": "multiway_valve_2",
|
||||||
|
"target": "intermediate_vessel_2",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_2": "5",
|
||||||
|
"intermediate_vessel_2": "inlet"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve2_celite",
|
||||||
|
"source": "multiway_valve_2",
|
||||||
|
"target": "filter_celite",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_2": "6",
|
||||||
|
"filter_celite": "inlet"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve2_silica_gel",
|
||||||
|
"source": "multiway_valve_2",
|
||||||
|
"target": "column_silica_gel",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_2": "7",
|
||||||
|
"column_silica_gel": "inlet"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve2_C18",
|
||||||
|
"source": "multiway_valve_2",
|
||||||
|
"target": "filter_C18",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_2": "8",
|
||||||
|
"filter_C18": "inlet"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_celite_collection1",
|
||||||
|
"source": "filter_celite",
|
||||||
|
"target": "collection_bottle_1",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"filter_celite": "outlet",
|
||||||
|
"collection_bottle_1": "inlet"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_silica_gel_collection2",
|
||||||
|
"source": "column_silica_gel",
|
||||||
|
"target": "collection_bottle_2",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"column_silica_gel": "outlet",
|
||||||
|
"collection_bottle_2": "inlet"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_C18_collection3",
|
||||||
|
"source": "filter_C18",
|
||||||
|
"target": "collection_bottle_3",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"filter_C18": "outlet",
|
||||||
|
"collection_bottle_3": "inlet"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_collection1_pure_product",
|
||||||
|
"source": "collection_bottle_1",
|
||||||
|
"target": "pure_product",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"collection_bottle_1": "outlet",
|
||||||
|
"pure_product": "inlet"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_collection2_pure_product",
|
||||||
|
"source": "collection_bottle_2",
|
||||||
|
"target": "pure_product",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"collection_bottle_2": "outlet",
|
||||||
|
"pure_product": "inlet"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_collection3_pure_product",
|
||||||
|
"source": "collection_bottle_3",
|
||||||
|
"target": "pure_product",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"collection_bottle_3": "outlet",
|
||||||
|
"pure_product": "inlet"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_waste_connection",
|
||||||
|
"source": "pure_product",
|
||||||
|
"target": "waste_workup",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"pure_product": "waste_outlet",
|
||||||
|
"waste_workup": "inlet"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,304 @@
|
|||||||
|
{
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"id": "SimpleProtocolStation",
|
||||||
|
"name": "简单协议工作站",
|
||||||
|
"children": [
|
||||||
|
"transfer_pump_1",
|
||||||
|
"multiway_valve_1",
|
||||||
|
"flask_DMF",
|
||||||
|
"flask_ethyl_acetate",
|
||||||
|
"flask_methanol",
|
||||||
|
"main_reactor",
|
||||||
|
"waste_workup",
|
||||||
|
"collection_bottle_1",
|
||||||
|
"flask_air"
|
||||||
|
],
|
||||||
|
"parent": null,
|
||||||
|
"type": "device",
|
||||||
|
"class": "workstation",
|
||||||
|
"position": {
|
||||||
|
"x": 500,
|
||||||
|
"y": 200,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"protocol_type": ["PumpTransferProtocol"]
|
||||||
|
},
|
||||||
|
"data": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "transfer_pump_1",
|
||||||
|
"name": "转移泵1",
|
||||||
|
"children": [],
|
||||||
|
"parent": "SimpleProtocolStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_transfer_pump",
|
||||||
|
"position": {
|
||||||
|
"x": 500,
|
||||||
|
"y": 300,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "VIRTUAL",
|
||||||
|
"max_volume": 25.0,
|
||||||
|
"transfer_rate": 5.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"position": 0.0,
|
||||||
|
"status": "Idle",
|
||||||
|
"valve_position": "0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "multiway_valve_1",
|
||||||
|
"name": "八通阀1",
|
||||||
|
"children": [],
|
||||||
|
"parent": "SimpleProtocolStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_multiway_valve",
|
||||||
|
"position": {
|
||||||
|
"x": 500,
|
||||||
|
"y": 400,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "VIRTUAL",
|
||||||
|
"positions": 8
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"current_position": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_DMF",
|
||||||
|
"name": "DMF试剂瓶",
|
||||||
|
"children": [],
|
||||||
|
"parent": "SimpleProtocolStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 200,
|
||||||
|
"y": 500,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": [
|
||||||
|
{
|
||||||
|
"liquid_type": "DMF",
|
||||||
|
"liquid_volume": 800.0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_ethyl_acetate",
|
||||||
|
"name": "乙酸乙酯试剂瓶",
|
||||||
|
"children": [],
|
||||||
|
"parent": "SimpleProtocolStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 300,
|
||||||
|
"y": 500,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": [
|
||||||
|
{
|
||||||
|
"liquid_type": "ethyl_acetate",
|
||||||
|
"liquid_volume": 800.0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_methanol",
|
||||||
|
"name": "甲醇试剂瓶",
|
||||||
|
"children": [],
|
||||||
|
"parent": "SimpleProtocolStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 400,
|
||||||
|
"y": 500,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": [
|
||||||
|
{
|
||||||
|
"liquid_type": "methanol",
|
||||||
|
"liquid_volume": 800.0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "main_reactor",
|
||||||
|
"name": "主反应器",
|
||||||
|
"children": [],
|
||||||
|
"parent": "SimpleProtocolStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 600,
|
||||||
|
"y": 500,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 2000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "waste_workup",
|
||||||
|
"name": "废液处理瓶",
|
||||||
|
"children": [],
|
||||||
|
"parent": "SimpleProtocolStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 700,
|
||||||
|
"y": 500,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 2000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "collection_bottle_1",
|
||||||
|
"name": "收集瓶1",
|
||||||
|
"children": [],
|
||||||
|
"parent": "SimpleProtocolStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 800,
|
||||||
|
"y": 500,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_air",
|
||||||
|
"name": "空气瓶",
|
||||||
|
"children": [],
|
||||||
|
"parent": "SimpleProtocolStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 100,
|
||||||
|
"y": 500,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"id": "link_pump_valve",
|
||||||
|
"source": "transfer_pump_1",
|
||||||
|
"target": "multiway_valve_1",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"transfer_pump_1": "transferpump",
|
||||||
|
"multiway_valve_1": "transferpump"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve_air",
|
||||||
|
"source": "multiway_valve_1",
|
||||||
|
"target": "flask_air",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_1": "1",
|
||||||
|
"flask_air": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve_DMF",
|
||||||
|
"source": "multiway_valve_1",
|
||||||
|
"target": "flask_DMF",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_1": "2",
|
||||||
|
"flask_DMF": "outlet"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve_ethyl_acetate",
|
||||||
|
"source": "multiway_valve_1",
|
||||||
|
"target": "flask_ethyl_acetate",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_1": "3",
|
||||||
|
"flask_ethyl_acetate": "outlet"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve_methanol",
|
||||||
|
"source": "multiway_valve_1",
|
||||||
|
"target": "flask_methanol",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_1": "4",
|
||||||
|
"flask_methanol": "outlet"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve_reactor",
|
||||||
|
"source": "multiway_valve_1",
|
||||||
|
"target": "main_reactor",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_1": "5",
|
||||||
|
"main_reactor": "inlet"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve_waste",
|
||||||
|
"source": "multiway_valve_1",
|
||||||
|
"target": "waste_workup",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_1": "6",
|
||||||
|
"waste_workup": "inlet"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve_collection",
|
||||||
|
"source": "multiway_valve_1",
|
||||||
|
"target": "collection_bottle_1",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_1": "7",
|
||||||
|
"collection_bottle_1": "inlet"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,432 @@
|
|||||||
|
{
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"id": "RunColumnTestStation",
|
||||||
|
"name": "柱层析测试工作站",
|
||||||
|
"children": [
|
||||||
|
"transfer_pump_1",
|
||||||
|
"multiway_valve_1",
|
||||||
|
"column_1",
|
||||||
|
"flask_sample",
|
||||||
|
"flask_hexane",
|
||||||
|
"flask_ethyl_acetate",
|
||||||
|
"flask_methanol",
|
||||||
|
"column_vessel",
|
||||||
|
"collection_flask_1",
|
||||||
|
"collection_flask_2",
|
||||||
|
"collection_flask_3",
|
||||||
|
"waste_flask",
|
||||||
|
"main_reactor"
|
||||||
|
],
|
||||||
|
"parent": null,
|
||||||
|
"type": "device",
|
||||||
|
"class": "workstation",
|
||||||
|
"position": {
|
||||||
|
"x": 500,
|
||||||
|
"y": 200,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"protocol_type": ["RunColumnProtocol", "PumpTransferProtocol"]
|
||||||
|
},
|
||||||
|
"data": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "transfer_pump_1",
|
||||||
|
"name": "转移泵",
|
||||||
|
"children": [],
|
||||||
|
"parent": "RunColumnTestStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_transfer_pump",
|
||||||
|
"position": {
|
||||||
|
"x": 300,
|
||||||
|
"y": 300,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "VIRTUAL_PUMP1",
|
||||||
|
"max_volume": 50.0,
|
||||||
|
"transfer_rate": 10.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"status": "Idle",
|
||||||
|
"position": 0.0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "multiway_valve_1",
|
||||||
|
"name": "八通阀门",
|
||||||
|
"children": [],
|
||||||
|
"parent": "RunColumnTestStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_multiway_valve",
|
||||||
|
"position": {
|
||||||
|
"x": 300,
|
||||||
|
"y": 400,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "VIRTUAL_VALVE1",
|
||||||
|
"positions": 8
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"current_position": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "column_1",
|
||||||
|
"name": "柱层析设备",
|
||||||
|
"children": [],
|
||||||
|
"parent": "RunColumnTestStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_column",
|
||||||
|
"position": {
|
||||||
|
"x": 600,
|
||||||
|
"y": 350,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "VIRTUAL_COLUMN1",
|
||||||
|
"max_flow_rate": 5.0,
|
||||||
|
"column_length": 30.0,
|
||||||
|
"column_diameter": 2.5
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"status": "Idle",
|
||||||
|
"column_state": "Ready"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_sample",
|
||||||
|
"name": "样品瓶",
|
||||||
|
"children": [],
|
||||||
|
"parent": "RunColumnTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 100,
|
||||||
|
"y": 500,
|
||||||
|
"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": 200,
|
||||||
|
"y": 500,
|
||||||
|
"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": 300,
|
||||||
|
"y": 500,
|
||||||
|
"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": 400,
|
||||||
|
"y": 500,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": [
|
||||||
|
{
|
||||||
|
"name": "methanol",
|
||||||
|
"volume": 800.0,
|
||||||
|
"concentration": 99.9
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "column_vessel",
|
||||||
|
"name": "柱容器",
|
||||||
|
"children": [],
|
||||||
|
"parent": "RunColumnTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 600,
|
||||||
|
"y": 450,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 300.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "collection_flask_1",
|
||||||
|
"name": "收集瓶1",
|
||||||
|
"children": [],
|
||||||
|
"parent": "RunColumnTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 700,
|
||||||
|
"y": 500,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "collection_flask_2",
|
||||||
|
"name": "收集瓶2",
|
||||||
|
"children": [],
|
||||||
|
"parent": "RunColumnTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 800,
|
||||||
|
"y": 500,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "collection_flask_3",
|
||||||
|
"name": "收集瓶3",
|
||||||
|
"children": [],
|
||||||
|
"parent": "RunColumnTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 900,
|
||||||
|
"y": 500,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "waste_flask",
|
||||||
|
"name": "废液瓶",
|
||||||
|
"children": [],
|
||||||
|
"parent": "RunColumnTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 1000,
|
||||||
|
"y": 500,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 2000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "main_reactor",
|
||||||
|
"name": "反应器",
|
||||||
|
"children": [],
|
||||||
|
"parent": "RunColumnTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 600,
|
||||||
|
"y": 300,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": [
|
||||||
|
{
|
||||||
|
"name": "reaction_mixture",
|
||||||
|
"volume": 300.0,
|
||||||
|
"concentration": 85.0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"id": "link_pump_valve",
|
||||||
|
"source": "transfer_pump_1",
|
||||||
|
"target": "multiway_valve_1",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"transfer_pump_1": "transferpump",
|
||||||
|
"multiway_valve_1": "transferpump"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve_sample",
|
||||||
|
"source": "multiway_valve_1",
|
||||||
|
"target": "flask_sample",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_1": "1",
|
||||||
|
"flask_sample": "outlet"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve_hexane",
|
||||||
|
"source": "multiway_valve_1",
|
||||||
|
"target": "flask_hexane",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_1": "2",
|
||||||
|
"flask_hexane": "outlet"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve_ethyl_acetate",
|
||||||
|
"source": "multiway_valve_1",
|
||||||
|
"target": "flask_ethyl_acetate",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_1": "3",
|
||||||
|
"flask_ethyl_acetate": "outlet"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve_methanol",
|
||||||
|
"source": "multiway_valve_1",
|
||||||
|
"target": "flask_methanol",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_1": "4",
|
||||||
|
"flask_methanol": "outlet"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve_column_vessel",
|
||||||
|
"source": "multiway_valve_1",
|
||||||
|
"target": "column_vessel",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_1": "5",
|
||||||
|
"column_vessel": "inlet"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve_collection1",
|
||||||
|
"source": "multiway_valve_1",
|
||||||
|
"target": "collection_flask_1",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_1": "6",
|
||||||
|
"collection_flask_1": "inlet"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve_collection2",
|
||||||
|
"source": "multiway_valve_1",
|
||||||
|
"target": "collection_flask_2",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_1": "7",
|
||||||
|
"collection_flask_2": "inlet"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve_waste",
|
||||||
|
"source": "multiway_valve_1",
|
||||||
|
"target": "waste_flask",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_1": "8",
|
||||||
|
"waste_flask": "inlet"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_column_device_vessel",
|
||||||
|
"source": "column_1",
|
||||||
|
"target": "column_vessel",
|
||||||
|
"type": "transport",
|
||||||
|
"port": {
|
||||||
|
"column_1": "columnin",
|
||||||
|
"column_vessel": "column_port"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_column_collection3",
|
||||||
|
"source": "column_1",
|
||||||
|
"target": "collection_flask_3",
|
||||||
|
"type": "transport",
|
||||||
|
"port": {
|
||||||
|
"column_1": "columnout",
|
||||||
|
"collection_flask_3": "column_outlet"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,141 @@
|
|||||||
|
{
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"id": "SimpleStirHeatChillTestStation",
|
||||||
|
"name": "搅拌加热测试站",
|
||||||
|
"children": [
|
||||||
|
"stirrer_1",
|
||||||
|
"heatchill_1",
|
||||||
|
"main_reactor",
|
||||||
|
"secondary_reactor"
|
||||||
|
],
|
||||||
|
"parent": null,
|
||||||
|
"type": "device",
|
||||||
|
"class": "workstation",
|
||||||
|
"position": {
|
||||||
|
"x": 500,
|
||||||
|
"y": 200,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"protocol_type": [
|
||||||
|
"StirProtocol",
|
||||||
|
"StartStirProtocol",
|
||||||
|
"StopStirProtocol",
|
||||||
|
"HeatChillProtocol",
|
||||||
|
"HeatChillStartProtocol",
|
||||||
|
"HeatChillStopProtocol"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"data": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "stirrer_1",
|
||||||
|
"name": "主搅拌器",
|
||||||
|
"children": [],
|
||||||
|
"parent": "SimpleStirHeatChillTestStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_stirrer",
|
||||||
|
"position": {
|
||||||
|
"x": 400,
|
||||||
|
"y": 350,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "VIRTUAL_STIRRER1",
|
||||||
|
"max_speed": 1500.0,
|
||||||
|
"min_speed": 50.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"status": "Idle"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "heatchill_1",
|
||||||
|
"name": "主加热冷却器",
|
||||||
|
"children": [],
|
||||||
|
"parent": "SimpleStirHeatChillTestStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_heatchill",
|
||||||
|
"position": {
|
||||||
|
"x": 600,
|
||||||
|
"y": 350,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"port": "VIRTUAL_HEATCHILL1",
|
||||||
|
"max_temp": 200.0,
|
||||||
|
"min_temp": -80.0,
|
||||||
|
"max_stir_speed": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"status": "Idle"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "main_reactor",
|
||||||
|
"name": "主反应器",
|
||||||
|
"children": [],
|
||||||
|
"parent": "SimpleStirHeatChillTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 500,
|
||||||
|
"y": 450,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 2000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": [
|
||||||
|
{
|
||||||
|
"liquid_type": "water",
|
||||||
|
"liquid_volume": 500.0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "secondary_reactor",
|
||||||
|
"name": "副反应器",
|
||||||
|
"children": [],
|
||||||
|
"parent": "SimpleStirHeatChillTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 700,
|
||||||
|
"y": 450,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 1000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"id": "link_stirrer1_main_reactor",
|
||||||
|
"source": "stirrer_1",
|
||||||
|
"target": "main_reactor",
|
||||||
|
"type": "mechanical",
|
||||||
|
"port": {
|
||||||
|
"stirrer_1": "stirrer",
|
||||||
|
"main_reactor": "stirrer_port"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_heatchill1_main_reactor",
|
||||||
|
"source": "heatchill_1",
|
||||||
|
"target": "main_reactor",
|
||||||
|
"type": "mechanical",
|
||||||
|
"port": {
|
||||||
|
"heatchill_1": "heatchill",
|
||||||
|
"main_reactor": "heating_jacket"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
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": []
|
||||||
|
}
|
||||||
36
test/experiments/comprehensive_protocol/checklist.md
Normal file
36
test/experiments/comprehensive_protocol/checklist.md
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
1. 用到的仪器
|
||||||
|
virtual_multiway_valve(√) 八通阀门
|
||||||
|
virtual_transfer_pump(√) 转移泵
|
||||||
|
virtual_centrifuge() 离心机
|
||||||
|
virtual_rotavap() 旋蒸仪
|
||||||
|
virtual_heatchill() 加热器
|
||||||
|
virtual_stirrer() 搅拌器
|
||||||
|
virtual_solenoid_valve() 电磁阀
|
||||||
|
virtual_vacuum_pump(√) vacuum_pump.mock 真空泵
|
||||||
|
virtual_gas_source(√) 气源
|
||||||
|
virtual_filter() 过滤器
|
||||||
|
virtual_column(√) 层析柱
|
||||||
|
separator() homemade_grbl_conductivity 分液漏斗
|
||||||
|
2. 用到的protocol
|
||||||
|
PumpTransferProtocol: generate_pump_protocol_with_rinsing, (√)
|
||||||
|
这个重复了,删掉CleanProtocol: generate_clean_protocol,
|
||||||
|
SeparateProtocol: generate_separate_protocol, (×)
|
||||||
|
EvaporateProtocol: generate_evaporate_protocol, (√)
|
||||||
|
EvacuateAndRefillProtocol: generate_evacuateandrefill_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, (×)
|
||||||
|
|
||||||
|
上下文体积搜索
|
||||||
@@ -0,0 +1,897 @@
|
|||||||
|
{
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"id": "OrganicSynthesisStation",
|
||||||
|
"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",
|
||||||
|
"PumpTransferProtocol"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"data": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "multiway_valve_1",
|
||||||
|
"name": "八通阀门1",
|
||||||
|
"children": [],
|
||||||
|
"parent": "OrganicSynthesisStation",
|
||||||
|
"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": "OrganicSynthesisStation",
|
||||||
|
"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": "OrganicSynthesisStation",
|
||||||
|
"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": "OrganicSynthesisStation",
|
||||||
|
"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": "OrganicSynthesisStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": "container",
|
||||||
|
"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": "OrganicSynthesisStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": "container",
|
||||||
|
"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": "OrganicSynthesisStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": "container",
|
||||||
|
"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": "OrganicSynthesisStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": "container",
|
||||||
|
"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": "OrganicSynthesisStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": "container",
|
||||||
|
"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": "OrganicSynthesisStation",
|
||||||
|
"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": "OrganicSynthesisStation",
|
||||||
|
"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": "OrganicSynthesisStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": "container",
|
||||||
|
"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": "OrganicSynthesisStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_heatchill",
|
||||||
|
"position": {
|
||||||
|
"x": 450,
|
||||||
|
"y": 450,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_temp": 200.0,
|
||||||
|
"min_temp": -20.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"status": "Idle",
|
||||||
|
"current_temp": 25.0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "stirrer_1",
|
||||||
|
"name": "搅拌器1",
|
||||||
|
"children": [],
|
||||||
|
"parent": "OrganicSynthesisStation",
|
||||||
|
"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": "OrganicSynthesisStation",
|
||||||
|
"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": "OrganicSynthesisStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": "container",
|
||||||
|
"position": {
|
||||||
|
"x": 500,
|
||||||
|
"y": 400,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"volume": 2000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"current_volume": 0.0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "waste_bottle_2",
|
||||||
|
"name": "废液瓶2",
|
||||||
|
"children": [],
|
||||||
|
"parent": "OrganicSynthesisStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": "container",
|
||||||
|
"position": {
|
||||||
|
"x": 1100,
|
||||||
|
"y": 500,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"volume": 2000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"current_volume": 0.0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "solenoid_valve_1",
|
||||||
|
"name": "电磁阀1",
|
||||||
|
"children": [],
|
||||||
|
"parent": "OrganicSynthesisStation",
|
||||||
|
"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": "OrganicSynthesisStation",
|
||||||
|
"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": "OrganicSynthesisStation",
|
||||||
|
"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": "OrganicSynthesisStation",
|
||||||
|
"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": "OrganicSynthesisStation",
|
||||||
|
"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": "OrganicSynthesisStation",
|
||||||
|
"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": "OrganicSynthesisStation",
|
||||||
|
"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": "OrganicSynthesisStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": "container",
|
||||||
|
"position": {
|
||||||
|
"x": 900,
|
||||||
|
"y": 500,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"volume": 250.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"current_volume": 0.0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "collection_bottle_2",
|
||||||
|
"name": "接收瓶2",
|
||||||
|
"children": [],
|
||||||
|
"parent": "OrganicSynthesisStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": "container",
|
||||||
|
"position": {
|
||||||
|
"x": 950,
|
||||||
|
"y": 500,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"volume": 250.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"current_volume": 0.0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "collection_bottle_3",
|
||||||
|
"name": "接收瓶3",
|
||||||
|
"children": [],
|
||||||
|
"parent": "OrganicSynthesisStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": "container",
|
||||||
|
"position": {
|
||||||
|
"x": 1050,
|
||||||
|
"y": 500,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"volume": 250.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"current_volume": 0.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"id": "link_pump1_valve1",
|
||||||
|
"source": "transfer_pump_1",
|
||||||
|
"target": "multiway_valve_1",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"transfer_pump_1": "transferpump",
|
||||||
|
"multiway_valve_1": "transferpump"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve1_reagent1",
|
||||||
|
"source": "multiway_valve_1",
|
||||||
|
"target": "reagent_bottle_1",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_1": "1",
|
||||||
|
"reagent_bottle_1": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve1_reagent2",
|
||||||
|
"source": "multiway_valve_1",
|
||||||
|
"target": "reagent_bottle_2",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_1": "2",
|
||||||
|
"reagent_bottle_2": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve1_reagent3",
|
||||||
|
"source": "multiway_valve_1",
|
||||||
|
"target": "reagent_bottle_3",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_1": "3",
|
||||||
|
"reagent_bottle_3": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve1_centrifuge",
|
||||||
|
"source": "multiway_valve_1",
|
||||||
|
"target": "centrifuge_1",
|
||||||
|
"type": "transport",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_1": "4",
|
||||||
|
"centrifuge_1": "centrifuge"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve1_rotavap",
|
||||||
|
"source": "multiway_valve_1",
|
||||||
|
"target": "rotavap_1",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_1": "5",
|
||||||
|
"rotavap_1": "sample_in"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve1_reactor",
|
||||||
|
"source": "multiway_valve_1",
|
||||||
|
"target": "main_reactor",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_1": "6",
|
||||||
|
"main_reactor": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve1_waste1",
|
||||||
|
"source": "multiway_valve_1",
|
||||||
|
"target": "waste_bottle_1",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_1": "7",
|
||||||
|
"waste_bottle_1": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve1_valve2",
|
||||||
|
"source": "multiway_valve_1",
|
||||||
|
"target": "multiway_valve_2",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_1": "8",
|
||||||
|
"multiway_valve_2": "1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_pump2_valve2",
|
||||||
|
"source": "transfer_pump_2",
|
||||||
|
"target": "multiway_valve_2",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"transfer_pump_2": "transferpump",
|
||||||
|
"multiway_valve_2": "transferpump"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve2_solenoid1",
|
||||||
|
"source": "multiway_valve_2",
|
||||||
|
"target": "solenoid_valve_1",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_2": "2",
|
||||||
|
"solenoid_valve_1": "in"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_vacuum_solenoid1",
|
||||||
|
"source": "vacuum_pump_1",
|
||||||
|
"target": "solenoid_valve_1",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"vacuum_pump_1": "vacuumpump",
|
||||||
|
"solenoid_valve_1": "out"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve2_solenoid2",
|
||||||
|
"source": "multiway_valve_2",
|
||||||
|
"target": "solenoid_valve_2",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_2": "3",
|
||||||
|
"solenoid_valve_2": "in"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_gas_solenoid2",
|
||||||
|
"source": "gas_source_1",
|
||||||
|
"target": "solenoid_valve_2",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"gas_source_1": "gassource",
|
||||||
|
"solenoid_valve_2": "out"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve2_filter",
|
||||||
|
"source": "multiway_valve_2",
|
||||||
|
"target": "filter_1",
|
||||||
|
"type": "transport",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_2": "4",
|
||||||
|
"filter_1": "filter_in"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve2_column",
|
||||||
|
"source": "multiway_valve_2",
|
||||||
|
"target": "column_1",
|
||||||
|
"type": "transport",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_2": "5",
|
||||||
|
"column_1": "columnin"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_column_collection2",
|
||||||
|
"source": "column_1",
|
||||||
|
"target": "collection_bottle_2",
|
||||||
|
"type": "transport",
|
||||||
|
"port": {
|
||||||
|
"column_1": "columnout",
|
||||||
|
"collection_bottle_2": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve2_separator",
|
||||||
|
"source": "multiway_valve_2",
|
||||||
|
"target": "separator_1",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_2": "6",
|
||||||
|
"separator_1": "separator_in"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_separator_collection3",
|
||||||
|
"source": "separator_1",
|
||||||
|
"target": "collection_bottle_3",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"separator_1": "bottom_phase_out",
|
||||||
|
"collection_bottle_3": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve2_reagent4",
|
||||||
|
"source": "multiway_valve_2",
|
||||||
|
"target": "reagent_bottle_4",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_2": "7",
|
||||||
|
"reagent_bottle_4": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve2_reagent5",
|
||||||
|
"source": "multiway_valve_2",
|
||||||
|
"target": "reagent_bottle_5",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"multiway_valve_2": "8",
|
||||||
|
"reagent_bottle_5": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "mech_stirrer_reactor",
|
||||||
|
"source": "stirrer_1",
|
||||||
|
"target": "main_reactor",
|
||||||
|
"type": "mechanical",
|
||||||
|
"port": {
|
||||||
|
"stirrer_1": "stirrer",
|
||||||
|
"main_reactor": "bind"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "thermal_heater_reactor",
|
||||||
|
"source": "heater_1",
|
||||||
|
"target": "main_reactor",
|
||||||
|
"type": "mechanical",
|
||||||
|
"port": {
|
||||||
|
"heater_1": "heatchill",
|
||||||
|
"main_reactor": "bind"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_separator_waste2",
|
||||||
|
"source": "separator_1",
|
||||||
|
"target": "waste_bottle_2",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"separator_1": "top_phase_out",
|
||||||
|
"waste_bottle_2": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "mech_stirrer2_separator",
|
||||||
|
"source": "stirrer_2",
|
||||||
|
"target": "separator_1",
|
||||||
|
"type": "mechanical",
|
||||||
|
"port": {
|
||||||
|
"stirrer_2": "stirrer",
|
||||||
|
"separator_1": "bind"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_filter_filtrate_to_collection1",
|
||||||
|
"source": "filter_1",
|
||||||
|
"target": "collection_bottle_1",
|
||||||
|
"type": "transport",
|
||||||
|
"port": {
|
||||||
|
"filter_1": "filtrate_out",
|
||||||
|
"collection_bottle_1": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_filter_retentate_to_waste1",
|
||||||
|
"source": "filter_1",
|
||||||
|
"target": "waste_bottle_1",
|
||||||
|
"type": "transport",
|
||||||
|
"port": {
|
||||||
|
"filter_1": "retentate_out",
|
||||||
|
"waste_bottle_1": "top"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
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
|
||||||
},
|
},
|
||||||
@@ -119,39 +122,39 @@
|
|||||||
],
|
],
|
||||||
"links": [
|
"links": [
|
||||||
{
|
{
|
||||||
"source": "reactor",
|
"source": "vacuum_valve",
|
||||||
"target": "vacuum_valve",
|
"target": "reactor",
|
||||||
"type": "physical",
|
"type": "fluid",
|
||||||
"port": {
|
"port": {
|
||||||
"reactor": "top",
|
"reactor": "top",
|
||||||
"vacuum_valve": "1"
|
"vacuum_valve": "out"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"source": "reactor",
|
"source": "gas_valve",
|
||||||
"target": "gas_valve",
|
"target": "reactor",
|
||||||
"type": "physical",
|
"type": "fluid",
|
||||||
"port": {
|
"port": {
|
||||||
"reactor": "top",
|
"reactor": "top",
|
||||||
"gas_valve": "1"
|
"gas_valve": "out"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"source": "vacuum_pump",
|
"source": "vacuum_pump",
|
||||||
"target": "vacuum_valve",
|
"target": "vacuum_valve",
|
||||||
"type": "physical",
|
"type": "fluid",
|
||||||
"port": {
|
"port": {
|
||||||
"vacuum_pump": "out",
|
"vacuum_pump": "out",
|
||||||
"vacuum_valve": "0"
|
"vacuum_valve": "in"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"source": "gas_source",
|
"source": "gas_source",
|
||||||
"target": "gas_valve",
|
"target": "gas_valve",
|
||||||
"type": "physical",
|
"type": "fluid",
|
||||||
"port": {
|
"port": {
|
||||||
"gas_source": "out",
|
"gas_source": "out",
|
||||||
"gas_valve": "0"
|
"gas_valve": "in"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
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": [
|
||||||
|
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -56,6 +56,8 @@ dependencies:
|
|||||||
- ros-humble-moveit-servo
|
- ros-humble-moveit-servo
|
||||||
# simulation
|
# simulation
|
||||||
- ros-humble-simulation
|
- ros-humble-simulation
|
||||||
|
- ros-humble-tf-transformations
|
||||||
|
- transforms3d
|
||||||
# ros-humble-gazebo-ros // ignored because of the conflict with ign-gazebo
|
# ros-humble-gazebo-ros // ignored because of the conflict with ign-gazebo
|
||||||
# ilab equipments
|
# ilab equipments
|
||||||
# - ros-humble-unilabos-msgs
|
# - ros-humble-unilabos-msgs
|
||||||
|
|||||||
@@ -56,6 +56,8 @@ dependencies:
|
|||||||
# - ros-humble-moveit-servo
|
# - ros-humble-moveit-servo
|
||||||
# simulation
|
# simulation
|
||||||
- ros-humble-simulation
|
- ros-humble-simulation
|
||||||
|
- ros-humble-tf-transformations
|
||||||
|
- transforms3d
|
||||||
# ros-humble-gazebo-ros // ignored because of the conflict with ign-gazebo
|
# ros-humble-gazebo-ros // ignored because of the conflict with ign-gazebo
|
||||||
# ilab equipments
|
# ilab equipments
|
||||||
# - ros-humble-unilabos-msgs
|
# - ros-humble-unilabos-msgs
|
||||||
|
|||||||
@@ -58,6 +58,8 @@ dependencies:
|
|||||||
- ros-humble-moveit-servo
|
- ros-humble-moveit-servo
|
||||||
# simulation
|
# simulation
|
||||||
- ros-humble-simulation
|
- ros-humble-simulation
|
||||||
|
- ros-humble-tf-transformations
|
||||||
|
- transforms3d
|
||||||
# ros-humble-gazebo-ros // ignored because of the conflict with ign-gazebo
|
# ros-humble-gazebo-ros // ignored because of the conflict with ign-gazebo
|
||||||
# ilab equipments
|
# ilab equipments
|
||||||
# - ros-humble-unilabos-msgs
|
# - ros-humble-unilabos-msgs
|
||||||
|
|||||||
@@ -56,6 +56,8 @@ dependencies:
|
|||||||
- ros-humble-moveit-servo
|
- ros-humble-moveit-servo
|
||||||
# simulation
|
# simulation
|
||||||
- ros-humble-simulation # ignored because of NO python3.11 package in WIN64
|
- ros-humble-simulation # ignored because of NO python3.11 package in WIN64
|
||||||
|
- ros-humble-tf-transformations
|
||||||
|
- transforms3d
|
||||||
# ros-humble-gazebo-ros // ignored because of the conflict with ign-gazebo
|
# ros-humble-gazebo-ros // ignored because of the conflict with ign-gazebo
|
||||||
# ilab equipments
|
# ilab equipments
|
||||||
# ros-humble-unilabos-msgs
|
# ros-humble-unilabos-msgs
|
||||||
|
|||||||
@@ -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,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -31,6 +31,6 @@ def job_add(req: JobAddReq) -> JobData:
|
|||||||
action_kwargs = {"command": json.dumps(action_kwargs)}
|
action_kwargs = {"command": json.dumps(action_kwargs)}
|
||||||
elif "command" in action_kwargs:
|
elif "command" in action_kwargs:
|
||||||
action_kwargs = action_kwargs["command"]
|
action_kwargs = action_kwargs["command"]
|
||||||
print(f"job_add:{req.device_id} {action_name} {action_kwargs}")
|
# print(f"job_add:{req.device_id} {action_name} {action_kwargs}")
|
||||||
HostNode.get_instance().send_goal(req.device_id, action_name=action_name, action_kwargs=action_kwargs, goal_uuid=req.job_id)
|
HostNode.get_instance().send_goal(req.device_id, action_name=action_name, action_kwargs=action_kwargs, goal_uuid=req.job_id, server_info=req.server_info)
|
||||||
return JobData(jobId=req.job_id)
|
return JobData(jobId=req.job_id)
|
||||||
|
|||||||
@@ -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))
|
||||||
@@ -18,7 +20,21 @@ if unilabos_dir not in sys.path:
|
|||||||
|
|
||||||
from unilabos.config.config import load_config, BasicConfig, _update_config_from_env
|
from unilabos.config.config import load_config, BasicConfig, _update_config_from_env
|
||||||
from unilabos.utils.banner_print import print_status, print_unilab_banner
|
from unilabos.utils.banner_print import print_status, print_unilab_banner
|
||||||
from unilabos.device_mesh.resource_visalization import ResourceVisualization
|
|
||||||
|
|
||||||
|
def load_config_from_file(config_path):
|
||||||
|
if config_path is None:
|
||||||
|
config_path = os.environ.get("UNILABOS.BASICCONFIG.CONFIG_PATH", None)
|
||||||
|
if config_path:
|
||||||
|
if not os.path.exists(config_path):
|
||||||
|
print_status(f"配置文件 {config_path} 不存在", "error")
|
||||||
|
elif not config_path.endswith(".py"):
|
||||||
|
print_status(f"配置文件 {config_path} 不是Python文件,必须以.py结尾", "error")
|
||||||
|
else:
|
||||||
|
load_config(config_path)
|
||||||
|
else:
|
||||||
|
print_status(f"启动 UniLab-OS时,配置文件参数未正确传入 --config '{config_path}' 尝试本地配置...", "warning")
|
||||||
|
load_config(config_path)
|
||||||
|
|
||||||
|
|
||||||
def parse_args():
|
def parse_args():
|
||||||
@@ -57,6 +73,11 @@ def parse_args():
|
|||||||
action="store_true",
|
action="store_true",
|
||||||
help="Slave模式下跳过等待host服务",
|
help="Slave模式下跳过等待host服务",
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--upload_registry",
|
||||||
|
action="store_true",
|
||||||
|
help="启动unilab时同时报送注册表信息",
|
||||||
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--config",
|
"--config",
|
||||||
type=str,
|
type=str,
|
||||||
@@ -96,22 +117,12 @@ def main():
|
|||||||
|
|
||||||
# 加载配置文件,优先加载config,然后从env读取
|
# 加载配置文件,优先加载config,然后从env读取
|
||||||
config_path = args_dict.get("config")
|
config_path = args_dict.get("config")
|
||||||
if config_path is None:
|
load_config_from_file(config_path)
|
||||||
config_path = os.environ.get("UNILABOS.BASICCONFIG.CONFIG_PATH", None)
|
|
||||||
if config_path:
|
|
||||||
if not os.path.exists(config_path):
|
|
||||||
print_status(f"配置文件 {config_path} 不存在", "error")
|
|
||||||
elif not config_path.endswith(".py"):
|
|
||||||
print_status(f"配置文件 {config_path} 不是Python文件,必须以.py结尾", "error")
|
|
||||||
else:
|
|
||||||
load_config(config_path)
|
|
||||||
else:
|
|
||||||
print_status(f"启动 UniLab-OS时,配置文件参数未正确传入 --config '{config_path}' 尝试本地配置...", "warning")
|
|
||||||
load_config(config_path)
|
|
||||||
|
|
||||||
# 设置BasicConfig参数
|
# 设置BasicConfig参数
|
||||||
BasicConfig.is_host_mode = not args_dict.get("without_host", False)
|
BasicConfig.is_host_mode = not args_dict.get("without_host", False)
|
||||||
BasicConfig.slave_no_host = args_dict.get("slave_no_host", False)
|
BasicConfig.slave_no_host = args_dict.get("slave_no_host", False)
|
||||||
|
BasicConfig.upload_registry = args_dict.get("upload_registry", False)
|
||||||
machine_name = os.popen("hostname").read().strip()
|
machine_name = os.popen("hostname").read().strip()
|
||||||
machine_name = "".join([c if c.isalnum() or c == "_" else "_" for c in machine_name])
|
machine_name = "".join([c if c.isalnum() or c == "_" else "_" for c in machine_name])
|
||||||
BasicConfig.machine_name = machine_name
|
BasicConfig.machine_name = machine_name
|
||||||
@@ -135,29 +146,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"]:
|
||||||
@@ -184,15 +196,17 @@ 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"
|
||||||
if devices_and_resources is not None:
|
if devices_and_resources is not None:
|
||||||
|
from unilabos.device_mesh.resource_visalization import ResourceVisualization # 此处开启后,logger会变更为INFO,有需要请调整
|
||||||
resource_visualization = ResourceVisualization(devices_and_resources, args_dict["resources_config"] ,enable_rviz=enable_rviz)
|
resource_visualization = ResourceVisualization(devices_and_resources, args_dict["resources_config"] ,enable_rviz=enable_rviz)
|
||||||
args_dict["resources_mesh_config"] = resource_visualization.resource_model
|
args_dict["resources_mesh_config"] = resource_visualization.resource_model
|
||||||
start_backend(**args_dict)
|
start_backend(**args_dict)
|
||||||
server_thread = threading.Thread(target=start_server, kwargs=dict(
|
server_thread = threading.Thread(target=start_server, kwargs=dict(
|
||||||
open_browser=not args_dict["disable_browser"]
|
open_browser=not args_dict["disable_browser"], port=args_dict["port"],
|
||||||
))
|
))
|
||||||
server_thread.start()
|
server_thread.start()
|
||||||
asyncio.set_event_loop(asyncio.new_event_loop())
|
asyncio.set_event_loop(asyncio.new_event_loop())
|
||||||
@@ -201,10 +215,10 @@ def main():
|
|||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
else:
|
else:
|
||||||
start_backend(**args_dict)
|
start_backend(**args_dict)
|
||||||
start_server(open_browser=not args_dict["disable_browser"])
|
start_server(open_browser=not args_dict["disable_browser"], port=args_dict["port"],)
|
||||||
else:
|
else:
|
||||||
start_backend(**args_dict)
|
start_backend(**args_dict)
|
||||||
start_server(open_browser=not args_dict["disable_browser"])
|
start_server(open_browser=not args_dict["disable_browser"], port=args_dict["port"],)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@@ -51,8 +51,9 @@ class Resp(BaseModel):
|
|||||||
class JobAddReq(BaseModel):
|
class JobAddReq(BaseModel):
|
||||||
device_id: str = Field(examples=["Gripper"], description="device id")
|
device_id: str = Field(examples=["Gripper"], description="device id")
|
||||||
data: dict = Field(examples=[{"position": 30, "torque": 5, "action": "push_to"}])
|
data: dict = Field(examples=[{"position": 30, "torque": 5, "action": "push_to"}])
|
||||||
job_id: str = Field(examples=["sfsfsfeq"], description="goal uuid")
|
job_id: str = Field(examples=["job_id"], description="goal uuid")
|
||||||
node_id: str = Field(examples=["sfsfsfeq"], description="node uuid")
|
node_id: str = Field(examples=["node_id"], description="node uuid")
|
||||||
|
server_info: dict = Field(examples=[{"send_timestamp": 1717000000.0}], description="server info")
|
||||||
|
|
||||||
|
|
||||||
class JobStepFinishReq(BaseModel):
|
class JobStepFinishReq(BaseModel):
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -12,7 +13,7 @@ import tempfile
|
|||||||
import os
|
import os
|
||||||
|
|
||||||
from unilabos.config.config import MQConfig
|
from unilabos.config.config import MQConfig
|
||||||
from unilabos.app.controler import devices, job_add
|
from unilabos.app.controler import job_add
|
||||||
from unilabos.app.model import JobAddReq
|
from unilabos.app.model import JobAddReq
|
||||||
from unilabos.utils import logger
|
from unilabos.utils import logger
|
||||||
from unilabos.utils.type_check import TypeEncoder
|
from unilabos.utils.type_check import TypeEncoder
|
||||||
@@ -26,6 +27,7 @@ class MQTTClient:
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.mqtt_disable = not MQConfig.lab_id
|
self.mqtt_disable = not MQConfig.lab_id
|
||||||
self.client_id = f"{MQConfig.group_id}@@@{MQConfig.lab_id}{uuid.uuid4()}"
|
self.client_id = f"{MQConfig.group_id}@@@{MQConfig.lab_id}{uuid.uuid4()}"
|
||||||
|
logger.info("[MQTT] Client_id: " + self.client_id)
|
||||||
self.client = mqtt.Client(CallbackAPIVersion.VERSION2, client_id=self.client_id, protocol=mqtt.MQTTv5)
|
self.client = mqtt.Client(CallbackAPIVersion.VERSION2, client_id=self.client_id, protocol=mqtt.MQTTv5)
|
||||||
self._setup_callbacks()
|
self._setup_callbacks()
|
||||||
|
|
||||||
@@ -42,20 +44,14 @@ class MQTTClient:
|
|||||||
def _on_connect(self, client, userdata, flags, rc, properties=None):
|
def _on_connect(self, client, userdata, flags, rc, properties=None):
|
||||||
logger.info("[MQTT] Connected with result code " + str(rc))
|
logger.info("[MQTT] Connected with result code " + str(rc))
|
||||||
client.subscribe(f"labs/{MQConfig.lab_id}/job/start/", 0)
|
client.subscribe(f"labs/{MQConfig.lab_id}/job/start/", 0)
|
||||||
isok, data = devices()
|
client.subscribe(f"labs/{MQConfig.lab_id}/pong/", 0)
|
||||||
if not isok:
|
|
||||||
logger.error("[MQTT] on_connect ErrorHostNotInit")
|
|
||||||
return
|
|
||||||
|
|
||||||
def _on_message(self, client, userdata, msg) -> None:
|
def _on_message(self, client, userdata, msg) -> None:
|
||||||
logger.info("[MQTT] on_message<<<< " + msg.topic + " " + str(msg.payload))
|
# logger.info("[MQTT] on_message<<<< " + msg.topic + " " + str(msg.payload))
|
||||||
try:
|
try:
|
||||||
payload_str = msg.payload.decode("utf-8")
|
payload_str = msg.payload.decode("utf-8")
|
||||||
payload_json = json.loads(payload_str)
|
payload_json = json.loads(payload_str)
|
||||||
logger.debug(f"Topic: {msg.topic}")
|
|
||||||
logger.debug("Payload:", json.dumps(payload_json, indent=2, ensure_ascii=False))
|
|
||||||
if msg.topic == f"labs/{MQConfig.lab_id}/job/start/":
|
if msg.topic == f"labs/{MQConfig.lab_id}/job/start/":
|
||||||
logger.debug("job_add", type(payload_json), payload_json)
|
|
||||||
if "data" not in payload_json:
|
if "data" not in payload_json:
|
||||||
payload_json["data"] = {}
|
payload_json["data"] = {}
|
||||||
if "action" in payload_json:
|
if "action" in payload_json:
|
||||||
@@ -65,6 +61,14 @@ class MQTTClient:
|
|||||||
job_req = JobAddReq.model_validate(payload_json)
|
job_req = JobAddReq.model_validate(payload_json)
|
||||||
data = job_add(job_req)
|
data = job_add(job_req)
|
||||||
return
|
return
|
||||||
|
elif msg.topic == f"labs/{MQConfig.lab_id}/pong/":
|
||||||
|
# 处理pong响应,通知HostNode
|
||||||
|
from unilabos.ros.nodes.presets.host_node import HostNode
|
||||||
|
|
||||||
|
host_instance = HostNode.get_instance(0)
|
||||||
|
if host_instance:
|
||||||
|
host_instance.handle_pong_response(payload_json)
|
||||||
|
return
|
||||||
|
|
||||||
except json.JSONDecodeError as e:
|
except json.JSONDecodeError as e:
|
||||||
logger.error(f"[MQTT] JSON 解析错误: {e}")
|
logger.error(f"[MQTT] JSON 解析错误: {e}")
|
||||||
@@ -158,21 +162,24 @@ 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, print_debug: bool = True):
|
||||||
if self.mqtt_disable:
|
if self.mqtt_disable:
|
||||||
return
|
return
|
||||||
address = f"labs/{MQConfig.lab_id}/registry/"
|
address = f"labs/{MQConfig.lab_id}/registry/"
|
||||||
registry_data = json.dumps({device_id: device_info}, ensure_ascii=False, cls=TypeEncoder)
|
registry_data = json.dumps({device_id: device_info}, ensure_ascii=False, cls=TypeEncoder)
|
||||||
self.client.publish(address, registry_data, qos=2)
|
self.client.publish(address, registry_data, qos=2)
|
||||||
logger.debug(f"Registry data published: address: {address}, {registry_data}")
|
if print_debug:
|
||||||
|
logger.debug(f"Registry data published: address: {address}, {registry_data}")
|
||||||
|
|
||||||
def publish_actions(self, action_id: str, action_info: dict):
|
def publish_actions(self, action_id: str, action_info: dict):
|
||||||
if self.mqtt_disable:
|
if self.mqtt_disable:
|
||||||
@@ -181,6 +188,28 @@ class MQTTClient:
|
|||||||
self.client.publish(address, json.dumps(action_info), qos=2)
|
self.client.publish(address, json.dumps(action_info), qos=2)
|
||||||
logger.debug(f"Action data published: address: {address}, {action_id}, {action_info}")
|
logger.debug(f"Action data published: address: {address}, {action_id}, {action_info}")
|
||||||
|
|
||||||
|
def send_ping(self, ping_id: str, timestamp: float):
|
||||||
|
"""发送ping消息到服务端"""
|
||||||
|
if self.mqtt_disable:
|
||||||
|
return
|
||||||
|
address = f"labs/{MQConfig.lab_id}/ping/"
|
||||||
|
ping_data = {"ping_id": ping_id, "client_timestamp": timestamp, "type": "ping"}
|
||||||
|
self.client.publish(address, json.dumps(ping_data), qos=2)
|
||||||
|
|
||||||
|
def setup_pong_subscription(self):
|
||||||
|
"""设置pong消息订阅"""
|
||||||
|
if self.mqtt_disable:
|
||||||
|
return
|
||||||
|
pong_topic = f"labs/{MQConfig.lab_id}/pong/"
|
||||||
|
self.client.subscribe(pong_topic, 0)
|
||||||
|
logger.debug(f"Subscribed to pong topic: {pong_topic}")
|
||||||
|
|
||||||
|
def handle_pong(self, pong_data: dict):
|
||||||
|
"""处理pong响应(这个方法会在收到pong消息时被调用)"""
|
||||||
|
logger.debug(f"Pong received: {pong_data}")
|
||||||
|
# 这里会被HostNode的ping-pong处理逻辑调用
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
mqtt_client = MQTTClient()
|
mqtt_client = MQTTClient()
|
||||||
|
|
||||||
|
|||||||
67
unilabos/app/register.py
Normal file
67
unilabos/app/register.py
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
import argparse
|
||||||
|
import time
|
||||||
|
|
||||||
|
from unilabos.registry.registry import build_registry
|
||||||
|
|
||||||
|
from unilabos.app.main import load_config_from_file
|
||||||
|
from unilabos.utils.log import logger
|
||||||
|
|
||||||
|
|
||||||
|
def register_devices_and_resources(mqtt_client, lab_registry):
|
||||||
|
"""
|
||||||
|
注册设备和资源到 MQTT
|
||||||
|
"""
|
||||||
|
logger.info("[UniLab Register] 开始注册设备和资源...")
|
||||||
|
|
||||||
|
# 注册设备信息
|
||||||
|
for device_info in lab_registry.obtain_registry_device_info():
|
||||||
|
mqtt_client.publish_registry(device_info["id"], device_info, False)
|
||||||
|
logger.debug(f"[UniLab Register] 注册设备: {device_info['id']}")
|
||||||
|
|
||||||
|
# 注册资源信息
|
||||||
|
for resource_info in lab_registry.obtain_registry_resource_info():
|
||||||
|
mqtt_client.publish_registry(resource_info["id"], resource_info, False)
|
||||||
|
logger.debug(f"[UniLab Register] 注册资源: {resource_info['id']}")
|
||||||
|
|
||||||
|
time.sleep(10)
|
||||||
|
|
||||||
|
logger.info("[UniLab Register] 设备和资源注册完成.")
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""
|
||||||
|
命令行入口函数
|
||||||
|
"""
|
||||||
|
parser = argparse.ArgumentParser(description="注册设备和资源到 MQTT")
|
||||||
|
parser.add_argument(
|
||||||
|
"--registry_path",
|
||||||
|
type=str,
|
||||||
|
default=None,
|
||||||
|
action="append",
|
||||||
|
help="注册表路径",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--config",
|
||||||
|
type=str,
|
||||||
|
default=None,
|
||||||
|
help="配置文件路径,支持.py格式的Python配置文件",
|
||||||
|
)
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
# 构建注册表
|
||||||
|
build_registry(args.registry_path)
|
||||||
|
load_config_from_file(args.config)
|
||||||
|
|
||||||
|
from unilabos.app.mq import mqtt_client
|
||||||
|
|
||||||
|
# 连接mqtt
|
||||||
|
mqtt_client.start()
|
||||||
|
|
||||||
|
from unilabos.registry.registry import lab_registry
|
||||||
|
|
||||||
|
# 注册设备和资源
|
||||||
|
register_devices_and_resources(mqtt_client, lab_registry)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -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=100,
|
||||||
)
|
)
|
||||||
|
if response.status_code != 200 and response.status_code != 201:
|
||||||
|
logger.error(f"添加物料关系失败: {response.status_code}, {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=100,
|
||||||
|
)
|
||||||
|
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,10 +82,10 @@ 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=20,
|
||||||
)
|
)
|
||||||
return response.json()
|
return response.json()
|
||||||
|
|
||||||
@@ -81,7 +103,7 @@ class HTTPClient:
|
|||||||
f"{self.remote_addr}/lab/resource/batch_delete/",
|
f"{self.remote_addr}/lab/resource/batch_delete/",
|
||||||
params={"id": id},
|
params={"id": id},
|
||||||
headers={"Authorization": f"lab {self.auth}"},
|
headers={"Authorization": f"lab {self.auth}"},
|
||||||
timeout=5,
|
timeout=20,
|
||||||
)
|
)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
@@ -96,10 +118,10 @@ 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=100,
|
||||||
)
|
)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|||||||
@@ -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,15 +5,45 @@ 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,
|
||||||
|
generate_heat_chill_to_temp_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.
|
||||||
action_protocol_generators = {
|
action_protocol_generators = {
|
||||||
PumpTransferProtocol: generate_pump_protocol_with_rinsing,
|
AddProtocol: generate_add_protocol,
|
||||||
CleanProtocol: generate_clean_protocol,
|
|
||||||
SeparateProtocol: generate_separate_protocol,
|
|
||||||
EvaporateProtocol: generate_evaporate_protocol,
|
|
||||||
EvacuateAndRefillProtocol: generate_evacuateandrefill_protocol,
|
|
||||||
AGVTransferProtocol: generate_agv_transfer_protocol,
|
AGVTransferProtocol: generate_agv_transfer_protocol,
|
||||||
}
|
CentrifugeProtocol: generate_centrifuge_protocol,
|
||||||
# End Protocols
|
CleanProtocol: generate_clean_protocol,
|
||||||
|
CleanVesselProtocol: generate_clean_vessel_protocol,
|
||||||
|
DissolveProtocol: generate_dissolve_protocol,
|
||||||
|
EvacuateAndRefillProtocol: generate_evacuateandrefill_protocol,
|
||||||
|
EvaporateProtocol: generate_evaporate_protocol,
|
||||||
|
FilterProtocol: generate_filter_protocol,
|
||||||
|
FilterThroughProtocol: generate_filter_through_protocol,
|
||||||
|
HeatChillProtocol: generate_heat_chill_protocol,
|
||||||
|
HeatChillStartProtocol: generate_heat_chill_start_protocol,
|
||||||
|
HeatChillStopProtocol: generate_heat_chill_stop_protocol,
|
||||||
|
PumpTransferProtocol: generate_pump_protocol_with_rinsing,
|
||||||
|
RunColumnProtocol: generate_run_column_protocol,
|
||||||
|
SeparateProtocol: generate_separate_protocol,
|
||||||
|
StartStirProtocol: generate_start_stir_protocol,
|
||||||
|
StirProtocol: generate_stir_protocol,
|
||||||
|
StopStirProtocol: generate_stop_stir_protocol,
|
||||||
|
TransferProtocol: generate_transfer_protocol,
|
||||||
|
WashSolidProtocol: generate_wash_solid_protocol,
|
||||||
|
}
|
||||||
627
unilabos/compile/add_protocol.py
Normal file
627
unilabos/compile/add_protocol.py
Normal file
@@ -0,0 +1,627 @@
|
|||||||
|
import networkx as nx
|
||||||
|
from typing import List, Dict, Any
|
||||||
|
from .pump_protocol import generate_pump_protocol_with_rinsing
|
||||||
|
|
||||||
|
|
||||||
|
def find_reagent_vessel(G: nx.DiGraph, reagent: str) -> str:
|
||||||
|
"""
|
||||||
|
根据试剂名称查找对应的试剂瓶,支持多种匹配模式:
|
||||||
|
1. 容器名称匹配(如 flask_DMF, reagent_bottle_1-DMF)
|
||||||
|
2. 容器内液体类型匹配(如 liquid_type: "DMF", name: "ethanol")
|
||||||
|
3. 试剂名称匹配(如 reagent_name: "DMF", config.reagent: "ethyl_acetate")
|
||||||
|
|
||||||
|
Args:
|
||||||
|
G: 网络图
|
||||||
|
reagent: 试剂名称
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: 试剂瓶的vessel ID
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ValueError: 如果找不到对应的试剂瓶
|
||||||
|
"""
|
||||||
|
print(f"ADD_PROTOCOL: 正在查找试剂 '{reagent}' 的容器...")
|
||||||
|
|
||||||
|
# 第一步:通过容器名称匹配
|
||||||
|
possible_names = [
|
||||||
|
f"flask_{reagent}", # flask_DMF, flask_ethanol
|
||||||
|
f"bottle_{reagent}", # bottle_DMF, bottle_ethanol
|
||||||
|
f"vessel_{reagent}", # vessel_DMF, vessel_ethanol
|
||||||
|
f"{reagent}_flask", # DMF_flask, ethanol_flask
|
||||||
|
f"{reagent}_bottle", # DMF_bottle, ethanol_bottle
|
||||||
|
f"{reagent}", # 直接用试剂名
|
||||||
|
f"reagent_{reagent}", # reagent_DMF, reagent_ethanol
|
||||||
|
f"reagent_bottle_{reagent}", # reagent_bottle_DMF
|
||||||
|
]
|
||||||
|
|
||||||
|
# 尝试名称匹配
|
||||||
|
for vessel_name in possible_names:
|
||||||
|
if vessel_name in G.nodes():
|
||||||
|
print(f"ADD_PROTOCOL: 通过名称匹配找到容器: {vessel_name}")
|
||||||
|
return vessel_name
|
||||||
|
|
||||||
|
# 第二步:通过模糊名称匹配(名称中包含试剂名)
|
||||||
|
for node_id in G.nodes():
|
||||||
|
if G.nodes[node_id].get('type') == 'container':
|
||||||
|
# 检查节点ID或名称中是否包含试剂名
|
||||||
|
node_name = G.nodes[node_id].get('name', '').lower()
|
||||||
|
if (reagent.lower() in node_id.lower() or
|
||||||
|
reagent.lower() in node_name):
|
||||||
|
print(f"ADD_PROTOCOL: 通过模糊名称匹配找到容器: {node_id} (名称: {node_name})")
|
||||||
|
return node_id
|
||||||
|
|
||||||
|
# 第三步:通过液体类型匹配
|
||||||
|
for node_id in G.nodes():
|
||||||
|
if G.nodes[node_id].get('type') == 'container':
|
||||||
|
vessel_data = G.nodes[node_id].get('data', {})
|
||||||
|
liquids = vessel_data.get('liquid', [])
|
||||||
|
|
||||||
|
for liquid in liquids:
|
||||||
|
if isinstance(liquid, dict):
|
||||||
|
# 支持两种格式的液体类型字段
|
||||||
|
liquid_type = liquid.get('liquid_type') or liquid.get('name', '')
|
||||||
|
reagent_name = vessel_data.get('reagent_name', '')
|
||||||
|
config_reagent = G.nodes[node_id].get('config', {}).get('reagent', '')
|
||||||
|
|
||||||
|
# 检查多个可能的字段
|
||||||
|
if (liquid_type.lower() == reagent.lower() or
|
||||||
|
reagent_name.lower() == reagent.lower() or
|
||||||
|
config_reagent.lower() == reagent.lower()):
|
||||||
|
print(f"ADD_PROTOCOL: 通过液体类型匹配找到容器: {node_id}")
|
||||||
|
print(f" - liquid_type: {liquid_type}")
|
||||||
|
print(f" - reagent_name: {reagent_name}")
|
||||||
|
print(f" - config.reagent: {config_reagent}")
|
||||||
|
return node_id
|
||||||
|
|
||||||
|
# 第四步:列出所有可用的容器信息帮助调试
|
||||||
|
available_containers = []
|
||||||
|
for node_id in G.nodes():
|
||||||
|
if G.nodes[node_id].get('type') == 'container':
|
||||||
|
vessel_data = G.nodes[node_id].get('data', {})
|
||||||
|
config_data = G.nodes[node_id].get('config', {})
|
||||||
|
liquids = vessel_data.get('liquid', [])
|
||||||
|
|
||||||
|
container_info = {
|
||||||
|
'id': node_id,
|
||||||
|
'name': G.nodes[node_id].get('name', ''),
|
||||||
|
'liquid_types': [],
|
||||||
|
'reagent_name': vessel_data.get('reagent_name', ''),
|
||||||
|
'config_reagent': config_data.get('reagent', '')
|
||||||
|
}
|
||||||
|
|
||||||
|
for liquid in liquids:
|
||||||
|
if isinstance(liquid, dict):
|
||||||
|
liquid_type = liquid.get('liquid_type') or liquid.get('name', '')
|
||||||
|
if liquid_type:
|
||||||
|
container_info['liquid_types'].append(liquid_type)
|
||||||
|
|
||||||
|
available_containers.append(container_info)
|
||||||
|
|
||||||
|
print(f"ADD_PROTOCOL: 可用容器列表:")
|
||||||
|
for container in available_containers:
|
||||||
|
print(f" - {container['id']}: {container['name']}")
|
||||||
|
print(f" 液体类型: {container['liquid_types']}")
|
||||||
|
print(f" 试剂名称: {container['reagent_name']}")
|
||||||
|
print(f" 配置试剂: {container['config_reagent']}")
|
||||||
|
|
||||||
|
raise ValueError(f"找不到试剂 '{reagent}' 对应的试剂瓶。尝试了名称匹配: {possible_names}")
|
||||||
|
|
||||||
|
|
||||||
|
def find_reagent_vessel_by_any_match(G: nx.DiGraph, reagent: str) -> str:
|
||||||
|
"""
|
||||||
|
增强版试剂容器查找,支持各种匹配方式的别名函数
|
||||||
|
"""
|
||||||
|
return find_reagent_vessel(G, reagent)
|
||||||
|
|
||||||
|
|
||||||
|
def get_vessel_reagent_volume(G: nx.DiGraph, vessel: str) -> float:
|
||||||
|
"""获取容器中的试剂体积"""
|
||||||
|
if vessel not in G.nodes():
|
||||||
|
return 0.0
|
||||||
|
|
||||||
|
vessel_data = G.nodes[vessel].get('data', {})
|
||||||
|
liquids = vessel_data.get('liquid', [])
|
||||||
|
|
||||||
|
total_volume = 0.0
|
||||||
|
for liquid in liquids:
|
||||||
|
if isinstance(liquid, dict):
|
||||||
|
# 支持两种格式:新格式 (name, volume) 和旧格式 (liquid_type, liquid_volume)
|
||||||
|
volume = liquid.get('volume') or liquid.get('liquid_volume', 0.0)
|
||||||
|
total_volume += volume
|
||||||
|
|
||||||
|
return total_volume
|
||||||
|
|
||||||
|
|
||||||
|
def get_vessel_reagent_types(G: nx.DiGraph, vessel: str) -> List[str]:
|
||||||
|
"""获取容器中所有试剂的类型"""
|
||||||
|
if vessel not in G.nodes():
|
||||||
|
return []
|
||||||
|
|
||||||
|
vessel_data = G.nodes[vessel].get('data', {})
|
||||||
|
liquids = vessel_data.get('liquid', [])
|
||||||
|
|
||||||
|
reagent_types = []
|
||||||
|
for liquid in liquids:
|
||||||
|
if isinstance(liquid, dict):
|
||||||
|
# 支持两种格式的试剂类型字段
|
||||||
|
reagent_type = liquid.get('liquid_type') or liquid.get('name', '')
|
||||||
|
if reagent_type:
|
||||||
|
reagent_types.append(reagent_type)
|
||||||
|
|
||||||
|
# 同时检查配置中的试剂信息
|
||||||
|
config_reagent = G.nodes[vessel].get('config', {}).get('reagent', '')
|
||||||
|
reagent_name = vessel_data.get('reagent_name', '')
|
||||||
|
|
||||||
|
if config_reagent and config_reagent not in reagent_types:
|
||||||
|
reagent_types.append(config_reagent)
|
||||||
|
if reagent_name and reagent_name not in reagent_types:
|
||||||
|
reagent_types.append(reagent_name)
|
||||||
|
|
||||||
|
return reagent_types
|
||||||
|
|
||||||
|
|
||||||
|
def find_vessels_by_reagent(G: nx.DiGraph, reagent: str) -> List[str]:
|
||||||
|
"""
|
||||||
|
根据试剂类型查找所有匹配的容器
|
||||||
|
返回匹配容器的ID列表
|
||||||
|
"""
|
||||||
|
matching_vessels = []
|
||||||
|
|
||||||
|
for node_id in G.nodes():
|
||||||
|
if G.nodes[node_id].get('type') == 'container':
|
||||||
|
# 检查容器名称匹配
|
||||||
|
node_name = G.nodes[node_id].get('name', '').lower()
|
||||||
|
if reagent.lower() in node_id.lower() or reagent.lower() in node_name:
|
||||||
|
matching_vessels.append(node_id)
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 检查试剂类型匹配
|
||||||
|
vessel_data = G.nodes[node_id].get('data', {})
|
||||||
|
liquids = vessel_data.get('liquid', [])
|
||||||
|
config_data = G.nodes[node_id].get('config', {})
|
||||||
|
|
||||||
|
# 检查 reagent_name 和 config.reagent
|
||||||
|
reagent_name = vessel_data.get('reagent_name', '').lower()
|
||||||
|
config_reagent = config_data.get('reagent', '').lower()
|
||||||
|
|
||||||
|
if (reagent.lower() == reagent_name or
|
||||||
|
reagent.lower() == config_reagent):
|
||||||
|
matching_vessels.append(node_id)
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 检查液体列表
|
||||||
|
for liquid in liquids:
|
||||||
|
if isinstance(liquid, dict):
|
||||||
|
liquid_type = liquid.get('liquid_type') or liquid.get('name', '')
|
||||||
|
if liquid_type.lower() == reagent.lower():
|
||||||
|
matching_vessels.append(node_id)
|
||||||
|
break
|
||||||
|
|
||||||
|
return matching_vessels
|
||||||
|
|
||||||
|
|
||||||
|
def find_connected_stirrer(G: nx.DiGraph, vessel: str) -> str:
|
||||||
|
"""
|
||||||
|
查找与指定容器相连的搅拌器
|
||||||
|
|
||||||
|
Args:
|
||||||
|
G: 网络图
|
||||||
|
vessel: 容器ID
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: 搅拌器ID,如果找不到则返回None
|
||||||
|
"""
|
||||||
|
# 查找所有搅拌器节点
|
||||||
|
stirrer_nodes = [node for node in G.nodes()
|
||||||
|
if (G.nodes[node].get('class') or '') == 'virtual_stirrer']
|
||||||
|
|
||||||
|
# 检查哪个搅拌器与目标容器相连
|
||||||
|
for stirrer in stirrer_nodes:
|
||||||
|
if G.has_edge(stirrer, vessel) or G.has_edge(vessel, stirrer):
|
||||||
|
return stirrer
|
||||||
|
|
||||||
|
# 如果没有直接连接,返回第一个可用的搅拌器
|
||||||
|
return stirrer_nodes[0] if stirrer_nodes else None
|
||||||
|
|
||||||
|
|
||||||
|
def generate_add_protocol(
|
||||||
|
G: nx.DiGraph,
|
||||||
|
vessel: str,
|
||||||
|
reagent: str,
|
||||||
|
volume: float,
|
||||||
|
mass: float = 0.0,
|
||||||
|
amount: str = "",
|
||||||
|
time: float = 0.0,
|
||||||
|
stir: bool = False,
|
||||||
|
stir_speed: float = 300.0,
|
||||||
|
viscous: bool = False,
|
||||||
|
purpose: str = "添加试剂"
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
生成添加试剂的协议序列,支持智能试剂匹配
|
||||||
|
|
||||||
|
基于pump_protocol的成熟算法,实现试剂添加功能:
|
||||||
|
1. 智能查找试剂瓶(支持名称匹配、液体类型匹配、试剂配置匹配)
|
||||||
|
2. **先启动搅拌,再进行转移** - 确保试剂添加更均匀
|
||||||
|
3. 使用pump_protocol实现液体转移
|
||||||
|
|
||||||
|
Args:
|
||||||
|
G: 有向图,节点为容器和设备,边为连接关系
|
||||||
|
vessel: 目标容器(要添加试剂的容器)
|
||||||
|
reagent: 试剂名称(用于查找对应的试剂瓶)
|
||||||
|
volume: 要添加的体积 (mL)
|
||||||
|
mass: 要添加的质量 (g) - 暂时未使用,预留接口
|
||||||
|
amount: 其他数量描述
|
||||||
|
time: 添加时间 (s),如果指定则计算流速
|
||||||
|
stir: 是否启用搅拌
|
||||||
|
stir_speed: 搅拌速度 (RPM)
|
||||||
|
viscous: 是否为粘稠液体
|
||||||
|
purpose: 添加目的描述
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List[Dict[str, Any]]: 动作序列
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ValueError: 当找不到必要的设备或容器时
|
||||||
|
"""
|
||||||
|
action_sequence = []
|
||||||
|
|
||||||
|
print(f"ADD_PROTOCOL: 开始生成添加试剂协议")
|
||||||
|
print(f" - 目标容器: {vessel}")
|
||||||
|
print(f" - 试剂: {reagent}")
|
||||||
|
print(f" - 体积: {volume} mL")
|
||||||
|
print(f" - 质量: {mass} g")
|
||||||
|
print(f" - 搅拌: {stir} (速度: {stir_speed} RPM)")
|
||||||
|
print(f" - 粘稠: {viscous}")
|
||||||
|
print(f" - 目的: {purpose}")
|
||||||
|
|
||||||
|
# 1. 验证目标容器存在
|
||||||
|
if vessel not in G.nodes():
|
||||||
|
raise ValueError(f"目标容器 '{vessel}' 不存在于系统中")
|
||||||
|
|
||||||
|
# 2. 智能查找试剂瓶
|
||||||
|
try:
|
||||||
|
reagent_vessel = find_reagent_vessel(G, reagent)
|
||||||
|
print(f"ADD_PROTOCOL: 找到试剂容器: {reagent_vessel}")
|
||||||
|
except ValueError as e:
|
||||||
|
raise ValueError(f"无法找到试剂 '{reagent}': {str(e)}")
|
||||||
|
|
||||||
|
# 3. 验证试剂容器中的试剂体积
|
||||||
|
available_volume = get_vessel_reagent_volume(G, reagent_vessel)
|
||||||
|
print(f"ADD_PROTOCOL: 试剂容器 {reagent_vessel} 中有 {available_volume} mL 试剂")
|
||||||
|
|
||||||
|
if available_volume < volume:
|
||||||
|
print(f"ADD_PROTOCOL: 警告 - 试剂容器中的试剂不足!需要 {volume} mL,可用 {available_volume} mL")
|
||||||
|
|
||||||
|
# 4. 验证是否存在从试剂瓶到目标容器的路径
|
||||||
|
try:
|
||||||
|
path = nx.shortest_path(G, source=reagent_vessel, target=vessel)
|
||||||
|
print(f"ADD_PROTOCOL: 找到路径 {reagent_vessel} -> {vessel}: {path}")
|
||||||
|
except nx.NetworkXNoPath:
|
||||||
|
raise ValueError(f"从试剂瓶 '{reagent_vessel}' 到目标容器 '{vessel}' 没有可用路径")
|
||||||
|
|
||||||
|
# 5. **先启动搅拌** - 关键改进!
|
||||||
|
if stir:
|
||||||
|
try:
|
||||||
|
stirrer_id = find_connected_stirrer(G, vessel)
|
||||||
|
|
||||||
|
if stirrer_id:
|
||||||
|
print(f"ADD_PROTOCOL: 找到搅拌器 {stirrer_id},将在添加前启动搅拌")
|
||||||
|
|
||||||
|
# 先启动搅拌
|
||||||
|
stir_action = {
|
||||||
|
"device_id": stirrer_id,
|
||||||
|
"action_name": "start_stir",
|
||||||
|
"action_kwargs": {
|
||||||
|
"vessel": vessel,
|
||||||
|
"stir_speed": stir_speed,
|
||||||
|
"purpose": f"{purpose}: 启动搅拌,准备添加 {reagent}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
action_sequence.append(stir_action)
|
||||||
|
print(f"ADD_PROTOCOL: 已添加搅拌动作,速度 {stir_speed} RPM")
|
||||||
|
|
||||||
|
# 等待搅拌稳定
|
||||||
|
action_sequence.append({
|
||||||
|
"action_name": "wait",
|
||||||
|
"action_kwargs": {"time": 5}
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
print(f"ADD_PROTOCOL: 警告 - 需要搅拌但未找到与容器 {vessel} 相连的搅拌器")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"ADD_PROTOCOL: 搅拌器配置出错: {str(e)}")
|
||||||
|
|
||||||
|
# 6. 如果指定了体积,执行液体转移
|
||||||
|
if volume > 0:
|
||||||
|
# 6.1 计算流速参数
|
||||||
|
if time > 0:
|
||||||
|
# 根据时间计算流速
|
||||||
|
transfer_flowrate = volume / time
|
||||||
|
flowrate = transfer_flowrate
|
||||||
|
else:
|
||||||
|
# 使用默认流速
|
||||||
|
if viscous:
|
||||||
|
transfer_flowrate = 0.3 # 粘稠液体用较慢速度
|
||||||
|
flowrate = 1.0
|
||||||
|
else:
|
||||||
|
transfer_flowrate = 0.5 # 普通液体默认速度
|
||||||
|
flowrate = 2.5
|
||||||
|
|
||||||
|
print(f"ADD_PROTOCOL: 准备转移 {volume} mL 从 {reagent_vessel} 到 {vessel}")
|
||||||
|
print(f"ADD_PROTOCOL: 转移流速={transfer_flowrate} mL/s, 注入流速={flowrate} mL/s")
|
||||||
|
|
||||||
|
# 6.2 使用pump_protocol的核心算法实现液体转移
|
||||||
|
try:
|
||||||
|
pump_actions = generate_pump_protocol_with_rinsing(
|
||||||
|
G=G,
|
||||||
|
from_vessel=reagent_vessel,
|
||||||
|
to_vessel=vessel,
|
||||||
|
volume=volume,
|
||||||
|
amount=amount,
|
||||||
|
time=time,
|
||||||
|
viscous=viscous,
|
||||||
|
rinsing_solvent="", # 添加试剂通常不需要清洗
|
||||||
|
rinsing_volume=0.0,
|
||||||
|
rinsing_repeats=0,
|
||||||
|
solid=False,
|
||||||
|
flowrate=flowrate,
|
||||||
|
transfer_flowrate=transfer_flowrate
|
||||||
|
)
|
||||||
|
|
||||||
|
# 添加pump actions到序列中
|
||||||
|
action_sequence.extend(pump_actions)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
raise ValueError(f"生成泵协议时出错: {str(e)}")
|
||||||
|
|
||||||
|
print(f"ADD_PROTOCOL: 生成了 {len(action_sequence)} 个动作")
|
||||||
|
print(f"ADD_PROTOCOL: 添加试剂协议生成完成")
|
||||||
|
|
||||||
|
return action_sequence
|
||||||
|
|
||||||
|
|
||||||
|
def generate_add_protocol_with_cleaning(
|
||||||
|
G: nx.DiGraph,
|
||||||
|
vessel: str,
|
||||||
|
reagent: str,
|
||||||
|
volume: float,
|
||||||
|
mass: float = 0.0,
|
||||||
|
amount: str = "",
|
||||||
|
time: float = 0.0,
|
||||||
|
stir: bool = False,
|
||||||
|
stir_speed: float = 300.0,
|
||||||
|
viscous: bool = False,
|
||||||
|
purpose: str = "添加试剂",
|
||||||
|
cleaning_solvent: str = "air",
|
||||||
|
cleaning_volume: float = 5.0,
|
||||||
|
cleaning_repeats: int = 1
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
生成带清洗的添加试剂协议,支持智能试剂匹配
|
||||||
|
|
||||||
|
与普通添加协议的区别是会在添加后进行管道清洗
|
||||||
|
|
||||||
|
Args:
|
||||||
|
G: 有向图
|
||||||
|
vessel: 目标容器
|
||||||
|
reagent: 试剂名称
|
||||||
|
volume: 添加体积
|
||||||
|
mass: 添加质量(预留)
|
||||||
|
amount: 其他数量描述
|
||||||
|
time: 添加时间
|
||||||
|
stir: 是否搅拌
|
||||||
|
stir_speed: 搅拌速度
|
||||||
|
viscous: 是否粘稠
|
||||||
|
purpose: 添加目的
|
||||||
|
cleaning_solvent: 清洗溶剂("air"表示空气清洗)
|
||||||
|
cleaning_volume: 清洗体积
|
||||||
|
cleaning_repeats: 清洗重复次数
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List[Dict[str, Any]]: 动作序列
|
||||||
|
"""
|
||||||
|
action_sequence = []
|
||||||
|
|
||||||
|
# 1. 智能查找试剂瓶
|
||||||
|
reagent_vessel = find_reagent_vessel(G, reagent)
|
||||||
|
|
||||||
|
# 2. **先启动搅拌**
|
||||||
|
if stir:
|
||||||
|
stirrer_id = find_connected_stirrer(G, vessel)
|
||||||
|
if stirrer_id:
|
||||||
|
action_sequence.append({
|
||||||
|
"device_id": stirrer_id,
|
||||||
|
"action_name": "start_stir",
|
||||||
|
"action_kwargs": {
|
||||||
|
"vessel": vessel,
|
||||||
|
"stir_speed": stir_speed,
|
||||||
|
"purpose": f"{purpose}: 启动搅拌,准备添加 {reagent}"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
# 等待搅拌稳定
|
||||||
|
action_sequence.append({
|
||||||
|
"action_name": "wait",
|
||||||
|
"action_kwargs": {"time": 5}
|
||||||
|
})
|
||||||
|
|
||||||
|
# 3. 计算流速
|
||||||
|
if time > 0:
|
||||||
|
transfer_flowrate = volume / time
|
||||||
|
flowrate = transfer_flowrate
|
||||||
|
else:
|
||||||
|
if viscous:
|
||||||
|
transfer_flowrate = 0.3
|
||||||
|
flowrate = 1.0
|
||||||
|
else:
|
||||||
|
transfer_flowrate = 0.5
|
||||||
|
flowrate = 2.5
|
||||||
|
|
||||||
|
# 4. 使用带清洗的pump_protocol
|
||||||
|
pump_actions = generate_pump_protocol_with_rinsing(
|
||||||
|
G=G,
|
||||||
|
from_vessel=reagent_vessel,
|
||||||
|
to_vessel=vessel,
|
||||||
|
volume=volume,
|
||||||
|
amount=amount,
|
||||||
|
time=time,
|
||||||
|
viscous=viscous,
|
||||||
|
rinsing_solvent=cleaning_solvent,
|
||||||
|
rinsing_volume=cleaning_volume,
|
||||||
|
rinsing_repeats=cleaning_repeats,
|
||||||
|
solid=False,
|
||||||
|
flowrate=flowrate,
|
||||||
|
transfer_flowrate=transfer_flowrate
|
||||||
|
)
|
||||||
|
|
||||||
|
action_sequence.extend(pump_actions)
|
||||||
|
|
||||||
|
return action_sequence
|
||||||
|
|
||||||
|
|
||||||
|
def generate_sequential_add_protocol(
|
||||||
|
G: nx.DiGraph,
|
||||||
|
vessel: str,
|
||||||
|
reagents: List[Dict[str, Any]],
|
||||||
|
stir_between_additions: bool = True,
|
||||||
|
final_stir: bool = True,
|
||||||
|
final_stir_speed: float = 400.0,
|
||||||
|
final_stir_time: float = 300.0
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
生成连续添加多种试剂的协议,支持智能试剂匹配
|
||||||
|
|
||||||
|
Args:
|
||||||
|
G: 网络图
|
||||||
|
vessel: 目标容器
|
||||||
|
reagents: 试剂列表,每个元素包含试剂添加参数
|
||||||
|
stir_between_additions: 是否在每次添加之间搅拌
|
||||||
|
final_stir: 是否在所有添加完成后进行最终搅拌
|
||||||
|
final_stir_speed: 最终搅拌速度
|
||||||
|
final_stir_time: 最终搅拌时间
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List[Dict[str, Any]]: 完整的动作序列
|
||||||
|
|
||||||
|
Example:
|
||||||
|
reagents = [
|
||||||
|
{
|
||||||
|
"reagent": "DMF", # 会匹配 reagent_bottle_1 (reagent_name: "DMF")
|
||||||
|
"volume": 10.0,
|
||||||
|
"viscous": False,
|
||||||
|
"stir_speed": 300.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"reagent": "ethyl_acetate", # 会匹配 reagent_bottle_2 (reagent_name: "ethyl_acetate")
|
||||||
|
"volume": 5.0,
|
||||||
|
"viscous": False,
|
||||||
|
"stir_speed": 350.0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
"""
|
||||||
|
action_sequence = []
|
||||||
|
|
||||||
|
print(f"ADD_PROTOCOL: 开始连续添加 {len(reagents)} 种试剂到容器 {vessel}")
|
||||||
|
|
||||||
|
for i, reagent_params in enumerate(reagents):
|
||||||
|
reagent_name = reagent_params.get('reagent')
|
||||||
|
print(f"ADD_PROTOCOL: 处理第 {i+1}/{len(reagents)} 个试剂: {reagent_name}")
|
||||||
|
|
||||||
|
# 生成单个试剂的添加协议
|
||||||
|
add_actions = generate_add_protocol(
|
||||||
|
G=G,
|
||||||
|
vessel=vessel,
|
||||||
|
reagent=reagent_name,
|
||||||
|
volume=reagent_params.get('volume', 0.0),
|
||||||
|
mass=reagent_params.get('mass', 0.0),
|
||||||
|
amount=reagent_params.get('amount', ''),
|
||||||
|
time=reagent_params.get('time', 0.0),
|
||||||
|
stir=stir_between_additions,
|
||||||
|
stir_speed=reagent_params.get('stir_speed', 300.0),
|
||||||
|
viscous=reagent_params.get('viscous', False),
|
||||||
|
purpose=reagent_params.get('purpose', f'添加试剂 {reagent_name} ({i+1}/{len(reagents)})')
|
||||||
|
)
|
||||||
|
|
||||||
|
action_sequence.extend(add_actions)
|
||||||
|
|
||||||
|
# 在添加之间加入等待时间
|
||||||
|
if i < len(reagents) - 1: # 不是最后一个试剂
|
||||||
|
action_sequence.append({
|
||||||
|
"action_name": "wait",
|
||||||
|
"action_kwargs": {"time": 10} # 试剂混合时间
|
||||||
|
})
|
||||||
|
|
||||||
|
# 最终搅拌
|
||||||
|
if final_stir:
|
||||||
|
stirrer_id = find_connected_stirrer(G, vessel)
|
||||||
|
if stirrer_id:
|
||||||
|
print(f"ADD_PROTOCOL: 添加最终搅拌动作,速度 {final_stir_speed} RPM,时间 {final_stir_time} 秒")
|
||||||
|
action_sequence.extend([
|
||||||
|
{
|
||||||
|
"device_id": stirrer_id,
|
||||||
|
"action_name": "stir",
|
||||||
|
"action_kwargs": {
|
||||||
|
"stir_time": final_stir_time,
|
||||||
|
"stir_speed": final_stir_speed,
|
||||||
|
"settling_time": 30.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
print(f"ADD_PROTOCOL: 连续添加协议生成完成,共 {len(action_sequence)} 个动作")
|
||||||
|
return action_sequence
|
||||||
|
|
||||||
|
|
||||||
|
# 便捷函数:常用添加方案
|
||||||
|
def generate_organic_add_protocol(
|
||||||
|
G: nx.DiGraph,
|
||||||
|
vessel: str,
|
||||||
|
organic_reagent: str,
|
||||||
|
volume: float,
|
||||||
|
stir_speed: float = 400.0
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
"""有机试剂添加:慢速、搅拌"""
|
||||||
|
return generate_add_protocol(
|
||||||
|
G, vessel, organic_reagent, volume, 0.0, "", 0.0,
|
||||||
|
True, stir_speed, False, f"添加有机试剂 {organic_reagent}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def generate_viscous_add_protocol(
|
||||||
|
G: nx.DiGraph,
|
||||||
|
vessel: str,
|
||||||
|
viscous_reagent: str,
|
||||||
|
volume: float,
|
||||||
|
addition_time: float = 120.0
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
"""粘稠试剂添加:慢速、长时间"""
|
||||||
|
return generate_add_protocol(
|
||||||
|
G, vessel, viscous_reagent, volume, 0.0, "", addition_time,
|
||||||
|
True, 250.0, True, f"缓慢添加粘稠试剂 {viscous_reagent}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def generate_solvent_add_protocol(
|
||||||
|
G: nx.DiGraph,
|
||||||
|
vessel: str,
|
||||||
|
solvent: str,
|
||||||
|
volume: float
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
"""溶剂添加:快速、无需特殊处理"""
|
||||||
|
return generate_add_protocol(
|
||||||
|
G, vessel, solvent, volume, 0.0, "", 0.0,
|
||||||
|
False, 300.0, False, f"添加溶剂 {solvent}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# 使用示例和测试函数
|
||||||
|
def test_add_protocol():
|
||||||
|
"""测试添加协议的示例"""
|
||||||
|
print("=== ADD PROTOCOL 智能匹配测试 ===")
|
||||||
|
print("测试完成")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
test_add_protocol()
|
||||||
285
unilabos/compile/centrifuge_protocol.py
Normal file
285
unilabos/compile/centrifuge_protocol.py
Normal file
@@ -0,0 +1,285 @@
|
|||||||
|
from typing import List, Dict, Any
|
||||||
|
import networkx as nx
|
||||||
|
from .pump_protocol import generate_pump_protocol
|
||||||
|
|
||||||
|
|
||||||
|
def get_vessel_liquid_volume(G: nx.DiGraph, vessel: str) -> float:
|
||||||
|
"""
|
||||||
|
获取容器中的液体体积
|
||||||
|
"""
|
||||||
|
if vessel not in G.nodes():
|
||||||
|
return 0.0
|
||||||
|
|
||||||
|
vessel_data = G.nodes[vessel].get('data', {})
|
||||||
|
liquids = vessel_data.get('liquid', [])
|
||||||
|
|
||||||
|
total_volume = 0.0
|
||||||
|
for liquid in liquids:
|
||||||
|
if isinstance(liquid, dict) and 'liquid_volume' in liquid:
|
||||||
|
total_volume += liquid['liquid_volume']
|
||||||
|
|
||||||
|
return total_volume
|
||||||
|
|
||||||
|
|
||||||
|
def find_centrifuge_device(G: nx.DiGraph) -> str:
|
||||||
|
"""
|
||||||
|
查找离心机设备
|
||||||
|
"""
|
||||||
|
centrifuge_nodes = [node for node in G.nodes()
|
||||||
|
if (G.nodes[node].get('class') or '') == 'virtual_centrifuge']
|
||||||
|
|
||||||
|
if centrifuge_nodes:
|
||||||
|
return centrifuge_nodes[0]
|
||||||
|
|
||||||
|
raise ValueError("系统中未找到离心机设备")
|
||||||
|
|
||||||
|
|
||||||
|
def find_centrifuge_vessel(G: nx.DiGraph) -> str:
|
||||||
|
"""
|
||||||
|
查找离心机专用容器
|
||||||
|
"""
|
||||||
|
possible_names = [
|
||||||
|
"centrifuge_tube",
|
||||||
|
"centrifuge_vessel",
|
||||||
|
"tube_centrifuge",
|
||||||
|
"vessel_centrifuge",
|
||||||
|
"centrifuge",
|
||||||
|
"tube_15ml",
|
||||||
|
"tube_50ml"
|
||||||
|
]
|
||||||
|
|
||||||
|
for vessel_name in possible_names:
|
||||||
|
if vessel_name in G.nodes():
|
||||||
|
return vessel_name
|
||||||
|
|
||||||
|
raise ValueError(f"未找到离心机容器。尝试了以下名称: {possible_names}")
|
||||||
|
|
||||||
|
|
||||||
|
def generate_centrifuge_protocol(
|
||||||
|
G: nx.DiGraph,
|
||||||
|
vessel: str,
|
||||||
|
speed: float,
|
||||||
|
time: float,
|
||||||
|
temp: float = 25.0
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
生成离心操作的协议序列,复用 pump_protocol 的成熟算法
|
||||||
|
|
||||||
|
离心流程:
|
||||||
|
1. 液体转移:将待离心溶液从源容器转移到离心机容器
|
||||||
|
2. 离心操作:执行离心分离
|
||||||
|
3. 上清液转移:将离心后的上清液转移回原容器或新容器
|
||||||
|
4. 沉淀处理:处理离心沉淀(可选)
|
||||||
|
|
||||||
|
Args:
|
||||||
|
G: 有向图,节点为设备和容器,边为流体管道
|
||||||
|
vessel: 包含待离心溶液的容器名称
|
||||||
|
speed: 离心速度 (rpm)
|
||||||
|
time: 离心时间 (秒)
|
||||||
|
temp: 离心温度 (°C),默认25°C
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List[Dict[str, Any]]: 离心操作的动作序列
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ValueError: 当找不到必要的设备时抛出异常
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
centrifuge_actions = generate_centrifuge_protocol(G, "reaction_mixture", 5000, 600, 4.0)
|
||||||
|
"""
|
||||||
|
action_sequence = []
|
||||||
|
|
||||||
|
print(f"CENTRIFUGE: 开始生成离心协议")
|
||||||
|
print(f" - 源容器: {vessel}")
|
||||||
|
print(f" - 离心速度: {speed} rpm")
|
||||||
|
print(f" - 离心时间: {time}s ({time/60:.1f}分钟)")
|
||||||
|
print(f" - 离心温度: {temp}°C")
|
||||||
|
|
||||||
|
# 验证源容器存在
|
||||||
|
if vessel not in G.nodes():
|
||||||
|
raise ValueError(f"源容器 '{vessel}' 不存在于系统中")
|
||||||
|
|
||||||
|
# 获取源容器中的液体体积
|
||||||
|
source_volume = get_vessel_liquid_volume(G, vessel)
|
||||||
|
print(f"CENTRIFUGE: 源容器 {vessel} 中有 {source_volume} mL 液体")
|
||||||
|
|
||||||
|
# 查找离心机设备
|
||||||
|
try:
|
||||||
|
centrifuge_id = find_centrifuge_device(G)
|
||||||
|
print(f"CENTRIFUGE: 找到离心机: {centrifuge_id}")
|
||||||
|
except ValueError as e:
|
||||||
|
raise ValueError(f"无法找到离心机: {str(e)}")
|
||||||
|
|
||||||
|
# 查找离心机容器
|
||||||
|
try:
|
||||||
|
centrifuge_vessel = find_centrifuge_vessel(G)
|
||||||
|
print(f"CENTRIFUGE: 找到离心机容器: {centrifuge_vessel}")
|
||||||
|
except ValueError as e:
|
||||||
|
raise ValueError(f"无法找到离心机容器: {str(e)}")
|
||||||
|
|
||||||
|
# === 简化的体积计算策略 ===
|
||||||
|
if source_volume > 0:
|
||||||
|
# 如果能检测到液体体积,使用实际体积的大部分
|
||||||
|
transfer_volume = min(source_volume * 0.9, 15.0) # 90%或最多15mL(离心管通常较小)
|
||||||
|
print(f"CENTRIFUGE: 检测到液体体积,将转移 {transfer_volume} mL")
|
||||||
|
else:
|
||||||
|
# 如果检测不到液体体积,默认转移标准量
|
||||||
|
transfer_volume = 10.0 # 标准离心管体积
|
||||||
|
print(f"CENTRIFUGE: 未检测到液体体积,默认转移 {transfer_volume} mL")
|
||||||
|
|
||||||
|
# === 第一步:将待离心溶液转移到离心机容器 ===
|
||||||
|
print(f"CENTRIFUGE: 将 {transfer_volume} mL 溶液从 {vessel} 转移到 {centrifuge_vessel}")
|
||||||
|
try:
|
||||||
|
# 使用成熟的 pump_protocol 算法进行液体转移
|
||||||
|
transfer_to_centrifuge_actions = generate_pump_protocol(
|
||||||
|
G=G,
|
||||||
|
from_vessel=vessel,
|
||||||
|
to_vessel=centrifuge_vessel,
|
||||||
|
volume=transfer_volume,
|
||||||
|
flowrate=1.0, # 离心转移用慢速,避免气泡
|
||||||
|
transfer_flowrate=1.0
|
||||||
|
)
|
||||||
|
action_sequence.extend(transfer_to_centrifuge_actions)
|
||||||
|
except Exception as e:
|
||||||
|
raise ValueError(f"无法将溶液转移到离心机: {str(e)}")
|
||||||
|
|
||||||
|
# 转移后等待
|
||||||
|
wait_action = {
|
||||||
|
"action_name": "wait",
|
||||||
|
"action_kwargs": {"time": 5}
|
||||||
|
}
|
||||||
|
action_sequence.append(wait_action)
|
||||||
|
|
||||||
|
# === 第二步:执行离心操作 ===
|
||||||
|
print(f"CENTRIFUGE: 执行离心操作")
|
||||||
|
centrifuge_action = {
|
||||||
|
"device_id": centrifuge_id,
|
||||||
|
"action_name": "centrifuge",
|
||||||
|
"action_kwargs": {
|
||||||
|
"vessel": centrifuge_vessel,
|
||||||
|
"speed": speed,
|
||||||
|
"time": time,
|
||||||
|
"temp": temp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
action_sequence.append(centrifuge_action)
|
||||||
|
|
||||||
|
# 离心后等待系统稳定
|
||||||
|
wait_action = {
|
||||||
|
"action_name": "wait",
|
||||||
|
"action_kwargs": {"time": 10} # 离心后等待稍长,让沉淀稳定
|
||||||
|
}
|
||||||
|
action_sequence.append(wait_action)
|
||||||
|
|
||||||
|
# === 第三步:将上清液转移回原容器 ===
|
||||||
|
print(f"CENTRIFUGE: 将上清液从离心机转移回 {vessel}")
|
||||||
|
try:
|
||||||
|
# 估算上清液体积(约为转移体积的80% - 假设20%成为沉淀)
|
||||||
|
supernatant_volume = transfer_volume * 0.8
|
||||||
|
print(f"CENTRIFUGE: 预计上清液体积 {supernatant_volume} mL")
|
||||||
|
|
||||||
|
transfer_back_actions = generate_pump_protocol(
|
||||||
|
G=G,
|
||||||
|
from_vessel=centrifuge_vessel,
|
||||||
|
to_vessel=vessel,
|
||||||
|
volume=supernatant_volume,
|
||||||
|
flowrate=0.5, # 上清液转移更慢,避免扰动沉淀
|
||||||
|
transfer_flowrate=0.5
|
||||||
|
)
|
||||||
|
action_sequence.extend(transfer_back_actions)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"CENTRIFUGE: 将上清液转移回容器失败: {str(e)}")
|
||||||
|
|
||||||
|
# === 第四步:清洗离心机容器 ===
|
||||||
|
print(f"CENTRIFUGE: 清洗离心机容器")
|
||||||
|
try:
|
||||||
|
# 查找清洗溶剂
|
||||||
|
cleaning_solvent = None
|
||||||
|
for solvent in ["flask_water", "flask_ethanol", "flask_acetone"]:
|
||||||
|
if solvent in G.nodes():
|
||||||
|
cleaning_solvent = solvent
|
||||||
|
break
|
||||||
|
|
||||||
|
if cleaning_solvent:
|
||||||
|
# 用少量溶剂清洗离心管
|
||||||
|
cleaning_volume = 5.0 # 5mL清洗
|
||||||
|
print(f"CENTRIFUGE: 用 {cleaning_volume} mL {cleaning_solvent} 清洗")
|
||||||
|
|
||||||
|
# 清洗溶剂加入
|
||||||
|
cleaning_actions = generate_pump_protocol(
|
||||||
|
G=G,
|
||||||
|
from_vessel=cleaning_solvent,
|
||||||
|
to_vessel=centrifuge_vessel,
|
||||||
|
volume=cleaning_volume,
|
||||||
|
flowrate=2.0,
|
||||||
|
transfer_flowrate=2.0
|
||||||
|
)
|
||||||
|
action_sequence.extend(cleaning_actions)
|
||||||
|
|
||||||
|
# 将清洗液转移到废液
|
||||||
|
if "waste_workup" in G.nodes():
|
||||||
|
waste_actions = generate_pump_protocol(
|
||||||
|
G=G,
|
||||||
|
from_vessel=centrifuge_vessel,
|
||||||
|
to_vessel="waste_workup",
|
||||||
|
volume=cleaning_volume,
|
||||||
|
flowrate=2.0,
|
||||||
|
transfer_flowrate=2.0
|
||||||
|
)
|
||||||
|
action_sequence.extend(waste_actions)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"CENTRIFUGE: 清洗步骤失败: {str(e)}")
|
||||||
|
|
||||||
|
print(f"CENTRIFUGE: 生成了 {len(action_sequence)} 个动作")
|
||||||
|
print(f"CENTRIFUGE: 离心协议生成完成")
|
||||||
|
print(f"CENTRIFUGE: 总处理体积: {transfer_volume} mL")
|
||||||
|
|
||||||
|
return action_sequence
|
||||||
|
|
||||||
|
|
||||||
|
# 便捷函数:常用离心方案
|
||||||
|
def generate_low_speed_centrifuge_protocol(
|
||||||
|
G: nx.DiGraph,
|
||||||
|
vessel: str,
|
||||||
|
time: float = 300.0 # 5分钟
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
"""低速离心:细胞分离或大颗粒沉淀"""
|
||||||
|
return generate_centrifuge_protocol(G, vessel, 1000.0, time, 4.0)
|
||||||
|
|
||||||
|
|
||||||
|
def generate_high_speed_centrifuge_protocol(
|
||||||
|
G: nx.DiGraph,
|
||||||
|
vessel: str,
|
||||||
|
time: float = 600.0 # 10分钟
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
"""高速离心:蛋白质沉淀或小颗粒分离"""
|
||||||
|
return generate_centrifuge_protocol(G, vessel, 12000.0, time, 4.0)
|
||||||
|
|
||||||
|
|
||||||
|
def generate_standard_centrifuge_protocol(
|
||||||
|
G: nx.DiGraph,
|
||||||
|
vessel: str,
|
||||||
|
time: float = 600.0 # 10分钟
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
"""标准离心:常规样品处理"""
|
||||||
|
return generate_centrifuge_protocol(G, vessel, 5000.0, time, 25.0)
|
||||||
|
|
||||||
|
|
||||||
|
def generate_cold_centrifuge_protocol(
|
||||||
|
G: nx.DiGraph,
|
||||||
|
vessel: str,
|
||||||
|
speed: float = 5000.0,
|
||||||
|
time: float = 600.0
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
"""冷冻离心:热敏感样品处理"""
|
||||||
|
return generate_centrifuge_protocol(G, vessel, speed, time, 4.0)
|
||||||
|
|
||||||
|
|
||||||
|
def generate_ultra_centrifuge_protocol(
|
||||||
|
G: nx.DiGraph,
|
||||||
|
vessel: str,
|
||||||
|
time: float = 1800.0 # 30分钟
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
"""超高速离心:超细颗粒分离"""
|
||||||
|
return generate_centrifuge_protocol(G, vessel, 15000.0, time, 4.0)
|
||||||
439
unilabos/compile/clean_vessel_protocol.py
Normal file
439
unilabos/compile/clean_vessel_protocol.py
Normal file
@@ -0,0 +1,439 @@
|
|||||||
|
from typing import List, Dict, Any
|
||||||
|
import networkx as nx
|
||||||
|
from .pump_protocol import generate_pump_protocol
|
||||||
|
|
||||||
|
|
||||||
|
def find_solvent_vessel(G: nx.DiGraph, solvent: str) -> str:
|
||||||
|
"""
|
||||||
|
查找溶剂容器,支持多种匹配模式:
|
||||||
|
1. 容器名称匹配(如 flask_water, reagent_bottle_1-DMF)
|
||||||
|
2. 容器内液体类型匹配(如 liquid_type: "DMF", "ethanol")
|
||||||
|
"""
|
||||||
|
print(f"CLEAN_VESSEL: 正在查找溶剂 '{solvent}' 的容器...")
|
||||||
|
|
||||||
|
# 第一步:通过容器名称匹配
|
||||||
|
possible_names = [
|
||||||
|
f"flask_{solvent}", # flask_water, flask_ethanol
|
||||||
|
f"bottle_{solvent}", # bottle_water, bottle_ethanol
|
||||||
|
f"vessel_{solvent}", # vessel_water, vessel_ethanol
|
||||||
|
f"{solvent}_flask", # water_flask, ethanol_flask
|
||||||
|
f"{solvent}_bottle", # water_bottle, ethanol_bottle
|
||||||
|
f"{solvent}", # 直接用溶剂名
|
||||||
|
f"solvent_{solvent}", # solvent_water, solvent_ethanol
|
||||||
|
f"reagent_bottle_{solvent}", # reagent_bottle_DMF
|
||||||
|
]
|
||||||
|
|
||||||
|
# 尝试名称匹配
|
||||||
|
for vessel_name in possible_names:
|
||||||
|
if vessel_name in G.nodes():
|
||||||
|
print(f"CLEAN_VESSEL: 通过名称匹配找到容器: {vessel_name}")
|
||||||
|
return vessel_name
|
||||||
|
|
||||||
|
# 第二步:通过模糊名称匹配(名称中包含溶剂名)
|
||||||
|
for node_id in G.nodes():
|
||||||
|
if G.nodes[node_id].get('type') == 'container':
|
||||||
|
# 检查节点ID或名称中是否包含溶剂名
|
||||||
|
node_name = G.nodes[node_id].get('name', '').lower()
|
||||||
|
if (solvent.lower() in node_id.lower() or
|
||||||
|
solvent.lower() in node_name):
|
||||||
|
print(f"CLEAN_VESSEL: 通过模糊名称匹配找到容器: {node_id} (名称: {node_name})")
|
||||||
|
return node_id
|
||||||
|
|
||||||
|
# 第三步:通过液体类型匹配
|
||||||
|
for node_id in G.nodes():
|
||||||
|
if G.nodes[node_id].get('type') == 'container':
|
||||||
|
vessel_data = G.nodes[node_id].get('data', {})
|
||||||
|
liquids = vessel_data.get('liquid', [])
|
||||||
|
|
||||||
|
for liquid in liquids:
|
||||||
|
if isinstance(liquid, dict):
|
||||||
|
# 支持两种格式的液体类型字段
|
||||||
|
liquid_type = liquid.get('liquid_type') or liquid.get('name', '')
|
||||||
|
reagent_name = vessel_data.get('reagent_name', '')
|
||||||
|
config_reagent = G.nodes[node_id].get('config', {}).get('reagent', '')
|
||||||
|
|
||||||
|
# 检查多个可能的字段
|
||||||
|
if (liquid_type.lower() == solvent.lower() or
|
||||||
|
reagent_name.lower() == solvent.lower() or
|
||||||
|
config_reagent.lower() == solvent.lower()):
|
||||||
|
print(f"CLEAN_VESSEL: 通过液体类型匹配找到容器: {node_id}")
|
||||||
|
print(f" - liquid_type: {liquid_type}")
|
||||||
|
print(f" - reagent_name: {reagent_name}")
|
||||||
|
print(f" - config.reagent: {config_reagent}")
|
||||||
|
return node_id
|
||||||
|
|
||||||
|
# 第四步:列出所有可用的容器信息帮助调试
|
||||||
|
available_containers = []
|
||||||
|
for node_id in G.nodes():
|
||||||
|
if G.nodes[node_id].get('type') == 'container':
|
||||||
|
vessel_data = G.nodes[node_id].get('data', {})
|
||||||
|
config_data = G.nodes[node_id].get('config', {})
|
||||||
|
liquids = vessel_data.get('liquid', [])
|
||||||
|
|
||||||
|
container_info = {
|
||||||
|
'id': node_id,
|
||||||
|
'name': G.nodes[node_id].get('name', ''),
|
||||||
|
'liquid_types': [],
|
||||||
|
'reagent_name': vessel_data.get('reagent_name', ''),
|
||||||
|
'config_reagent': config_data.get('reagent', '')
|
||||||
|
}
|
||||||
|
|
||||||
|
for liquid in liquids:
|
||||||
|
if isinstance(liquid, dict):
|
||||||
|
liquid_type = liquid.get('liquid_type') or liquid.get('name', '')
|
||||||
|
if liquid_type:
|
||||||
|
container_info['liquid_types'].append(liquid_type)
|
||||||
|
|
||||||
|
available_containers.append(container_info)
|
||||||
|
|
||||||
|
print(f"CLEAN_VESSEL: 可用容器列表:")
|
||||||
|
for container in available_containers:
|
||||||
|
print(f" - {container['id']}: {container['name']}")
|
||||||
|
print(f" 液体类型: {container['liquid_types']}")
|
||||||
|
print(f" 试剂名称: {container['reagent_name']}")
|
||||||
|
print(f" 配置试剂: {container['config_reagent']}")
|
||||||
|
|
||||||
|
raise ValueError(f"未找到溶剂 '{solvent}' 的容器。尝试了名称匹配: {possible_names}")
|
||||||
|
|
||||||
|
|
||||||
|
def find_solvent_vessel_by_any_match(G: nx.DiGraph, solvent: str) -> str:
|
||||||
|
"""
|
||||||
|
增强版溶剂容器查找,支持各种匹配方式的别名函数
|
||||||
|
"""
|
||||||
|
return find_solvent_vessel(G, solvent)
|
||||||
|
|
||||||
|
|
||||||
|
def find_waste_vessel(G: nx.DiGraph) -> str:
|
||||||
|
"""
|
||||||
|
查找废液容器
|
||||||
|
"""
|
||||||
|
possible_waste_names = [
|
||||||
|
"waste_workup",
|
||||||
|
"flask_waste",
|
||||||
|
"bottle_waste",
|
||||||
|
"waste",
|
||||||
|
"waste_vessel",
|
||||||
|
"waste_container"
|
||||||
|
]
|
||||||
|
|
||||||
|
for waste_name in possible_waste_names:
|
||||||
|
if waste_name in G.nodes():
|
||||||
|
return waste_name
|
||||||
|
|
||||||
|
raise ValueError(f"未找到废液容器。尝试了以下名称: {possible_waste_names}")
|
||||||
|
|
||||||
|
|
||||||
|
def find_connected_heatchill(G: nx.DiGraph, vessel: str) -> str:
|
||||||
|
"""
|
||||||
|
查找与指定容器相连的加热冷却设备
|
||||||
|
"""
|
||||||
|
# 查找所有加热冷却设备节点
|
||||||
|
heatchill_nodes = [node for node in G.nodes()
|
||||||
|
if (G.nodes[node].get('class') or '') == 'virtual_heatchill']
|
||||||
|
|
||||||
|
# 检查哪个加热设备与目标容器相连(机械连接)
|
||||||
|
for heatchill in heatchill_nodes:
|
||||||
|
if G.has_edge(heatchill, vessel) or G.has_edge(vessel, heatchill):
|
||||||
|
return heatchill
|
||||||
|
|
||||||
|
# 如果没有直接连接,返回第一个可用的加热设备
|
||||||
|
if heatchill_nodes:
|
||||||
|
return heatchill_nodes[0]
|
||||||
|
|
||||||
|
return None # 没有加热设备也可以工作,只是不能加热
|
||||||
|
|
||||||
|
|
||||||
|
def generate_clean_vessel_protocol(
|
||||||
|
G: nx.DiGraph,
|
||||||
|
vessel: str,
|
||||||
|
solvent: str,
|
||||||
|
volume: float,
|
||||||
|
temp: float,
|
||||||
|
repeats: int = 1
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
生成容器清洗操作的协议序列,复用 pump_protocol 的成熟算法
|
||||||
|
|
||||||
|
清洗流程:
|
||||||
|
1. 查找溶剂容器和废液容器
|
||||||
|
2. 如果需要加热,启动加热设备
|
||||||
|
3. 重复以下操作 repeats 次:
|
||||||
|
a. 使用 pump_protocol 将溶剂从溶剂容器转移到目标容器
|
||||||
|
b. (可选) 等待清洗作用时间
|
||||||
|
c. 使用 pump_protocol 将清洗液从目标容器转移到废液容器
|
||||||
|
4. 如果加热了,停止加热
|
||||||
|
|
||||||
|
Args:
|
||||||
|
G: 有向图,节点为设备和容器,边为流体管道
|
||||||
|
vessel: 要清洗的容器名称
|
||||||
|
solvent: 用于清洗的溶剂名称
|
||||||
|
volume: 每次清洗使用的溶剂体积
|
||||||
|
temp: 清洗时的温度
|
||||||
|
repeats: 清洗操作的重复次数,默认为 1
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List[Dict[str, Any]]: 容器清洗操作的动作序列
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ValueError: 当找不到必要的容器或设备时抛出异常
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
clean_protocol = generate_clean_vessel_protocol(G, "main_reactor", "water", 100.0, 60.0, 2)
|
||||||
|
"""
|
||||||
|
action_sequence = []
|
||||||
|
|
||||||
|
print(f"CLEAN_VESSEL: 开始生成容器清洗协议")
|
||||||
|
print(f" - 目标容器: {vessel}")
|
||||||
|
print(f" - 清洗溶剂: {solvent}")
|
||||||
|
print(f" - 清洗体积: {volume} mL")
|
||||||
|
print(f" - 清洗温度: {temp}°C")
|
||||||
|
print(f" - 重复次数: {repeats}")
|
||||||
|
|
||||||
|
# 验证目标容器存在
|
||||||
|
if vessel not in G.nodes():
|
||||||
|
raise ValueError(f"目标容器 '{vessel}' 不存在于系统中")
|
||||||
|
|
||||||
|
# 查找溶剂容器
|
||||||
|
try:
|
||||||
|
solvent_vessel = find_solvent_vessel(G, solvent)
|
||||||
|
print(f"CLEAN_VESSEL: 找到溶剂容器: {solvent_vessel}")
|
||||||
|
except ValueError as e:
|
||||||
|
raise ValueError(f"无法找到溶剂容器: {str(e)}")
|
||||||
|
|
||||||
|
# 查找废液容器
|
||||||
|
try:
|
||||||
|
waste_vessel = find_waste_vessel(G)
|
||||||
|
print(f"CLEAN_VESSEL: 找到废液容器: {waste_vessel}")
|
||||||
|
except ValueError as e:
|
||||||
|
raise ValueError(f"无法找到废液容器: {str(e)}")
|
||||||
|
|
||||||
|
# 查找加热设备(可选)
|
||||||
|
heatchill_id = find_connected_heatchill(G, vessel)
|
||||||
|
if heatchill_id:
|
||||||
|
print(f"CLEAN_VESSEL: 找到加热设备: {heatchill_id}")
|
||||||
|
else:
|
||||||
|
print(f"CLEAN_VESSEL: 未找到加热设备,将在室温下清洗")
|
||||||
|
|
||||||
|
# 第一步:如果需要加热且有加热设备,启动加热
|
||||||
|
if temp > 25.0 and heatchill_id:
|
||||||
|
print(f"CLEAN_VESSEL: 启动加热至 {temp}°C")
|
||||||
|
heatchill_start_action = {
|
||||||
|
"device_id": heatchill_id,
|
||||||
|
"action_name": "heat_chill_start",
|
||||||
|
"action_kwargs": {
|
||||||
|
"vessel": vessel,
|
||||||
|
"temp": temp,
|
||||||
|
"purpose": f"cleaning with {solvent}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
action_sequence.append(heatchill_start_action)
|
||||||
|
|
||||||
|
# 等待温度稳定
|
||||||
|
wait_action = {
|
||||||
|
"action_name": "wait",
|
||||||
|
"action_kwargs": {"time": 30} # 等待30秒让温度稳定
|
||||||
|
}
|
||||||
|
action_sequence.append(wait_action)
|
||||||
|
|
||||||
|
# 第二步:重复清洗操作
|
||||||
|
for repeat in range(repeats):
|
||||||
|
print(f"CLEAN_VESSEL: 执行第 {repeat + 1} 次清洗")
|
||||||
|
|
||||||
|
# 2a. 使用 pump_protocol 将溶剂转移到目标容器
|
||||||
|
print(f"CLEAN_VESSEL: 将 {volume} mL {solvent} 转移到 {vessel}")
|
||||||
|
try:
|
||||||
|
# 调用成熟的 pump_protocol 算法
|
||||||
|
add_solvent_actions = generate_pump_protocol(
|
||||||
|
G=G,
|
||||||
|
from_vessel=solvent_vessel,
|
||||||
|
to_vessel=vessel,
|
||||||
|
volume=volume,
|
||||||
|
flowrate=2.5, # 适中的流速,避免飞溅
|
||||||
|
transfer_flowrate=2.5
|
||||||
|
)
|
||||||
|
action_sequence.extend(add_solvent_actions)
|
||||||
|
except Exception as e:
|
||||||
|
raise ValueError(f"无法将溶剂转移到容器: {str(e)}")
|
||||||
|
|
||||||
|
# 2b. 等待清洗作用时间(让溶剂充分清洗容器)
|
||||||
|
cleaning_wait_time = 60 if temp > 50.0 else 30 # 高温下等待更久
|
||||||
|
print(f"CLEAN_VESSEL: 等待清洗作用 {cleaning_wait_time} 秒")
|
||||||
|
wait_action = {
|
||||||
|
"action_name": "wait",
|
||||||
|
"action_kwargs": {"time": cleaning_wait_time}
|
||||||
|
}
|
||||||
|
action_sequence.append(wait_action)
|
||||||
|
|
||||||
|
# 2c. 使用 pump_protocol 将清洗液转移到废液容器
|
||||||
|
print(f"CLEAN_VESSEL: 将清洗液从 {vessel} 转移到废液容器")
|
||||||
|
try:
|
||||||
|
# 调用成熟的 pump_protocol 算法
|
||||||
|
remove_waste_actions = generate_pump_protocol(
|
||||||
|
G=G,
|
||||||
|
from_vessel=vessel,
|
||||||
|
to_vessel=waste_vessel,
|
||||||
|
volume=volume,
|
||||||
|
flowrate=2.5, # 适中的流速
|
||||||
|
transfer_flowrate=2.5
|
||||||
|
)
|
||||||
|
action_sequence.extend(remove_waste_actions)
|
||||||
|
except Exception as e:
|
||||||
|
raise ValueError(f"无法将清洗液转移到废液容器: {str(e)}")
|
||||||
|
|
||||||
|
# 2d. 清洗循环间的短暂等待
|
||||||
|
if repeat < repeats - 1: # 不是最后一次清洗
|
||||||
|
print(f"CLEAN_VESSEL: 清洗循环间等待")
|
||||||
|
wait_action = {
|
||||||
|
"action_name": "wait",
|
||||||
|
"action_kwargs": {"time": 10}
|
||||||
|
}
|
||||||
|
action_sequence.append(wait_action)
|
||||||
|
|
||||||
|
# 第三步:如果加热了,停止加热
|
||||||
|
if temp > 25.0 and heatchill_id:
|
||||||
|
print(f"CLEAN_VESSEL: 停止加热")
|
||||||
|
heatchill_stop_action = {
|
||||||
|
"device_id": heatchill_id,
|
||||||
|
"action_name": "heat_chill_stop",
|
||||||
|
"action_kwargs": {
|
||||||
|
"vessel": vessel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
action_sequence.append(heatchill_stop_action)
|
||||||
|
|
||||||
|
print(f"CLEAN_VESSEL: 生成了 {len(action_sequence)} 个动作")
|
||||||
|
print(f"CLEAN_VESSEL: 清洗协议生成完成")
|
||||||
|
|
||||||
|
return action_sequence
|
||||||
|
|
||||||
|
|
||||||
|
# 便捷函数:常用清洗方案
|
||||||
|
def generate_quick_clean_protocol(
|
||||||
|
G: nx.DiGraph,
|
||||||
|
vessel: str,
|
||||||
|
solvent: str = "water",
|
||||||
|
volume: float = 100.0
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
"""快速清洗:室温,单次清洗"""
|
||||||
|
return generate_clean_vessel_protocol(G, vessel, solvent, volume, 25.0, 1)
|
||||||
|
|
||||||
|
|
||||||
|
def generate_thorough_clean_protocol(
|
||||||
|
G: nx.DiGraph,
|
||||||
|
vessel: str,
|
||||||
|
solvent: str = "water",
|
||||||
|
volume: float = 150.0,
|
||||||
|
temp: float = 60.0
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
"""深度清洗:加热,多次清洗"""
|
||||||
|
return generate_clean_vessel_protocol(G, vessel, solvent, volume, temp, 3)
|
||||||
|
|
||||||
|
|
||||||
|
def generate_organic_clean_protocol(
|
||||||
|
G: nx.DiGraph,
|
||||||
|
vessel: str,
|
||||||
|
volume: float = 100.0
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
"""有机清洗:先用有机溶剂,再用水清洗"""
|
||||||
|
action_sequence = []
|
||||||
|
|
||||||
|
# 第一步:有机溶剂清洗
|
||||||
|
try:
|
||||||
|
organic_actions = generate_clean_vessel_protocol(
|
||||||
|
G, vessel, "acetone", volume, 25.0, 2
|
||||||
|
)
|
||||||
|
action_sequence.extend(organic_actions)
|
||||||
|
except ValueError:
|
||||||
|
# 如果没有丙酮,尝试乙醇
|
||||||
|
try:
|
||||||
|
organic_actions = generate_clean_vessel_protocol(
|
||||||
|
G, vessel, "ethanol", volume, 25.0, 2
|
||||||
|
)
|
||||||
|
action_sequence.extend(organic_actions)
|
||||||
|
except ValueError:
|
||||||
|
print("警告:未找到有机溶剂,跳过有机清洗步骤")
|
||||||
|
|
||||||
|
# 第二步:水清洗
|
||||||
|
water_actions = generate_clean_vessel_protocol(
|
||||||
|
G, vessel, "water", volume, 25.0, 2
|
||||||
|
)
|
||||||
|
action_sequence.extend(water_actions)
|
||||||
|
|
||||||
|
return action_sequence
|
||||||
|
|
||||||
|
|
||||||
|
def get_vessel_liquid_volume(G: nx.DiGraph, vessel: str) -> float:
|
||||||
|
"""获取容器中的液体体积(修复版)"""
|
||||||
|
if vessel not in G.nodes():
|
||||||
|
return 0.0
|
||||||
|
|
||||||
|
vessel_data = G.nodes[vessel].get('data', {})
|
||||||
|
liquids = vessel_data.get('liquid', [])
|
||||||
|
|
||||||
|
total_volume = 0.0
|
||||||
|
for liquid in liquids:
|
||||||
|
if isinstance(liquid, dict):
|
||||||
|
# 支持两种格式:新格式 (name, volume) 和旧格式 (liquid_type, liquid_volume)
|
||||||
|
volume = liquid.get('volume') or liquid.get('liquid_volume', 0.0)
|
||||||
|
total_volume += volume
|
||||||
|
|
||||||
|
return total_volume
|
||||||
|
|
||||||
|
|
||||||
|
def get_vessel_liquid_types(G: nx.DiGraph, vessel: str) -> List[str]:
|
||||||
|
"""获取容器中所有液体的类型"""
|
||||||
|
if vessel not in G.nodes():
|
||||||
|
return []
|
||||||
|
|
||||||
|
vessel_data = G.nodes[vessel].get('data', {})
|
||||||
|
liquids = vessel_data.get('liquid', [])
|
||||||
|
|
||||||
|
liquid_types = []
|
||||||
|
for liquid in liquids:
|
||||||
|
if isinstance(liquid, dict):
|
||||||
|
# 支持两种格式的液体类型字段
|
||||||
|
liquid_type = liquid.get('liquid_type') or liquid.get('name', '')
|
||||||
|
if liquid_type:
|
||||||
|
liquid_types.append(liquid_type)
|
||||||
|
|
||||||
|
return liquid_types
|
||||||
|
|
||||||
|
|
||||||
|
def find_vessel_by_content(G: nx.DiGraph, content: str) -> List[str]:
|
||||||
|
"""
|
||||||
|
根据内容物查找所有匹配的容器
|
||||||
|
返回匹配容器的ID列表
|
||||||
|
"""
|
||||||
|
matching_vessels = []
|
||||||
|
|
||||||
|
for node_id in G.nodes():
|
||||||
|
if G.nodes[node_id].get('type') == 'container':
|
||||||
|
# 检查容器名称匹配
|
||||||
|
node_name = G.nodes[node_id].get('name', '').lower()
|
||||||
|
if content.lower() in node_id.lower() or content.lower() in node_name:
|
||||||
|
matching_vessels.append(node_id)
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 检查液体类型匹配
|
||||||
|
vessel_data = G.nodes[node_id].get('data', {})
|
||||||
|
liquids = vessel_data.get('liquid', [])
|
||||||
|
config_data = G.nodes[node_id].get('config', {})
|
||||||
|
|
||||||
|
# 检查 reagent_name 和 config.reagent
|
||||||
|
reagent_name = vessel_data.get('reagent_name', '').lower()
|
||||||
|
config_reagent = config_data.get('reagent', '').lower()
|
||||||
|
|
||||||
|
if (content.lower() == reagent_name or
|
||||||
|
content.lower() == config_reagent):
|
||||||
|
matching_vessels.append(node_id)
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 检查液体列表
|
||||||
|
for liquid in liquids:
|
||||||
|
if isinstance(liquid, dict):
|
||||||
|
liquid_type = liquid.get('liquid_type') or liquid.get('name', '')
|
||||||
|
if liquid_type.lower() == content.lower():
|
||||||
|
matching_vessels.append(node_id)
|
||||||
|
break
|
||||||
|
|
||||||
|
return matching_vessels
|
||||||
359
unilabos/compile/dissolve_protocol.py
Normal file
359
unilabos/compile/dissolve_protocol.py
Normal file
@@ -0,0 +1,359 @@
|
|||||||
|
from typing import List, Dict, Any
|
||||||
|
import networkx as nx
|
||||||
|
from .pump_protocol import generate_pump_protocol
|
||||||
|
|
||||||
|
|
||||||
|
def find_solvent_vessel(G: nx.DiGraph, solvent: str) -> str:
|
||||||
|
"""
|
||||||
|
查找溶剂容器
|
||||||
|
"""
|
||||||
|
# 按照pump_protocol的命名规则查找溶剂瓶
|
||||||
|
solvent_vessel_id = f"flask_{solvent}"
|
||||||
|
|
||||||
|
if solvent_vessel_id in G.nodes():
|
||||||
|
return solvent_vessel_id
|
||||||
|
|
||||||
|
# 如果直接匹配失败,尝试模糊匹配
|
||||||
|
for node in G.nodes():
|
||||||
|
if node.startswith('flask_') and solvent.lower() in node.lower():
|
||||||
|
return node
|
||||||
|
|
||||||
|
# 如果还是找不到,列出所有可用的溶剂瓶
|
||||||
|
available_flasks = [node for node in G.nodes()
|
||||||
|
if node.startswith('flask_')
|
||||||
|
and G.nodes[node].get('type') == 'container']
|
||||||
|
|
||||||
|
raise ValueError(f"找不到溶剂 '{solvent}' 对应的溶剂瓶。可用溶剂瓶: {available_flasks}")
|
||||||
|
|
||||||
|
|
||||||
|
def find_connected_heatchill(G: nx.DiGraph, vessel: str) -> str:
|
||||||
|
"""
|
||||||
|
查找与指定容器相连的加热搅拌器
|
||||||
|
"""
|
||||||
|
# 查找所有加热搅拌器节点
|
||||||
|
heatchill_nodes = [node for node in G.nodes()
|
||||||
|
if G.nodes[node].get('class') == 'virtual_heatchill']
|
||||||
|
|
||||||
|
# 检查哪个加热器与目标容器相连
|
||||||
|
for heatchill in heatchill_nodes:
|
||||||
|
if G.has_edge(heatchill, vessel) or G.has_edge(vessel, heatchill):
|
||||||
|
return heatchill
|
||||||
|
|
||||||
|
# 如果没有直接连接,返回第一个可用的加热器
|
||||||
|
return heatchill_nodes[0] if heatchill_nodes else None
|
||||||
|
|
||||||
|
|
||||||
|
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 = 300.0
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
生成溶解操作的协议序列,复用 pump_protocol 的成熟算法
|
||||||
|
|
||||||
|
溶解流程:
|
||||||
|
1. 溶剂转移:将溶剂从溶剂瓶转移到目标容器
|
||||||
|
2. 启动加热搅拌:设置温度和搅拌
|
||||||
|
3. 等待溶解:监控溶解过程
|
||||||
|
4. 停止加热搅拌:完成溶解
|
||||||
|
|
||||||
|
Args:
|
||||||
|
G: 有向图,节点为设备和容器,边为流体管道
|
||||||
|
vessel: 目标容器(要进行溶解的容器)
|
||||||
|
solvent: 溶剂名称(用于查找对应的溶剂瓶)
|
||||||
|
volume: 溶剂体积 (mL)
|
||||||
|
amount: 要溶解的物质描述
|
||||||
|
temp: 溶解温度 (°C),默认25°C(室温)
|
||||||
|
time: 溶解时间 (秒),默认0(立即完成)
|
||||||
|
stir_speed: 搅拌速度 (RPM),默认300 RPM
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List[Dict[str, Any]]: 溶解操作的动作序列
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ValueError: 当找不到必要的设备或容器时
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
dissolve_actions = generate_dissolve_protocol(G, "reaction_mixture", "DMF", 10.0, "NaCl 2g", 60.0, 600.0, 400.0)
|
||||||
|
"""
|
||||||
|
action_sequence = []
|
||||||
|
|
||||||
|
print(f"DISSOLVE: 开始生成溶解协议")
|
||||||
|
print(f" - 目标容器: {vessel}")
|
||||||
|
print(f" - 溶剂: {solvent}")
|
||||||
|
print(f" - 溶剂体积: {volume} mL")
|
||||||
|
print(f" - 要溶解的物质: {amount}")
|
||||||
|
print(f" - 溶解温度: {temp}°C")
|
||||||
|
print(f" - 溶解时间: {time}s ({time/60:.1f}分钟)" if time > 0 else " - 溶解时间: 立即完成")
|
||||||
|
print(f" - 搅拌速度: {stir_speed} RPM")
|
||||||
|
|
||||||
|
# 验证目标容器存在
|
||||||
|
if vessel not in G.nodes():
|
||||||
|
raise ValueError(f"目标容器 '{vessel}' 不存在于系统中")
|
||||||
|
|
||||||
|
# 查找溶剂瓶
|
||||||
|
try:
|
||||||
|
solvent_vessel = find_solvent_vessel(G, solvent)
|
||||||
|
print(f"DISSOLVE: 找到溶剂瓶: {solvent_vessel}")
|
||||||
|
except ValueError as e:
|
||||||
|
raise ValueError(f"无法找到溶剂 '{solvent}': {str(e)}")
|
||||||
|
|
||||||
|
# 验证是否存在从溶剂瓶到目标容器的路径
|
||||||
|
try:
|
||||||
|
path = nx.shortest_path(G, source=solvent_vessel, target=vessel)
|
||||||
|
print(f"DISSOLVE: 找到路径 {solvent_vessel} -> {vessel}: {path}")
|
||||||
|
except nx.NetworkXNoPath:
|
||||||
|
raise ValueError(f"从溶剂瓶 '{solvent_vessel}' 到目标容器 '{vessel}' 没有可用路径")
|
||||||
|
|
||||||
|
# 查找加热搅拌器
|
||||||
|
heatchill_id = None
|
||||||
|
if temp > 25.0 or stir_speed > 0 or time > 0:
|
||||||
|
try:
|
||||||
|
heatchill_id = find_connected_heatchill(G, vessel)
|
||||||
|
if heatchill_id:
|
||||||
|
print(f"DISSOLVE: 找到加热搅拌器: {heatchill_id}")
|
||||||
|
else:
|
||||||
|
print(f"DISSOLVE: 警告 - 需要加热/搅拌但未找到与容器 {vessel} 相连的加热搅拌器")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"DISSOLVE: 加热搅拌器配置出错: {str(e)}")
|
||||||
|
|
||||||
|
# === 第一步:启动加热搅拌(在添加溶剂前) ===
|
||||||
|
if heatchill_id and (temp > 25.0 or time > 0):
|
||||||
|
print(f"DISSOLVE: 启动加热搅拌器,温度: {temp}°C")
|
||||||
|
|
||||||
|
if time > 0:
|
||||||
|
# 如果指定了时间,使用定时加热搅拌
|
||||||
|
heatchill_action = {
|
||||||
|
"device_id": heatchill_id,
|
||||||
|
"action_name": "heat_chill",
|
||||||
|
"action_kwargs": {
|
||||||
|
"vessel": vessel,
|
||||||
|
"temp": temp,
|
||||||
|
"time": time,
|
||||||
|
"stir": True,
|
||||||
|
"stir_speed": stir_speed,
|
||||||
|
"purpose": f"溶解 {amount} 在 {solvent} 中"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
# 如果没有指定时间,使用持续加热搅拌
|
||||||
|
heatchill_action = {
|
||||||
|
"device_id": heatchill_id,
|
||||||
|
"action_name": "heat_chill_start",
|
||||||
|
"action_kwargs": {
|
||||||
|
"vessel": vessel,
|
||||||
|
"temp": temp,
|
||||||
|
"purpose": f"溶解 {amount} 在 {solvent} 中"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
action_sequence.append(heatchill_action)
|
||||||
|
|
||||||
|
# 等待温度稳定
|
||||||
|
if temp > 25.0:
|
||||||
|
wait_time = min(60, abs(temp - 25.0) * 1.5) # 根据温差估算预热时间
|
||||||
|
action_sequence.append({
|
||||||
|
"action_name": "wait",
|
||||||
|
"action_kwargs": {"time": wait_time}
|
||||||
|
})
|
||||||
|
|
||||||
|
# === 第二步:添加溶剂到目标容器 ===
|
||||||
|
if volume > 0:
|
||||||
|
print(f"DISSOLVE: 将 {volume} mL {solvent} 从 {solvent_vessel} 转移到 {vessel}")
|
||||||
|
|
||||||
|
# 计算流速 - 溶解时通常用较慢的速度,避免飞溅
|
||||||
|
transfer_flowrate = 1.0 # 较慢的转移速度
|
||||||
|
flowrate = 0.5 # 较慢的注入速度
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 使用成熟的 pump_protocol 算法进行液体转移
|
||||||
|
pump_actions = generate_pump_protocol(
|
||||||
|
G=G,
|
||||||
|
from_vessel=solvent_vessel,
|
||||||
|
to_vessel=vessel,
|
||||||
|
volume=volume,
|
||||||
|
flowrate=flowrate, # 注入速度 - 较慢避免飞溅
|
||||||
|
transfer_flowrate=transfer_flowrate # 转移速度
|
||||||
|
)
|
||||||
|
|
||||||
|
action_sequence.extend(pump_actions)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
raise ValueError(f"生成泵协议时出错: {str(e)}")
|
||||||
|
|
||||||
|
# 溶剂添加后等待
|
||||||
|
action_sequence.append({
|
||||||
|
"action_name": "wait",
|
||||||
|
"action_kwargs": {"time": 5}
|
||||||
|
})
|
||||||
|
|
||||||
|
# === 第三步:如果没有使用定时加热搅拌,但需要等待溶解 ===
|
||||||
|
if time > 0 and heatchill_id and temp <= 25.0:
|
||||||
|
# 只需要搅拌等待,不需要加热
|
||||||
|
print(f"DISSOLVE: 室温搅拌 {time}s 等待溶解")
|
||||||
|
|
||||||
|
stir_action = {
|
||||||
|
"device_id": heatchill_id,
|
||||||
|
"action_name": "heat_chill",
|
||||||
|
"action_kwargs": {
|
||||||
|
"vessel": vessel,
|
||||||
|
"temp": 25.0, # 室温
|
||||||
|
"time": time,
|
||||||
|
"stir": True,
|
||||||
|
"stir_speed": stir_speed,
|
||||||
|
"purpose": f"室温搅拌溶解 {amount}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
action_sequence.append(stir_action)
|
||||||
|
|
||||||
|
# === 第四步:如果使用了持续加热,需要手动停止 ===
|
||||||
|
if heatchill_id and time == 0 and temp > 25.0:
|
||||||
|
print(f"DISSOLVE: 停止加热搅拌器")
|
||||||
|
|
||||||
|
stop_action = {
|
||||||
|
"device_id": heatchill_id,
|
||||||
|
"action_name": "heat_chill_stop",
|
||||||
|
"action_kwargs": {
|
||||||
|
"vessel": vessel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
action_sequence.append(stop_action)
|
||||||
|
|
||||||
|
print(f"DISSOLVE: 生成了 {len(action_sequence)} 个动作")
|
||||||
|
print(f"DISSOLVE: 溶解协议生成完成")
|
||||||
|
|
||||||
|
return action_sequence
|
||||||
|
|
||||||
|
|
||||||
|
# 便捷函数:常用溶解方案
|
||||||
|
def generate_room_temp_dissolve_protocol(
|
||||||
|
G: nx.DiGraph,
|
||||||
|
vessel: str,
|
||||||
|
solvent: str,
|
||||||
|
volume: float,
|
||||||
|
amount: str = "",
|
||||||
|
stir_time: float = 300.0 # 5分钟
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
"""室温溶解:快速搅拌,短时间"""
|
||||||
|
return generate_dissolve_protocol(G, vessel, solvent, volume, amount, 25.0, stir_time, 400.0)
|
||||||
|
|
||||||
|
|
||||||
|
def generate_heated_dissolve_protocol(
|
||||||
|
G: nx.DiGraph,
|
||||||
|
vessel: str,
|
||||||
|
solvent: str,
|
||||||
|
volume: float,
|
||||||
|
amount: str = "",
|
||||||
|
temp: float = 60.0,
|
||||||
|
dissolve_time: float = 900.0 # 15分钟
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
"""加热溶解:中等温度,较长时间"""
|
||||||
|
return generate_dissolve_protocol(G, vessel, solvent, volume, amount, temp, dissolve_time, 300.0)
|
||||||
|
|
||||||
|
|
||||||
|
def generate_gentle_dissolve_protocol(
|
||||||
|
G: nx.DiGraph,
|
||||||
|
vessel: str,
|
||||||
|
solvent: str,
|
||||||
|
volume: float,
|
||||||
|
amount: str = "",
|
||||||
|
temp: float = 40.0,
|
||||||
|
dissolve_time: float = 1800.0 # 30分钟
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
"""温和溶解:低温,长时间,慢搅拌"""
|
||||||
|
return generate_dissolve_protocol(G, vessel, solvent, volume, amount, temp, dissolve_time, 200.0)
|
||||||
|
|
||||||
|
|
||||||
|
def generate_hot_dissolve_protocol(
|
||||||
|
G: nx.DiGraph,
|
||||||
|
vessel: str,
|
||||||
|
solvent: str,
|
||||||
|
volume: float,
|
||||||
|
amount: str = "",
|
||||||
|
temp: float = 80.0,
|
||||||
|
dissolve_time: float = 600.0 # 10分钟
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
"""高温溶解:高温,中等时间,快搅拌"""
|
||||||
|
return generate_dissolve_protocol(G, vessel, solvent, volume, amount, temp, dissolve_time, 500.0)
|
||||||
|
|
||||||
|
|
||||||
|
def generate_sequential_dissolve_protocol(
|
||||||
|
G: nx.DiGraph,
|
||||||
|
vessel: str,
|
||||||
|
dissolve_steps: List[Dict[str, Any]]
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
生成连续溶解多种物质的协议
|
||||||
|
|
||||||
|
Args:
|
||||||
|
G: 网络图
|
||||||
|
vessel: 目标容器
|
||||||
|
dissolve_steps: 溶解步骤列表,每个元素包含溶解参数
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List[Dict[str, Any]]: 完整的动作序列
|
||||||
|
|
||||||
|
Example:
|
||||||
|
dissolve_steps = [
|
||||||
|
{
|
||||||
|
"solvent": "water",
|
||||||
|
"volume": 5.0,
|
||||||
|
"amount": "NaCl 1g",
|
||||||
|
"temp": 25.0,
|
||||||
|
"time": 300.0,
|
||||||
|
"stir_speed": 300.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"solvent": "ethanol",
|
||||||
|
"volume": 2.0,
|
||||||
|
"amount": "organic compound 0.5g",
|
||||||
|
"temp": 40.0,
|
||||||
|
"time": 600.0,
|
||||||
|
"stir_speed": 400.0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
"""
|
||||||
|
action_sequence = []
|
||||||
|
|
||||||
|
for i, step in enumerate(dissolve_steps):
|
||||||
|
print(f"DISSOLVE: 处理第 {i+1}/{len(dissolve_steps)} 个溶解步骤")
|
||||||
|
|
||||||
|
# 生成单个溶解步骤的协议
|
||||||
|
dissolve_actions = generate_dissolve_protocol(
|
||||||
|
G=G,
|
||||||
|
vessel=vessel,
|
||||||
|
solvent=step.get('solvent'),
|
||||||
|
volume=step.get('volume', 0.0),
|
||||||
|
amount=step.get('amount', ''),
|
||||||
|
temp=step.get('temp', 25.0),
|
||||||
|
time=step.get('time', 0.0),
|
||||||
|
stir_speed=step.get('stir_speed', 300.0)
|
||||||
|
)
|
||||||
|
|
||||||
|
action_sequence.extend(dissolve_actions)
|
||||||
|
|
||||||
|
# 在步骤之间加入等待时间
|
||||||
|
if i < len(dissolve_steps) - 1: # 不是最后一个步骤
|
||||||
|
action_sequence.append({
|
||||||
|
"action_name": "wait",
|
||||||
|
"action_kwargs": {"time": 10}
|
||||||
|
})
|
||||||
|
|
||||||
|
print(f"DISSOLVE: 连续溶解协议生成完成,共 {len(action_sequence)} 个动作")
|
||||||
|
return action_sequence
|
||||||
|
|
||||||
|
|
||||||
|
# 测试函数
|
||||||
|
def test_dissolve_protocol():
|
||||||
|
"""测试溶解协议的示例"""
|
||||||
|
print("=== DISSOLVE PROTOCOL 测试 ===")
|
||||||
|
print("测试完成")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
test_dissolve_protocol()
|
||||||
@@ -1,143 +1,437 @@
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
import networkx as nx
|
import networkx as nx
|
||||||
|
from typing import List, Dict, Any, Optional
|
||||||
|
from .pump_protocol import generate_pump_protocol_with_rinsing, generate_pump_protocol
|
||||||
|
|
||||||
|
|
||||||
|
def find_gas_source(G: nx.DiGraph, gas: str) -> str:
|
||||||
|
"""根据气体名称查找对应的气源"""
|
||||||
|
# 按照命名规则查找气源
|
||||||
|
gas_source_patterns = [
|
||||||
|
f"gas_source_{gas}",
|
||||||
|
f"gas_{gas}",
|
||||||
|
f"flask_{gas}",
|
||||||
|
f"{gas}_source"
|
||||||
|
]
|
||||||
|
|
||||||
|
for pattern in gas_source_patterns:
|
||||||
|
if pattern in G.nodes():
|
||||||
|
return pattern
|
||||||
|
|
||||||
|
# 模糊匹配
|
||||||
|
for node in G.nodes():
|
||||||
|
node_class = G.nodes[node].get('class', '') or ''
|
||||||
|
if 'gas_source' in node_class and gas.lower() in node.lower():
|
||||||
|
return node
|
||||||
|
if node.startswith('flask_') and gas.lower() in node.lower():
|
||||||
|
return node
|
||||||
|
|
||||||
|
# 查找所有可用的气源
|
||||||
|
available_gas_sources = [
|
||||||
|
node for node in G.nodes()
|
||||||
|
if ((G.nodes[node].get('class') or '').startswith('virtual_gas_source')
|
||||||
|
or ('gas' in node and 'source' in node)
|
||||||
|
or (node.startswith('flask_') and any(g in node.lower() for g in ['air', 'nitrogen', 'argon', 'vacuum'])))
|
||||||
|
]
|
||||||
|
|
||||||
|
raise ValueError(f"找不到气体 '{gas}' 对应的气源。可用气源: {available_gas_sources}")
|
||||||
|
|
||||||
|
|
||||||
|
def find_vacuum_pump(G: nx.DiGraph) -> str:
|
||||||
|
"""查找真空泵设备"""
|
||||||
|
vacuum_pumps = [
|
||||||
|
node for node in G.nodes()
|
||||||
|
if ((G.nodes[node].get('class') or '').startswith('virtual_vacuum_pump')
|
||||||
|
or 'vacuum_pump' in node
|
||||||
|
or 'vacuum' in (G.nodes[node].get('class') or ''))
|
||||||
|
]
|
||||||
|
|
||||||
|
if not vacuum_pumps:
|
||||||
|
raise ValueError("系统中未找到真空泵设备")
|
||||||
|
|
||||||
|
return vacuum_pumps[0]
|
||||||
|
|
||||||
|
|
||||||
|
def find_connected_stirrer(G: nx.DiGraph, vessel: str) -> str:
|
||||||
|
"""查找与指定容器相连的搅拌器"""
|
||||||
|
stirrer_nodes = [node for node in G.nodes()
|
||||||
|
if (G.nodes[node].get('class') or '') == 'virtual_stirrer']
|
||||||
|
|
||||||
|
# 检查哪个搅拌器与目标容器相连
|
||||||
|
for stirrer in stirrer_nodes:
|
||||||
|
if G.has_edge(stirrer, vessel) or G.has_edge(vessel, stirrer):
|
||||||
|
return stirrer
|
||||||
|
|
||||||
|
return stirrer_nodes[0] if stirrer_nodes else None
|
||||||
|
|
||||||
|
|
||||||
|
def find_associated_solenoid_valve(G: nx.DiGraph, device_id: str) -> Optional[str]:
|
||||||
|
"""查找与指定设备相关联的电磁阀"""
|
||||||
|
solenoid_valves = [
|
||||||
|
node for node in G.nodes()
|
||||||
|
if ('solenoid' in (G.nodes[node].get('class') or '').lower()
|
||||||
|
or 'solenoid_valve' in node)
|
||||||
|
]
|
||||||
|
|
||||||
|
# 通过网络连接查找直接相连的电磁阀
|
||||||
|
for solenoid in solenoid_valves:
|
||||||
|
if G.has_edge(device_id, solenoid) or G.has_edge(solenoid, device_id):
|
||||||
|
return solenoid
|
||||||
|
|
||||||
|
# 通过命名规则查找关联的电磁阀
|
||||||
|
device_type = ""
|
||||||
|
if 'vacuum' in device_id.lower():
|
||||||
|
device_type = "vacuum"
|
||||||
|
elif 'gas' in device_id.lower():
|
||||||
|
device_type = "gas"
|
||||||
|
|
||||||
|
if device_type:
|
||||||
|
for solenoid in solenoid_valves:
|
||||||
|
if device_type in solenoid.lower():
|
||||||
|
return solenoid
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def generate_evacuateandrefill_protocol(
|
def generate_evacuateandrefill_protocol(
|
||||||
G: nx.DiGraph,
|
G: nx.DiGraph,
|
||||||
vessel: str,
|
vessel: str,
|
||||||
gas: str,
|
gas: str,
|
||||||
repeats: int = 1
|
repeats: int = 1
|
||||||
) -> list[dict]:
|
) -> List[Dict[str, Any]]:
|
||||||
"""
|
"""
|
||||||
生成泵操作的动作序列。
|
生成抽真空和充气操作的动作序列
|
||||||
|
|
||||||
:param G: 有向图, 节点为容器和注射泵, 边为流体管道, A→B边的属性为管道接A端的阀门位置
|
**修复版本**: 正确调用 pump_protocol 并处理异常
|
||||||
:param from_vessel: 容器A
|
|
||||||
:param to_vessel: 容器B
|
|
||||||
:param volume: 转移的体积
|
|
||||||
:param flowrate: 最终注入容器B时的流速
|
|
||||||
:param transfer_flowrate: 泵骨架中转移流速(若不指定,默认与注入流速相同)
|
|
||||||
:return: 泵操作的动作序列
|
|
||||||
"""
|
"""
|
||||||
|
action_sequence = []
|
||||||
|
|
||||||
# 生成电磁阀、真空泵、气源操作的动作序列
|
# 参数设置 - 关键修复:减小体积避免超出泵容量
|
||||||
vacuum_action_sequence = []
|
VACUUM_VOLUME = 20.0 # 减小抽真空体积
|
||||||
nodes = G.nodes(data=True)
|
REFILL_VOLUME = 20.0 # 减小充气体积
|
||||||
|
PUMP_FLOW_RATE = 2.5 # 降低流速
|
||||||
|
STIR_SPEED = 300.0
|
||||||
|
|
||||||
# 找到和 vessel 相连的电磁阀和真空泵、气源
|
print(f"EVACUATE_REFILL: 开始生成协议,目标容器: {vessel}, 气体: {gas}, 重复次数: {repeats}")
|
||||||
vacuum_backbone = {"vessel": vessel}
|
|
||||||
|
|
||||||
for neighbor in G.neighbors(vessel):
|
# 1. 验证设备存在
|
||||||
if nodes[neighbor]["class"].startswith("solenoid_valve"):
|
if vessel not in G.nodes():
|
||||||
for neighbor2 in G.neighbors(neighbor):
|
raise ValueError(f"目标容器 '{vessel}' 不存在于系统中")
|
||||||
if neighbor2 == vessel:
|
|
||||||
continue
|
|
||||||
if nodes[neighbor2]["class"].startswith("vacuum_pump"):
|
|
||||||
vacuum_backbone.update({"vacuum_valve": neighbor, "pump": neighbor2})
|
|
||||||
break
|
|
||||||
elif nodes[neighbor2]["class"].startswith("gas_source"):
|
|
||||||
vacuum_backbone.update({"gas_valve": neighbor, "gas": neighbor2})
|
|
||||||
break
|
|
||||||
# 判断是否设备齐全
|
|
||||||
if len(vacuum_backbone) < 5:
|
|
||||||
print(f"\n\n\n{vacuum_backbone}\n\n\n")
|
|
||||||
raise ValueError("Not all devices are connected to the vessel.")
|
|
||||||
|
|
||||||
# 生成操作的动作序列
|
# 2. 查找设备
|
||||||
for i in range(repeats):
|
try:
|
||||||
# 打开真空泵阀门、关闭气源阀门
|
vacuum_pump = find_vacuum_pump(G)
|
||||||
vacuum_action_sequence.append([
|
vacuum_solenoid = find_associated_solenoid_valve(G, vacuum_pump)
|
||||||
{
|
gas_source = find_gas_source(G, gas)
|
||||||
"device_id": vacuum_backbone["vacuum_valve"],
|
gas_solenoid = find_associated_solenoid_valve(G, gas_source)
|
||||||
"action_name": "set_valve_position",
|
stirrer_id = find_connected_stirrer(G, vessel)
|
||||||
"action_kwargs": {
|
|
||||||
"command": "OPEN"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"device_id": vacuum_backbone["gas_valve"],
|
|
||||||
"action_name": "set_valve_position",
|
|
||||||
"action_kwargs": {
|
|
||||||
"command": "CLOSED"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
])
|
|
||||||
|
|
||||||
# 打开真空泵、关闭气源
|
print(f"EVACUATE_REFILL: 找到设备")
|
||||||
vacuum_action_sequence.append([
|
print(f" - 真空泵: {vacuum_pump}")
|
||||||
{
|
print(f" - 气源: {gas_source}")
|
||||||
"device_id": vacuum_backbone["pump"],
|
print(f" - 真空电磁阀: {vacuum_solenoid}")
|
||||||
"action_name": "set_status",
|
print(f" - 气源电磁阀: {gas_solenoid}")
|
||||||
"action_kwargs": {
|
print(f" - 搅拌器: {stirrer_id}")
|
||||||
"command": "ON"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"device_id": vacuum_backbone["gas"],
|
|
||||||
"action_name": "set_status",
|
|
||||||
"action_kwargs": {
|
|
||||||
"command": "OFF"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
])
|
|
||||||
vacuum_action_sequence.append({"action_name": "wait", "action_kwargs": {"time": 60}})
|
|
||||||
|
|
||||||
# 关闭真空泵阀门、打开气源阀门
|
except ValueError as e:
|
||||||
vacuum_action_sequence.append([
|
raise ValueError(f"设备查找失败: {str(e)}")
|
||||||
{
|
|
||||||
"device_id": vacuum_backbone["vacuum_valve"],
|
# 3. **关键修复**: 验证路径存在性
|
||||||
"action_name": "set_valve_position",
|
try:
|
||||||
"action_kwargs": {
|
# 验证抽真空路径
|
||||||
"command": "CLOSED"
|
vacuum_path = nx.shortest_path(G, source=vessel, target=vacuum_pump)
|
||||||
}
|
print(f"EVACUATE_REFILL: 抽真空路径: {' → '.join(vacuum_path)}")
|
||||||
},
|
|
||||||
{
|
|
||||||
"device_id": vacuum_backbone["gas_valve"],
|
|
||||||
"action_name": "set_valve_position",
|
|
||||||
"action_kwargs": {
|
|
||||||
"command": "OPEN"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
])
|
|
||||||
|
|
||||||
# 关闭真空泵、打开气源
|
# 验证充气路径
|
||||||
vacuum_action_sequence.append([
|
gas_path = nx.shortest_path(G, source=gas_source, target=vessel)
|
||||||
{
|
print(f"EVACUATE_REFILL: 充气路径: {' → '.join(gas_path)}")
|
||||||
"device_id": vacuum_backbone["pump"],
|
|
||||||
"action_name": "set_status",
|
# **新增**: 检查路径中的边数据
|
||||||
"action_kwargs": {
|
for i in range(len(vacuum_path) - 1):
|
||||||
"command": "OFF"
|
nodeA, nodeB = vacuum_path[i], vacuum_path[i + 1]
|
||||||
}
|
edge_data = G.get_edge_data(nodeA, nodeB)
|
||||||
},
|
if not edge_data or 'port' not in edge_data:
|
||||||
{
|
raise ValueError(f"路径 {nodeA} → {nodeB} 缺少端口信息")
|
||||||
"device_id": vacuum_backbone["gas"],
|
print(f" 抽真空路径边 {nodeA} → {nodeB}: {edge_data}")
|
||||||
"action_name": "set_status",
|
|
||||||
"action_kwargs": {
|
for i in range(len(gas_path) - 1):
|
||||||
"command": "ON"
|
nodeA, nodeB = gas_path[i], gas_path[i + 1]
|
||||||
}
|
edge_data = G.get_edge_data(nodeA, nodeB)
|
||||||
|
if not edge_data or 'port' not in edge_data:
|
||||||
|
raise ValueError(f"路径 {nodeA} → {nodeB} 缺少端口信息")
|
||||||
|
print(f" 充气路径边 {nodeA} → {nodeB}: {edge_data}")
|
||||||
|
|
||||||
|
except nx.NetworkXNoPath as e:
|
||||||
|
raise ValueError(f"路径不存在: {str(e)}")
|
||||||
|
except Exception as e:
|
||||||
|
raise ValueError(f"路径验证失败: {str(e)}")
|
||||||
|
|
||||||
|
# 4. 启动搅拌器
|
||||||
|
if stirrer_id:
|
||||||
|
action_sequence.append({
|
||||||
|
"device_id": stirrer_id,
|
||||||
|
"action_name": "start_stir",
|
||||||
|
"action_kwargs": {
|
||||||
|
"vessel": vessel,
|
||||||
|
"stir_speed": STIR_SPEED,
|
||||||
|
"purpose": "抽真空充气操作前启动搅拌"
|
||||||
}
|
}
|
||||||
])
|
})
|
||||||
vacuum_action_sequence.append({"action_name": "wait", "action_kwargs": {"time": 60}})
|
|
||||||
|
# 5. 执行多次抽真空-充气循环
|
||||||
|
for cycle in range(repeats):
|
||||||
|
print(f"EVACUATE_REFILL: === 第 {cycle+1}/{repeats} 次循环 ===")
|
||||||
|
|
||||||
|
# ============ 抽真空阶段 ============
|
||||||
|
print(f"EVACUATE_REFILL: 抽真空阶段开始")
|
||||||
|
|
||||||
|
# 启动真空泵
|
||||||
|
action_sequence.append({
|
||||||
|
"device_id": vacuum_pump,
|
||||||
|
"action_name": "set_status",
|
||||||
|
"action_kwargs": {"string": "ON"}
|
||||||
|
})
|
||||||
|
|
||||||
|
# 开启真空电磁阀
|
||||||
|
if vacuum_solenoid:
|
||||||
|
action_sequence.append({
|
||||||
|
"device_id": vacuum_solenoid,
|
||||||
|
"action_name": "set_valve_position",
|
||||||
|
"action_kwargs": {"command": "OPEN"}
|
||||||
|
})
|
||||||
|
|
||||||
|
# **关键修复**: 改进 pump_protocol 调用和错误处理
|
||||||
|
print(f"EVACUATE_REFILL: 调用抽真空 pump_protocol: {vessel} → {vacuum_pump}")
|
||||||
|
print(f" - 体积: {VACUUM_VOLUME} mL")
|
||||||
|
print(f" - 流速: {PUMP_FLOW_RATE} mL/s")
|
||||||
|
|
||||||
|
try:
|
||||||
|
vacuum_transfer_actions = generate_pump_protocol_with_rinsing(
|
||||||
|
G=G,
|
||||||
|
from_vessel=vessel,
|
||||||
|
to_vessel=vacuum_pump,
|
||||||
|
volume=VACUUM_VOLUME,
|
||||||
|
amount="",
|
||||||
|
time=0.0,
|
||||||
|
viscous=False,
|
||||||
|
rinsing_solvent="", # **修复**: 明确不使用清洗
|
||||||
|
rinsing_volume=0.0,
|
||||||
|
rinsing_repeats=0,
|
||||||
|
solid=False,
|
||||||
|
flowrate=PUMP_FLOW_RATE,
|
||||||
|
transfer_flowrate=PUMP_FLOW_RATE
|
||||||
|
)
|
||||||
|
|
||||||
|
if vacuum_transfer_actions:
|
||||||
|
action_sequence.extend(vacuum_transfer_actions)
|
||||||
|
print(f"EVACUATE_REFILL: ✅ 成功添加 {len(vacuum_transfer_actions)} 个抽真空动作")
|
||||||
|
else:
|
||||||
|
print(f"EVACUATE_REFILL: ⚠️ 抽真空 pump_protocol 返回空序列")
|
||||||
|
# **修复**: 添加手动泵动作作为备选
|
||||||
|
action_sequence.extend([
|
||||||
|
{
|
||||||
|
"device_id": "multiway_valve_1",
|
||||||
|
"action_name": "set_valve_position",
|
||||||
|
"action_kwargs": {"command": "5"} # 连接到反应器
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"device_id": "transfer_pump_1",
|
||||||
|
"action_name": "set_position",
|
||||||
|
"action_kwargs": {
|
||||||
|
"position": VACUUM_VOLUME,
|
||||||
|
"max_velocity": PUMP_FLOW_RATE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
])
|
||||||
|
print(f"EVACUATE_REFILL: 使用备选手动泵动作")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"EVACUATE_REFILL: ❌ 抽真空 pump_protocol 失败: {str(e)}")
|
||||||
|
import traceback
|
||||||
|
print(f"EVACUATE_REFILL: 详细错误:\n{traceback.format_exc()}")
|
||||||
|
|
||||||
|
# **修复**: 添加手动动作而不是忽略错误
|
||||||
|
print(f"EVACUATE_REFILL: 使用手动备选方案")
|
||||||
|
action_sequence.extend([
|
||||||
|
{
|
||||||
|
"device_id": "multiway_valve_1",
|
||||||
|
"action_name": "set_valve_position",
|
||||||
|
"action_kwargs": {"command": "5"} # 反应器端口
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"device_id": "transfer_pump_1",
|
||||||
|
"action_name": "set_position",
|
||||||
|
"action_kwargs": {
|
||||||
|
"position": VACUUM_VOLUME,
|
||||||
|
"max_velocity": PUMP_FLOW_RATE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
# 关闭真空电磁阀
|
||||||
|
if vacuum_solenoid:
|
||||||
|
action_sequence.append({
|
||||||
|
"device_id": vacuum_solenoid,
|
||||||
|
"action_name": "set_valve_position",
|
||||||
|
"action_kwargs": {"command": "CLOSED"}
|
||||||
|
})
|
||||||
|
|
||||||
|
# 关闭真空泵
|
||||||
|
action_sequence.append({
|
||||||
|
"device_id": vacuum_pump,
|
||||||
|
"action_name": "set_status",
|
||||||
|
"action_kwargs": {"string": "OFF"}
|
||||||
|
})
|
||||||
|
|
||||||
|
# ============ 充气阶段 ============
|
||||||
|
print(f"EVACUATE_REFILL: 充气阶段开始")
|
||||||
|
|
||||||
|
# 启动气源
|
||||||
|
action_sequence.append({
|
||||||
|
"device_id": gas_source,
|
||||||
|
"action_name": "set_status",
|
||||||
|
"action_kwargs": {"string": "ON"}
|
||||||
|
})
|
||||||
|
|
||||||
|
# 开启气源电磁阀
|
||||||
|
if gas_solenoid:
|
||||||
|
action_sequence.append({
|
||||||
|
"device_id": gas_solenoid,
|
||||||
|
"action_name": "set_valve_position",
|
||||||
|
"action_kwargs": {"command": "OPEN"}
|
||||||
|
})
|
||||||
|
|
||||||
|
# **关键修复**: 改进充气 pump_protocol 调用
|
||||||
|
print(f"EVACUATE_REFILL: 调用充气 pump_protocol: {gas_source} → {vessel}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
gas_transfer_actions = generate_pump_protocol_with_rinsing(
|
||||||
|
G=G,
|
||||||
|
from_vessel=gas_source,
|
||||||
|
to_vessel=vessel,
|
||||||
|
volume=REFILL_VOLUME,
|
||||||
|
amount="",
|
||||||
|
time=0.0,
|
||||||
|
viscous=False,
|
||||||
|
rinsing_solvent="", # **修复**: 明确不使用清洗
|
||||||
|
rinsing_volume=0.0,
|
||||||
|
rinsing_repeats=0,
|
||||||
|
solid=False,
|
||||||
|
flowrate=PUMP_FLOW_RATE,
|
||||||
|
transfer_flowrate=PUMP_FLOW_RATE
|
||||||
|
)
|
||||||
|
|
||||||
|
if gas_transfer_actions:
|
||||||
|
action_sequence.extend(gas_transfer_actions)
|
||||||
|
print(f"EVACUATE_REFILL: ✅ 成功添加 {len(gas_transfer_actions)} 个充气动作")
|
||||||
|
else:
|
||||||
|
print(f"EVACUATE_REFILL: ⚠️ 充气 pump_protocol 返回空序列")
|
||||||
|
# **修复**: 添加手动充气动作
|
||||||
|
action_sequence.extend([
|
||||||
|
{
|
||||||
|
"device_id": "multiway_valve_2",
|
||||||
|
"action_name": "set_valve_position",
|
||||||
|
"action_kwargs": {"command": "8"} # 氮气端口
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"device_id": "transfer_pump_2",
|
||||||
|
"action_name": "set_position",
|
||||||
|
"action_kwargs": {
|
||||||
|
"position": REFILL_VOLUME,
|
||||||
|
"max_velocity": PUMP_FLOW_RATE
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"device_id": "multiway_valve_2",
|
||||||
|
"action_name": "set_valve_position",
|
||||||
|
"action_kwargs": {"command": "5"} # 反应器端口
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"device_id": "transfer_pump_2",
|
||||||
|
"action_name": "set_position",
|
||||||
|
"action_kwargs": {
|
||||||
|
"position": 0.0,
|
||||||
|
"max_velocity": PUMP_FLOW_RATE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"EVACUATE_REFILL: ❌ 充气 pump_protocol 失败: {str(e)}")
|
||||||
|
import traceback
|
||||||
|
print(f"EVACUATE_REFILL: 详细错误:\n{traceback.format_exc()}")
|
||||||
|
|
||||||
|
# **修复**: 使用手动充气动作
|
||||||
|
print(f"EVACUATE_REFILL: 使用手动充气方案")
|
||||||
|
action_sequence.extend([
|
||||||
|
{
|
||||||
|
"device_id": "multiway_valve_2",
|
||||||
|
"action_name": "set_valve_position",
|
||||||
|
"action_kwargs": {"command": "8"} # 连接气源
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"device_id": "transfer_pump_2",
|
||||||
|
"action_name": "set_position",
|
||||||
|
"action_kwargs": {
|
||||||
|
"position": REFILL_VOLUME,
|
||||||
|
"max_velocity": PUMP_FLOW_RATE
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"device_id": "multiway_valve_2",
|
||||||
|
"action_name": "set_valve_position",
|
||||||
|
"action_kwargs": {"command": "5"} # 连接反应器
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"device_id": "transfer_pump_2",
|
||||||
|
"action_name": "set_position",
|
||||||
|
"action_kwargs": {
|
||||||
|
"position": 0.0,
|
||||||
|
"max_velocity": PUMP_FLOW_RATE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
# 关闭气源电磁阀
|
||||||
|
if gas_solenoid:
|
||||||
|
action_sequence.append({
|
||||||
|
"device_id": gas_solenoid,
|
||||||
|
"action_name": "set_valve_position",
|
||||||
|
"action_kwargs": {"command": "CLOSED"}
|
||||||
|
})
|
||||||
|
|
||||||
# 关闭气源
|
# 关闭气源
|
||||||
vacuum_action_sequence.append(
|
action_sequence.append({
|
||||||
{
|
"device_id": gas_source,
|
||||||
"device_id": vacuum_backbone["gas"],
|
"action_name": "set_status",
|
||||||
"action_name": "set_status",
|
"action_kwargs": {"string": "OFF"}
|
||||||
"action_kwargs": {
|
})
|
||||||
"command": "OFF"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
# 关闭阀门
|
# 等待下一次循环
|
||||||
vacuum_action_sequence.append(
|
if cycle < repeats - 1:
|
||||||
{
|
action_sequence.append({
|
||||||
"device_id": vacuum_backbone["gas_valve"],
|
"action_name": "wait",
|
||||||
"action_name": "set_valve_position",
|
"action_kwargs": {"time": 2.0}
|
||||||
"action_kwargs": {
|
})
|
||||||
"command": "CLOSED"
|
|
||||||
}
|
# 停止搅拌器
|
||||||
}
|
if stirrer_id:
|
||||||
)
|
action_sequence.append({
|
||||||
return vacuum_action_sequence
|
"device_id": stirrer_id,
|
||||||
|
"action_name": "stop_stir",
|
||||||
|
"action_kwargs": {"vessel": vessel}
|
||||||
|
})
|
||||||
|
|
||||||
|
print(f"EVACUATE_REFILL: 协议生成完成,共 {len(action_sequence)} 个动作")
|
||||||
|
return action_sequence
|
||||||
|
|
||||||
|
|
||||||
|
# 测试函数
|
||||||
|
def test_evacuateandrefill_protocol():
|
||||||
|
"""测试抽真空充气协议"""
|
||||||
|
print("=== EVACUATE AND REFILL PROTOCOL 测试 ===")
|
||||||
|
print("测试完成")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
test_evacuateandrefill_protocol()
|
||||||
143
unilabos/compile/evacuateandrefill_protocol_old.py
Normal file
143
unilabos/compile/evacuateandrefill_protocol_old.py
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
# import numpy as np
|
||||||
|
# import networkx as nx
|
||||||
|
|
||||||
|
|
||||||
|
# def generate_evacuateandrefill_protocol(
|
||||||
|
# G: nx.DiGraph,
|
||||||
|
# vessel: str,
|
||||||
|
# gas: str,
|
||||||
|
# repeats: int = 1
|
||||||
|
# ) -> list[dict]:
|
||||||
|
# """
|
||||||
|
# 生成泵操作的动作序列。
|
||||||
|
|
||||||
|
# :param G: 有向图, 节点为容器和注射泵, 边为流体管道, A→B边的属性为管道接A端的阀门位置
|
||||||
|
# :param from_vessel: 容器A
|
||||||
|
# :param to_vessel: 容器B
|
||||||
|
# :param volume: 转移的体积
|
||||||
|
# :param flowrate: 最终注入容器B时的流速
|
||||||
|
# :param transfer_flowrate: 泵骨架中转移流速(若不指定,默认与注入流速相同)
|
||||||
|
# :return: 泵操作的动作序列
|
||||||
|
# """
|
||||||
|
|
||||||
|
# # 生成电磁阀、真空泵、气源操作的动作序列
|
||||||
|
# vacuum_action_sequence = []
|
||||||
|
# nodes = G.nodes(data=True)
|
||||||
|
|
||||||
|
# # 找到和 vessel 相连的电磁阀和真空泵、气源
|
||||||
|
# vacuum_backbone = {"vessel": vessel}
|
||||||
|
|
||||||
|
# for neighbor in G.neighbors(vessel):
|
||||||
|
# if nodes[neighbor]["class"].startswith("solenoid_valve"):
|
||||||
|
# for neighbor2 in G.neighbors(neighbor):
|
||||||
|
# if neighbor2 == vessel:
|
||||||
|
# continue
|
||||||
|
# if nodes[neighbor2]["class"].startswith("vacuum_pump"):
|
||||||
|
# vacuum_backbone.update({"vacuum_valve": neighbor, "pump": neighbor2})
|
||||||
|
# break
|
||||||
|
# elif nodes[neighbor2]["class"].startswith("gas_source"):
|
||||||
|
# vacuum_backbone.update({"gas_valve": neighbor, "gas": neighbor2})
|
||||||
|
# break
|
||||||
|
# # 判断是否设备齐全
|
||||||
|
# if len(vacuum_backbone) < 5:
|
||||||
|
# print(f"\n\n\n{vacuum_backbone}\n\n\n")
|
||||||
|
# raise ValueError("Not all devices are connected to the vessel.")
|
||||||
|
|
||||||
|
# # 生成操作的动作序列
|
||||||
|
# for i in range(repeats):
|
||||||
|
# # 打开真空泵阀门、关闭气源阀门
|
||||||
|
# vacuum_action_sequence.append([
|
||||||
|
# {
|
||||||
|
# "device_id": vacuum_backbone["vacuum_valve"],
|
||||||
|
# "action_name": "set_valve_position",
|
||||||
|
# "action_kwargs": {
|
||||||
|
# "command": "OPEN"
|
||||||
|
# }
|
||||||
|
# },
|
||||||
|
# {
|
||||||
|
# "device_id": vacuum_backbone["gas_valve"],
|
||||||
|
# "action_name": "set_valve_position",
|
||||||
|
# "action_kwargs": {
|
||||||
|
# "command": "CLOSED"
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
# ])
|
||||||
|
|
||||||
|
# # 打开真空泵、关闭气源
|
||||||
|
# vacuum_action_sequence.append([
|
||||||
|
# {
|
||||||
|
# "device_id": vacuum_backbone["pump"],
|
||||||
|
# "action_name": "set_status",
|
||||||
|
# "action_kwargs": {
|
||||||
|
# "string": "ON"
|
||||||
|
# }
|
||||||
|
# },
|
||||||
|
# {
|
||||||
|
# "device_id": vacuum_backbone["gas"],
|
||||||
|
# "action_name": "set_status",
|
||||||
|
# "action_kwargs": {
|
||||||
|
# "string": "OFF"
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
# ])
|
||||||
|
# vacuum_action_sequence.append({"action_name": "wait", "action_kwargs": {"time": 60}})
|
||||||
|
|
||||||
|
# # 关闭真空泵阀门、打开气源阀门
|
||||||
|
# vacuum_action_sequence.append([
|
||||||
|
# {
|
||||||
|
# "device_id": vacuum_backbone["vacuum_valve"],
|
||||||
|
# "action_name": "set_valve_position",
|
||||||
|
# "action_kwargs": {
|
||||||
|
# "command": "CLOSED"
|
||||||
|
# }
|
||||||
|
# },
|
||||||
|
# {
|
||||||
|
# "device_id": vacuum_backbone["gas_valve"],
|
||||||
|
# "action_name": "set_valve_position",
|
||||||
|
# "action_kwargs": {
|
||||||
|
# "command": "OPEN"
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
# ])
|
||||||
|
|
||||||
|
# # 关闭真空泵、打开气源
|
||||||
|
# vacuum_action_sequence.append([
|
||||||
|
# {
|
||||||
|
# "device_id": vacuum_backbone["pump"],
|
||||||
|
# "action_name": "set_status",
|
||||||
|
# "action_kwargs": {
|
||||||
|
# "string": "OFF"
|
||||||
|
# }
|
||||||
|
# },
|
||||||
|
# {
|
||||||
|
# "device_id": vacuum_backbone["gas"],
|
||||||
|
# "action_name": "set_status",
|
||||||
|
# "action_kwargs": {
|
||||||
|
# "string": "ON"
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
# ])
|
||||||
|
# vacuum_action_sequence.append({"action_name": "wait", "action_kwargs": {"time": 60}})
|
||||||
|
|
||||||
|
# # 关闭气源
|
||||||
|
# vacuum_action_sequence.append(
|
||||||
|
# {
|
||||||
|
# "device_id": vacuum_backbone["gas"],
|
||||||
|
# "action_name": "set_status",
|
||||||
|
# "action_kwargs": {
|
||||||
|
# "string": "OFF"
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
# )
|
||||||
|
|
||||||
|
# # 关闭阀门
|
||||||
|
# vacuum_action_sequence.append(
|
||||||
|
# {
|
||||||
|
# "device_id": vacuum_backbone["gas_valve"],
|
||||||
|
# "action_name": "set_valve_position",
|
||||||
|
# "action_kwargs": {
|
||||||
|
# "command": "CLOSED"
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
# )
|
||||||
|
# return vacuum_action_sequence
|
||||||
@@ -1,81 +1,326 @@
|
|||||||
import numpy as np
|
from typing import List, Dict, Any
|
||||||
import networkx as nx
|
import networkx as nx
|
||||||
|
from .pump_protocol import generate_pump_protocol
|
||||||
|
|
||||||
|
|
||||||
|
def get_vessel_liquid_volume(G: nx.DiGraph, vessel: str) -> float:
|
||||||
|
"""
|
||||||
|
获取容器中的液体体积
|
||||||
|
"""
|
||||||
|
if vessel not in G.nodes():
|
||||||
|
return 0.0
|
||||||
|
|
||||||
|
vessel_data = G.nodes[vessel].get('data', {})
|
||||||
|
liquids = vessel_data.get('liquid', [])
|
||||||
|
|
||||||
|
total_volume = 0.0
|
||||||
|
for liquid in liquids:
|
||||||
|
if isinstance(liquid, dict) and 'liquid_volume' in liquid:
|
||||||
|
total_volume += liquid['liquid_volume']
|
||||||
|
|
||||||
|
return total_volume
|
||||||
|
|
||||||
|
|
||||||
|
def find_rotavap_device(G: nx.DiGraph) -> str:
|
||||||
|
"""查找旋转蒸发仪设备"""
|
||||||
|
rotavap_nodes = [node for node in G.nodes()
|
||||||
|
if (G.nodes[node].get('class') or '') == 'virtual_rotavap']
|
||||||
|
|
||||||
|
if rotavap_nodes:
|
||||||
|
return rotavap_nodes[0]
|
||||||
|
|
||||||
|
raise ValueError("系统中未找到旋转蒸发仪设备")
|
||||||
|
|
||||||
|
|
||||||
|
def find_solvent_recovery_vessel(G: nx.DiGraph) -> str:
|
||||||
|
"""查找溶剂回收容器"""
|
||||||
|
possible_names = [
|
||||||
|
"flask_distillate",
|
||||||
|
"bottle_distillate",
|
||||||
|
"vessel_distillate",
|
||||||
|
"distillate",
|
||||||
|
"solvent_recovery",
|
||||||
|
"flask_solvent_recovery",
|
||||||
|
"collection_flask"
|
||||||
|
]
|
||||||
|
|
||||||
|
for vessel_name in possible_names:
|
||||||
|
if vessel_name in G.nodes():
|
||||||
|
return vessel_name
|
||||||
|
|
||||||
|
# 如果找不到专门的回收容器,使用废液容器
|
||||||
|
waste_names = ["waste_workup", "flask_waste", "bottle_waste", "waste"]
|
||||||
|
for vessel_name in waste_names:
|
||||||
|
if vessel_name in G.nodes():
|
||||||
|
return vessel_name
|
||||||
|
|
||||||
|
raise ValueError(f"未找到溶剂回收容器。尝试了以下名称: {possible_names + waste_names}")
|
||||||
|
|
||||||
|
|
||||||
def generate_evaporate_protocol(
|
def generate_evaporate_protocol(
|
||||||
G: nx.DiGraph,
|
G: nx.DiGraph,
|
||||||
vessel: str,
|
vessel: str,
|
||||||
pressure: float,
|
pressure: float = 0.1,
|
||||||
temp: float,
|
temp: float = 60.0,
|
||||||
time: float,
|
time: float = 1800.0,
|
||||||
stir_speed: float
|
stir_speed: float = 100.0
|
||||||
) -> list[dict]:
|
) -> List[Dict[str, Any]]:
|
||||||
"""
|
"""
|
||||||
Generate a protocol to evaporate a solution from a vessel.
|
生成蒸发操作的协议序列
|
||||||
|
|
||||||
:param G: Directed graph. Nodes are containers and pumps, edges are fluidic connections.
|
蒸发流程:
|
||||||
:param vessel: Vessel to clean.
|
1. 液体转移:将待蒸发溶液从源容器转移到旋转蒸发仪
|
||||||
:param solvent: Solvent to clean vessel with.
|
2. 蒸发操作:执行旋转蒸发
|
||||||
:param volume: Volume of solvent to clean vessel with.
|
3. (可选) 溶剂回收:将冷凝的溶剂转移到回收容器
|
||||||
:param temp: Temperature to heat vessel to while cleaning.
|
4. 残留物转移:将浓缩物从旋转蒸发仪转移回原容器或新容器
|
||||||
:param repeats: Number of cleaning cycles to perform.
|
|
||||||
:return: List of actions to clean vessel.
|
Args:
|
||||||
|
G: 有向图,节点为设备和容器,边为流体管道
|
||||||
|
vessel: 包含待蒸发溶液的容器名称
|
||||||
|
pressure: 蒸发时的真空度 (bar),默认0.1 bar
|
||||||
|
temp: 蒸发时的加热温度 (°C),默认60°C
|
||||||
|
time: 蒸发时间 (秒),默认1800秒(30分钟)
|
||||||
|
stir_speed: 旋转速度 (RPM),默认100 RPM
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List[Dict[str, Any]]: 蒸发操作的动作序列
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ValueError: 当找不到必要的设备时抛出异常
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
evaporate_actions = generate_evaporate_protocol(G, "reaction_mixture", 0.05, 80.0, 3600.0)
|
||||||
"""
|
"""
|
||||||
|
action_sequence = []
|
||||||
|
|
||||||
# 生成泵操作的动作序列
|
print(f"EVAPORATE: 开始生成蒸发协议")
|
||||||
pump_action_sequence = []
|
print(f" - 源容器: {vessel}")
|
||||||
reactor_volume = 500.0
|
print(f" - 真空度: {pressure} bar")
|
||||||
transfer_flowrate = flowrate = 2.5
|
print(f" - 温度: {temp}°C")
|
||||||
|
print(f" - 时间: {time}s ({time/60:.1f}分钟)")
|
||||||
|
print(f" - 旋转速度: {stir_speed} RPM")
|
||||||
|
|
||||||
# 开启冷凝器
|
# 验证源容器存在
|
||||||
pump_action_sequence.append({
|
if vessel not in G.nodes():
|
||||||
"device_id": "rotavap_chiller",
|
raise ValueError(f"源容器 '{vessel}' 不存在于系统中")
|
||||||
"action_name": "set_temperature",
|
|
||||||
"action_kwargs": {
|
# 获取源容器中的液体体积
|
||||||
"command": "-40"
|
source_volume = get_vessel_liquid_volume(G, vessel)
|
||||||
}
|
print(f"EVAPORATE: 源容器 {vessel} 中有 {source_volume} mL 液体")
|
||||||
})
|
|
||||||
# TODO: 通过温度反馈改为 HeatChillToTemp,而非等待固定时间
|
# 查找旋转蒸发仪
|
||||||
pump_action_sequence.append({
|
try:
|
||||||
|
rotavap_id = find_rotavap_device(G)
|
||||||
|
print(f"EVAPORATE: 找到旋转蒸发仪: {rotavap_id}")
|
||||||
|
except ValueError as e:
|
||||||
|
raise ValueError(f"无法找到旋转蒸发仪: {str(e)}")
|
||||||
|
|
||||||
|
# 查找旋转蒸发仪样品容器
|
||||||
|
rotavap_vessel = None
|
||||||
|
possible_rotavap_vessels = ["rotavap_flask", "rotavap", "flask_rotavap", "evaporation_flask"]
|
||||||
|
for rv in possible_rotavap_vessels:
|
||||||
|
if rv in G.nodes():
|
||||||
|
rotavap_vessel = rv
|
||||||
|
break
|
||||||
|
|
||||||
|
if not rotavap_vessel:
|
||||||
|
raise ValueError(f"未找到旋转蒸发仪样品容器。尝试了: {possible_rotavap_vessels}")
|
||||||
|
|
||||||
|
print(f"EVAPORATE: 找到旋转蒸发仪样品容器: {rotavap_vessel}")
|
||||||
|
|
||||||
|
# 查找溶剂回收容器
|
||||||
|
try:
|
||||||
|
distillate_vessel = find_solvent_recovery_vessel(G)
|
||||||
|
print(f"EVAPORATE: 找到溶剂回收容器: {distillate_vessel}")
|
||||||
|
except ValueError as e:
|
||||||
|
print(f"EVAPORATE: 警告 - {str(e)}")
|
||||||
|
distillate_vessel = None
|
||||||
|
|
||||||
|
# === 简化的体积计算策略 ===
|
||||||
|
if source_volume > 0:
|
||||||
|
# 如果能检测到液体体积,使用实际体积的大部分
|
||||||
|
transfer_volume = min(source_volume * 0.9, 250.0) # 90%或最多250mL
|
||||||
|
print(f"EVAPORATE: 检测到液体体积,将转移 {transfer_volume} mL")
|
||||||
|
else:
|
||||||
|
# 如果检测不到液体体积,默认转移一整瓶 250mL
|
||||||
|
transfer_volume = 250.0
|
||||||
|
print(f"EVAPORATE: 未检测到液体体积,默认转移整瓶 {transfer_volume} mL")
|
||||||
|
|
||||||
|
# === 第一步:将待蒸发溶液转移到旋转蒸发仪 ===
|
||||||
|
print(f"EVAPORATE: 将 {transfer_volume} mL 溶液从 {vessel} 转移到 {rotavap_vessel}")
|
||||||
|
try:
|
||||||
|
transfer_to_rotavap_actions = generate_pump_protocol(
|
||||||
|
G=G,
|
||||||
|
from_vessel=vessel,
|
||||||
|
to_vessel=rotavap_vessel,
|
||||||
|
volume=transfer_volume,
|
||||||
|
flowrate=2.0,
|
||||||
|
transfer_flowrate=2.0
|
||||||
|
)
|
||||||
|
action_sequence.extend(transfer_to_rotavap_actions)
|
||||||
|
except Exception as e:
|
||||||
|
raise ValueError(f"无法将溶液转移到旋转蒸发仪: {str(e)}")
|
||||||
|
|
||||||
|
# 转移后等待
|
||||||
|
wait_action = {
|
||||||
"action_name": "wait",
|
"action_name": "wait",
|
||||||
"action_kwargs": {
|
"action_kwargs": {"time": 10}
|
||||||
"time": 1800
|
}
|
||||||
}
|
action_sequence.append(wait_action)
|
||||||
})
|
|
||||||
|
|
||||||
# 开启旋蒸真空泵、旋转,在液体转移后运行time时间
|
# === 第二步:执行旋转蒸发 ===
|
||||||
pump_action_sequence.append({
|
print(f"EVAPORATE: 执行旋转蒸发操作")
|
||||||
"device_id": "rotavap_controller",
|
evaporate_action = {
|
||||||
"action_name": "set_pump_time",
|
"device_id": rotavap_id,
|
||||||
|
"action_name": "evaporate",
|
||||||
"action_kwargs": {
|
"action_kwargs": {
|
||||||
"command": str(time + reactor_volume / flowrate * 3)
|
"vessel": rotavap_vessel,
|
||||||
|
"pressure": pressure,
|
||||||
|
"temp": temp,
|
||||||
|
"time": time,
|
||||||
|
"stir_speed": stir_speed
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
pump_action_sequence.append({
|
action_sequence.append(evaporate_action)
|
||||||
"device_id": "rotavap_controller",
|
|
||||||
"action_name": "set_pump_time",
|
|
||||||
"action_kwargs": {
|
|
||||||
"command": str(time + reactor_volume / flowrate * 3)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
# 液体转入旋转蒸发器
|
# 蒸发后等待系统稳定
|
||||||
pump_action_sequence.append({
|
wait_action = {
|
||||||
"device_id": "",
|
|
||||||
"action_name": "PumpTransferProtocol",
|
|
||||||
"action_kwargs": {
|
|
||||||
"from_vessel": vessel,
|
|
||||||
"to_vessel": "rotavap",
|
|
||||||
"volume": reactor_volume,
|
|
||||||
"time": reactor_volume / flowrate,
|
|
||||||
# "transfer_flowrate": transfer_flowrate,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
pump_action_sequence.append({
|
|
||||||
"action_name": "wait",
|
"action_name": "wait",
|
||||||
"action_kwargs": {
|
"action_kwargs": {"time": 30}
|
||||||
"time": time
|
}
|
||||||
}
|
action_sequence.append(wait_action)
|
||||||
})
|
|
||||||
return pump_action_sequence
|
# === 第三步:溶剂回收(如果有回收容器)===
|
||||||
|
if distillate_vessel:
|
||||||
|
print(f"EVAPORATE: 回收冷凝溶剂到 {distillate_vessel}")
|
||||||
|
try:
|
||||||
|
condenser_vessel = "rotavap_condenser"
|
||||||
|
if condenser_vessel in G.nodes():
|
||||||
|
# 估算回收体积(约为转移体积的70% - 大部分溶剂被蒸发回收)
|
||||||
|
recovery_volume = transfer_volume * 0.7
|
||||||
|
print(f"EVAPORATE: 预计回收 {recovery_volume} mL 溶剂")
|
||||||
|
|
||||||
|
recovery_actions = generate_pump_protocol(
|
||||||
|
G=G,
|
||||||
|
from_vessel=condenser_vessel,
|
||||||
|
to_vessel=distillate_vessel,
|
||||||
|
volume=recovery_volume,
|
||||||
|
flowrate=3.0,
|
||||||
|
transfer_flowrate=3.0
|
||||||
|
)
|
||||||
|
action_sequence.extend(recovery_actions)
|
||||||
|
else:
|
||||||
|
print("EVAPORATE: 未找到冷凝器容器,跳过溶剂回收")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"EVAPORATE: 溶剂回收失败: {str(e)}")
|
||||||
|
|
||||||
|
# === 第四步:将浓缩物转移回原容器 ===
|
||||||
|
print(f"EVAPORATE: 将浓缩物从旋转蒸发仪转移回 {vessel}")
|
||||||
|
try:
|
||||||
|
# 估算浓缩物体积(约为转移体积的20% - 大部分溶剂已蒸发)
|
||||||
|
concentrate_volume = transfer_volume * 0.2
|
||||||
|
print(f"EVAPORATE: 预计浓缩物体积 {concentrate_volume} mL")
|
||||||
|
|
||||||
|
transfer_back_actions = generate_pump_protocol(
|
||||||
|
G=G,
|
||||||
|
from_vessel=rotavap_vessel,
|
||||||
|
to_vessel=vessel,
|
||||||
|
volume=concentrate_volume,
|
||||||
|
flowrate=1.0, # 浓缩物可能粘稠,用较慢流速
|
||||||
|
transfer_flowrate=1.0
|
||||||
|
)
|
||||||
|
action_sequence.extend(transfer_back_actions)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"EVAPORATE: 将浓缩物转移回容器失败: {str(e)}")
|
||||||
|
|
||||||
|
# === 第五步:清洗旋转蒸发仪 ===
|
||||||
|
print(f"EVAPORATE: 清洗旋转蒸发仪")
|
||||||
|
try:
|
||||||
|
# 查找清洗溶剂
|
||||||
|
cleaning_solvent = None
|
||||||
|
for solvent in ["flask_ethanol", "flask_acetone", "flask_water"]:
|
||||||
|
if solvent in G.nodes():
|
||||||
|
cleaning_solvent = solvent
|
||||||
|
break
|
||||||
|
|
||||||
|
if cleaning_solvent and distillate_vessel:
|
||||||
|
# 用固定量溶剂清洗(不依赖检测体积)
|
||||||
|
cleaning_volume = 50.0 # 固定50mL清洗
|
||||||
|
print(f"EVAPORATE: 用 {cleaning_volume} mL {cleaning_solvent} 清洗")
|
||||||
|
|
||||||
|
# 清洗溶剂加入
|
||||||
|
cleaning_actions = generate_pump_protocol(
|
||||||
|
G=G,
|
||||||
|
from_vessel=cleaning_solvent,
|
||||||
|
to_vessel=rotavap_vessel,
|
||||||
|
volume=cleaning_volume,
|
||||||
|
flowrate=2.0,
|
||||||
|
transfer_flowrate=2.0
|
||||||
|
)
|
||||||
|
action_sequence.extend(cleaning_actions)
|
||||||
|
|
||||||
|
# 将清洗液转移到废液/回收容器
|
||||||
|
waste_actions = generate_pump_protocol(
|
||||||
|
G=G,
|
||||||
|
from_vessel=rotavap_vessel,
|
||||||
|
to_vessel=distillate_vessel, # 使用回收容器作为废液
|
||||||
|
volume=cleaning_volume,
|
||||||
|
flowrate=2.0,
|
||||||
|
transfer_flowrate=2.0
|
||||||
|
)
|
||||||
|
action_sequence.extend(waste_actions)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"EVAPORATE: 清洗步骤失败: {str(e)}")
|
||||||
|
|
||||||
|
print(f"EVAPORATE: 生成了 {len(action_sequence)} 个动作")
|
||||||
|
print(f"EVAPORATE: 蒸发协议生成完成")
|
||||||
|
print(f"EVAPORATE: 总处理体积: {transfer_volume} mL")
|
||||||
|
|
||||||
|
return action_sequence
|
||||||
|
|
||||||
|
|
||||||
|
# 便捷函数:常用蒸发方案 - 都使用250mL标准瓶体积
|
||||||
|
def generate_quick_evaporate_protocol(
|
||||||
|
G: nx.DiGraph,
|
||||||
|
vessel: str,
|
||||||
|
temp: float = 40.0,
|
||||||
|
time: float = 900.0 # 15分钟
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
"""快速蒸发:低温、短时间、整瓶处理"""
|
||||||
|
return generate_evaporate_protocol(G, vessel, 0.2, temp, time, 80.0)
|
||||||
|
|
||||||
|
|
||||||
|
def generate_gentle_evaporate_protocol(
|
||||||
|
G: nx.DiGraph,
|
||||||
|
vessel: str,
|
||||||
|
temp: float = 50.0,
|
||||||
|
time: float = 2700.0 # 45分钟
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
"""温和蒸发:中等条件、较长时间、整瓶处理"""
|
||||||
|
return generate_evaporate_protocol(G, vessel, 0.1, temp, time, 60.0)
|
||||||
|
|
||||||
|
|
||||||
|
def generate_high_vacuum_evaporate_protocol(
|
||||||
|
G: nx.DiGraph,
|
||||||
|
vessel: str,
|
||||||
|
temp: float = 35.0,
|
||||||
|
time: float = 3600.0 # 1小时
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
"""高真空蒸发:低温、高真空、长时间、整瓶处理"""
|
||||||
|
return generate_evaporate_protocol(G, vessel, 0.01, temp, time, 120.0)
|
||||||
|
|
||||||
|
|
||||||
|
def generate_standard_evaporate_protocol(
|
||||||
|
G: nx.DiGraph,
|
||||||
|
vessel: str
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
"""标准蒸发:常用参数、整瓶250mL处理"""
|
||||||
|
return generate_evaporate_protocol(
|
||||||
|
G=G,
|
||||||
|
vessel=vessel,
|
||||||
|
pressure=0.1, # 标准真空度
|
||||||
|
temp=60.0, # 适中温度
|
||||||
|
time=1800.0, # 30分钟
|
||||||
|
stir_speed=100.0 # 适中旋转速度
|
||||||
|
)
|
||||||
|
|||||||
304
unilabos/compile/filter_protocol.py
Normal file
304
unilabos/compile/filter_protocol.py
Normal file
@@ -0,0 +1,304 @@
|
|||||||
|
from typing import List, Dict, Any
|
||||||
|
import networkx as nx
|
||||||
|
from .pump_protocol import generate_pump_protocol
|
||||||
|
|
||||||
|
|
||||||
|
def get_vessel_liquid_volume(G: nx.DiGraph, vessel: str) -> float:
|
||||||
|
"""获取容器中的液体体积"""
|
||||||
|
if vessel not in G.nodes():
|
||||||
|
return 0.0
|
||||||
|
|
||||||
|
vessel_data = G.nodes[vessel].get('data', {})
|
||||||
|
liquids = vessel_data.get('liquid', [])
|
||||||
|
|
||||||
|
total_volume = 0.0
|
||||||
|
for liquid in liquids:
|
||||||
|
if isinstance(liquid, dict) and 'liquid_volume' in liquid:
|
||||||
|
total_volume += liquid['liquid_volume']
|
||||||
|
|
||||||
|
return total_volume
|
||||||
|
|
||||||
|
|
||||||
|
def find_filter_device(G: nx.DiGraph) -> str:
|
||||||
|
"""查找过滤器设备"""
|
||||||
|
filter_nodes = [node for node in G.nodes()
|
||||||
|
if (G.nodes[node].get('class') or '') == 'virtual_filter']
|
||||||
|
|
||||||
|
if filter_nodes:
|
||||||
|
return filter_nodes[0]
|
||||||
|
|
||||||
|
raise ValueError("系统中未找到过滤器设备")
|
||||||
|
|
||||||
|
|
||||||
|
def find_filter_vessel(G: nx.DiGraph) -> str:
|
||||||
|
"""查找过滤器专用容器"""
|
||||||
|
possible_names = [
|
||||||
|
"filter_vessel", # 标准过滤器容器
|
||||||
|
"filtration_vessel", # 备选名称
|
||||||
|
"vessel_filter", # 备选名称
|
||||||
|
"filter_unit", # 备选名称
|
||||||
|
"filter" # 简单名称
|
||||||
|
]
|
||||||
|
|
||||||
|
for vessel_name in possible_names:
|
||||||
|
if vessel_name in G.nodes():
|
||||||
|
return vessel_name
|
||||||
|
|
||||||
|
raise ValueError(f"未找到过滤器容器。尝试了以下名称: {possible_names}")
|
||||||
|
|
||||||
|
|
||||||
|
def find_filtrate_vessel(G: nx.DiGraph, filtrate_vessel: str = "") -> str:
|
||||||
|
"""查找滤液收集容器"""
|
||||||
|
if filtrate_vessel and filtrate_vessel in G.nodes():
|
||||||
|
return filtrate_vessel
|
||||||
|
|
||||||
|
# 自动查找滤液容器
|
||||||
|
possible_names = [
|
||||||
|
"filtrate_vessel",
|
||||||
|
"collection_bottle_1",
|
||||||
|
"collection_bottle_2",
|
||||||
|
"waste_workup"
|
||||||
|
]
|
||||||
|
|
||||||
|
for vessel_name in possible_names:
|
||||||
|
if vessel_name in G.nodes():
|
||||||
|
return vessel_name
|
||||||
|
|
||||||
|
raise ValueError(f"未找到滤液收集容器。尝试了以下名称: {possible_names}")
|
||||||
|
|
||||||
|
|
||||||
|
def find_connected_heatchill(G: nx.DiGraph, vessel: str) -> str:
|
||||||
|
"""查找与指定容器相连的加热搅拌器"""
|
||||||
|
# 查找所有加热搅拌器节点
|
||||||
|
heatchill_nodes = [node for node in G.nodes()
|
||||||
|
if G.nodes[node].get('class') == 'virtual_heatchill']
|
||||||
|
|
||||||
|
# 检查哪个加热器与目标容器相连
|
||||||
|
for heatchill in heatchill_nodes:
|
||||||
|
if G.has_edge(heatchill, vessel) or G.has_edge(vessel, heatchill):
|
||||||
|
return heatchill
|
||||||
|
|
||||||
|
# 如果没有直接连接,返回第一个可用的加热器
|
||||||
|
if heatchill_nodes:
|
||||||
|
return heatchill_nodes[0]
|
||||||
|
|
||||||
|
raise ValueError(f"未找到与容器 {vessel} 相连的加热搅拌器")
|
||||||
|
|
||||||
|
|
||||||
|
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]]:
|
||||||
|
"""
|
||||||
|
生成过滤操作的协议序列,复用 pump_protocol 的成熟算法
|
||||||
|
|
||||||
|
过滤流程:
|
||||||
|
1. 液体转移:将待过滤溶液从源容器转移到过滤器
|
||||||
|
2. 启动加热搅拌:设置温度和搅拌
|
||||||
|
3. 执行过滤:通过过滤器分离固液
|
||||||
|
4. (可选) 继续或停止加热搅拌
|
||||||
|
|
||||||
|
Args:
|
||||||
|
G: 有向图,节点为设备和容器,边为流体管道
|
||||||
|
vessel: 包含待过滤溶液的容器名称
|
||||||
|
filtrate_vessel: 滤液收集容器(可选,自动查找)
|
||||||
|
stir: 是否在过滤过程中搅拌
|
||||||
|
stir_speed: 搅拌速度 (RPM)
|
||||||
|
temp: 过滤温度 (°C)
|
||||||
|
continue_heatchill: 过滤后是否继续加热搅拌
|
||||||
|
volume: 预期过滤体积 (mL),0表示全部过滤
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List[Dict[str, Any]]: 过滤操作的动作序列
|
||||||
|
"""
|
||||||
|
action_sequence = []
|
||||||
|
|
||||||
|
print(f"FILTER: 开始生成过滤协议")
|
||||||
|
print(f" - 源容器: {vessel}")
|
||||||
|
print(f" - 滤液容器: {filtrate_vessel}")
|
||||||
|
print(f" - 搅拌: {stir} ({stir_speed} RPM)" if stir else " - 搅拌: 否")
|
||||||
|
print(f" - 过滤温度: {temp}°C")
|
||||||
|
print(f" - 预期过滤体积: {volume} mL" if volume > 0 else " - 预期过滤体积: 全部")
|
||||||
|
print(f" - 继续加热搅拌: {continue_heatchill}")
|
||||||
|
|
||||||
|
# 验证源容器存在
|
||||||
|
if vessel not in G.nodes():
|
||||||
|
raise ValueError(f"源容器 '{vessel}' 不存在于系统中")
|
||||||
|
|
||||||
|
# 获取源容器中的液体体积
|
||||||
|
source_volume = get_vessel_liquid_volume(G, vessel)
|
||||||
|
print(f"FILTER: 源容器 {vessel} 中有 {source_volume} mL 液体")
|
||||||
|
|
||||||
|
# 查找过滤器设备
|
||||||
|
try:
|
||||||
|
filter_id = find_filter_device(G)
|
||||||
|
print(f"FILTER: 找到过滤器: {filter_id}")
|
||||||
|
except ValueError as e:
|
||||||
|
raise ValueError(f"无法找到过滤器: {str(e)}")
|
||||||
|
|
||||||
|
# 查找过滤器容器
|
||||||
|
try:
|
||||||
|
filter_vessel_id = find_filter_vessel(G)
|
||||||
|
print(f"FILTER: 找到过滤器容器: {filter_vessel_id}")
|
||||||
|
except ValueError as e:
|
||||||
|
raise ValueError(f"无法找到过滤器容器: {str(e)}")
|
||||||
|
|
||||||
|
# 查找滤液收集容器
|
||||||
|
try:
|
||||||
|
actual_filtrate_vessel = find_filtrate_vessel(G, filtrate_vessel)
|
||||||
|
print(f"FILTER: 找到滤液收集容器: {actual_filtrate_vessel}")
|
||||||
|
except ValueError as e:
|
||||||
|
raise ValueError(f"无法找到滤液收集容器: {str(e)}")
|
||||||
|
|
||||||
|
# 查找加热搅拌器(如果需要温度控制或搅拌)
|
||||||
|
heatchill_id = None
|
||||||
|
if temp != 25.0 or stir or continue_heatchill:
|
||||||
|
try:
|
||||||
|
heatchill_id = find_connected_heatchill(G, filter_vessel_id)
|
||||||
|
print(f"FILTER: 找到加热搅拌器: {heatchill_id}")
|
||||||
|
except ValueError as e:
|
||||||
|
print(f"FILTER: 警告 - {str(e)}")
|
||||||
|
|
||||||
|
# === 简化的体积计算策略 ===
|
||||||
|
if volume > 0:
|
||||||
|
transfer_volume = min(volume, source_volume if source_volume > 0 else volume)
|
||||||
|
print(f"FILTER: 指定过滤体积 {transfer_volume} mL")
|
||||||
|
elif source_volume > 0:
|
||||||
|
transfer_volume = source_volume * 0.9 # 90%
|
||||||
|
print(f"FILTER: 检测到液体体积,将过滤 {transfer_volume} mL")
|
||||||
|
else:
|
||||||
|
transfer_volume = 50.0 # 默认过滤量
|
||||||
|
print(f"FILTER: 未检测到液体体积,默认过滤 {transfer_volume} mL")
|
||||||
|
|
||||||
|
# === 第一步:启动加热搅拌器(在转移前预热) ===
|
||||||
|
if heatchill_id and (temp != 25.0 or stir):
|
||||||
|
print(f"FILTER: 启动加热搅拌器,温度: {temp}°C,搅拌: {stir}")
|
||||||
|
|
||||||
|
heatchill_action = {
|
||||||
|
"device_id": heatchill_id,
|
||||||
|
"action_name": "heat_chill_start",
|
||||||
|
"action_kwargs": {
|
||||||
|
"vessel": filter_vessel_id,
|
||||||
|
"temp": temp,
|
||||||
|
"purpose": f"过滤过程温度控制和搅拌"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
action_sequence.append(heatchill_action)
|
||||||
|
|
||||||
|
# 等待温度稳定
|
||||||
|
if temp != 25.0:
|
||||||
|
wait_time = min(30, abs(temp - 25.0) * 1.0) # 根据温差估算预热时间
|
||||||
|
action_sequence.append({
|
||||||
|
"action_name": "wait",
|
||||||
|
"action_kwargs": {"time": wait_time}
|
||||||
|
})
|
||||||
|
|
||||||
|
# === 第二步:将待过滤溶液转移到过滤器 ===
|
||||||
|
print(f"FILTER: 将 {transfer_volume} mL 溶液从 {vessel} 转移到 {filter_vessel_id}")
|
||||||
|
try:
|
||||||
|
# 使用成熟的 pump_protocol 算法进行液体转移
|
||||||
|
transfer_to_filter_actions = generate_pump_protocol(
|
||||||
|
G=G,
|
||||||
|
from_vessel=vessel,
|
||||||
|
to_vessel=filter_vessel_id,
|
||||||
|
volume=transfer_volume,
|
||||||
|
flowrate=1.0, # 过滤转移用较慢速度,避免扰动
|
||||||
|
transfer_flowrate=1.5
|
||||||
|
)
|
||||||
|
action_sequence.extend(transfer_to_filter_actions)
|
||||||
|
except Exception as e:
|
||||||
|
raise ValueError(f"无法将溶液转移到过滤器: {str(e)}")
|
||||||
|
|
||||||
|
# 转移后等待
|
||||||
|
action_sequence.append({
|
||||||
|
"action_name": "wait",
|
||||||
|
"action_kwargs": {"time": 5}
|
||||||
|
})
|
||||||
|
|
||||||
|
# === 第三步:执行过滤操作(完全按照 Filter.action 参数) ===
|
||||||
|
print(f"FILTER: 执行过滤操作")
|
||||||
|
filter_action = {
|
||||||
|
"device_id": filter_id,
|
||||||
|
"action_name": "filter",
|
||||||
|
"action_kwargs": {
|
||||||
|
"vessel": filter_vessel_id,
|
||||||
|
"filtrate_vessel": actual_filtrate_vessel,
|
||||||
|
"stir": stir,
|
||||||
|
"stir_speed": stir_speed,
|
||||||
|
"temp": temp,
|
||||||
|
"continue_heatchill": continue_heatchill,
|
||||||
|
"volume": transfer_volume
|
||||||
|
}
|
||||||
|
}
|
||||||
|
action_sequence.append(filter_action)
|
||||||
|
|
||||||
|
# 过滤后等待
|
||||||
|
action_sequence.append({
|
||||||
|
"action_name": "wait",
|
||||||
|
"action_kwargs": {"time": 10}
|
||||||
|
})
|
||||||
|
|
||||||
|
# === 第四步:如果不继续加热搅拌,停止加热器 ===
|
||||||
|
if heatchill_id and not continue_heatchill and (temp != 25.0 or stir):
|
||||||
|
print(f"FILTER: 停止加热搅拌器")
|
||||||
|
|
||||||
|
stop_action = {
|
||||||
|
"device_id": heatchill_id,
|
||||||
|
"action_name": "heat_chill_stop",
|
||||||
|
"action_kwargs": {
|
||||||
|
"vessel": filter_vessel_id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
action_sequence.append(stop_action)
|
||||||
|
|
||||||
|
print(f"FILTER: 生成了 {len(action_sequence)} 个动作")
|
||||||
|
print(f"FILTER: 过滤协议生成完成")
|
||||||
|
|
||||||
|
return action_sequence
|
||||||
|
|
||||||
|
|
||||||
|
# 便捷函数:常用过滤方案
|
||||||
|
def generate_gravity_filter_protocol(
|
||||||
|
G: nx.DiGraph,
|
||||||
|
vessel: str,
|
||||||
|
filtrate_vessel: str = ""
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
"""重力过滤:室温,无搅拌"""
|
||||||
|
return generate_filter_protocol(G, vessel, filtrate_vessel, False, 0.0, 25.0, False, 0.0)
|
||||||
|
|
||||||
|
|
||||||
|
def generate_hot_filter_protocol(
|
||||||
|
G: nx.DiGraph,
|
||||||
|
vessel: str,
|
||||||
|
filtrate_vessel: str = "",
|
||||||
|
temp: float = 60.0
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
"""热过滤:高温过滤,防止结晶析出"""
|
||||||
|
return generate_filter_protocol(G, vessel, filtrate_vessel, False, 0.0, temp, False, 0.0)
|
||||||
|
|
||||||
|
|
||||||
|
def generate_stirred_filter_protocol(
|
||||||
|
G: nx.DiGraph,
|
||||||
|
vessel: str,
|
||||||
|
filtrate_vessel: str = "",
|
||||||
|
stir_speed: float = 200.0
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
"""搅拌过滤:低速搅拌,防止滤饼堵塞"""
|
||||||
|
return generate_filter_protocol(G, vessel, filtrate_vessel, True, stir_speed, 25.0, False, 0.0)
|
||||||
|
|
||||||
|
|
||||||
|
def generate_hot_stirred_filter_protocol(
|
||||||
|
G: nx.DiGraph,
|
||||||
|
vessel: str,
|
||||||
|
filtrate_vessel: str = "",
|
||||||
|
temp: float = 60.0,
|
||||||
|
stir_speed: float = 300.0
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
"""热搅拌过滤:高温搅拌过滤"""
|
||||||
|
return generate_filter_protocol(G, vessel, filtrate_vessel, True, stir_speed, temp, False, 0.0)
|
||||||
387
unilabos/compile/filter_through_protocol.py
Normal file
387
unilabos/compile/filter_through_protocol.py
Normal file
@@ -0,0 +1,387 @@
|
|||||||
|
from typing import List, Dict, Any
|
||||||
|
import networkx as nx
|
||||||
|
from .pump_protocol import generate_pump_protocol
|
||||||
|
|
||||||
|
|
||||||
|
def get_vessel_liquid_volume(G: nx.DiGraph, vessel: str) -> float:
|
||||||
|
"""获取容器中的液体体积"""
|
||||||
|
if vessel not in G.nodes():
|
||||||
|
return 0.0
|
||||||
|
|
||||||
|
vessel_data = G.nodes[vessel].get('data', {})
|
||||||
|
liquids = vessel_data.get('liquid', [])
|
||||||
|
|
||||||
|
total_volume = 0.0
|
||||||
|
for liquid in liquids:
|
||||||
|
if isinstance(liquid, dict) and 'liquid_volume' in liquid:
|
||||||
|
total_volume += liquid['liquid_volume']
|
||||||
|
|
||||||
|
return total_volume
|
||||||
|
|
||||||
|
|
||||||
|
def find_filter_through_vessel(G: nx.DiGraph, filter_through: str) -> str:
|
||||||
|
"""查找过滤介质容器"""
|
||||||
|
# 直接使用 filter_through 参数作为容器名称
|
||||||
|
if filter_through in G.nodes():
|
||||||
|
return filter_through
|
||||||
|
|
||||||
|
# 尝试常见的过滤介质容器命名
|
||||||
|
possible_names = [
|
||||||
|
f"filter_{filter_through}",
|
||||||
|
f"{filter_through}_filter",
|
||||||
|
f"column_{filter_through}",
|
||||||
|
f"{filter_through}_column",
|
||||||
|
"filter_through_vessel",
|
||||||
|
"column_vessel",
|
||||||
|
"chromatography_column",
|
||||||
|
"filter_column"
|
||||||
|
]
|
||||||
|
|
||||||
|
for vessel_name in possible_names:
|
||||||
|
if vessel_name in G.nodes():
|
||||||
|
return vessel_name
|
||||||
|
|
||||||
|
raise ValueError(f"未找到过滤介质容器 '{filter_through}'。尝试了以下名称: {[filter_through] + possible_names}")
|
||||||
|
|
||||||
|
|
||||||
|
def find_eluting_solvent_vessel(G: nx.DiGraph, eluting_solvent: str) -> str:
|
||||||
|
"""查找洗脱溶剂容器"""
|
||||||
|
if not eluting_solvent:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
# 按照命名规则查找溶剂瓶
|
||||||
|
solvent_vessel_id = f"flask_{eluting_solvent}"
|
||||||
|
|
||||||
|
if solvent_vessel_id in G.nodes():
|
||||||
|
return solvent_vessel_id
|
||||||
|
|
||||||
|
# 如果直接匹配失败,尝试模糊匹配
|
||||||
|
for node in G.nodes():
|
||||||
|
if node.startswith('flask_') and eluting_solvent.lower() in node.lower():
|
||||||
|
return node
|
||||||
|
|
||||||
|
# 如果还是找不到,列出所有可用的溶剂瓶
|
||||||
|
available_flasks = [node for node in G.nodes()
|
||||||
|
if node.startswith('flask_')
|
||||||
|
and G.nodes[node].get('type') == 'container']
|
||||||
|
|
||||||
|
raise ValueError(f"找不到洗脱溶剂 '{eluting_solvent}' 对应的溶剂瓶。可用溶剂瓶: {available_flasks}")
|
||||||
|
|
||||||
|
|
||||||
|
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]]:
|
||||||
|
"""
|
||||||
|
生成通过过滤介质过滤的协议序列,复用 pump_protocol 的成熟算法
|
||||||
|
|
||||||
|
过滤流程:
|
||||||
|
1. 液体转移:将样品从源容器转移到过滤介质
|
||||||
|
2. 重力过滤:液体通过过滤介质自动流到目标容器
|
||||||
|
3. 洗脱操作:将洗脱溶剂通过过滤介质洗脱目标物质
|
||||||
|
|
||||||
|
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_actions = generate_filter_through_protocol(
|
||||||
|
G, "reaction_mixture", "collection_bottle_1", "celite", "ethanol", 20.0, 2, 30.0
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
action_sequence = []
|
||||||
|
|
||||||
|
print(f"FILTER_THROUGH: 开始生成通过过滤协议")
|
||||||
|
print(f" - 源容器: {from_vessel}")
|
||||||
|
print(f" - 目标容器: {to_vessel}")
|
||||||
|
print(f" - 过滤介质: {filter_through}")
|
||||||
|
print(f" - 洗脱溶剂: {eluting_solvent}")
|
||||||
|
print(f" - 洗脱体积: {eluting_volume} mL" if eluting_volume > 0 else " - 洗脱体积: 无")
|
||||||
|
print(f" - 洗脱重复次数: {eluting_repeats}")
|
||||||
|
print(f" - 停留时间: {residence_time}s" if residence_time > 0 else " - 停留时间: 无")
|
||||||
|
|
||||||
|
# 验证源容器和目标容器存在
|
||||||
|
if from_vessel not in G.nodes():
|
||||||
|
raise ValueError(f"源容器 '{from_vessel}' 不存在于系统中")
|
||||||
|
|
||||||
|
if to_vessel not in G.nodes():
|
||||||
|
raise ValueError(f"目标容器 '{to_vessel}' 不存在于系统中")
|
||||||
|
|
||||||
|
# 获取源容器中的液体体积
|
||||||
|
source_volume = get_vessel_liquid_volume(G, from_vessel)
|
||||||
|
print(f"FILTER_THROUGH: 源容器 {from_vessel} 中有 {source_volume} mL 液体")
|
||||||
|
|
||||||
|
# 查找过滤介质容器
|
||||||
|
try:
|
||||||
|
filter_through_vessel = find_filter_through_vessel(G, filter_through)
|
||||||
|
print(f"FILTER_THROUGH: 找到过滤介质容器: {filter_through_vessel}")
|
||||||
|
except ValueError as e:
|
||||||
|
raise ValueError(f"无法找到过滤介质容器: {str(e)}")
|
||||||
|
|
||||||
|
# 查找洗脱溶剂容器(如果需要)
|
||||||
|
eluting_vessel = ""
|
||||||
|
if eluting_solvent and eluting_volume > 0 and eluting_repeats > 0:
|
||||||
|
try:
|
||||||
|
eluting_vessel = find_eluting_solvent_vessel(G, eluting_solvent)
|
||||||
|
print(f"FILTER_THROUGH: 找到洗脱溶剂容器: {eluting_vessel}")
|
||||||
|
except ValueError as e:
|
||||||
|
raise ValueError(f"无法找到洗脱溶剂容器: {str(e)}")
|
||||||
|
|
||||||
|
# === 第一步:将样品从源容器转移到过滤介质 ===
|
||||||
|
transfer_volume = source_volume if source_volume > 0 else 100.0 # 默认100mL
|
||||||
|
print(f"FILTER_THROUGH: 将 {transfer_volume} mL 样品从 {from_vessel} 转移到 {filter_through_vessel}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 使用成熟的 pump_protocol 算法进行液体转移
|
||||||
|
sample_transfer_actions = generate_pump_protocol(
|
||||||
|
G=G,
|
||||||
|
from_vessel=from_vessel,
|
||||||
|
to_vessel=filter_through_vessel,
|
||||||
|
volume=transfer_volume,
|
||||||
|
flowrate=0.8, # 较慢的流速,避免冲击过滤介质
|
||||||
|
transfer_flowrate=1.2
|
||||||
|
)
|
||||||
|
action_sequence.extend(sample_transfer_actions)
|
||||||
|
except Exception as e:
|
||||||
|
raise ValueError(f"无法将样品转移到过滤介质: {str(e)}")
|
||||||
|
|
||||||
|
# === 第二步:等待样品通过过滤介质(停留时间) ===
|
||||||
|
if residence_time > 0:
|
||||||
|
print(f"FILTER_THROUGH: 等待样品在过滤介质中停留 {residence_time}s")
|
||||||
|
action_sequence.append({
|
||||||
|
"action_name": "wait",
|
||||||
|
"action_kwargs": {"time": residence_time}
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
# 即使没有指定停留时间,也等待一段时间让液体通过
|
||||||
|
default_wait_time = max(10, transfer_volume / 10) # 根据体积估算等待时间
|
||||||
|
print(f"FILTER_THROUGH: 等待样品通过过滤介质 {default_wait_time}s")
|
||||||
|
action_sequence.append({
|
||||||
|
"action_name": "wait",
|
||||||
|
"action_kwargs": {"time": default_wait_time}
|
||||||
|
})
|
||||||
|
|
||||||
|
# === 第三步:洗脱操作(如果指定了洗脱参数) ===
|
||||||
|
if eluting_solvent and eluting_volume > 0 and eluting_repeats > 0 and eluting_vessel:
|
||||||
|
print(f"FILTER_THROUGH: 开始洗脱操作 - {eluting_repeats} 次,每次 {eluting_volume} mL {eluting_solvent}")
|
||||||
|
|
||||||
|
for repeat_idx in range(eluting_repeats):
|
||||||
|
print(f"FILTER_THROUGH: 第 {repeat_idx + 1}/{eluting_repeats} 次洗脱")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 将洗脱溶剂转移到过滤介质
|
||||||
|
eluting_transfer_actions = generate_pump_protocol(
|
||||||
|
G=G,
|
||||||
|
from_vessel=eluting_vessel,
|
||||||
|
to_vessel=filter_through_vessel,
|
||||||
|
volume=eluting_volume,
|
||||||
|
flowrate=0.6, # 洗脱用更慢的流速
|
||||||
|
transfer_flowrate=1.0
|
||||||
|
)
|
||||||
|
action_sequence.extend(eluting_transfer_actions)
|
||||||
|
except Exception as e:
|
||||||
|
raise ValueError(f"第 {repeat_idx + 1} 次洗脱转移失败: {str(e)}")
|
||||||
|
|
||||||
|
# 等待洗脱溶剂通过过滤介质
|
||||||
|
eluting_wait_time = max(30, eluting_volume / 5) # 根据洗脱体积估算等待时间
|
||||||
|
print(f"FILTER_THROUGH: 等待第 {repeat_idx + 1} 次洗脱液通过 {eluting_wait_time}s")
|
||||||
|
action_sequence.append({
|
||||||
|
"action_name": "wait",
|
||||||
|
"action_kwargs": {"time": eluting_wait_time}
|
||||||
|
})
|
||||||
|
|
||||||
|
# 洗脱间隔等待
|
||||||
|
if repeat_idx < eluting_repeats - 1: # 不是最后一次洗脱
|
||||||
|
action_sequence.append({
|
||||||
|
"action_name": "wait",
|
||||||
|
"action_kwargs": {"time": 10}
|
||||||
|
})
|
||||||
|
|
||||||
|
# === 第四步:最终等待,确保所有液体完全通过 ===
|
||||||
|
print(f"FILTER_THROUGH: 最终等待,确保所有液体完全通过过滤介质")
|
||||||
|
action_sequence.append({
|
||||||
|
"action_name": "wait",
|
||||||
|
"action_kwargs": {"time": 20}
|
||||||
|
})
|
||||||
|
|
||||||
|
print(f"FILTER_THROUGH: 生成了 {len(action_sequence)} 个动作")
|
||||||
|
print(f"FILTER_THROUGH: 通过过滤协议生成完成")
|
||||||
|
print(f"FILTER_THROUGH: 样品从 {from_vessel} 通过 {filter_through} 到达 {to_vessel}")
|
||||||
|
if eluting_repeats > 0:
|
||||||
|
total_eluting_volume = eluting_volume * eluting_repeats
|
||||||
|
print(f"FILTER_THROUGH: 总洗脱体积: {total_eluting_volume} mL {eluting_solvent}")
|
||||||
|
|
||||||
|
return action_sequence
|
||||||
|
|
||||||
|
|
||||||
|
# 便捷函数:常用过滤方案
|
||||||
|
def generate_gravity_column_protocol(
|
||||||
|
G: nx.DiGraph,
|
||||||
|
from_vessel: str,
|
||||||
|
to_vessel: str,
|
||||||
|
column_material: str = "silica_gel"
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
"""重力柱层析:简单重力过滤,无洗脱"""
|
||||||
|
return generate_filter_through_protocol(G, from_vessel, to_vessel, column_material, "", 0.0, 0, 0.0)
|
||||||
|
|
||||||
|
|
||||||
|
def generate_celite_filter_protocol(
|
||||||
|
G: nx.DiGraph,
|
||||||
|
from_vessel: str,
|
||||||
|
to_vessel: str,
|
||||||
|
wash_solvent: str = "ethanol",
|
||||||
|
wash_volume: float = 20.0
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
"""硅藻土过滤:用于去除固体杂质"""
|
||||||
|
return generate_filter_through_protocol(G, from_vessel, to_vessel, "celite", wash_solvent, wash_volume, 1, 30.0)
|
||||||
|
|
||||||
|
|
||||||
|
def generate_column_chromatography_protocol(
|
||||||
|
G: nx.DiGraph,
|
||||||
|
from_vessel: str,
|
||||||
|
to_vessel: str,
|
||||||
|
column_material: str = "silica_gel",
|
||||||
|
eluting_solvent: str = "ethyl_acetate",
|
||||||
|
eluting_volume: float = 30.0,
|
||||||
|
eluting_repeats: int = 3
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
"""柱层析:多次洗脱分离"""
|
||||||
|
return generate_filter_through_protocol(
|
||||||
|
G, from_vessel, to_vessel, column_material, eluting_solvent, eluting_volume, eluting_repeats, 60.0
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def generate_solid_phase_extraction_protocol(
|
||||||
|
G: nx.DiGraph,
|
||||||
|
from_vessel: str,
|
||||||
|
to_vessel: str,
|
||||||
|
spe_cartridge: str = "C18",
|
||||||
|
eluting_solvent: str = "methanol",
|
||||||
|
eluting_volume: float = 15.0,
|
||||||
|
eluting_repeats: int = 2
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
"""固相萃取:C18柱或其他SPE柱"""
|
||||||
|
return generate_filter_through_protocol(
|
||||||
|
G, from_vessel, to_vessel, spe_cartridge, eluting_solvent, eluting_volume, eluting_repeats, 120.0
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def generate_resin_filter_protocol(
|
||||||
|
G: nx.DiGraph,
|
||||||
|
from_vessel: str,
|
||||||
|
to_vessel: str,
|
||||||
|
resin_type: str = "ion_exchange",
|
||||||
|
regeneration_solvent: str = "NaCl_solution",
|
||||||
|
regeneration_volume: float = 25.0
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
"""树脂过滤:离子交换树脂或其他功能树脂"""
|
||||||
|
return generate_filter_through_protocol(
|
||||||
|
G, from_vessel, to_vessel, resin_type, regeneration_solvent, regeneration_volume, 1, 180.0
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def generate_multi_step_purification_protocol(
|
||||||
|
G: nx.DiGraph,
|
||||||
|
from_vessel: str,
|
||||||
|
to_vessel: str,
|
||||||
|
filter_steps: List[Dict[str, Any]]
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
多步骤纯化:连续多个过滤介质
|
||||||
|
|
||||||
|
Args:
|
||||||
|
G: 网络图
|
||||||
|
from_vessel: 源容器
|
||||||
|
to_vessel: 最终目标容器
|
||||||
|
filter_steps: 过滤步骤列表,每个元素包含过滤参数
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List[Dict[str, Any]]: 完整的动作序列
|
||||||
|
|
||||||
|
Example:
|
||||||
|
filter_steps = [
|
||||||
|
{
|
||||||
|
"to_vessel": "intermediate_vessel_1",
|
||||||
|
"filter_through": "celite",
|
||||||
|
"eluting_solvent": "",
|
||||||
|
"eluting_volume": 0.0,
|
||||||
|
"eluting_repeats": 0,
|
||||||
|
"residence_time": 30.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"from_vessel": "intermediate_vessel_1",
|
||||||
|
"to_vessel": "final_vessel",
|
||||||
|
"filter_through": "silica_gel",
|
||||||
|
"eluting_solvent": "ethyl_acetate",
|
||||||
|
"eluting_volume": 20.0,
|
||||||
|
"eluting_repeats": 2,
|
||||||
|
"residence_time": 60.0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
"""
|
||||||
|
action_sequence = []
|
||||||
|
|
||||||
|
current_from_vessel = from_vessel
|
||||||
|
|
||||||
|
for i, step in enumerate(filter_steps):
|
||||||
|
print(f"FILTER_THROUGH: 处理第 {i+1}/{len(filter_steps)} 个过滤步骤")
|
||||||
|
|
||||||
|
# 使用步骤中指定的参数,或使用默认值
|
||||||
|
step_from_vessel = step.get('from_vessel', current_from_vessel)
|
||||||
|
step_to_vessel = step.get('to_vessel', to_vessel if i == len(filter_steps) - 1 else f"intermediate_vessel_{i+1}")
|
||||||
|
|
||||||
|
# 生成单个过滤步骤的协议
|
||||||
|
step_actions = generate_filter_through_protocol(
|
||||||
|
G=G,
|
||||||
|
from_vessel=step_from_vessel,
|
||||||
|
to_vessel=step_to_vessel,
|
||||||
|
filter_through=step.get('filter_through', 'silica_gel'),
|
||||||
|
eluting_solvent=step.get('eluting_solvent', ''),
|
||||||
|
eluting_volume=step.get('eluting_volume', 0.0),
|
||||||
|
eluting_repeats=step.get('eluting_repeats', 0),
|
||||||
|
residence_time=step.get('residence_time', 0.0)
|
||||||
|
)
|
||||||
|
|
||||||
|
action_sequence.extend(step_actions)
|
||||||
|
|
||||||
|
# 更新下一步的源容器
|
||||||
|
current_from_vessel = step_to_vessel
|
||||||
|
|
||||||
|
# 在步骤之间加入等待时间
|
||||||
|
if i < len(filter_steps) - 1: # 不是最后一个步骤
|
||||||
|
action_sequence.append({
|
||||||
|
"action_name": "wait",
|
||||||
|
"action_kwargs": {"time": 15}
|
||||||
|
})
|
||||||
|
|
||||||
|
print(f"FILTER_THROUGH: 多步骤纯化协议生成完成,共 {len(action_sequence)} 个动作")
|
||||||
|
return action_sequence
|
||||||
|
|
||||||
|
|
||||||
|
# 测试函数
|
||||||
|
def test_filter_through_protocol():
|
||||||
|
"""测试通过过滤协议的示例"""
|
||||||
|
print("=== FILTER THROUGH PROTOCOL 测试 ===")
|
||||||
|
print("测试完成")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
test_filter_through_protocol()
|
||||||
373
unilabos/compile/heatchill_protocol.py
Normal file
373
unilabos/compile/heatchill_protocol.py
Normal file
@@ -0,0 +1,373 @@
|
|||||||
|
from typing import List, Dict, Any, Optional
|
||||||
|
import networkx as nx
|
||||||
|
|
||||||
|
|
||||||
|
def find_connected_heatchill(G: nx.DiGraph, vessel: str) -> str:
|
||||||
|
"""
|
||||||
|
查找与指定容器相连的加热/冷却设备
|
||||||
|
"""
|
||||||
|
# 查找所有加热/冷却设备节点
|
||||||
|
heatchill_nodes = [node for node in G.nodes()
|
||||||
|
if (G.nodes[node].get('class') or '') == 'virtual_heatchill']
|
||||||
|
|
||||||
|
# 检查哪个加热/冷却设备与目标容器相连(机械连接)
|
||||||
|
for heatchill in heatchill_nodes:
|
||||||
|
if G.has_edge(heatchill, vessel) or G.has_edge(vessel, heatchill):
|
||||||
|
return heatchill
|
||||||
|
|
||||||
|
# 如果没有直接连接,返回第一个可用的加热/冷却设备
|
||||||
|
if heatchill_nodes:
|
||||||
|
return heatchill_nodes[0]
|
||||||
|
|
||||||
|
raise ValueError("系统中未找到可用的加热/冷却设备")
|
||||||
|
|
||||||
|
|
||||||
|
def generate_heat_chill_protocol(
|
||||||
|
G: nx.DiGraph,
|
||||||
|
vessel: str,
|
||||||
|
temp: float,
|
||||||
|
time: float,
|
||||||
|
stir: bool = False,
|
||||||
|
stir_speed: float = 300.0,
|
||||||
|
purpose: str = "加热/冷却操作"
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
生成加热/冷却操作的协议序列 - 带时间限制的完整操作
|
||||||
|
"""
|
||||||
|
action_sequence = []
|
||||||
|
|
||||||
|
print(f"HEATCHILL: 开始生成加热/冷却协议")
|
||||||
|
print(f" - 容器: {vessel}")
|
||||||
|
print(f" - 目标温度: {temp}°C")
|
||||||
|
print(f" - 持续时间: {time}秒")
|
||||||
|
print(f" - 使用内置搅拌: {stir}, 速度: {stir_speed} RPM")
|
||||||
|
print(f" - 目的: {purpose}")
|
||||||
|
|
||||||
|
# 1. 验证容器存在
|
||||||
|
if vessel not in G.nodes():
|
||||||
|
raise ValueError(f"容器 '{vessel}' 不存在于系统中")
|
||||||
|
|
||||||
|
# 2. 查找加热/冷却设备
|
||||||
|
try:
|
||||||
|
heatchill_id = find_connected_heatchill(G, vessel)
|
||||||
|
print(f"HEATCHILL: 找到加热/冷却设备: {heatchill_id}")
|
||||||
|
except ValueError as e:
|
||||||
|
raise ValueError(f"无法找到加热/冷却设备: {str(e)}")
|
||||||
|
|
||||||
|
# 3. 执行加热/冷却操作
|
||||||
|
heatchill_action = {
|
||||||
|
"device_id": heatchill_id,
|
||||||
|
"action_name": "heat_chill",
|
||||||
|
"action_kwargs": {
|
||||||
|
"vessel": vessel,
|
||||||
|
"temp": temp,
|
||||||
|
"time": time,
|
||||||
|
"stir": stir,
|
||||||
|
"stir_speed": stir_speed,
|
||||||
|
"status": "start"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
action_sequence.append(heatchill_action)
|
||||||
|
|
||||||
|
print(f"HEATCHILL: 生成了 {len(action_sequence)} 个动作")
|
||||||
|
return action_sequence
|
||||||
|
|
||||||
|
|
||||||
|
def generate_heat_chill_start_protocol(
|
||||||
|
G: nx.DiGraph,
|
||||||
|
vessel: str,
|
||||||
|
temp: float,
|
||||||
|
purpose: str = "开始加热/冷却"
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
生成开始加热/冷却操作的协议序列
|
||||||
|
"""
|
||||||
|
action_sequence = []
|
||||||
|
|
||||||
|
print(f"HEATCHILL_START: 开始生成加热/冷却启动协议")
|
||||||
|
print(f" - 容器: {vessel}")
|
||||||
|
print(f" - 目标温度: {temp}°C")
|
||||||
|
print(f" - 目的: {purpose}")
|
||||||
|
|
||||||
|
# 1. 验证容器存在
|
||||||
|
if vessel not in G.nodes():
|
||||||
|
raise ValueError(f"容器 '{vessel}' 不存在于系统中")
|
||||||
|
|
||||||
|
# 2. 查找加热/冷却设备
|
||||||
|
try:
|
||||||
|
heatchill_id = find_connected_heatchill(G, vessel)
|
||||||
|
print(f"HEATCHILL_START: 找到加热/冷却设备: {heatchill_id}")
|
||||||
|
except ValueError as e:
|
||||||
|
raise ValueError(f"无法找到加热/冷却设备: {str(e)}")
|
||||||
|
|
||||||
|
# 3. 执行开始加热/冷却操作
|
||||||
|
heatchill_start_action = {
|
||||||
|
"device_id": heatchill_id,
|
||||||
|
"action_name": "heat_chill_start",
|
||||||
|
"action_kwargs": {
|
||||||
|
"vessel": vessel,
|
||||||
|
"temp": temp,
|
||||||
|
"purpose": purpose
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
action_sequence.append(heatchill_start_action)
|
||||||
|
|
||||||
|
print(f"HEATCHILL_START: 生成了 {len(action_sequence)} 个动作")
|
||||||
|
return action_sequence
|
||||||
|
|
||||||
|
|
||||||
|
def generate_heat_chill_stop_protocol(
|
||||||
|
G: nx.DiGraph,
|
||||||
|
vessel: str
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
生成停止加热/冷却操作的协议序列
|
||||||
|
"""
|
||||||
|
action_sequence = []
|
||||||
|
|
||||||
|
print(f"HEATCHILL_STOP: 开始生成加热/冷却停止协议")
|
||||||
|
print(f" - 容器: {vessel}")
|
||||||
|
|
||||||
|
# 1. 验证容器存在
|
||||||
|
if vessel not in G.nodes():
|
||||||
|
raise ValueError(f"容器 '{vessel}' 不存在于系统中")
|
||||||
|
|
||||||
|
# 2. 查找加热/冷却设备
|
||||||
|
try:
|
||||||
|
heatchill_id = find_connected_heatchill(G, vessel)
|
||||||
|
print(f"HEATCHILL_STOP: 找到加热/冷却设备: {heatchill_id}")
|
||||||
|
except ValueError as e:
|
||||||
|
raise ValueError(f"无法找到加热/冷却设备: {str(e)}")
|
||||||
|
|
||||||
|
# 3. 执行停止加热/冷却操作
|
||||||
|
heatchill_stop_action = {
|
||||||
|
"device_id": heatchill_id,
|
||||||
|
"action_name": "heat_chill_stop",
|
||||||
|
"action_kwargs": {
|
||||||
|
"vessel": vessel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
action_sequence.append(heatchill_stop_action)
|
||||||
|
|
||||||
|
print(f"HEATCHILL_STOP: 生成了 {len(action_sequence)} 个动作")
|
||||||
|
return action_sequence
|
||||||
|
|
||||||
|
|
||||||
|
def generate_heat_chill_to_temp_protocol(
|
||||||
|
G: nx.DiGraph,
|
||||||
|
vessel: str,
|
||||||
|
temp: float,
|
||||||
|
active: bool = True,
|
||||||
|
continue_heatchill: bool = False,
|
||||||
|
stir: bool = False,
|
||||||
|
stir_speed: Optional[float] = None,
|
||||||
|
purpose: Optional[str] = None
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
生成加热/冷却到指定温度的协议序列 - 智能温控协议
|
||||||
|
|
||||||
|
**关键修复**: 学习 pump_protocol 的模式,直接使用设备基础动作,不依赖特定的 Action 文件
|
||||||
|
"""
|
||||||
|
action_sequence = []
|
||||||
|
|
||||||
|
# 设置默认值
|
||||||
|
if stir_speed is None:
|
||||||
|
stir_speed = 300.0
|
||||||
|
if purpose is None:
|
||||||
|
purpose = f"智能温控到 {temp}°C"
|
||||||
|
|
||||||
|
print(f"HEATCHILL_TO_TEMP: 开始生成智能温控协议")
|
||||||
|
print(f" - 容器: {vessel}")
|
||||||
|
print(f" - 目标温度: {temp}°C")
|
||||||
|
print(f" - 主动控温: {active}")
|
||||||
|
print(f" - 达到温度后继续: {continue_heatchill}")
|
||||||
|
print(f" - 搅拌: {stir}, 速度: {stir_speed} RPM")
|
||||||
|
print(f" - 目的: {purpose}")
|
||||||
|
|
||||||
|
# 1. 验证容器存在
|
||||||
|
if vessel not in G.nodes():
|
||||||
|
raise ValueError(f"容器 '{vessel}' 不存在于系统中")
|
||||||
|
|
||||||
|
# 2. 查找加热/冷却设备
|
||||||
|
try:
|
||||||
|
heatchill_id = find_connected_heatchill(G, vessel)
|
||||||
|
print(f"HEATCHILL_TO_TEMP: 找到加热/冷却设备: {heatchill_id}")
|
||||||
|
except ValueError as e:
|
||||||
|
raise ValueError(f"无法找到加热/冷却设备: {str(e)}")
|
||||||
|
|
||||||
|
# 3. 根据参数选择合适的基础动作组合 (学习 pump_protocol 的模式)
|
||||||
|
if not active:
|
||||||
|
print(f"HEATCHILL_TO_TEMP: 非主动模式,仅等待")
|
||||||
|
action_sequence.append({
|
||||||
|
"action_name": "wait",
|
||||||
|
"action_kwargs": {
|
||||||
|
"time": 10.0,
|
||||||
|
"purpose": f"等待容器 {vessel} 自然达到 {temp}°C"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
if continue_heatchill:
|
||||||
|
# 持续模式:使用 heat_chill_start 基础动作
|
||||||
|
print(f"HEATCHILL_TO_TEMP: 使用持续温控模式")
|
||||||
|
action_sequence.append({
|
||||||
|
"device_id": heatchill_id,
|
||||||
|
"action_name": "heat_chill_start", # ← 直接使用设备基础动作
|
||||||
|
"action_kwargs": {
|
||||||
|
"vessel": vessel,
|
||||||
|
"temp": temp,
|
||||||
|
"purpose": f"{purpose} (持续保温)"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
# 一次性模式:使用 heat_chill 基础动作
|
||||||
|
print(f"HEATCHILL_TO_TEMP: 使用一次性温控模式")
|
||||||
|
estimated_time = max(60.0, min(900.0, abs(temp - 25.0) * 30.0))
|
||||||
|
print(f"HEATCHILL_TO_TEMP: 估算所需时间: {estimated_time}秒")
|
||||||
|
|
||||||
|
action_sequence.append({
|
||||||
|
"device_id": heatchill_id,
|
||||||
|
"action_name": "heat_chill", # ← 直接使用设备基础动作
|
||||||
|
"action_kwargs": {
|
||||||
|
"vessel": vessel,
|
||||||
|
"temp": temp,
|
||||||
|
"time": estimated_time,
|
||||||
|
"stir": stir,
|
||||||
|
"stir_speed": stir_speed,
|
||||||
|
"status": "start"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
print(f"HEATCHILL_TO_TEMP: 生成了 {len(action_sequence)} 个动作")
|
||||||
|
return action_sequence
|
||||||
|
|
||||||
|
|
||||||
|
# 扩展版本的加热/冷却协议,集成智能温控功能
|
||||||
|
def generate_smart_heat_chill_protocol(
|
||||||
|
G: nx.DiGraph,
|
||||||
|
vessel: str,
|
||||||
|
temp: float,
|
||||||
|
time: float = 0.0, # 0表示自动估算
|
||||||
|
active: bool = True,
|
||||||
|
continue_heatchill: bool = False,
|
||||||
|
stir: bool = False,
|
||||||
|
stir_speed: float = 300.0,
|
||||||
|
purpose: str = "智能加热/冷却"
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
这个函数集成了 generate_heat_chill_to_temp_protocol 的智能逻辑,
|
||||||
|
但使用现有的 Action 类型
|
||||||
|
"""
|
||||||
|
# 如果时间为0,自动估算
|
||||||
|
if time == 0.0:
|
||||||
|
estimated_time = max(60.0, min(900.0, abs(temp - 25.0) * 30.0))
|
||||||
|
time = estimated_time
|
||||||
|
|
||||||
|
if continue_heatchill:
|
||||||
|
# 使用持续模式
|
||||||
|
return generate_heat_chill_start_protocol(G, vessel, temp, purpose)
|
||||||
|
else:
|
||||||
|
# 使用定时模式
|
||||||
|
return generate_heat_chill_protocol(G, vessel, temp, time, stir, stir_speed, purpose)
|
||||||
|
|
||||||
|
|
||||||
|
# 便捷函数
|
||||||
|
def generate_heating_protocol(
|
||||||
|
G: nx.DiGraph,
|
||||||
|
vessel: str,
|
||||||
|
temp: float,
|
||||||
|
time: float = 300.0,
|
||||||
|
stir: bool = True,
|
||||||
|
stir_speed: float = 300.0
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
"""生成加热协议的便捷函数"""
|
||||||
|
return generate_heat_chill_protocol(
|
||||||
|
G=G, vessel=vessel, temp=temp, time=time,
|
||||||
|
stir=stir, stir_speed=stir_speed, purpose=f"加热到 {temp}°C"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def generate_cooling_protocol(
|
||||||
|
G: nx.DiGraph,
|
||||||
|
vessel: str,
|
||||||
|
temp: float,
|
||||||
|
time: float = 600.0,
|
||||||
|
stir: bool = True,
|
||||||
|
stir_speed: float = 200.0
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
"""生成冷却协议的便捷函数"""
|
||||||
|
return generate_heat_chill_protocol(
|
||||||
|
G=G, vessel=vessel, temp=temp, time=time,
|
||||||
|
stir=stir, stir_speed=stir_speed, purpose=f"冷却到 {temp}°C"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# # 温度预设快捷函数
|
||||||
|
# def generate_room_temp_protocol(
|
||||||
|
# G: nx.DiGraph,
|
||||||
|
# vessel: str,
|
||||||
|
# stir: bool = False
|
||||||
|
# ) -> List[Dict[str, Any]]:
|
||||||
|
# """返回室温的快捷函数"""
|
||||||
|
# return generate_heat_chill_to_temp_protocol(
|
||||||
|
# G=G,
|
||||||
|
# vessel=vessel,
|
||||||
|
# temp=25.0,
|
||||||
|
# active=True,
|
||||||
|
# continue_heatchill=False,
|
||||||
|
# stir=stir,
|
||||||
|
# purpose="冷却到室温"
|
||||||
|
# )
|
||||||
|
|
||||||
|
|
||||||
|
# def generate_reflux_heating_protocol(
|
||||||
|
# G: nx.DiGraph,
|
||||||
|
# vessel: str,
|
||||||
|
# temp: float,
|
||||||
|
# time: float = 3600.0 # 1小时回流
|
||||||
|
# ) -> List[Dict[str, Any]]:
|
||||||
|
# """回流加热的快捷函数"""
|
||||||
|
# return generate_heat_chill_protocol(
|
||||||
|
# G=G,
|
||||||
|
# vessel=vessel,
|
||||||
|
# temp=temp,
|
||||||
|
# time=time,
|
||||||
|
# stir=True,
|
||||||
|
# stir_speed=400.0, # 回流时较快搅拌
|
||||||
|
# purpose=f"回流加热到 {temp}°C"
|
||||||
|
# )
|
||||||
|
|
||||||
|
|
||||||
|
# def generate_ice_bath_protocol(
|
||||||
|
# G: nx.DiGraph,
|
||||||
|
# vessel: str,
|
||||||
|
# time: float = 600.0 # 10分钟冰浴
|
||||||
|
# ) -> List[Dict[str, Any]]:
|
||||||
|
# """冰浴冷却的快捷函数"""
|
||||||
|
# return generate_heat_chill_protocol(
|
||||||
|
# G=G,
|
||||||
|
# vessel=vessel,
|
||||||
|
# temp=0.0,
|
||||||
|
# time=time,
|
||||||
|
# stir=True,
|
||||||
|
# stir_speed=150.0, # 冰浴时缓慢搅拌
|
||||||
|
# purpose="冰浴冷却到 0°C"
|
||||||
|
# )
|
||||||
|
|
||||||
|
|
||||||
|
# 测试函数
|
||||||
|
def test_heatchill_protocol():
|
||||||
|
"""测试加热/冷却协议的示例"""
|
||||||
|
print("=== HEAT CHILL PROTOCOL 测试 ===")
|
||||||
|
print("完整的四个协议函数:")
|
||||||
|
print("1. generate_heat_chill_protocol - 带时间限制的完整操作")
|
||||||
|
print("2. generate_heat_chill_start_protocol - 持续加热/冷却")
|
||||||
|
print("3. generate_heat_chill_stop_protocol - 停止加热/冷却")
|
||||||
|
print("4. generate_heat_chill_to_temp_protocol - 智能温控 (您的 HeatChillToTemp)")
|
||||||
|
print("测试完成")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
test_heatchill_protocol()
|
||||||
@@ -2,17 +2,43 @@ 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):
|
||||||
|
node_class = G.nodes[neighbor].get("class") or "" # 防止 None
|
||||||
|
if "pump" in node_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,
|
||||||
to_vessel: str,
|
to_vessel: str,
|
||||||
volume: float,
|
volume: float,
|
||||||
flowrate: float = 0.5,
|
flowrate: float = 0.5,
|
||||||
transfer_flowrate: float = 0,
|
transfer_flowrate: float = 0,
|
||||||
) -> list[dict]:
|
) -> list[dict]:
|
||||||
"""
|
"""
|
||||||
生成泵操作的动作序列。
|
生成泵操作的动作序列。
|
||||||
|
|
||||||
:param G: 有向图, 节点为容器和注射泵, 边为流体管道, A→B边的属性为管道接A端的阀门位置
|
:param G: 有向图, 节点为容器和注射泵, 边为流体管道, A→B边的属性为管道接A端的阀门位置
|
||||||
:param from_vessel: 容器A
|
:param from_vessel: 容器A
|
||||||
:param to_vessel: 容器B
|
:param to_vessel: 容器B
|
||||||
@@ -21,7 +47,7 @@ def generate_pump_protocol(
|
|||||||
:param transfer_flowrate: 泵骨架中转移流速(若不指定,默认与注入流速相同)
|
:param transfer_flowrate: 泵骨架中转移流速(若不指定,默认与注入流速相同)
|
||||||
:return: 泵操作的动作序列
|
:return: 泵操作的动作序列
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# 生成泵操作的动作序列
|
# 生成泵操作的动作序列
|
||||||
pump_action_sequence = []
|
pump_action_sequence = []
|
||||||
nodes = G.nodes(data=True)
|
nodes = G.nodes(data=True)
|
||||||
@@ -34,31 +60,33 @@ def generate_pump_protocol(
|
|||||||
pump_backbone = pump_backbone[1:]
|
pump_backbone = pump_backbone[1:]
|
||||||
if not to_vessel.startswith("pump"):
|
if not to_vessel.startswith("pump"):
|
||||||
pump_backbone = pump_backbone[:-1]
|
pump_backbone = pump_backbone[:-1]
|
||||||
|
|
||||||
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.")
|
||||||
|
|
||||||
volume_left = volume
|
volume_left = volume
|
||||||
|
|
||||||
# 生成泵操作的动作序列
|
# 生成泵操作的动作序列
|
||||||
for i in range(repeats):
|
for i in range(repeats):
|
||||||
# 单泵依次执行阀指令、活塞指令,将液体吸入与之相连的第一台泵
|
# 单泵依次执行阀指令、活塞指令,将液体吸入与之相连的第一台泵
|
||||||
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,57 +95,57 @@ 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": valve_from_node[nodeB],
|
||||||
|
"action_name": "set_valve_position",
|
||||||
|
"action_kwargs": {
|
||||||
|
"command": G.get_edge_data(nodeB, nodeA)["port"][nodeB],
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
{
|
|
||||||
"device_id": pumpB,
|
|
||||||
"action_name": "set_valve_position",
|
|
||||||
"action_kwargs": {
|
|
||||||
"command": G.get_edge_data(pumpB, pumpA)["port"][pumpB],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
])
|
])
|
||||||
# 相邻两泵液体转移:泵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,
|
||||||
"max_velocity": transfer_flowrate
|
"max_velocity": transfer_flowrate
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"device_id": pumps_from_node[nodeB],
|
||||||
|
"action_name": "set_position",
|
||||||
|
"action_kwargs": {
|
||||||
|
"position": float(min(volume_left, min_transfer_volume)),
|
||||||
|
"max_velocity": transfer_flowrate
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
{
|
|
||||||
"device_id": pumpB,
|
|
||||||
"action_name": "set_position",
|
|
||||||
"action_kwargs": {
|
|
||||||
"position": float(min(volume_left, min_transfer_volume)),
|
|
||||||
"max_velocity": transfer_flowrate
|
|
||||||
}
|
|
||||||
}
|
|
||||||
])
|
])
|
||||||
pump_action_sequence.append({"action_name": "wait", "action_kwargs": {"time": 5}})
|
pump_action_sequence.append({"action_name": "wait", "action_kwargs": {"time": 5}})
|
||||||
|
|
||||||
if not to_vessel.startswith("pump"):
|
if not to_vessel.startswith("pump"):
|
||||||
# 单泵依次执行阀指令、活塞指令,将最后一台泵液体缓慢加入容器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,
|
||||||
@@ -126,30 +154,30 @@ 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}})
|
||||||
|
|
||||||
volume_left -= min_transfer_volume
|
volume_left -= min_transfer_volume
|
||||||
return pump_action_sequence
|
return pump_action_sequence
|
||||||
|
|
||||||
|
|
||||||
# Pump protocol compilation
|
# Pump protocol compilation
|
||||||
def generate_pump_protocol_with_rinsing(
|
def generate_pump_protocol_with_rinsing(
|
||||||
G: nx.DiGraph,
|
G: nx.DiGraph,
|
||||||
from_vessel: str,
|
from_vessel: str,
|
||||||
to_vessel: str,
|
to_vessel: str,
|
||||||
volume: float,
|
volume: float,
|
||||||
amount: str = "",
|
amount: str = "",
|
||||||
time: float = 0,
|
time: float = 0,
|
||||||
viscous: bool = False,
|
viscous: bool = False,
|
||||||
rinsing_solvent: str = "air",
|
rinsing_solvent: str = "air",
|
||||||
rinsing_volume: float = 5.0,
|
rinsing_volume: float = 5.0,
|
||||||
rinsing_repeats: int = 2,
|
rinsing_repeats: int = 2,
|
||||||
solid: bool = False,
|
solid: bool = False,
|
||||||
flowrate: float = 2.5,
|
flowrate: float = 2.5,
|
||||||
transfer_flowrate: float = 0.5,
|
transfer_flowrate: float = 0.5,
|
||||||
) -> list[dict]:
|
) -> list[dict]:
|
||||||
"""
|
"""
|
||||||
Generates a pump protocol for transferring a specified volume between vessels, including rinsing steps with a chosen solvent. This function constructs a sequence of pump actions based on the provided parameters and the shortest path in a directed graph.
|
Generates a pump protocol for transferring a specified volume between vessels, including rinsing steps with a chosen solvent. This function constructs a sequence of pump actions based on the provided parameters and the shortest path in a directed graph.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
G (nx.DiGraph): The directed graph representing the vessels and connections. 有向图, 节点为容器和注射泵, 边为流体管道, A→B边的属性为管道接A端的阀门位置
|
G (nx.DiGraph): The directed graph representing the vessels and connections. 有向图, 节点为容器和注射泵, 边为流体管道, A→B边的属性为管道接A端的阀门位置
|
||||||
from_vessel (str): The name of the vessel to transfer from.
|
from_vessel (str): The name of the vessel to transfer from.
|
||||||
@@ -164,50 +192,64 @@ def generate_pump_protocol_with_rinsing(
|
|||||||
solid (bool, optional): Indicates if the transfer involves a solid (default is False).
|
solid (bool, optional): Indicates if the transfer involves a solid (default is False).
|
||||||
flowrate (float, optional): The flow rate for the transfer (default is 2.5). 最终注入容器B时的流速
|
flowrate (float, optional): The flow rate for the transfer (default is 2.5). 最终注入容器B时的流速
|
||||||
transfer_flowrate (float, optional): The flow rate for the transfer action (default is 0.5). 泵骨架中转移流速(若不指定,默认与注入流速相同)
|
transfer_flowrate (float, optional): The flow rate for the transfer action (default is 0.5). 泵骨架中转移流速(若不指定,默认与注入流速相同)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
list[dict]: A sequence of pump actions to be executed for the transfer and rinsing process. 泵操作的动作序列.
|
list[dict]: A sequence of pump actions to be executed for the transfer and rinsing process. 泵操作的动作序列.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
AssertionError: If the number of rinsing solvents does not match the number of rinsing repeats.
|
AssertionError: If the number of rinsing solvents does not match the number of rinsing repeats.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
pump_protocol = generate_pump_protocol_with_rinsing(G, "vessel_A", "vessel_B", 0.1, rinsing_solvent="water")
|
pump_protocol = generate_pump_protocol_with_rinsing(G, "vessel_A", "vessel_B", 0.1, rinsing_solvent="water")
|
||||||
"""
|
"""
|
||||||
air_vessel = "flask_air"
|
air_vessel = "flask_air"
|
||||||
waste_vessel = f"waste_workup"
|
waste_vessel = f"waste_workup"
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
for rinsing_solvent in rinsing_solvents:
|
for rinsing_solvent in rinsing_solvents:
|
||||||
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
|
||||||
|
|||||||
312
unilabos/compile/run_column_protocol.py
Normal file
312
unilabos/compile/run_column_protocol.py
Normal file
@@ -0,0 +1,312 @@
|
|||||||
|
from typing import List, Dict, Any
|
||||||
|
import networkx as nx
|
||||||
|
from .pump_protocol import generate_pump_protocol
|
||||||
|
|
||||||
|
|
||||||
|
def get_vessel_liquid_volume(G: nx.DiGraph, vessel: str) -> float:
|
||||||
|
"""获取容器中的液体体积"""
|
||||||
|
if vessel not in G.nodes():
|
||||||
|
return 0.0
|
||||||
|
|
||||||
|
vessel_data = G.nodes[vessel].get('data', {})
|
||||||
|
liquids = vessel_data.get('liquid', [])
|
||||||
|
|
||||||
|
total_volume = 0.0
|
||||||
|
for liquid in liquids:
|
||||||
|
if isinstance(liquid, dict):
|
||||||
|
# 支持两种格式:新格式 (name, volume) 和旧格式 (liquid_type, liquid_volume)
|
||||||
|
volume = liquid.get('volume') or liquid.get('liquid_volume', 0.0)
|
||||||
|
total_volume += volume
|
||||||
|
|
||||||
|
return total_volume
|
||||||
|
|
||||||
|
|
||||||
|
def find_column_device(G: nx.DiGraph, column: str) -> str:
|
||||||
|
"""查找柱层析设备"""
|
||||||
|
# 首先检查是否有虚拟柱设备
|
||||||
|
column_nodes = [node for node in G.nodes()
|
||||||
|
if (G.nodes[node].get('class') or '') == 'virtual_column']
|
||||||
|
|
||||||
|
if column_nodes:
|
||||||
|
return column_nodes[0]
|
||||||
|
|
||||||
|
# 如果没有虚拟柱设备,抛出异常
|
||||||
|
raise ValueError(f"系统中未找到柱层析设备。请确保配置了 virtual_column 设备")
|
||||||
|
|
||||||
|
|
||||||
|
def find_column_vessel(G: nx.DiGraph, column: str) -> str:
|
||||||
|
"""查找柱容器"""
|
||||||
|
# 直接使用 column 参数作为容器名称
|
||||||
|
if column in G.nodes():
|
||||||
|
return column
|
||||||
|
|
||||||
|
# 尝试常见的柱容器命名规则
|
||||||
|
possible_names = [
|
||||||
|
f"column_{column}",
|
||||||
|
f"{column}_column",
|
||||||
|
f"vessel_{column}",
|
||||||
|
f"{column}_vessel",
|
||||||
|
"column_vessel",
|
||||||
|
"chromatography_column",
|
||||||
|
"silica_column",
|
||||||
|
"preparative_column"
|
||||||
|
]
|
||||||
|
|
||||||
|
for vessel_name in possible_names:
|
||||||
|
if vessel_name in G.nodes():
|
||||||
|
return vessel_name
|
||||||
|
|
||||||
|
raise ValueError(f"未找到柱容器 '{column}'。尝试了以下名称: {[column] + possible_names}")
|
||||||
|
|
||||||
|
|
||||||
|
def find_eluting_solvent_vessel(G: nx.DiGraph, eluting_solvent: str) -> str:
|
||||||
|
"""查找洗脱溶剂容器"""
|
||||||
|
if not eluting_solvent:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
# 按照命名规则查找溶剂瓶
|
||||||
|
solvent_vessel_id = f"flask_{eluting_solvent}"
|
||||||
|
|
||||||
|
if solvent_vessel_id in G.nodes():
|
||||||
|
return solvent_vessel_id
|
||||||
|
|
||||||
|
# 如果直接匹配失败,尝试模糊匹配
|
||||||
|
for node in G.nodes():
|
||||||
|
if node.startswith('flask_') and eluting_solvent.lower() in node.lower():
|
||||||
|
return node
|
||||||
|
|
||||||
|
# 如果还是找不到,列出所有可用的溶剂瓶
|
||||||
|
available_flasks = [node for node in G.nodes()
|
||||||
|
if node.startswith('flask_')
|
||||||
|
and G.nodes[node].get('type') == 'container']
|
||||||
|
|
||||||
|
raise ValueError(f"找不到洗脱溶剂 '{eluting_solvent}' 对应的溶剂瓶。可用溶剂瓶: {available_flasks}")
|
||||||
|
|
||||||
|
|
||||||
|
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]]: 柱层析分离操作的动作序列
|
||||||
|
"""
|
||||||
|
action_sequence = []
|
||||||
|
|
||||||
|
print(f"RUN_COLUMN: 开始生成柱层析协议")
|
||||||
|
print(f" - 源容器: {from_vessel}")
|
||||||
|
print(f" - 目标容器: {to_vessel}")
|
||||||
|
print(f" - 柱子: {column}")
|
||||||
|
|
||||||
|
# 验证源容器和目标容器存在
|
||||||
|
if from_vessel not in G.nodes():
|
||||||
|
raise ValueError(f"源容器 '{from_vessel}' 不存在于系统中")
|
||||||
|
|
||||||
|
if to_vessel not in G.nodes():
|
||||||
|
raise ValueError(f"目标容器 '{to_vessel}' 不存在于系统中")
|
||||||
|
|
||||||
|
# 查找柱层析设备
|
||||||
|
column_device_id = None
|
||||||
|
column_nodes = [node for node in G.nodes()
|
||||||
|
if (G.nodes[node].get('class') or '') == 'virtual_column']
|
||||||
|
|
||||||
|
if column_nodes:
|
||||||
|
column_device_id = column_nodes[0]
|
||||||
|
print(f"RUN_COLUMN: 找到柱层析设备: {column_device_id}")
|
||||||
|
else:
|
||||||
|
print(f"RUN_COLUMN: 警告 - 未找到柱层析设备")
|
||||||
|
|
||||||
|
# 获取源容器中的液体体积
|
||||||
|
source_volume = get_vessel_liquid_volume(G, from_vessel)
|
||||||
|
print(f"RUN_COLUMN: 源容器 {from_vessel} 中有 {source_volume} mL 液体")
|
||||||
|
|
||||||
|
# === 第一步:样品转移到柱子(如果柱子是容器) ===
|
||||||
|
if column in G.nodes() and G.nodes[column].get('type') == 'container':
|
||||||
|
print(f"RUN_COLUMN: 样品转移 - {source_volume} mL 从 {from_vessel} 到 {column}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
sample_transfer_actions = generate_pump_protocol(
|
||||||
|
G=G,
|
||||||
|
from_vessel=from_vessel,
|
||||||
|
to_vessel=column,
|
||||||
|
volume=source_volume if source_volume > 0 else 100.0,
|
||||||
|
flowrate=2.0
|
||||||
|
)
|
||||||
|
action_sequence.extend(sample_transfer_actions)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"RUN_COLUMN: 样品转移失败: {str(e)}")
|
||||||
|
|
||||||
|
# === 第二步:使用柱层析设备执行分离 ===
|
||||||
|
if column_device_id:
|
||||||
|
print(f"RUN_COLUMN: 使用柱层析设备执行分离")
|
||||||
|
|
||||||
|
column_separation_action = {
|
||||||
|
"device_id": column_device_id,
|
||||||
|
"action_name": "run_column",
|
||||||
|
"action_kwargs": {
|
||||||
|
"from_vessel": from_vessel,
|
||||||
|
"to_vessel": to_vessel,
|
||||||
|
"column": column
|
||||||
|
}
|
||||||
|
}
|
||||||
|
action_sequence.append(column_separation_action)
|
||||||
|
|
||||||
|
# 等待柱层析设备完成分离
|
||||||
|
action_sequence.append({
|
||||||
|
"action_name": "wait",
|
||||||
|
"action_kwargs": {"time": 60}
|
||||||
|
})
|
||||||
|
|
||||||
|
# === 第三步:从柱子转移到目标容器(如果需要) ===
|
||||||
|
if column in G.nodes() and column != to_vessel:
|
||||||
|
print(f"RUN_COLUMN: 产物转移 - 从 {column} 到 {to_vessel}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
product_transfer_actions = generate_pump_protocol(
|
||||||
|
G=G,
|
||||||
|
from_vessel=column,
|
||||||
|
to_vessel=to_vessel,
|
||||||
|
volume=source_volume * 0.8 if source_volume > 0 else 80.0, # 假设有一些损失
|
||||||
|
flowrate=1.5
|
||||||
|
)
|
||||||
|
action_sequence.extend(product_transfer_actions)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"RUN_COLUMN: 产物转移失败: {str(e)}")
|
||||||
|
|
||||||
|
print(f"RUN_COLUMN: 生成了 {len(action_sequence)} 个动作")
|
||||||
|
return action_sequence
|
||||||
|
|
||||||
|
|
||||||
|
# 便捷函数:常用柱层析方案
|
||||||
|
def generate_flash_column_protocol(
|
||||||
|
G: nx.DiGraph,
|
||||||
|
from_vessel: str,
|
||||||
|
to_vessel: str,
|
||||||
|
column_material: str = "silica_gel",
|
||||||
|
mobile_phase: str = "ethyl_acetate",
|
||||||
|
mobile_phase_volume: float = 100.0
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
"""快速柱层析:高流速分离"""
|
||||||
|
return generate_run_column_protocol(
|
||||||
|
G, from_vessel, to_vessel, column_material,
|
||||||
|
mobile_phase, mobile_phase_volume, 1, "", 0.0, 3.0
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def generate_preparative_column_protocol(
|
||||||
|
G: nx.DiGraph,
|
||||||
|
from_vessel: str,
|
||||||
|
to_vessel: str,
|
||||||
|
column_material: str = "silica_gel",
|
||||||
|
equilibration_solvent: str = "hexane",
|
||||||
|
eluting_solvent: str = "ethyl_acetate",
|
||||||
|
eluting_volume: float = 50.0,
|
||||||
|
eluting_repeats: int = 3
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
"""制备柱层析:带平衡和多次洗脱"""
|
||||||
|
return generate_run_column_protocol(
|
||||||
|
G, from_vessel, to_vessel, column_material,
|
||||||
|
eluting_solvent, eluting_volume, eluting_repeats,
|
||||||
|
equilibration_solvent, 30.0, 1.5
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def generate_gradient_column_protocol(
|
||||||
|
G: nx.DiGraph,
|
||||||
|
from_vessel: str,
|
||||||
|
to_vessel: str,
|
||||||
|
column_material: str = "silica_gel",
|
||||||
|
gradient_solvents: List[str] = None,
|
||||||
|
gradient_volumes: List[float] = None
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
"""梯度洗脱柱层析:多种溶剂系统"""
|
||||||
|
if gradient_solvents is None:
|
||||||
|
gradient_solvents = ["hexane", "ethyl_acetate", "methanol"]
|
||||||
|
if gradient_volumes is None:
|
||||||
|
gradient_volumes = [50.0, 50.0, 30.0]
|
||||||
|
|
||||||
|
action_sequence = []
|
||||||
|
|
||||||
|
# 每种溶剂单独执行一次柱层析
|
||||||
|
for i, (solvent, volume) in enumerate(zip(gradient_solvents, gradient_volumes)):
|
||||||
|
print(f"RUN_COLUMN: 梯度洗脱第 {i+1}/{len(gradient_solvents)} 步: {volume} mL {solvent}")
|
||||||
|
|
||||||
|
# 第一步使用源容器,后续步骤使用柱子作为源
|
||||||
|
step_from_vessel = from_vessel if i == 0 else column_material
|
||||||
|
# 最后一步使用目标容器,其他步骤使用柱子作为目标
|
||||||
|
step_to_vessel = to_vessel if i == len(gradient_solvents) - 1 else column_material
|
||||||
|
|
||||||
|
step_actions = generate_run_column_protocol(
|
||||||
|
G, step_from_vessel, step_to_vessel, column_material,
|
||||||
|
solvent, volume, 1, "", 0.0, 1.0
|
||||||
|
)
|
||||||
|
action_sequence.extend(step_actions)
|
||||||
|
|
||||||
|
# 在梯度步骤之间加入等待时间
|
||||||
|
if i < len(gradient_solvents) - 1:
|
||||||
|
action_sequence.append({
|
||||||
|
"action_name": "wait",
|
||||||
|
"action_kwargs": {"time": 20}
|
||||||
|
})
|
||||||
|
|
||||||
|
return action_sequence
|
||||||
|
|
||||||
|
|
||||||
|
def generate_reverse_phase_column_protocol(
|
||||||
|
G: nx.DiGraph,
|
||||||
|
from_vessel: str,
|
||||||
|
to_vessel: str,
|
||||||
|
column_material: str = "C18",
|
||||||
|
aqueous_phase: str = "water",
|
||||||
|
organic_phase: str = "methanol",
|
||||||
|
gradient_ratio: float = 0.5
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
"""反相柱层析:C18柱,水-有机相梯度"""
|
||||||
|
# 先用水相平衡
|
||||||
|
equilibration_volume = 20.0
|
||||||
|
# 然后用有机相洗脱
|
||||||
|
eluting_volume = 30.0 * gradient_ratio
|
||||||
|
|
||||||
|
return generate_run_column_protocol(
|
||||||
|
G, from_vessel, to_vessel, column_material,
|
||||||
|
organic_phase, eluting_volume, 2,
|
||||||
|
aqueous_phase, equilibration_volume, 0.8
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def generate_ion_exchange_column_protocol(
|
||||||
|
G: nx.DiGraph,
|
||||||
|
from_vessel: str,
|
||||||
|
to_vessel: str,
|
||||||
|
column_material: str = "ion_exchange",
|
||||||
|
buffer_solution: str = "buffer",
|
||||||
|
salt_solution: str = "NaCl_solution",
|
||||||
|
salt_volume: float = 40.0
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
"""离子交换柱层析:缓冲液平衡,盐溶液洗脱"""
|
||||||
|
return generate_run_column_protocol(
|
||||||
|
G, from_vessel, to_vessel, column_material,
|
||||||
|
salt_solution, salt_volume, 1,
|
||||||
|
buffer_solution, 25.0, 0.5
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# 测试函数
|
||||||
|
def test_run_column_protocol():
|
||||||
|
"""测试柱层析协议的示例"""
|
||||||
|
print("=== RUN COLUMN PROTOCOL 测试 ===")
|
||||||
|
print("测试完成")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
test_run_column_protocol()
|
||||||
166
unilabos/compile/stir_protocol.py
Normal file
166
unilabos/compile/stir_protocol.py
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
from typing import List, Dict, Any
|
||||||
|
import networkx as nx
|
||||||
|
|
||||||
|
|
||||||
|
def find_connected_stirrer(G: nx.DiGraph, vessel: str = None) -> str:
|
||||||
|
"""
|
||||||
|
查找与指定容器相连的搅拌设备,或查找可用的搅拌设备
|
||||||
|
"""
|
||||||
|
# 查找所有搅拌设备节点
|
||||||
|
stirrer_nodes = [node for node in G.nodes()
|
||||||
|
if (G.nodes[node].get('class') or '') == 'virtual_stirrer']
|
||||||
|
|
||||||
|
if vessel:
|
||||||
|
# 检查哪个搅拌设备与目标容器相连(机械连接)
|
||||||
|
for stirrer in stirrer_nodes:
|
||||||
|
if G.has_edge(stirrer, vessel) or G.has_edge(vessel, stirrer):
|
||||||
|
return stirrer
|
||||||
|
|
||||||
|
# 如果没有指定容器或没有直接连接,返回第一个可用的搅拌设备
|
||||||
|
if stirrer_nodes:
|
||||||
|
return stirrer_nodes[0]
|
||||||
|
|
||||||
|
raise ValueError("系统中未找到可用的搅拌设备")
|
||||||
|
|
||||||
|
|
||||||
|
def generate_stir_protocol(
|
||||||
|
G: nx.DiGraph,
|
||||||
|
stir_time: float,
|
||||||
|
stir_speed: float,
|
||||||
|
settling_time: float
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
生成搅拌操作的协议序列 - 定时搅拌 + 沉降
|
||||||
|
"""
|
||||||
|
action_sequence = []
|
||||||
|
|
||||||
|
print(f"STIR: 开始生成搅拌协议")
|
||||||
|
print(f" - 搅拌时间: {stir_time}秒")
|
||||||
|
print(f" - 搅拌速度: {stir_speed} RPM")
|
||||||
|
print(f" - 沉降时间: {settling_time}秒")
|
||||||
|
|
||||||
|
# 查找搅拌设备
|
||||||
|
try:
|
||||||
|
stirrer_id = find_connected_stirrer(G)
|
||||||
|
print(f"STIR: 找到搅拌设备: {stirrer_id}")
|
||||||
|
except ValueError as e:
|
||||||
|
raise ValueError(f"无法找到搅拌设备: {str(e)}")
|
||||||
|
|
||||||
|
# 执行搅拌操作
|
||||||
|
stir_action = {
|
||||||
|
"device_id": stirrer_id,
|
||||||
|
"action_name": "stir",
|
||||||
|
"action_kwargs": {
|
||||||
|
"stir_time": stir_time,
|
||||||
|
"stir_speed": stir_speed,
|
||||||
|
"settling_time": settling_time
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
action_sequence.append(stir_action)
|
||||||
|
|
||||||
|
print(f"STIR: 生成了 {len(action_sequence)} 个动作")
|
||||||
|
return action_sequence
|
||||||
|
|
||||||
|
|
||||||
|
def generate_start_stir_protocol(
|
||||||
|
G: nx.DiGraph,
|
||||||
|
vessel: str,
|
||||||
|
stir_speed: float,
|
||||||
|
purpose: str
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
生成开始搅拌操作的协议序列 - 持续搅拌
|
||||||
|
"""
|
||||||
|
action_sequence = []
|
||||||
|
|
||||||
|
print(f"START_STIR: 开始生成启动搅拌协议")
|
||||||
|
print(f" - 容器: {vessel}")
|
||||||
|
print(f" - 搅拌速度: {stir_speed} RPM")
|
||||||
|
print(f" - 目的: {purpose}")
|
||||||
|
|
||||||
|
# 验证容器存在
|
||||||
|
if vessel not in G.nodes():
|
||||||
|
raise ValueError(f"容器 '{vessel}' 不存在于系统中")
|
||||||
|
|
||||||
|
# 查找搅拌设备
|
||||||
|
try:
|
||||||
|
stirrer_id = find_connected_stirrer(G, vessel)
|
||||||
|
print(f"START_STIR: 找到搅拌设备: {stirrer_id}")
|
||||||
|
except ValueError as e:
|
||||||
|
raise ValueError(f"无法找到搅拌设备: {str(e)}")
|
||||||
|
|
||||||
|
# 执行开始搅拌操作
|
||||||
|
start_stir_action = {
|
||||||
|
"device_id": stirrer_id,
|
||||||
|
"action_name": "start_stir",
|
||||||
|
"action_kwargs": {
|
||||||
|
"vessel": vessel,
|
||||||
|
"stir_speed": stir_speed,
|
||||||
|
"purpose": purpose
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
action_sequence.append(start_stir_action)
|
||||||
|
|
||||||
|
print(f"START_STIR: 生成了 {len(action_sequence)} 个动作")
|
||||||
|
return action_sequence
|
||||||
|
|
||||||
|
|
||||||
|
def generate_stop_stir_protocol(
|
||||||
|
G: nx.DiGraph,
|
||||||
|
vessel: str
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
生成停止搅拌操作的协议序列
|
||||||
|
"""
|
||||||
|
action_sequence = []
|
||||||
|
|
||||||
|
print(f"STOP_STIR: 开始生成停止搅拌协议")
|
||||||
|
print(f" - 容器: {vessel}")
|
||||||
|
|
||||||
|
# 验证容器存在
|
||||||
|
if vessel not in G.nodes():
|
||||||
|
raise ValueError(f"容器 '{vessel}' 不存在于系统中")
|
||||||
|
|
||||||
|
# 查找搅拌设备
|
||||||
|
try:
|
||||||
|
stirrer_id = find_connected_stirrer(G, vessel)
|
||||||
|
print(f"STOP_STIR: 找到搅拌设备: {stirrer_id}")
|
||||||
|
except ValueError as e:
|
||||||
|
raise ValueError(f"无法找到搅拌设备: {str(e)}")
|
||||||
|
|
||||||
|
# 执行停止搅拌操作
|
||||||
|
stop_stir_action = {
|
||||||
|
"device_id": stirrer_id,
|
||||||
|
"action_name": "stop_stir",
|
||||||
|
"action_kwargs": {
|
||||||
|
"vessel": vessel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
action_sequence.append(stop_stir_action)
|
||||||
|
|
||||||
|
print(f"STOP_STIR: 生成了 {len(action_sequence)} 个动作")
|
||||||
|
return action_sequence
|
||||||
|
|
||||||
|
|
||||||
|
# 便捷函数
|
||||||
|
def generate_fast_stir_protocol(
|
||||||
|
G: nx.DiGraph,
|
||||||
|
time: float = 300.0,
|
||||||
|
speed: float = 800.0,
|
||||||
|
settling: float = 60.0
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
"""快速搅拌的便捷函数"""
|
||||||
|
return generate_stir_protocol(G, time, speed, settling)
|
||||||
|
|
||||||
|
|
||||||
|
def generate_gentle_stir_protocol(
|
||||||
|
G: nx.DiGraph,
|
||||||
|
time: float = 600.0,
|
||||||
|
speed: float = 200.0,
|
||||||
|
settling: float = 120.0
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
"""温和搅拌的便捷函数"""
|
||||||
|
return generate_stir_protocol(G, time, speed, settling)
|
||||||
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
|
||||||
@@ -10,8 +10,9 @@ from unilabos.utils import logger
|
|||||||
class BasicConfig:
|
class BasicConfig:
|
||||||
ENV = "pro" # 'test'
|
ENV = "pro" # 'test'
|
||||||
config_path = ""
|
config_path = ""
|
||||||
is_host_mode = True # 从registry.py移动过来
|
is_host_mode = True
|
||||||
slave_no_host = False # 是否跳过rclient.wait_for_service()
|
slave_no_host = False # 是否跳过rclient.wait_for_service()
|
||||||
|
upload_registry = False
|
||||||
machine_name = "undefined"
|
machine_name = "undefined"
|
||||||
vis_2d_enable = False
|
vis_2d_enable = False
|
||||||
|
|
||||||
|
|||||||
@@ -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.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user