mirror of
https://github.com/deepmodeling/Uni-Lab-OS
synced 2026-03-28 23:33:09 +00:00
Compare commits
126 Commits
v0.9.5
...
4456529cfb
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4456529cfb | ||
|
|
694a779c66 | ||
|
|
5d214ebcd8 | ||
|
|
0e11dacead | ||
|
|
7b68545db3 | ||
|
|
25960c2ed5 | ||
|
|
72c67ba25c | ||
|
|
cd9e7ef12c | ||
|
|
b85722f44d | ||
|
|
5a2cc2d709 | ||
|
|
644feced55 | ||
|
|
61ee446542 | ||
|
|
18f6685e18 | ||
|
|
e2052d4a2c | ||
|
|
50282664e0 | ||
|
|
ce8667f937 | ||
|
|
bef44b2293 | ||
|
|
b78c6c6ba9 | ||
|
|
0d512c9e38 | ||
|
|
c8c755057c | ||
|
|
a6ec20e279 | ||
|
|
b69aceaff3 | ||
|
|
21afdb62bc | ||
|
|
d7d43af40a | ||
|
|
132955617d | ||
|
|
e7521972e4 | ||
|
|
f2753fc69a | ||
|
|
09ad905280 | ||
|
|
7714c71cd2 | ||
|
|
64832718be | ||
|
|
68871358c2 | ||
|
|
498b3cad6a | ||
|
|
157da1759d | ||
|
|
be0a73eb19 | ||
|
|
9be6e1069a | ||
|
|
817e88cfc4 | ||
|
|
15f3f8518b | ||
|
|
bbc49e9aab | ||
|
|
4139e079f4 | ||
|
|
f9a9e91d56 | ||
|
|
96e9c76709 | ||
|
|
06b7962ef9 | ||
|
|
efc0a9fbbc | ||
|
|
6faa19a250 | ||
|
|
46cec82a51 | ||
|
|
f7db8d17c5 | ||
|
|
a354965f8e | ||
|
|
934276d2f7 | ||
|
|
803809480b | ||
|
|
5478ba3237 | ||
|
|
49f1aa9c28 | ||
|
|
d5d516f0ef | ||
|
|
4471fed4b8 | ||
|
|
30d143e1a5 | ||
|
|
75ea45f21e | ||
|
|
66af337d6c | ||
|
|
ae3c65c1d3 | ||
|
|
11e4f053f1 | ||
|
|
96f37b3b0d | ||
|
|
d7d0a27976 | ||
|
|
34151f5cb2 | ||
|
|
369a21b904 | ||
|
|
90169981c1 | ||
|
|
d297abfd19 | ||
|
|
9c515a252a | ||
|
|
ea5e7a5ce2 | ||
|
|
2e9a0a4677 | ||
|
|
4c7aa8a89a | ||
|
|
d8a0c5e715 | ||
|
|
133ffaac17 | ||
|
|
729a0fcf0c | ||
|
|
6ae77e0408 | ||
|
|
bab4b1d67a | ||
|
|
12c17ec26e | ||
|
|
6577fe12eb | ||
|
|
f1fee5fad9 | ||
|
|
9b3377aedb | ||
|
|
526327727d | ||
|
|
aaa86314e3 | ||
|
|
6a14104e6b | ||
|
|
ab0c4b708b | ||
|
|
c0b7f2decd | ||
|
|
b6c9530c61 | ||
|
|
8698821c52 | ||
|
|
3f53f88390 | ||
|
|
e840516ba4 | ||
|
|
146d8c5296 | ||
|
|
6573c9e02e | ||
|
|
c7b9c6a825 | ||
|
|
48c43d3303 | ||
|
|
55be5e8188 | ||
|
|
1b9f3c666d | ||
|
|
097114d38c | ||
|
|
5bec899479 | ||
|
|
5e86112ebf | ||
|
|
24ecb13b79 | ||
|
|
2573d34713 | ||
|
|
106d71e1db | ||
|
|
3c2a4a64ac | ||
|
|
1e00a66a65 | ||
|
|
46da42deef | ||
|
|
101c1bc3cc | ||
|
|
a62112ae26 | ||
|
|
dd5a7cab75 | ||
|
|
39de3ac58e | ||
|
|
b99969278c | ||
|
|
b957ad2f71 | ||
|
|
e1a7c3a103 | ||
|
|
e63c15997c | ||
|
|
c5a495f409 | ||
|
|
5b240cb0ea | ||
|
|
147b8f47c0 | ||
|
|
6d2489af5f | ||
|
|
807dcdd226 | ||
|
|
8a29bc5597 | ||
|
|
6f6c70ee57 | ||
|
|
478a85951c | ||
|
|
0f2555c90c | ||
|
|
d2dda6ee03 | ||
|
|
208540b307 | ||
|
|
cb7c56a1d9 | ||
|
|
ea2e9c3e3a | ||
|
|
0452a68180 | ||
|
|
90a0f3db9b | ||
|
|
055d120ba8 | ||
|
|
a948f09f60 |
132
.github/workflows/multi-platform-build.yml
vendored
Normal file
132
.github/workflows/multi-platform-build.yml
vendored
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
name: Multi-Platform Conda Build
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ main, dev ]
|
||||||
|
tags: [ 'v*' ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ main, dev ]
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
platforms:
|
||||||
|
description: '选择构建平台 (逗号分隔): linux-64, osx-64, osx-arm64, win-64'
|
||||||
|
required: false
|
||||||
|
default: 'osx-arm64'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- os: ubuntu-latest
|
||||||
|
platform: linux-64
|
||||||
|
env_file: unilabos-linux-64.yaml
|
||||||
|
- os: macos-13 # Intel
|
||||||
|
platform: osx-64
|
||||||
|
env_file: unilabos-osx-64.yaml
|
||||||
|
- os: macos-latest # ARM64
|
||||||
|
platform: osx-arm64
|
||||||
|
env_file: unilabos-osx-arm64.yaml
|
||||||
|
- os: windows-latest
|
||||||
|
platform: win-64
|
||||||
|
env_file: unilabos-win64.yaml
|
||||||
|
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
shell: bash -l {0}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Check if platform should be built
|
||||||
|
id: should_build
|
||||||
|
run: |
|
||||||
|
if [[ "${{ github.event_name }}" != "workflow_dispatch" ]]; then
|
||||||
|
echo "should_build=true" >> $GITHUB_OUTPUT
|
||||||
|
elif [[ -z "${{ github.event.inputs.platforms }}" ]]; then
|
||||||
|
echo "should_build=true" >> $GITHUB_OUTPUT
|
||||||
|
elif [[ "${{ github.event.inputs.platforms }}" == *"${{ matrix.platform }}"* ]]; then
|
||||||
|
echo "should_build=true" >> $GITHUB_OUTPUT
|
||||||
|
else
|
||||||
|
echo "should_build=false" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Setup Miniconda
|
||||||
|
if: steps.should_build.outputs.should_build == 'true'
|
||||||
|
uses: conda-incubator/setup-miniconda@v3
|
||||||
|
with:
|
||||||
|
miniconda-version: "latest"
|
||||||
|
channels: conda-forge,robostack-staging,defaults
|
||||||
|
channel-priority: strict
|
||||||
|
activate-environment: build-env
|
||||||
|
auto-activate-base: false
|
||||||
|
auto-update-conda: false
|
||||||
|
show-channel-urls: true
|
||||||
|
|
||||||
|
- name: Install boa and build tools
|
||||||
|
if: steps.should_build.outputs.should_build == 'true'
|
||||||
|
run: |
|
||||||
|
conda install -c conda-forge boa conda-build
|
||||||
|
|
||||||
|
- name: Show environment info
|
||||||
|
if: steps.should_build.outputs.should_build == 'true'
|
||||||
|
run: |
|
||||||
|
conda info
|
||||||
|
conda list | grep -E "(boa|conda-build)"
|
||||||
|
echo "Platform: ${{ matrix.platform }}"
|
||||||
|
echo "OS: ${{ matrix.os }}"
|
||||||
|
|
||||||
|
- name: Build conda package
|
||||||
|
if: steps.should_build.outputs.should_build == 'true'
|
||||||
|
run: |
|
||||||
|
if [[ "${{ matrix.platform }}" == "osx-arm64" ]]; then
|
||||||
|
boa build -m ./recipes/conda_build_config.yaml -m ./recipes/macos_sdk_config.yaml ./recipes/ros-humble-unilabos-msgs
|
||||||
|
else
|
||||||
|
boa build -m ./recipes/conda_build_config.yaml ./recipes/ros-humble-unilabos-msgs
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: List built packages
|
||||||
|
if: steps.should_build.outputs.should_build == 'true'
|
||||||
|
run: |
|
||||||
|
echo "Built packages in conda-bld:"
|
||||||
|
find $CONDA_PREFIX/conda-bld -name "*.tar.bz2" | head -10
|
||||||
|
ls -la $CONDA_PREFIX/conda-bld/${{ matrix.platform }}/ || echo "${{ matrix.platform }} directory not found"
|
||||||
|
ls -la $CONDA_PREFIX/conda-bld/noarch/ || echo "noarch directory not found"
|
||||||
|
echo "CONDA_PREFIX: $CONDA_PREFIX"
|
||||||
|
echo "Full path would be: $CONDA_PREFIX/conda-bld/**/*.tar.bz2"
|
||||||
|
|
||||||
|
- name: Prepare artifacts for upload
|
||||||
|
if: steps.should_build.outputs.should_build == 'true'
|
||||||
|
run: |
|
||||||
|
mkdir -p ${{ runner.temp }}/conda-packages
|
||||||
|
find $CONDA_PREFIX/conda-bld -name "*.tar.bz2" -exec cp {} ${{ runner.temp }}/conda-packages/ \;
|
||||||
|
echo "Copied files to temp directory:"
|
||||||
|
ls -la ${{ runner.temp }}/conda-packages/
|
||||||
|
|
||||||
|
- name: Upload conda package artifacts
|
||||||
|
if: steps.should_build.outputs.should_build == 'true'
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: conda-package-${{ matrix.platform }}
|
||||||
|
path: ${{ runner.temp }}/conda-packages
|
||||||
|
if-no-files-found: warn
|
||||||
|
retention-days: 30
|
||||||
|
|
||||||
|
- name: Create release assets (on tags)
|
||||||
|
if: steps.should_build.outputs.should_build == 'true' && startsWith(github.ref, 'refs/tags/')
|
||||||
|
run: |
|
||||||
|
mkdir -p release-assets
|
||||||
|
find $CONDA_PREFIX/conda-bld -name "*.tar.bz2" -exec cp {} release-assets/ \;
|
||||||
|
|
||||||
|
- name: Upload to release
|
||||||
|
if: steps.should_build.outputs.should_build == 'true' && startsWith(github.ref, 'refs/tags/')
|
||||||
|
uses: softprops/action-gh-release@v1
|
||||||
|
with:
|
||||||
|
files: release-assets/*
|
||||||
|
draft: false
|
||||||
|
prerelease: false
|
||||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -1,3 +1,4 @@
|
|||||||
|
configs/
|
||||||
## Python
|
## Python
|
||||||
|
|
||||||
# Byte-compiled / optimized / DLL files
|
# Byte-compiled / optimized / DLL files
|
||||||
@@ -234,3 +235,7 @@ CATKIN_IGNORE
|
|||||||
|
|
||||||
*.graphml
|
*.graphml
|
||||||
unilabos/device_mesh/view_robot.rviz
|
unilabos/device_mesh/view_robot.rviz
|
||||||
|
|
||||||
|
|
||||||
|
# Certs
|
||||||
|
**/.certs
|
||||||
@@ -49,7 +49,7 @@ conda env update --file unilabos-[YOUR_OS].yml -n environment_name
|
|||||||
|
|
||||||
# Currently, you need to install the `unilabos_msgs` package
|
# Currently, you need to install the `unilabos_msgs` package
|
||||||
# You can download the system-specific package from the Release page
|
# You can download the system-specific package from the Release page
|
||||||
conda install ros-humble-unilabos-msgs-0.9.5-xxxxx.tar.bz2
|
conda install ros-humble-unilabos-msgs-0.9.8-xxxxx.tar.bz2
|
||||||
|
|
||||||
# Install PyLabRobot and other prerequisites
|
# Install PyLabRobot and other prerequisites
|
||||||
git clone https://github.com/PyLabRobot/pylabrobot plr_repo
|
git clone https://github.com/PyLabRobot/pylabrobot plr_repo
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ conda env update --file unilabos-[YOUR_OS].yml -n 环境名
|
|||||||
|
|
||||||
# 现阶段,需要安装 `unilabos_msgs` 包
|
# 现阶段,需要安装 `unilabos_msgs` 包
|
||||||
# 可以前往 Release 页面下载系统对应的包进行安装
|
# 可以前往 Release 页面下载系统对应的包进行安装
|
||||||
conda install ros-humble-unilabos-msgs-0.9.5-xxxxx.tar.bz2
|
conda install ros-humble-unilabos-msgs-0.9.8-xxxxx.tar.bz2
|
||||||
|
|
||||||
# 安装PyLabRobot等前置
|
# 安装PyLabRobot等前置
|
||||||
git clone https://github.com/PyLabRobot/pylabrobot plr_repo
|
git clone https://github.com/PyLabRobot/pylabrobot plr_repo
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
channel_sources:
|
||||||
|
- robostack,robostack-staging,conda-forge,defaults
|
||||||
|
|
||||||
gazebo:
|
gazebo:
|
||||||
- '11'
|
- '11'
|
||||||
libpqxx:
|
libpqxx:
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package:
|
package:
|
||||||
name: ros-humble-unilabos-msgs
|
name: ros-humble-unilabos-msgs
|
||||||
version: 0.9.5
|
version: 0.9.8
|
||||||
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.5"
|
version: "0.9.8"
|
||||||
|
|
||||||
source:
|
source:
|
||||||
path: ../..
|
path: ../..
|
||||||
|
|||||||
3
setup.py
3
setup.py
@@ -4,7 +4,7 @@ package_name = 'unilabos'
|
|||||||
|
|
||||||
setup(
|
setup(
|
||||||
name=package_name,
|
name=package_name,
|
||||||
version='0.9.5',
|
version='0.9.8',
|
||||||
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"
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -2,4 +2,10 @@
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
ros2 action send_goal /devices/host_node/create_resource_detailed unilabos_msgs/action/_resource_create_from_outer/ResourceCreateFromOuter "{ resources: [ { 'category': '', 'children': [], 'config': { 'type': 'Well', 'size_x': 6.86, 'size_y': 6.86, 'size_z': 10.67, 'rotation': { 'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation' }, 'category': 'well', 'model': null, 'max_volume': 360, 'material_z_thickness': 0.5, 'compute_volume_from_height': null, 'compute_height_from_volume': null, 'bottom_type': 'flat', 'cross_section_type': 'circle' }, 'data': { 'liquids': [], 'pending_liquids': [], 'liquid_history': [] }, 'id': 'plate_well_11_7', 'name': 'plate_well_11_7', 'pose': { 'orientation': { 'w': 1.0, 'x': 0.0, 'y': 0.0, 'z': 0.0 }, 'position': { 'x': 0.0, 'y': 0.0, 'z': 0.0 } }, 'sample_id': '', 'parent': 'plate', 'type': 'device' } ], device_ids: [ 'PLR_STATION' ], bind_parent_ids: [ 'plate' ], bind_locations: [ { 'x': 0.0, 'y': 0.0, 'z': 0.0 } ], other_calling_params: [ '{}' ] }"
|
ros2 action send_goal /devices/host_node/create_resource_detailed unilabos_msgs/action/_resource_create_from_outer/ResourceCreateFromOuter "{ resources: [ { 'category': '', 'children': [], 'config': { 'type': 'Well', 'size_x': 6.86, 'size_y': 6.86, 'size_z': 10.67, 'rotation': { 'x': 0, 'y': 0, 'z': 0, 'type': 'Rotation' }, 'category': 'well', 'model': null, 'max_volume': 360, 'material_z_thickness': 0.5, 'compute_volume_from_height': null, 'compute_height_from_volume': null, 'bottom_type': 'flat', 'cross_section_type': 'circle' }, 'data': { 'liquids': [], 'pending_liquids': [], 'liquid_history': [] }, 'id': 'plate_well_11_7', 'name': 'plate_well_11_7', 'pose': { 'orientation': { 'w': 1.0, 'x': 0.0, 'y': 0.0, 'z': 0.0 }, 'position': { 'x': 0.0, 'y': 0.0, 'z': 0.0 } }, 'sample_id': '', 'parent': 'plate', 'type': 'device' } ], device_ids: [ 'PLR_STATION' ], bind_parent_ids: [ 'plate' ], bind_locations: [ { 'x': 0.0, 'y': 0.0, 'z': 0.0 } ], other_calling_params: [ '{}' ] }"
|
||||||
|
```
|
||||||
|
|
||||||
|
使用mock_all.json启动,重新捕获MockContainerForChiller1
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ros2 action send_goal /devices/host_node/create_resource unilabos_msgs/action/_resource_create_from_outer_easy/ResourceCreateFromOuterEasy "{ 'res_id': 'MockContainerForChiller1', 'device_id': 'MockChiller1', 'class_name': 'container', 'parent': 'MockChiller1', 'bind_locations': { 'x': 0.0, 'y': 0.0, 'z': 0.0 }, 'liquid_input_slot': [ -1 ], 'liquid_type': [ 'CuCl2' ], 'liquid_volume': [ 100.0 ], 'slot_on_deck': '' }"
|
||||||
```
|
```
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
45
test/experiments/camera.json
Normal file
45
test/experiments/camera.json
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
{
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"id": "Camera",
|
||||||
|
"name": "摄像头",
|
||||||
|
"children": [
|
||||||
|
],
|
||||||
|
"parent": null,
|
||||||
|
"type": "device",
|
||||||
|
"class": "camera",
|
||||||
|
"position": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"camera_index": 0,
|
||||||
|
"period": 0.05
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "Gripper1",
|
||||||
|
"name": "假夹爪",
|
||||||
|
"children": [
|
||||||
|
],
|
||||||
|
"parent": null,
|
||||||
|
"type": "device",
|
||||||
|
"class": "gripper.mock",
|
||||||
|
"position": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"links": [
|
||||||
|
|
||||||
|
]
|
||||||
|
}
|
||||||
222
test/experiments/comprehensive_protocol/checklist.md
Normal file
222
test/experiments/comprehensive_protocol/checklist.md
Normal file
@@ -0,0 +1,222 @@
|
|||||||
|
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, (√)
|
||||||
|
HeatChillToTempProtocol:
|
||||||
|
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, (√)<RunColumn Rf="?" column="column" from_vessel="rotavap" ratio="5:95" solvent1="methanol" solvent2="chloroform" to_vessel="rotavap"/>
|
||||||
|
|
||||||
|
上下文体积搜索
|
||||||
|
3. 还没创建的protocol
|
||||||
|
ResetHandling 写完了 <ResetHandling solvent="methanol"/>
|
||||||
|
Dry 写完了 <Dry compound="product" vessel="filter"/>
|
||||||
|
AdjustPH 写完了 <AdjustPH pH="8.0" reagent="hydrochloric acid" vessel="main_reactor"/>
|
||||||
|
Recrystallize 写完了 <Recrystallize ratio="?" solvent1="dichloromethane" solvent2="methanol" vessel="filter" volume="?"/>
|
||||||
|
TakeSample <TakeSample id="a" vessel="rotavap"/>
|
||||||
|
Hydrogenate <Hydrogenate temp="45 °C" time="?" vessel="main_reactor"/>
|
||||||
|
4. 参数对齐
|
||||||
|
|
||||||
|
class PumpTransferProtocol(BaseModel):
|
||||||
|
from_vessel: str
|
||||||
|
to_vessel: str
|
||||||
|
volume: float
|
||||||
|
amount: str = ""
|
||||||
|
time: float = 0
|
||||||
|
viscous: bool = False
|
||||||
|
rinsing_solvent: str = "air" <Transfer from_vessel="main_reactor" to_vessel="rotavap"/>
|
||||||
|
rinsing_volume: float = 5000
|
||||||
|
rinsing_repeats: int = 2
|
||||||
|
solid: bool = False
|
||||||
|
flowrate: float = 500
|
||||||
|
transfer_flowrate: float = 2500
|
||||||
|
|
||||||
|
class SeparateProtocol(BaseModel):
|
||||||
|
purpose: str
|
||||||
|
product_phase: str
|
||||||
|
from_vessel: str
|
||||||
|
separation_vessel: str
|
||||||
|
to_vessel: str
|
||||||
|
waste_phase_to_vessel: str
|
||||||
|
solvent: str
|
||||||
|
solvent_volume: float <Separate product_phase="bottom" purpose="wash" solvent="water" vessel="separator" volume="?"/>
|
||||||
|
through: str
|
||||||
|
repeats: int
|
||||||
|
stir_time: float
|
||||||
|
stir_speed: float
|
||||||
|
settling_time: float
|
||||||
|
|
||||||
|
|
||||||
|
class EvaporateProtocol(BaseModel):
|
||||||
|
vessel: str
|
||||||
|
pressure: float
|
||||||
|
temp: float <Evaporate solvent="ethanol" vessel="rotavap"/>
|
||||||
|
time: float
|
||||||
|
stir_speed: float
|
||||||
|
|
||||||
|
|
||||||
|
class EvacuateAndRefillProtocol(BaseModel):
|
||||||
|
vessel: str
|
||||||
|
gas: str <EvacuateAndRefill gas="nitrogen" vessel="main_reactor"/>
|
||||||
|
repeats: int
|
||||||
|
|
||||||
|
class AddProtocol(BaseModel):
|
||||||
|
vessel: str
|
||||||
|
reagent: str
|
||||||
|
volume: float
|
||||||
|
mass: float
|
||||||
|
amount: str
|
||||||
|
time: float
|
||||||
|
stir: bool
|
||||||
|
stir_speed: float <Add reagent="ethanol" vessel="main_reactor" volume="2.7 mL"/>
|
||||||
|
viscous: bool
|
||||||
|
purpose: str
|
||||||
|
|
||||||
|
class CentrifugeProtocol(BaseModel):
|
||||||
|
vessel: str
|
||||||
|
speed: float
|
||||||
|
time: float 自创的
|
||||||
|
temp: float
|
||||||
|
|
||||||
|
class FilterProtocol(BaseModel):
|
||||||
|
vessel: str
|
||||||
|
filtrate_vessel: str
|
||||||
|
stir: bool <Filter vessel="filter"/>
|
||||||
|
stir_speed: float
|
||||||
|
temp: float
|
||||||
|
continue_heatchill: bool
|
||||||
|
volume: float
|
||||||
|
|
||||||
|
class HeatChillProtocol(BaseModel):
|
||||||
|
vessel: str
|
||||||
|
temp: float
|
||||||
|
time: float <HeatChill pressure="1 mbar" temp_spec="room temperature" time="?" vessel="main_reactor"/>
|
||||||
|
<HeatChill temp_spec="room temperature" time_spec="overnight" vessel="main_reactor"/>
|
||||||
|
stir: bool
|
||||||
|
stir_speed: float
|
||||||
|
purpose: str
|
||||||
|
|
||||||
|
class HeatChillStartProtocol(BaseModel):
|
||||||
|
vessel: str
|
||||||
|
temp: float 疑似没有
|
||||||
|
purpose: str
|
||||||
|
|
||||||
|
class HeatChillStopProtocol(BaseModel):
|
||||||
|
vessel: str 疑似没有
|
||||||
|
|
||||||
|
class StirProtocol(BaseModel):
|
||||||
|
stir_time: float
|
||||||
|
stir_speed: float <Stir time="0.5 h" vessel="main_reactor"/>
|
||||||
|
settling_time: float
|
||||||
|
|
||||||
|
class StartStirProtocol(BaseModel):
|
||||||
|
vessel: str
|
||||||
|
stir_speed: float 疑似没有
|
||||||
|
purpose: str
|
||||||
|
|
||||||
|
class StopStirProtocol(BaseModel):
|
||||||
|
vessel: str 疑似没有
|
||||||
|
|
||||||
|
class TransferProtocol(BaseModel):
|
||||||
|
from_vessel: str
|
||||||
|
to_vessel: str
|
||||||
|
volume: float
|
||||||
|
amount: str = ""
|
||||||
|
time: float = 0
|
||||||
|
viscous: bool = False <Transfer from_vessel="main_reactor" to_vessel="rotavap"/>
|
||||||
|
rinsing_solvent: str = ""
|
||||||
|
rinsing_volume: float = 0.0
|
||||||
|
rinsing_repeats: int = 0
|
||||||
|
solid: bool = False
|
||||||
|
|
||||||
|
class CleanVesselProtocol(BaseModel):
|
||||||
|
vessel: str
|
||||||
|
solvent: str
|
||||||
|
volume: float
|
||||||
|
temp: float
|
||||||
|
repeats: int = 1 <CleanVessel vessel="centrifuge"/>
|
||||||
|
|
||||||
|
class DissolveProtocol(BaseModel):
|
||||||
|
vessel: str
|
||||||
|
solvent: str
|
||||||
|
volume: float <Dissolve mass="2.9 g" mol="0.12 mol" reagent="magnesium" vessel="main_reactor"/>
|
||||||
|
amount: str = ""
|
||||||
|
temp: float = 25.0
|
||||||
|
time: float = 0.0
|
||||||
|
stir_speed: float = 0.0
|
||||||
|
|
||||||
|
class FilterThroughProtocol(BaseModel):
|
||||||
|
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
|
||||||
|
|
||||||
|
class RunColumnProtocol(BaseModel):
|
||||||
|
from_vessel: str
|
||||||
|
to_vessel: str <RunColumn Rf="?" column="column" from_vessel="rotavap" pct1="40 %" pct2="50 %" solvent1="ethyl acetate" solvent2="hexane" to_vessel="rotavap"/>
|
||||||
|
column: str
|
||||||
|
|
||||||
|
class WashSolidProtocol(BaseModel):
|
||||||
|
vessel: str
|
||||||
|
solvent: str
|
||||||
|
volume: float
|
||||||
|
filtrate_vessel: str = ""
|
||||||
|
temp: float = 25.0 <WashSolid filtrate_vessel="rotavap" solvent="formic acid" vessel="main_reactor" volume="?"/>
|
||||||
|
stir: bool = False
|
||||||
|
stir_speed: float = 0.0
|
||||||
|
time: float = 0.0
|
||||||
|
repeats: int = 1
|
||||||
|
|
||||||
|
class AdjustPHProtocol(BaseModel):
|
||||||
|
vessel: str = Field(..., description="目标容器")
|
||||||
|
ph_value: float = Field(..., description="目标pH值") # 改为 ph_value
|
||||||
|
reagent: str = Field(..., description="酸碱试剂名称")
|
||||||
|
# 移除其他可选参数,使用默认值 <新写的,没问题>
|
||||||
|
|
||||||
|
class ResetHandlingProtocol(BaseModel):
|
||||||
|
solvent: str = Field(..., description="溶剂名称") <新写的,没问题>
|
||||||
|
|
||||||
|
class DryProtocol(BaseModel):
|
||||||
|
compound: str = Field(..., description="化合物名称") <新写的,没问题>
|
||||||
|
vessel: str = Field(..., description="目标容器")
|
||||||
|
|
||||||
|
class RecrystallizeProtocol(BaseModel):
|
||||||
|
ratio: str = Field(..., description="溶剂比例(如 '1:1', '3:7')")
|
||||||
|
solvent1: str = Field(..., description="第一种溶剂名称") <新写的,没问题>
|
||||||
|
solvent2: str = Field(..., description="第二种溶剂名称")
|
||||||
|
vessel: str = Field(..., description="目标容器")
|
||||||
|
volume: float = Field(..., description="总体积 (mL)")
|
||||||
|
|
||||||
|
class HydrogenateProtocol(BaseModel):
|
||||||
|
temp: str = Field(..., description="反应温度(如 '45 °C')")
|
||||||
|
time: str = Field(..., description="反应时间(如 '2 h')") <新写的,没问题>
|
||||||
|
vessel: str = Field(..., description="反应容器")
|
||||||
@@ -0,0 +1,969 @@
|
|||||||
|
{
|
||||||
|
"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",
|
||||||
|
"solenoid_valve_3",
|
||||||
|
"vacuum_pump_1",
|
||||||
|
"gas_source_1",
|
||||||
|
"h2_gas_source",
|
||||||
|
"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",
|
||||||
|
"AdjustPHProtocol",
|
||||||
|
"ResetHandlingProtocol",
|
||||||
|
"DryProtocol",
|
||||||
|
"HydrogenateProtocol",
|
||||||
|
"RecrystallizeProtocol"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"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": "solenoid_valve_3",
|
||||||
|
"name": "氢气电磁阀",
|
||||||
|
"children": [],
|
||||||
|
"parent": "OrganicSynthesisStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_solenoid_valve",
|
||||||
|
"position": {
|
||||||
|
"x": 450,
|
||||||
|
"y": 400,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"voltage": 12.0,
|
||||||
|
"response_time": 0.1,
|
||||||
|
"gas_compatible": true
|
||||||
|
},
|
||||||
|
"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": "h2_gas_source",
|
||||||
|
"name": "氢气气源",
|
||||||
|
"children": [],
|
||||||
|
"parent": "OrganicSynthesisStation",
|
||||||
|
"type": "device",
|
||||||
|
"class": "virtual_gas_source",
|
||||||
|
"position": {
|
||||||
|
"x": 500,
|
||||||
|
"y": 350,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_pressure": 10.0,
|
||||||
|
"gas_type": "hydrogen"
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"gas_type": "hydrogen",
|
||||||
|
"max_pressure": 10.0,
|
||||||
|
"current_pressure": 0.0,
|
||||||
|
"status": "OFF"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"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": "samplein"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"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": "filterin"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"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": "separatorin"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_separator_collection3",
|
||||||
|
"source": "separator_1",
|
||||||
|
"target": "collection_bottle_3",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"separator_1": "bottomphaseout",
|
||||||
|
"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": "topphaseout",
|
||||||
|
"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": "filtrateout",
|
||||||
|
"collection_bottle_1": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_filter_retentate_to_waste1",
|
||||||
|
"source": "filter_1",
|
||||||
|
"target": "waste_bottle_1",
|
||||||
|
"type": "transport",
|
||||||
|
"port": {
|
||||||
|
"filter_1": "retentateout",
|
||||||
|
"waste_bottle_1": "top"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_h2_gas_to_valve3",
|
||||||
|
"source": "h2_gas_source",
|
||||||
|
"target": "solenoid_valve_3",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"h2_gas_source": "gassource",
|
||||||
|
"solenoid_valve_3": "in"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "link_valve3_to_reactor",
|
||||||
|
"source": "solenoid_valve_3",
|
||||||
|
"target": "main_reactor",
|
||||||
|
"type": "fluid",
|
||||||
|
"port": {
|
||||||
|
"solenoid_valve_3": "out",
|
||||||
|
"main_reactor": "top"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -3,7 +3,9 @@
|
|||||||
{
|
{
|
||||||
"id": "MockChiller1",
|
"id": "MockChiller1",
|
||||||
"name": "模拟冷却器",
|
"name": "模拟冷却器",
|
||||||
"children": [],
|
"children": [
|
||||||
|
"MockContainerForChiller1"
|
||||||
|
],
|
||||||
"parent": null,
|
"parent": null,
|
||||||
"type": "device",
|
"type": "device",
|
||||||
"class": "mock_chiller",
|
"class": "mock_chiller",
|
||||||
@@ -25,6 +27,22 @@
|
|||||||
"purpose": ""
|
"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",
|
"id": "MockFilter1",
|
||||||
"name": "模拟过滤器",
|
"name": "模拟过滤器",
|
||||||
|
|||||||
@@ -4,58 +4,83 @@
|
|||||||
"id": "AddTestStation",
|
"id": "AddTestStation",
|
||||||
"name": "添加试剂测试工作站",
|
"name": "添加试剂测试工作站",
|
||||||
"children": [
|
"children": [
|
||||||
"pump_add",
|
"transfer_pump",
|
||||||
"flask_1",
|
"multiway_valve",
|
||||||
"flask_2",
|
|
||||||
"flask_3",
|
|
||||||
"flask_4",
|
|
||||||
"reactor",
|
|
||||||
"stirrer",
|
"stirrer",
|
||||||
"flask_air"
|
"flask_reagent1",
|
||||||
|
"flask_reagent2",
|
||||||
|
"flask_reagent3",
|
||||||
|
"flask_reagent4",
|
||||||
|
"reactor",
|
||||||
|
"flask_waste",
|
||||||
|
"flask_rinsing",
|
||||||
|
"flask_buffer"
|
||||||
],
|
],
|
||||||
"parent": null,
|
"parent": null,
|
||||||
"type": "device",
|
"type": "device",
|
||||||
"class": "workstation",
|
"class": "workstation",
|
||||||
"position": {
|
"position": {
|
||||||
"x": 620.6111111111111,
|
"x": 620,
|
||||||
"y": 171,
|
"y": 171,
|
||||||
"z": 0
|
"z": 0
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"protocol_type": ["AddProtocol", "PumpTransferProtocol", "CleanProtocol"]
|
"protocol_type": ["AddProtocol", "TransferProtocol", "StartStirProtocol", "StopStirProtocol"]
|
||||||
},
|
},
|
||||||
"data": {}
|
"data": {}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "pump_add",
|
"id": "transfer_pump",
|
||||||
"name": "pump_add",
|
"name": "注射器泵",
|
||||||
"children": [],
|
"children": [],
|
||||||
"parent": "AddTestStation",
|
"parent": "AddTestStation",
|
||||||
"type": "device",
|
"type": "device",
|
||||||
"class": "virtual_pump",
|
"class": "virtual_transfer_pump",
|
||||||
"position": {
|
"position": {
|
||||||
"x": 520.6111111111111,
|
"x": 520,
|
||||||
"y": 300,
|
"y": 300,
|
||||||
"z": 0
|
"z": 0
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"port": "VIRTUAL",
|
"port": "VIRTUAL",
|
||||||
"max_volume": 25.0
|
"max_volume": 50.0,
|
||||||
|
"transfer_rate": 5.0
|
||||||
},
|
},
|
||||||
"data": {
|
"data": {
|
||||||
"status": "Idle"
|
"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",
|
"id": "stirrer",
|
||||||
"name": "stirrer",
|
"name": "搅拌器",
|
||||||
"children": [],
|
"children": [],
|
||||||
"parent": "AddTestStation",
|
"parent": "AddTestStation",
|
||||||
"type": "device",
|
"type": "device",
|
||||||
"class": "virtual_stirrer",
|
"class": "virtual_stirrer",
|
||||||
"position": {
|
"position": {
|
||||||
"x": 698.1111111111111,
|
"x": 720,
|
||||||
"y": 478,
|
"y": 450,
|
||||||
"z": 0
|
"z": 0
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
@@ -68,110 +93,115 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "flask_1",
|
"id": "flask_reagent1",
|
||||||
"name": "通用试剂瓶1",
|
"name": "试剂瓶1 (甲醇)",
|
||||||
"children": [],
|
"children": [],
|
||||||
"parent": "AddTestStation",
|
"parent": "AddTestStation",
|
||||||
"type": "container",
|
"type": "container",
|
||||||
"class": null,
|
"class": null,
|
||||||
"position": {
|
"position": {
|
||||||
"x": 100,
|
"x": 100,
|
||||||
"y": 428,
|
"y": 400,
|
||||||
"z": 0
|
"z": 0
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"max_volume": 2000.0
|
"max_volume": 1000.0
|
||||||
},
|
},
|
||||||
"data": {
|
"data": {
|
||||||
"liquid": []
|
"liquid": [
|
||||||
|
{
|
||||||
|
"name": "甲醇",
|
||||||
|
"volume": 800.0,
|
||||||
|
"concentration": "99.9%"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "flask_2",
|
"id": "flask_reagent2",
|
||||||
"name": "通用试剂瓶2",
|
"name": "试剂瓶2 (乙醇)",
|
||||||
"children": [],
|
"children": [],
|
||||||
"parent": "AddTestStation",
|
"parent": "AddTestStation",
|
||||||
"type": "container",
|
"type": "container",
|
||||||
"class": null,
|
"class": null,
|
||||||
"position": {
|
"position": {
|
||||||
"x": 250,
|
"x": 180,
|
||||||
"y": 428,
|
"y": 400,
|
||||||
"z": 0
|
"z": 0
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"max_volume": 2000.0
|
"max_volume": 1000.0
|
||||||
},
|
},
|
||||||
"data": {
|
"data": {
|
||||||
"liquid": []
|
"liquid": [
|
||||||
|
{
|
||||||
|
"name": "乙醇",
|
||||||
|
"volume": 750.0,
|
||||||
|
"concentration": "95%"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "flask_3",
|
"id": "flask_reagent3",
|
||||||
"name": "通用试剂瓶3",
|
"name": "试剂瓶3 (丙酮)",
|
||||||
"children": [],
|
"children": [],
|
||||||
"parent": "AddTestStation",
|
"parent": "AddTestStation",
|
||||||
"type": "container",
|
"type": "container",
|
||||||
"class": null,
|
"class": null,
|
||||||
"position": {
|
"position": {
|
||||||
"x": 400,
|
"x": 260,
|
||||||
"y": 428,
|
"y": 400,
|
||||||
"z": 0
|
"z": 0
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"max_volume": 2000.0
|
"max_volume": 1000.0
|
||||||
},
|
},
|
||||||
"data": {
|
"data": {
|
||||||
"liquid": []
|
"liquid": [
|
||||||
|
{
|
||||||
|
"name": "丙酮",
|
||||||
|
"volume": 900.0,
|
||||||
|
"concentration": "99.5%"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "flask_4",
|
"id": "flask_reagent4",
|
||||||
"name": "通用试剂瓶4",
|
"name": "试剂瓶4 (二氯甲烷)",
|
||||||
"children": [],
|
"children": [],
|
||||||
"parent": "AddTestStation",
|
"parent": "AddTestStation",
|
||||||
"type": "container",
|
"type": "container",
|
||||||
"class": null,
|
"class": null,
|
||||||
"position": {
|
"position": {
|
||||||
"x": 550,
|
"x": 340,
|
||||||
"y": 428,
|
"y": 400,
|
||||||
"z": 0
|
"z": 0
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"max_volume": 2000.0
|
"max_volume": 1000.0
|
||||||
},
|
},
|
||||||
"data": {
|
"data": {
|
||||||
"liquid": []
|
"liquid": [
|
||||||
|
{
|
||||||
|
"name": "二氯甲烷",
|
||||||
|
"volume": 850.0,
|
||||||
|
"concentration": "99.8%"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "reactor",
|
"id": "reactor",
|
||||||
"name": "reactor",
|
"name": "反应器",
|
||||||
"children": [],
|
"children": [],
|
||||||
"parent": "AddTestStation",
|
"parent": "AddTestStation",
|
||||||
"type": "container",
|
"type": "container",
|
||||||
"class": null,
|
"class": null,
|
||||||
"position": {
|
"position": {
|
||||||
"x": 698.1111111111111,
|
"x": 720,
|
||||||
"y": 428,
|
"y": 400,
|
||||||
"z": 0
|
|
||||||
},
|
|
||||||
"config": {
|
|
||||||
"max_volume": 5000.0
|
|
||||||
},
|
|
||||||
"data": {
|
|
||||||
"liquid": []
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "flask_air",
|
|
||||||
"name": "flask_air",
|
|
||||||
"children": [],
|
|
||||||
"parent": "AddTestStation",
|
|
||||||
"type": "container",
|
|
||||||
"class": null,
|
|
||||||
"position": {
|
|
||||||
"x": 800,
|
|
||||||
"y": 300,
|
|
||||||
"z": 0
|
"z": 0
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
@@ -180,70 +210,166 @@
|
|||||||
"data": {
|
"data": {
|
||||||
"liquid": []
|
"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": [
|
"links": [
|
||||||
{
|
{
|
||||||
"source": "stirrer",
|
"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",
|
"target": "reactor",
|
||||||
"type": "physical",
|
"type": "physical",
|
||||||
"port": {
|
"port": {
|
||||||
"stirrer": "top",
|
"multiway_valve": "multiway-valve-port-5",
|
||||||
"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": "flask_4",
|
|
||||||
"type": "physical",
|
|
||||||
"port": {
|
|
||||||
"pump_add": "inlet",
|
|
||||||
"flask_4": "top"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"source": "pump_add",
|
|
||||||
"target": "reactor",
|
|
||||||
"type": "physical",
|
|
||||||
"port": {
|
|
||||||
"pump_add": "outlet",
|
|
||||||
"reactor": "top"
|
"reactor": "top"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"source": "pump_add",
|
"source": "multiway_valve",
|
||||||
"target": "flask_air",
|
"target": "flask_waste",
|
||||||
"type": "physical",
|
"type": "physical",
|
||||||
"port": {
|
"port": {
|
||||||
"pump_add": "inlet",
|
"multiway_valve": "multiway-valve-port-6",
|
||||||
"flask_air": "top"
|
"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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
216
test/experiments/prcxi.json
Normal file
216
test/experiments/prcxi.json
Normal file
@@ -0,0 +1,216 @@
|
|||||||
|
{
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"id": "PRCXI",
|
||||||
|
"name": "PRCXI",
|
||||||
|
"parent": null,
|
||||||
|
"type": "device",
|
||||||
|
"class": "liquid_handler.prcxi",
|
||||||
|
"position": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"deck": {
|
||||||
|
"_resource_child_name": "deck",
|
||||||
|
"_resource_type": "unilabos.devices.liquid_handling.prcxi.prcxi:PRCXI9300Deck"
|
||||||
|
},
|
||||||
|
"host": "192.168.3.9",
|
||||||
|
"port": 9999,
|
||||||
|
"timeout": 10.0,
|
||||||
|
"setup": true
|
||||||
|
},
|
||||||
|
"data": {},
|
||||||
|
"children": [
|
||||||
|
"deck"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "deck",
|
||||||
|
"name": "deck",
|
||||||
|
"sample_id": null,
|
||||||
|
"children": [
|
||||||
|
"rackT1",
|
||||||
|
"plateT2",
|
||||||
|
"plateT3",
|
||||||
|
"rackT4",
|
||||||
|
"plateT5",
|
||||||
|
"plateT6"
|
||||||
|
],
|
||||||
|
"parent": "PRCXI",
|
||||||
|
"type": "device",
|
||||||
|
"class": "",
|
||||||
|
"position": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"type": "PRCXI9300Deck"
|
||||||
|
},
|
||||||
|
"data": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "rackT1",
|
||||||
|
"name": "rackT1",
|
||||||
|
"sample_id": null,
|
||||||
|
"children": [],
|
||||||
|
"parent": "deck",
|
||||||
|
"type": "device",
|
||||||
|
"class": "",
|
||||||
|
"position": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"type": "PRCXI9300Container",
|
||||||
|
"size_x": 120.98,
|
||||||
|
"size_y": 82.12,
|
||||||
|
"size_z": 50.3
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"Material": {
|
||||||
|
"uuid": "80652665f6a54402b2408d50b40398df",
|
||||||
|
"Code": "ZX-001-1000",
|
||||||
|
"Name": "1000μL Tip头",
|
||||||
|
"SummaryName": "1000μL Tip头",
|
||||||
|
"PipetteHeight": 100,
|
||||||
|
"materialEnum": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "plateT2",
|
||||||
|
"name": "plateT2",
|
||||||
|
"sample_id": null,
|
||||||
|
"children": [],
|
||||||
|
"parent": "deck",
|
||||||
|
"type": "device",
|
||||||
|
"class": "",
|
||||||
|
"position": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"type": "PRCXI9300Container",
|
||||||
|
"size_x": 120.98,
|
||||||
|
"size_y": 82.12,
|
||||||
|
"size_z": 50.3
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"Material": {
|
||||||
|
"uuid": "57b1e4711e9e4a32b529f3132fc5931f"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "plateT3",
|
||||||
|
"name": "plateT3",
|
||||||
|
"sample_id": null,
|
||||||
|
"children": [],
|
||||||
|
"parent": "deck",
|
||||||
|
"type": "device",
|
||||||
|
"class": "",
|
||||||
|
"position": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"type": "PRCXI9300Container",
|
||||||
|
"size_x": 120.98,
|
||||||
|
"size_y": 82.12,
|
||||||
|
"size_z": 50.3
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"Material": {
|
||||||
|
"uuid": "57b1e4711e9e4a32b529f3132fc5931f"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "rackT4",
|
||||||
|
"name": "rackT4",
|
||||||
|
"sample_id": null,
|
||||||
|
"children": [],
|
||||||
|
"parent": "deck",
|
||||||
|
"type": "device",
|
||||||
|
"class": "",
|
||||||
|
"position": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"type": "PRCXI9300Container",
|
||||||
|
"size_x": 120.98,
|
||||||
|
"size_y": 82.12,
|
||||||
|
"size_z": 50.3
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"Material": {
|
||||||
|
"uuid": "80652665f6a54402b2408d50b40398df",
|
||||||
|
"Code": "ZX-001-1000",
|
||||||
|
"Name": "1000μL Tip头",
|
||||||
|
"SummaryName": "1000μL Tip头",
|
||||||
|
"PipetteHeight": 100,
|
||||||
|
"materialEnum": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "plateT5",
|
||||||
|
"name": "plateT5",
|
||||||
|
"sample_id": null,
|
||||||
|
"children": [],
|
||||||
|
"parent": "deck",
|
||||||
|
"type": "device",
|
||||||
|
"class": "",
|
||||||
|
"position": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"type": "PRCXI9300Container",
|
||||||
|
"size_x": 120.98,
|
||||||
|
"size_y": 82.12,
|
||||||
|
"size_z": 50.3
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"Material": {
|
||||||
|
"uuid": "57b1e4711e9e4a32b529f3132fc5931f"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "plateT6",
|
||||||
|
"name": "plateT6",
|
||||||
|
"sample_id": null,
|
||||||
|
"children": [],
|
||||||
|
"parent": "deck",
|
||||||
|
"type": "device",
|
||||||
|
"class": "",
|
||||||
|
"position": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"type": "PRCXI9300Container",
|
||||||
|
"size_x": 120.98,
|
||||||
|
"size_y": 82.12,
|
||||||
|
"size_z": 50.3
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"Material": {
|
||||||
|
"uuid": "57b1e4711e9e4a32b529f3132fc5931f"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"links": []
|
||||||
|
}
|
||||||
@@ -48,8 +48,9 @@ dependencies:
|
|||||||
- ros-humble-ros2-control
|
- ros-humble-ros2-control
|
||||||
- ros-humble-robot-state-publisher
|
- ros-humble-robot-state-publisher
|
||||||
- ros-humble-joint-state-publisher
|
- ros-humble-joint-state-publisher
|
||||||
# web
|
# web and visualization
|
||||||
- ros-humble-rosbridge-server
|
- ros-humble-rosbridge-server
|
||||||
|
- ros-humble-cv-bridge
|
||||||
# geometry & motion planning
|
# geometry & motion planning
|
||||||
- ros-humble-tf2
|
- ros-humble-tf2
|
||||||
- ros-humble-moveit
|
- ros-humble-moveit
|
||||||
|
|||||||
@@ -50,8 +50,9 @@ dependencies:
|
|||||||
- ros-humble-ros2-control
|
- ros-humble-ros2-control
|
||||||
- ros-humble-robot-state-publisher
|
- ros-humble-robot-state-publisher
|
||||||
- ros-humble-joint-state-publisher
|
- ros-humble-joint-state-publisher
|
||||||
# web
|
# web and visualization
|
||||||
- ros-humble-rosbridge-server
|
- ros-humble-rosbridge-server
|
||||||
|
- ros-humble-cv-bridge
|
||||||
# geometry & motion planning
|
# geometry & motion planning
|
||||||
- ros-humble-tf2
|
- ros-humble-tf2
|
||||||
- ros-humble-moveit
|
- ros-humble-moveit
|
||||||
|
|||||||
@@ -6,12 +6,12 @@ channels:
|
|||||||
dependencies:
|
dependencies:
|
||||||
# Basics
|
# Basics
|
||||||
- python=3.11.11
|
- python=3.11.11
|
||||||
- compilers
|
# - compilers
|
||||||
- cmake
|
# - cmake
|
||||||
- make
|
# - make
|
||||||
- ninja
|
# - ninja
|
||||||
- sphinx
|
# - sphinx
|
||||||
- sphinx_rtd_theme
|
# - sphinx_rtd_theme
|
||||||
# Data Visualization
|
# Data Visualization
|
||||||
- numpy
|
- numpy
|
||||||
- scipy
|
- scipy
|
||||||
@@ -23,7 +23,7 @@ dependencies:
|
|||||||
- pyserial
|
- pyserial
|
||||||
- pyusb
|
- pyusb
|
||||||
- pylibftdi
|
- pylibftdi
|
||||||
- pymodbus
|
- pymodbus==3.6.9
|
||||||
- python-can
|
- python-can
|
||||||
- pyvisa
|
- pyvisa
|
||||||
- opencv
|
- opencv
|
||||||
@@ -48,8 +48,9 @@ dependencies:
|
|||||||
- ros-humble-ros2-control
|
- ros-humble-ros2-control
|
||||||
- ros-humble-robot-state-publisher
|
- ros-humble-robot-state-publisher
|
||||||
- ros-humble-joint-state-publisher
|
- ros-humble-joint-state-publisher
|
||||||
# web
|
# web and visualization
|
||||||
- ros-humble-rosbridge-server
|
- ros-humble-rosbridge-server
|
||||||
|
- ros-humble-cv-bridge
|
||||||
# geometry & motion planning
|
# geometry & motion planning
|
||||||
- ros-humble-tf2
|
- ros-humble-tf2
|
||||||
- ros-humble-moveit
|
- ros-humble-moveit
|
||||||
@@ -61,5 +62,12 @@ dependencies:
|
|||||||
# 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
|
||||||
|
# driver
|
||||||
|
#- crcmod
|
||||||
- pip:
|
- pip:
|
||||||
- paho-mqtt
|
- paho-mqtt
|
||||||
|
# driver
|
||||||
|
#- ur-rtde # set PYTHONUTF8=1
|
||||||
|
#- pyautogui
|
||||||
|
#- pywinauto
|
||||||
|
#- pywinauto_recorder
|
||||||
@@ -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,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
|
|
||||||
import json
|
import json
|
||||||
|
import traceback
|
||||||
import uuid
|
import uuid
|
||||||
from unilabos.app.model import JobAddReq, JobData
|
from unilabos.app.model import JobAddReq, JobData
|
||||||
from unilabos.ros.nodes.presets.host_node import HostNode
|
from unilabos.ros.nodes.presets.host_node import HostNode
|
||||||
|
from unilabos.utils.type_check import serialize_result_info
|
||||||
|
|
||||||
|
|
||||||
def get_resources() -> tuple:
|
def get_resources() -> tuple:
|
||||||
@@ -25,12 +27,18 @@ def job_add(req: JobAddReq) -> JobData:
|
|||||||
if req.job_id is None:
|
if req.job_id is None:
|
||||||
req.job_id = str(uuid.uuid4())
|
req.job_id = str(uuid.uuid4())
|
||||||
action_name = req.data["action"]
|
action_name = req.data["action"]
|
||||||
action_kwargs = req.data["action_kwargs"]
|
action_type = req.data.get("action_type", "LocalUnknown")
|
||||||
req.data['action'] = action_name
|
action_args = req.data.get("action_kwargs", None) # 兼容老版本,后续删除
|
||||||
if action_name == "execute_command_from_outer":
|
if action_args is None:
|
||||||
action_kwargs = {"command": json.dumps(action_kwargs)}
|
action_args = req.data.get("action_args")
|
||||||
elif "command" in action_kwargs:
|
else:
|
||||||
action_kwargs = action_kwargs["command"]
|
if "command" in action_args:
|
||||||
|
action_args = action_args["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, server_info=req.server_info)
|
try:
|
||||||
|
HostNode.get_instance().send_goal(req.device_id, action_type=action_type, action_name=action_name, action_kwargs=action_args, goal_uuid=req.job_id, server_info=req.server_info)
|
||||||
|
except Exception as e:
|
||||||
|
for bridge in HostNode.get_instance().bridges:
|
||||||
|
if hasattr(bridge, "publish_job_status"):
|
||||||
|
bridge.publish_job_status({}, req.job_id, "failed", serialize_result_info(traceback.format_exc(), False, {}))
|
||||||
return JobData(jobId=req.job_id)
|
return JobData(jobId=req.job_id)
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ from copy import deepcopy
|
|||||||
|
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
from unilabos.resources.graphio import tree_to_list
|
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__))
|
||||||
@@ -22,6 +22,21 @@ from unilabos.config.config import load_config, BasicConfig, _update_config_from
|
|||||||
from unilabos.utils.banner_print import print_status, print_unilab_banner
|
from unilabos.utils.banner_print import print_status, print_unilab_banner
|
||||||
|
|
||||||
|
|
||||||
|
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():
|
||||||
"""解析命令行参数"""
|
"""解析命令行参数"""
|
||||||
parser = argparse.ArgumentParser(description="Start Uni-Lab Edge server.")
|
parser = argparse.ArgumentParser(description="Start Uni-Lab Edge server.")
|
||||||
@@ -58,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,
|
||||||
@@ -97,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
|
||||||
@@ -136,15 +146,16 @@ 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["resources_config"] = list(devices_and_resources.values())
|
||||||
@@ -185,6 +196,7 @@ def main():
|
|||||||
signal.signal(signal.SIGTERM, _exit)
|
signal.signal(signal.SIGTERM, _exit)
|
||||||
mqtt_client.start()
|
mqtt_client.start()
|
||||||
args_dict["resources_mesh_config"] = {}
|
args_dict["resources_mesh_config"] = {}
|
||||||
|
args_dict["resources_edge_config"] = resource_edge_info
|
||||||
# web visiualize 2D
|
# web visiualize 2D
|
||||||
if args_dict["visual"] != "disable":
|
if args_dict["visual"] != "disable":
|
||||||
enable_rviz = args_dict["visual"] == "rviz"
|
enable_rviz = args_dict["visual"] == "rviz"
|
||||||
|
|||||||
@@ -56,6 +56,10 @@ class MQTTClient:
|
|||||||
payload_json["data"] = {}
|
payload_json["data"] = {}
|
||||||
if "action" in payload_json:
|
if "action" in payload_json:
|
||||||
payload_json["data"]["action"] = payload_json.pop("action")
|
payload_json["data"]["action"] = payload_json.pop("action")
|
||||||
|
if "action_type" in payload_json:
|
||||||
|
payload_json["data"]["action_type"] = payload_json.pop("action_type")
|
||||||
|
if "action_args" in payload_json:
|
||||||
|
payload_json["data"]["action_args"] = payload_json.pop("action_args")
|
||||||
if "action_kwargs" in payload_json:
|
if "action_kwargs" in payload_json:
|
||||||
payload_json["data"]["action_kwargs"] = payload_json.pop("action_kwargs")
|
payload_json["data"]["action_kwargs"] = payload_json.pop("action_kwargs")
|
||||||
job_req = JobAddReq.model_validate(payload_json)
|
job_req = JobAddReq.model_validate(payload_json)
|
||||||
@@ -159,10 +163,10 @@ class MQTTClient:
|
|||||||
# status = device_status.get(device_id, {})
|
# status = device_status.get(device_id, {})
|
||||||
if self.mqtt_disable:
|
if self.mqtt_disable:
|
||||||
return
|
return
|
||||||
status = {"data": device_status.get(device_id, {}), "device_id": device_id}
|
status = {"data": device_status.get(device_id, {}), "device_id": device_id, "timestamp": time.time()}
|
||||||
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.debug(f"Device status published: address: {address}, {status}")
|
logger.info(f"Device status published: address: {address}, {status}")
|
||||||
|
|
||||||
def publish_job_status(self, feedback_data: dict, job_id: str, status: str, return_info: Optional[str] = None):
|
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:
|
||||||
@@ -172,13 +176,14 @@ class MQTTClient:
|
|||||||
jobdata = {"job_id": job_id, "data": feedback_data, "status": status, "return_info": 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:
|
||||||
|
|||||||
73
unilabos/app/register.py
Normal file
73
unilabos/app/register.py
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
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",
|
||||||
|
type=str,
|
||||||
|
default=None,
|
||||||
|
action="append",
|
||||||
|
help="注册表路径",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--config",
|
||||||
|
type=str,
|
||||||
|
default=None,
|
||||||
|
help="配置文件路径,支持.py格式的Python配置文件",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--complete_registry",
|
||||||
|
action="store_true",
|
||||||
|
default=False,
|
||||||
|
help="是否补全注册表",
|
||||||
|
)
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
# 构建注册表
|
||||||
|
build_registry(args.registry, args.complete_registry)
|
||||||
|
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,7 +30,27 @@ 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]], database_process_later:bool) -> requests.Response:
|
def resource_edge_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/edge/batch_create/?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 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:
|
||||||
"""
|
"""
|
||||||
添加资源
|
添加资源
|
||||||
|
|
||||||
@@ -44,8 +64,10 @@ class HTTPClient:
|
|||||||
f"{self.remote_addr}/lab/resource/?database_process_later={1 if database_process_later else 0}",
|
f"{self.remote_addr}/lab/resource/?database_process_later={1 if database_process_later else 0}",
|
||||||
json=resources,
|
json=resources,
|
||||||
headers={"Authorization": f"lab {self.auth}"},
|
headers={"Authorization": f"lab {self.auth}"},
|
||||||
timeout=5,
|
timeout=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]:
|
||||||
@@ -63,7 +85,7 @@ class HTTPClient:
|
|||||||
f"{self.remote_addr}/lab/resource/?edge_format=1",
|
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
|
||||||
|
|
||||||
@@ -99,7 +121,7 @@ class HTTPClient:
|
|||||||
f"{self.remote_addr}/lab/resource/batch_update/?edge_format=1",
|
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
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ Web页面模块
|
|||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
import traceback
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
|
|
||||||
@@ -16,9 +17,8 @@ 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, debug
|
||||||
from unilabos.utils.type_check import TypeEncoder
|
from unilabos.utils.type_check import TypeEncoder
|
||||||
from unilabos.app.web.utils.device_utils import get_registry_info
|
from unilabos.app.web.utils.device_utils import get_registry_info
|
||||||
from unilabos.app.web.utils.host_utils import get_host_node_info
|
from unilabos.app.web.utils.host_utils import get_host_node_info
|
||||||
@@ -124,6 +124,7 @@ def setup_web_pages(router: APIRouter) -> None:
|
|||||||
|
|
||||||
return html
|
return html
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
debug(traceback.format_exc())
|
||||||
error(f"生成状态页面时出错: {str(e)}")
|
error(f"生成状态页面时出错: {str(e)}")
|
||||||
raise HTTPException(status_code=500, detail=f"Error generating status page: {str(e)}")
|
raise HTTPException(status_code=500, detail=f"Error generating status page: {str(e)}")
|
||||||
|
|
||||||
|
|||||||
@@ -65,6 +65,8 @@ def get_yaml_from_goal_type(goal_type) -> str:
|
|||||||
Returns:
|
Returns:
|
||||||
str: 默认Goal参数的YAML格式字符串
|
str: 默认Goal参数的YAML格式字符串
|
||||||
"""
|
"""
|
||||||
|
if isinstance(goal_type, str):
|
||||||
|
return "{}"
|
||||||
if not goal_type:
|
if not goal_type:
|
||||||
return "{}"
|
return "{}"
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,12 @@ from .agv_transfer_protocol import generate_agv_transfer_protocol
|
|||||||
from .add_protocol import generate_add_protocol
|
from .add_protocol import generate_add_protocol
|
||||||
from .centrifuge_protocol import generate_centrifuge_protocol
|
from .centrifuge_protocol import generate_centrifuge_protocol
|
||||||
from .filter_protocol import generate_filter_protocol
|
from .filter_protocol import generate_filter_protocol
|
||||||
from .heatchill_protocol import generate_heat_chill_protocol, generate_heat_chill_start_protocol, generate_heat_chill_stop_protocol
|
from .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 .stir_protocol import generate_stir_protocol, generate_start_stir_protocol, generate_stop_stir_protocol
|
||||||
from .transfer_protocol import generate_transfer_protocol
|
from .transfer_protocol import generate_transfer_protocol
|
||||||
from .clean_vessel_protocol import generate_clean_vessel_protocol
|
from .clean_vessel_protocol import generate_clean_vessel_protocol
|
||||||
@@ -16,29 +21,39 @@ from .dissolve_protocol import generate_dissolve_protocol
|
|||||||
from .filter_through_protocol import generate_filter_through_protocol
|
from .filter_through_protocol import generate_filter_through_protocol
|
||||||
from .run_column_protocol import generate_run_column_protocol
|
from .run_column_protocol import generate_run_column_protocol
|
||||||
from .wash_solid_protocol import generate_wash_solid_protocol
|
from .wash_solid_protocol import generate_wash_solid_protocol
|
||||||
|
from .adjustph_protocol import generate_adjust_ph_protocol
|
||||||
|
from .reset_handling_protocol import generate_reset_handling_protocol
|
||||||
|
from .dry_protocol import generate_dry_protocol
|
||||||
|
from .recrystallize_protocol import generate_recrystallize_protocol
|
||||||
|
from .hydrogenate_protocol import generate_hydrogenate_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,
|
|
||||||
CleanProtocol: generate_clean_protocol,
|
|
||||||
SeparateProtocol: generate_separate_protocol,
|
|
||||||
EvaporateProtocol: generate_evaporate_protocol,
|
|
||||||
EvacuateAndRefillProtocol: generate_evacuateandrefill_protocol,
|
|
||||||
AGVTransferProtocol: generate_agv_transfer_protocol,
|
|
||||||
CentrifugeProtocol: generate_centrifuge_protocol,
|
|
||||||
AddProtocol: generate_add_protocol,
|
AddProtocol: generate_add_protocol,
|
||||||
|
AGVTransferProtocol: generate_agv_transfer_protocol,
|
||||||
|
AdjustPHProtocol: generate_adjust_ph_protocol,
|
||||||
|
CentrifugeProtocol: generate_centrifuge_protocol,
|
||||||
|
CleanProtocol: generate_clean_protocol,
|
||||||
|
CleanVesselProtocol: generate_clean_vessel_protocol,
|
||||||
|
DissolveProtocol: generate_dissolve_protocol,
|
||||||
|
DryProtocol: generate_dry_protocol,
|
||||||
|
EvacuateAndRefillProtocol: generate_evacuateandrefill_protocol,
|
||||||
|
EvaporateProtocol: generate_evaporate_protocol,
|
||||||
FilterProtocol: generate_filter_protocol,
|
FilterProtocol: generate_filter_protocol,
|
||||||
|
FilterThroughProtocol: generate_filter_through_protocol,
|
||||||
HeatChillProtocol: generate_heat_chill_protocol,
|
HeatChillProtocol: generate_heat_chill_protocol,
|
||||||
HeatChillStartProtocol: generate_heat_chill_start_protocol,
|
HeatChillStartProtocol: generate_heat_chill_start_protocol,
|
||||||
HeatChillStopProtocol: generate_heat_chill_stop_protocol,
|
HeatChillStopProtocol: generate_heat_chill_stop_protocol,
|
||||||
StirProtocol: generate_stir_protocol,
|
HydrogenateProtocol: generate_hydrogenate_protocol,
|
||||||
|
PumpTransferProtocol: generate_pump_protocol_with_rinsing,
|
||||||
|
RecrystallizeProtocol: generate_recrystallize_protocol,
|
||||||
|
ResetHandlingProtocol: generate_reset_handling_protocol,
|
||||||
|
RunColumnProtocol: generate_run_column_protocol,
|
||||||
|
SeparateProtocol: generate_separate_protocol,
|
||||||
StartStirProtocol: generate_start_stir_protocol,
|
StartStirProtocol: generate_start_stir_protocol,
|
||||||
|
StirProtocol: generate_stir_protocol,
|
||||||
StopStirProtocol: generate_stop_stir_protocol,
|
StopStirProtocol: generate_stop_stir_protocol,
|
||||||
TransferProtocol: generate_transfer_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,
|
WashSolidProtocol: generate_wash_solid_protocol,
|
||||||
}
|
}
|
||||||
@@ -1,74 +1,627 @@
|
|||||||
import networkx as nx
|
import networkx as nx
|
||||||
from typing import List, Dict, Any
|
from typing import List, Dict, Any
|
||||||
|
from .pump_protocol import generate_pump_protocol_with_rinsing
|
||||||
def generate_add_protocol(
|
|
||||||
G: nx.DiGraph,
|
|
||||||
vessel: str,
|
def find_reagent_vessel(G: nx.DiGraph, reagent: str) -> str:
|
||||||
reagent: str,
|
"""
|
||||||
volume: float,
|
根据试剂名称查找对应的试剂瓶,支持多种匹配模式:
|
||||||
mass: float,
|
1. 容器名称匹配(如 flask_DMF, reagent_bottle_1-DMF)
|
||||||
amount: str,
|
2. 容器内液体类型匹配(如 liquid_type: "DMF", name: "ethanol")
|
||||||
time: float,
|
3. 试剂名称匹配(如 reagent_name: "DMF", config.reagent: "ethyl_acetate")
|
||||||
stir: bool,
|
|
||||||
stir_speed: float,
|
Args:
|
||||||
viscous: bool,
|
G: 网络图
|
||||||
purpose: str
|
reagent: 试剂名称
|
||||||
) -> List[Dict[str, Any]]:
|
|
||||||
"""
|
Returns:
|
||||||
生成添加试剂的协议序列 - 严格按照 Add.action
|
str: 试剂瓶的vessel ID
|
||||||
"""
|
|
||||||
action_sequence = []
|
Raises:
|
||||||
|
ValueError: 如果找不到对应的试剂瓶
|
||||||
# 如果指定了体积,执行液体转移
|
"""
|
||||||
if volume > 0:
|
print(f"ADD_PROTOCOL: 正在查找试剂 '{reagent}' 的容器...")
|
||||||
# 查找可用的试剂瓶
|
|
||||||
available_flasks = [node for node in G.nodes()
|
# 第一步:通过容器名称匹配
|
||||||
if node.startswith('flask_')
|
possible_names = [
|
||||||
and G.nodes[node].get('type') == 'container']
|
f"flask_{reagent}", # flask_DMF, flask_ethanol
|
||||||
|
f"bottle_{reagent}", # bottle_DMF, bottle_ethanol
|
||||||
if not available_flasks:
|
f"vessel_{reagent}", # vessel_DMF, vessel_ethanol
|
||||||
raise ValueError("没有找到可用的试剂容器")
|
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', [])
|
||||||
|
|
||||||
reagent_vessel = available_flasks[0]
|
for liquid in liquids:
|
||||||
|
if isinstance(liquid, dict):
|
||||||
# 查找泵设备
|
# 支持两种格式的液体类型字段
|
||||||
pump_nodes = [node for node in G.nodes()
|
liquid_type = liquid.get('liquid_type') or liquid.get('name', '')
|
||||||
if G.nodes[node].get('class') == 'virtual_pump']
|
reagent_name = vessel_data.get('reagent_name', '')
|
||||||
|
config_reagent = G.nodes[node_id].get('config', {}).get('reagent', '')
|
||||||
if pump_nodes:
|
|
||||||
pump_id = pump_nodes[0]
|
# 检查多个可能的字段
|
||||||
action_sequence.append({
|
if (liquid_type.lower() == reagent.lower() or
|
||||||
"device_id": pump_id,
|
reagent_name.lower() == reagent.lower() or
|
||||||
"action_name": "transfer",
|
config_reagent.lower() == reagent.lower()):
|
||||||
"action_kwargs": {
|
print(f"ADD_PROTOCOL: 通过液体类型匹配找到容器: {node_id}")
|
||||||
"from_vessel": reagent_vessel,
|
print(f" - liquid_type: {liquid_type}")
|
||||||
"to_vessel": vessel,
|
print(f" - reagent_name: {reagent_name}")
|
||||||
"volume": volume,
|
print(f" - config.reagent: {config_reagent}")
|
||||||
"amount": amount,
|
return node_id
|
||||||
"time": time,
|
|
||||||
"viscous": viscous,
|
# 第四步:列出所有可用的容器信息帮助调试
|
||||||
"rinsing_solvent": "",
|
available_containers = []
|
||||||
"rinsing_volume": 0.0,
|
for node_id in G.nodes():
|
||||||
"rinsing_repeats": 0,
|
if G.nodes[node_id].get('type') == 'container':
|
||||||
"solid": False
|
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)
|
||||||
# 如果需要搅拌,使用 StartStir 而不是 Stir
|
print(f"ADD_PROTOCOL: 已添加搅拌动作,速度 {stir_speed} RPM")
|
||||||
if stir:
|
|
||||||
stirrer_nodes = [node for node in G.nodes()
|
# 等待搅拌稳定
|
||||||
if G.nodes[node].get('class') == 'virtual_stirrer']
|
action_sequence.append({
|
||||||
|
"action_name": "wait",
|
||||||
if stirrer_nodes:
|
"action_kwargs": {"time": 5}
|
||||||
stirrer_id = stirrer_nodes[0]
|
})
|
||||||
action_sequence.append({
|
else:
|
||||||
"device_id": stirrer_id,
|
print(f"ADD_PROTOCOL: 警告 - 需要搅拌但未找到与容器 {vessel} 相连的搅拌器")
|
||||||
"action_name": "start_stir", # 使用 start_stir 而不是 stir
|
|
||||||
"action_kwargs": {
|
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,
|
"vessel": vessel,
|
||||||
"stir_speed": stir_speed,
|
"stir_speed": stir_speed,
|
||||||
"purpose": f"添加 {reagent} 后搅拌"
|
"purpose": f"{purpose}: 启动搅拌,准备添加 {reagent}"
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
return action_sequence
|
# 等待搅拌稳定
|
||||||
|
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()
|
||||||
411
unilabos/compile/adjustph_protocol.py
Normal file
411
unilabos/compile/adjustph_protocol.py
Normal file
@@ -0,0 +1,411 @@
|
|||||||
|
import networkx as nx
|
||||||
|
from typing import List, Dict, Any
|
||||||
|
from .pump_protocol import generate_pump_protocol_with_rinsing
|
||||||
|
|
||||||
|
|
||||||
|
def find_acid_base_vessel(G: nx.DiGraph, reagent: str) -> str:
|
||||||
|
"""
|
||||||
|
查找酸碱试剂容器,支持多种匹配模式
|
||||||
|
|
||||||
|
Args:
|
||||||
|
G: 网络图
|
||||||
|
reagent: 试剂名称(如 "hydrochloric acid", "sodium hydroxide")
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: 试剂容器ID
|
||||||
|
"""
|
||||||
|
print(f"ADJUST_PH: 正在查找试剂 '{reagent}' 的容器...")
|
||||||
|
|
||||||
|
# 常见酸碱试剂的别名映射
|
||||||
|
reagent_aliases = {
|
||||||
|
"hydrochloric acid": ["HCl", "hydrochloric_acid", "hcl", "muriatic_acid"],
|
||||||
|
"sodium hydroxide": ["NaOH", "sodium_hydroxide", "naoh", "caustic_soda"],
|
||||||
|
"sulfuric acid": ["H2SO4", "sulfuric_acid", "h2so4"],
|
||||||
|
"nitric acid": ["HNO3", "nitric_acid", "hno3"],
|
||||||
|
"acetic acid": ["CH3COOH", "acetic_acid", "glacial_acetic_acid"],
|
||||||
|
"ammonia": ["NH3", "ammonium_hydroxide", "nh3"],
|
||||||
|
"potassium hydroxide": ["KOH", "potassium_hydroxide", "koh"]
|
||||||
|
}
|
||||||
|
|
||||||
|
# 构建搜索名称列表
|
||||||
|
search_names = [reagent.lower()]
|
||||||
|
|
||||||
|
# 添加别名
|
||||||
|
for base_name, aliases in reagent_aliases.items():
|
||||||
|
if reagent.lower() in base_name.lower() or base_name.lower() in reagent.lower():
|
||||||
|
search_names.extend([alias.lower() for alias in aliases])
|
||||||
|
|
||||||
|
# 构建可能的容器名称
|
||||||
|
possible_names = []
|
||||||
|
for name in search_names:
|
||||||
|
name_clean = name.replace(" ", "_").replace("-", "_")
|
||||||
|
possible_names.extend([
|
||||||
|
f"flask_{name_clean}",
|
||||||
|
f"bottle_{name_clean}",
|
||||||
|
f"reagent_{name_clean}",
|
||||||
|
f"acid_{name_clean}" if "acid" in name else f"base_{name_clean}",
|
||||||
|
f"{name_clean}_bottle",
|
||||||
|
f"{name_clean}_flask",
|
||||||
|
name_clean
|
||||||
|
])
|
||||||
|
|
||||||
|
# 第一步:通过容器名称匹配
|
||||||
|
for vessel_name in possible_names:
|
||||||
|
if vessel_name in G.nodes():
|
||||||
|
print(f"ADJUST_PH: 通过名称匹配找到容器: {vessel_name}")
|
||||||
|
return vessel_name
|
||||||
|
|
||||||
|
# 第二步:通过模糊匹配
|
||||||
|
for node_id in G.nodes():
|
||||||
|
if G.nodes[node_id].get('type') == 'container':
|
||||||
|
node_name = G.nodes[node_id].get('name', '').lower()
|
||||||
|
|
||||||
|
# 检查是否包含任何搜索名称
|
||||||
|
for search_name in search_names:
|
||||||
|
if search_name in node_id.lower() or search_name in node_name:
|
||||||
|
print(f"ADJUST_PH: 通过模糊匹配找到容器: {node_id}")
|
||||||
|
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', '')).lower()
|
||||||
|
reagent_name = vessel_data.get('reagent_name', '').lower()
|
||||||
|
|
||||||
|
for search_name in search_names:
|
||||||
|
if search_name in liquid_type or search_name in reagent_name:
|
||||||
|
print(f"ADJUST_PH: 通过液体类型匹配找到容器: {node_id}")
|
||||||
|
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', {})
|
||||||
|
liquids = vessel_data.get('liquid', [])
|
||||||
|
liquid_types = [liquid.get('liquid_type', '') or liquid.get('name', '')
|
||||||
|
for liquid in liquids if isinstance(liquid, dict)]
|
||||||
|
|
||||||
|
available_containers.append({
|
||||||
|
'id': node_id,
|
||||||
|
'name': G.nodes[node_id].get('name', ''),
|
||||||
|
'liquids': liquid_types,
|
||||||
|
'reagent_name': vessel_data.get('reagent_name', '')
|
||||||
|
})
|
||||||
|
|
||||||
|
print(f"ADJUST_PH: 可用容器列表:")
|
||||||
|
for container in available_containers:
|
||||||
|
print(f" - {container['id']}: {container['name']}")
|
||||||
|
print(f" 液体: {container['liquids']}")
|
||||||
|
print(f" 试剂: {container['reagent_name']}")
|
||||||
|
|
||||||
|
raise ValueError(f"找不到试剂 '{reagent}' 对应的容器。尝试了: {possible_names}")
|
||||||
|
|
||||||
|
|
||||||
|
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 calculate_reagent_volume(target_ph_value: float, reagent: str, vessel_volume: float = 100.0) -> float: # 改为 target_ph_value
|
||||||
|
"""
|
||||||
|
估算需要的试剂体积来调节pH
|
||||||
|
|
||||||
|
Args:
|
||||||
|
target_ph_value: 目标pH值 # 改为 target_ph_value
|
||||||
|
reagent: 试剂名称
|
||||||
|
vessel_volume: 容器体积 (mL)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
float: 估算的试剂体积 (mL)
|
||||||
|
"""
|
||||||
|
# 简化的pH调节体积估算(实际应用中需要更精确的计算)
|
||||||
|
if "acid" in reagent.lower() or "hcl" in reagent.lower():
|
||||||
|
# 酸性试剂:pH越低需要的体积越大
|
||||||
|
if target_ph_value < 3: # 改为 target_ph_value
|
||||||
|
return vessel_volume * 0.05 # 5%
|
||||||
|
elif target_ph_value < 5: # 改为 target_ph_value
|
||||||
|
return vessel_volume * 0.02 # 2%
|
||||||
|
else:
|
||||||
|
return vessel_volume * 0.01 # 1%
|
||||||
|
|
||||||
|
elif "hydroxide" in reagent.lower() or "naoh" in reagent.lower():
|
||||||
|
# 碱性试剂:pH越高需要的体积越大
|
||||||
|
if target_ph_value > 11: # 改为 target_ph_value
|
||||||
|
return vessel_volume * 0.05 # 5%
|
||||||
|
elif target_ph_value > 9: # 改为 target_ph_value
|
||||||
|
return vessel_volume * 0.02 # 2%
|
||||||
|
else:
|
||||||
|
return vessel_volume * 0.01 # 1%
|
||||||
|
|
||||||
|
else:
|
||||||
|
# 未知试剂,使用默认值
|
||||||
|
return vessel_volume * 0.01
|
||||||
|
|
||||||
|
|
||||||
|
def generate_adjust_ph_protocol(
|
||||||
|
G: nx.DiGraph,
|
||||||
|
vessel: str,
|
||||||
|
ph_value: float, # 改为 ph_value
|
||||||
|
reagent: str,
|
||||||
|
**kwargs
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
生成调节pH的协议序列
|
||||||
|
|
||||||
|
Args:
|
||||||
|
G: 有向图,节点为容器和设备
|
||||||
|
vessel: 目标容器(需要调节pH的容器)
|
||||||
|
ph_value: 目标pH值(从XDL传入) # 改为 ph_value
|
||||||
|
reagent: 酸碱试剂名称(从XDL传入)
|
||||||
|
**kwargs: 其他可选参数,使用默认值
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List[Dict[str, Any]]: 动作序列
|
||||||
|
"""
|
||||||
|
action_sequence = []
|
||||||
|
|
||||||
|
# 从kwargs中获取可选参数,如果没有则使用默认值
|
||||||
|
volume = kwargs.get('volume', 0.0) # 自动估算体积
|
||||||
|
stir = kwargs.get('stir', True) # 默认搅拌
|
||||||
|
stir_speed = kwargs.get('stir_speed', 300.0) # 默认搅拌速度
|
||||||
|
stir_time = kwargs.get('stir_time', 60.0) # 默认搅拌时间
|
||||||
|
settling_time = kwargs.get('settling_time', 30.0) # 默认平衡时间
|
||||||
|
|
||||||
|
print(f"ADJUST_PH: 开始生成pH调节协议")
|
||||||
|
print(f" - 目标容器: {vessel}")
|
||||||
|
print(f" - 目标pH: {ph_value}") # 改为 ph_value
|
||||||
|
print(f" - 试剂: {reagent}")
|
||||||
|
print(f" - 使用默认参数: 体积=自动估算, 搅拌=True, 搅拌速度=300RPM")
|
||||||
|
|
||||||
|
# 1. 验证目标容器存在
|
||||||
|
if vessel not in G.nodes():
|
||||||
|
raise ValueError(f"目标容器 '{vessel}' 不存在于系统中")
|
||||||
|
|
||||||
|
# 2. 查找酸碱试剂容器
|
||||||
|
try:
|
||||||
|
reagent_vessel = find_acid_base_vessel(G, reagent)
|
||||||
|
print(f"ADJUST_PH: 找到试剂容器: {reagent_vessel}")
|
||||||
|
except ValueError as e:
|
||||||
|
raise ValueError(f"无法找到试剂 '{reagent}': {str(e)}")
|
||||||
|
|
||||||
|
# 3. 如果未指定体积,自动估算
|
||||||
|
if volume <= 0:
|
||||||
|
# 获取目标容器的体积信息
|
||||||
|
vessel_data = G.nodes[vessel].get('data', {})
|
||||||
|
vessel_volume = vessel_data.get('max_volume', 100.0) # 默认100mL
|
||||||
|
|
||||||
|
estimated_volume = calculate_reagent_volume(ph_value, reagent, vessel_volume) # 改为 ph_value
|
||||||
|
volume = estimated_volume
|
||||||
|
print(f"ADJUST_PH: 自动估算试剂体积: {volume:.2f} mL")
|
||||||
|
|
||||||
|
# 4. 验证路径存在
|
||||||
|
try:
|
||||||
|
path = nx.shortest_path(G, source=reagent_vessel, target=vessel)
|
||||||
|
print(f"ADJUST_PH: 找到路径: {' → '.join(path)}")
|
||||||
|
except nx.NetworkXNoPath:
|
||||||
|
raise ValueError(f"从试剂容器 '{reagent_vessel}' 到目标容器 '{vessel}' 没有可用路径")
|
||||||
|
|
||||||
|
# 5. 先启动搅拌(如果需要)
|
||||||
|
stirrer_id = None
|
||||||
|
if stir:
|
||||||
|
try:
|
||||||
|
stirrer_id = find_connected_stirrer(G, vessel)
|
||||||
|
|
||||||
|
if stirrer_id:
|
||||||
|
print(f"ADJUST_PH: 找到搅拌器 {stirrer_id},启动搅拌")
|
||||||
|
action_sequence.append({
|
||||||
|
"device_id": stirrer_id,
|
||||||
|
"action_name": "start_stir",
|
||||||
|
"action_kwargs": {
|
||||||
|
"vessel": vessel,
|
||||||
|
"stir_speed": stir_speed,
|
||||||
|
"purpose": f"pH调节: 启动搅拌,准备添加 {reagent}"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
# 等待搅拌稳定
|
||||||
|
action_sequence.append({
|
||||||
|
"action_name": "wait",
|
||||||
|
"action_kwargs": {"time": 5}
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
print(f"ADJUST_PH: 警告 - 未找到搅拌器,继续执行")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"ADJUST_PH: 搅拌器配置出错: {str(e)}")
|
||||||
|
|
||||||
|
# 6. 缓慢添加试剂 - 使用pump_protocol
|
||||||
|
print(f"ADJUST_PH: 开始添加试剂 {volume:.2f} mL")
|
||||||
|
|
||||||
|
# 计算添加时间(pH调节需要缓慢添加)
|
||||||
|
addition_time = max(30.0, volume * 2.0) # 至少30秒,每mL需要2秒
|
||||||
|
|
||||||
|
try:
|
||||||
|
pump_actions = generate_pump_protocol_with_rinsing(
|
||||||
|
G=G,
|
||||||
|
from_vessel=reagent_vessel,
|
||||||
|
to_vessel=vessel,
|
||||||
|
volume=volume,
|
||||||
|
amount="",
|
||||||
|
time=addition_time,
|
||||||
|
viscous=False,
|
||||||
|
rinsing_solvent="", # pH调节不需要清洗
|
||||||
|
rinsing_volume=0.0,
|
||||||
|
rinsing_repeats=0,
|
||||||
|
solid=False,
|
||||||
|
flowrate=0.5 # 缓慢注入
|
||||||
|
)
|
||||||
|
|
||||||
|
action_sequence.extend(pump_actions)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
raise ValueError(f"生成泵协议时出错: {str(e)}")
|
||||||
|
|
||||||
|
# 7. 持续搅拌以混合和平衡
|
||||||
|
if stir and stirrer_id:
|
||||||
|
print(f"ADJUST_PH: 持续搅拌 {stir_time} 秒以混合试剂")
|
||||||
|
action_sequence.append({
|
||||||
|
"device_id": stirrer_id,
|
||||||
|
"action_name": "stir",
|
||||||
|
"action_kwargs": {
|
||||||
|
"stir_time": stir_time,
|
||||||
|
"stir_speed": stir_speed,
|
||||||
|
"settling_time": settling_time,
|
||||||
|
"purpose": f"pH调节: 混合试剂,目标pH={ph_value}" # 改为 ph_value
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
# 8. 等待反应平衡
|
||||||
|
action_sequence.append({
|
||||||
|
"action_name": "wait",
|
||||||
|
"action_kwargs": {
|
||||||
|
"time": settling_time,
|
||||||
|
"description": f"等待pH平衡到目标值 {ph_value}" # 改为 ph_value
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
print(f"ADJUST_PH: 协议生成完成,共 {len(action_sequence)} 个动作")
|
||||||
|
print(f"ADJUST_PH: 预计总时间: {addition_time + stir_time + settling_time:.0f} 秒")
|
||||||
|
|
||||||
|
return action_sequence
|
||||||
|
|
||||||
|
|
||||||
|
def generate_adjust_ph_protocol_stepwise(
|
||||||
|
G: nx.DiGraph,
|
||||||
|
vessel: str,
|
||||||
|
ph_value: float,
|
||||||
|
reagent: str,
|
||||||
|
max_volume: float = 10.0,
|
||||||
|
steps: int = 3
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
分步调节pH的协议(更安全,避免过度调节)
|
||||||
|
|
||||||
|
Args:
|
||||||
|
G: 网络图
|
||||||
|
vessel: 目标容器
|
||||||
|
pH: 目标pH值
|
||||||
|
reagent: 酸碱试剂
|
||||||
|
max_volume: 最大试剂体积
|
||||||
|
steps: 分步数量
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List[Dict[str, Any]]: 动作序列
|
||||||
|
"""
|
||||||
|
action_sequence = []
|
||||||
|
|
||||||
|
print(f"ADJUST_PH: 开始分步pH调节({steps}步)")
|
||||||
|
|
||||||
|
# 每步添加的体积
|
||||||
|
step_volume = max_volume / steps
|
||||||
|
|
||||||
|
for i in range(steps):
|
||||||
|
print(f"ADJUST_PH: 第 {i+1}/{steps} 步,添加 {step_volume} mL")
|
||||||
|
|
||||||
|
# 生成单步协议
|
||||||
|
step_actions = generate_adjust_ph_protocol(
|
||||||
|
G=G,
|
||||||
|
vessel=vessel,
|
||||||
|
ph_value=ph_value,
|
||||||
|
reagent=reagent,
|
||||||
|
volume=step_volume,
|
||||||
|
stir=True,
|
||||||
|
stir_speed=300.0,
|
||||||
|
stir_time=30.0,
|
||||||
|
settling_time=20.0
|
||||||
|
)
|
||||||
|
|
||||||
|
action_sequence.extend(step_actions)
|
||||||
|
|
||||||
|
# 步骤间等待
|
||||||
|
if i < steps - 1:
|
||||||
|
action_sequence.append({
|
||||||
|
"action_name": "wait",
|
||||||
|
"action_kwargs": {
|
||||||
|
"time": 30,
|
||||||
|
"description": f"pH调节第{i+1}步完成,等待下一步"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
print(f"ADJUST_PH: 分步pH调节完成")
|
||||||
|
return action_sequence
|
||||||
|
|
||||||
|
|
||||||
|
# 便捷函数:常用pH调节
|
||||||
|
def generate_acidify_protocol(
|
||||||
|
G: nx.DiGraph,
|
||||||
|
vessel: str,
|
||||||
|
target_ph: float = 2.0,
|
||||||
|
acid: str = "hydrochloric acid"
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
"""酸化协议"""
|
||||||
|
return generate_adjust_ph_protocol(
|
||||||
|
G, vessel, target_ph, acid, 0.0, True, 300.0, 120.0, 60.0
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def generate_basify_protocol(
|
||||||
|
G: nx.DiGraph,
|
||||||
|
vessel: str,
|
||||||
|
target_ph: float = 12.0,
|
||||||
|
base: str = "sodium hydroxide"
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
"""碱化协议"""
|
||||||
|
return generate_adjust_ph_protocol(
|
||||||
|
G, vessel, target_ph, base, 0.0, True, 300.0, 120.0, 60.0
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def generate_neutralize_protocol(
|
||||||
|
G: nx.DiGraph,
|
||||||
|
vessel: str,
|
||||||
|
reagent: str = "sodium hydroxide"
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
"""中和协议(pH=7)"""
|
||||||
|
return generate_adjust_ph_protocol(
|
||||||
|
G, vessel, 7.0, reagent, 0.0, True, 350.0, 180.0, 90.0
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# 测试函数
|
||||||
|
def test_adjust_ph_protocol():
|
||||||
|
"""测试pH调节协议"""
|
||||||
|
print("=== ADJUST PH PROTOCOL 测试 ===")
|
||||||
|
print("测试完成")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
test_adjust_ph_protocol()
|
||||||
@@ -1,5 +1,59 @@
|
|||||||
from typing import List, Dict, Any
|
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_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(
|
def generate_centrifuge_protocol(
|
||||||
G: nx.DiGraph,
|
G: nx.DiGraph,
|
||||||
@@ -9,115 +63,223 @@ def generate_centrifuge_protocol(
|
|||||||
temp: float = 25.0
|
temp: float = 25.0
|
||||||
) -> List[Dict[str, Any]]:
|
) -> List[Dict[str, Any]]:
|
||||||
"""
|
"""
|
||||||
生成离心操作的协议序列
|
生成离心操作的协议序列,复用 pump_protocol 的成熟算法
|
||||||
|
|
||||||
|
离心流程:
|
||||||
|
1. 液体转移:将待离心溶液从源容器转移到离心机容器
|
||||||
|
2. 离心操作:执行离心分离
|
||||||
|
3. 上清液转移:将离心后的上清液转移回原容器或新容器
|
||||||
|
4. 沉淀处理:处理离心沉淀(可选)
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
G: 有向图,节点为设备和容器
|
G: 有向图,节点为设备和容器,边为流体管道
|
||||||
vessel: 离心容器名称
|
vessel: 包含待离心溶液的容器名称
|
||||||
speed: 离心速度 (rpm)
|
speed: 离心速度 (rpm)
|
||||||
time: 离心时间 (秒)
|
time: 离心时间 (秒)
|
||||||
temp: 温度 (摄氏度,可选)
|
temp: 离心温度 (°C),默认25°C
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
List[Dict[str, Any]]: 离心操作的动作序列
|
List[Dict[str, Any]]: 离心操作的动作序列
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
ValueError: 当找不到离心机设备时抛出异常
|
ValueError: 当找不到必要的设备时抛出异常
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
centrifuge_protocol = generate_centrifuge_protocol(G, "reactor", 5000, 300, 4.0)
|
centrifuge_actions = generate_centrifuge_protocol(G, "reaction_mixture", 5000, 600, 4.0)
|
||||||
"""
|
"""
|
||||||
action_sequence = []
|
action_sequence = []
|
||||||
|
|
||||||
# 查找离心机设备
|
print(f"CENTRIFUGE: 开始生成离心协议")
|
||||||
centrifuge_nodes = [node for node in G.nodes()
|
print(f" - 源容器: {vessel}")
|
||||||
if G.nodes[node].get('class') == 'virtual_centrifuge']
|
print(f" - 离心速度: {speed} rpm")
|
||||||
|
print(f" - 离心时间: {time}s ({time/60:.1f}分钟)")
|
||||||
|
print(f" - 离心温度: {temp}°C")
|
||||||
|
|
||||||
if not centrifuge_nodes:
|
# 验证源容器存在
|
||||||
raise ValueError("没有找到可用的离心机设备")
|
|
||||||
|
|
||||||
# 使用第一个可用的离心机
|
|
||||||
centrifuge_id = centrifuge_nodes[0]
|
|
||||||
|
|
||||||
# 验证容器是否存在
|
|
||||||
if vessel not in G.nodes():
|
if vessel not in G.nodes():
|
||||||
raise ValueError(f"容器 {vessel} 不存在于图中")
|
raise ValueError(f"源容器 '{vessel}' 不存在于系统中")
|
||||||
|
|
||||||
# 执行离心操作
|
# 获取源容器中的液体体积
|
||||||
action_sequence.append({
|
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,
|
"device_id": centrifuge_id,
|
||||||
"action_name": "centrifuge",
|
"action_name": "centrifuge",
|
||||||
"action_kwargs": {
|
"action_kwargs": {
|
||||||
"vessel": vessel,
|
"vessel": centrifuge_vessel,
|
||||||
"speed": speed,
|
"speed": speed,
|
||||||
"time": time,
|
"time": time,
|
||||||
"temp": temp
|
"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
|
return action_sequence
|
||||||
|
|
||||||
|
|
||||||
def generate_multi_step_centrifuge_protocol(
|
# 便捷函数:常用离心方案
|
||||||
|
def generate_low_speed_centrifuge_protocol(
|
||||||
G: nx.DiGraph,
|
G: nx.DiGraph,
|
||||||
vessel: str,
|
vessel: str,
|
||||||
steps: List[Dict[str, Any]]
|
time: float = 300.0 # 5分钟
|
||||||
) -> List[Dict[str, Any]]:
|
) -> List[Dict[str, Any]]:
|
||||||
"""
|
"""低速离心:细胞分离或大颗粒沉淀"""
|
||||||
生成多步骤离心操作的协议序列
|
return generate_centrifuge_protocol(G, vessel, 1000.0, time, 4.0)
|
||||||
|
|
||||||
Args:
|
|
||||||
G: 有向图,节点为设备和容器
|
def generate_high_speed_centrifuge_protocol(
|
||||||
vessel: 离心容器名称
|
G: nx.DiGraph,
|
||||||
steps: 离心步骤列表,每个步骤包含 speed, time, temp 参数
|
vessel: str,
|
||||||
|
time: float = 600.0 # 10分钟
|
||||||
Returns:
|
) -> List[Dict[str, Any]]:
|
||||||
List[Dict[str, Any]]: 多步骤离心操作的动作序列
|
"""高速离心:蛋白质沉淀或小颗粒分离"""
|
||||||
|
return generate_centrifuge_protocol(G, vessel, 12000.0, time, 4.0)
|
||||||
Examples:
|
|
||||||
steps = [
|
|
||||||
{"speed": 1000, "time": 60, "temp": 4.0}, # 低速预离心
|
def generate_standard_centrifuge_protocol(
|
||||||
{"speed": 12000, "time": 600, "temp": 4.0} # 高速离心
|
G: nx.DiGraph,
|
||||||
]
|
vessel: str,
|
||||||
protocol = generate_multi_step_centrifuge_protocol(G, "reactor", steps)
|
time: float = 600.0 # 10分钟
|
||||||
"""
|
) -> List[Dict[str, Any]]:
|
||||||
action_sequence = []
|
"""标准离心:常规样品处理"""
|
||||||
|
return generate_centrifuge_protocol(G, vessel, 5000.0, time, 25.0)
|
||||||
# 查找离心机设备
|
|
||||||
centrifuge_nodes = [node for node in G.nodes()
|
|
||||||
if G.nodes[node].get('class') == 'virtual_centrifuge']
|
def generate_cold_centrifuge_protocol(
|
||||||
|
G: nx.DiGraph,
|
||||||
if not centrifuge_nodes:
|
vessel: str,
|
||||||
raise ValueError("没有找到可用的离心机设备")
|
speed: float = 5000.0,
|
||||||
|
time: float = 600.0
|
||||||
centrifuge_id = centrifuge_nodes[0]
|
) -> List[Dict[str, Any]]:
|
||||||
|
"""冷冻离心:热敏感样品处理"""
|
||||||
# 验证容器是否存在
|
return generate_centrifuge_protocol(G, vessel, speed, time, 4.0)
|
||||||
if vessel not in G.nodes():
|
|
||||||
raise ValueError(f"容器 {vessel} 不存在于图中")
|
|
||||||
|
def generate_ultra_centrifuge_protocol(
|
||||||
# 执行每个离心步骤
|
G: nx.DiGraph,
|
||||||
for i, step in enumerate(steps):
|
vessel: str,
|
||||||
speed = step.get('speed', 5000)
|
time: float = 1800.0 # 30分钟
|
||||||
time = step.get('time', 300)
|
) -> List[Dict[str, Any]]:
|
||||||
temp = step.get('temp', 25.0)
|
"""超高速离心:超细颗粒分离"""
|
||||||
|
return generate_centrifuge_protocol(G, vessel, 15000.0, time, 4.0)
|
||||||
action_sequence.append({
|
|
||||||
"device_id": centrifuge_id,
|
|
||||||
"action_name": "centrifuge",
|
|
||||||
"action_kwargs": {
|
|
||||||
"vessel": vessel,
|
|
||||||
"speed": speed,
|
|
||||||
"time": time,
|
|
||||||
"temp": temp
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
# 步骤间等待时间(除了最后一步)
|
|
||||||
if i < len(steps) - 1:
|
|
||||||
action_sequence.append({
|
|
||||||
"action_name": "wait",
|
|
||||||
"action_kwargs": {"time": 3}
|
|
||||||
})
|
|
||||||
|
|
||||||
return action_sequence
|
|
||||||
@@ -1,5 +1,147 @@
|
|||||||
from typing import List, Dict, Any
|
from typing import List, Dict, Any
|
||||||
import networkx as nx
|
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(
|
def generate_clean_vessel_protocol(
|
||||||
G: nx.DiGraph,
|
G: nx.DiGraph,
|
||||||
@@ -10,13 +152,22 @@ def generate_clean_vessel_protocol(
|
|||||||
repeats: int = 1
|
repeats: int = 1
|
||||||
) -> List[Dict[str, Any]]:
|
) -> List[Dict[str, Any]]:
|
||||||
"""
|
"""
|
||||||
生成容器清洗操作的协议序列,使用transfer操作实现清洗
|
生成容器清洗操作的协议序列,复用 pump_protocol 的成熟算法
|
||||||
|
|
||||||
|
清洗流程:
|
||||||
|
1. 查找溶剂容器和废液容器
|
||||||
|
2. 如果需要加热,启动加热设备
|
||||||
|
3. 重复以下操作 repeats 次:
|
||||||
|
a. 使用 pump_protocol 将溶剂从溶剂容器转移到目标容器
|
||||||
|
b. (可选) 等待清洗作用时间
|
||||||
|
c. 使用 pump_protocol 将清洗液从目标容器转移到废液容器
|
||||||
|
4. 如果加热了,停止加热
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
G: 有向图,节点为设备和容器
|
G: 有向图,节点为设备和容器,边为流体管道
|
||||||
vessel: 要清洗的容器名称
|
vessel: 要清洗的容器名称
|
||||||
solvent: 用于清洗容器的溶剂名称
|
solvent: 用于清洗的溶剂名称
|
||||||
volume: 清洗溶剂的体积
|
volume: 每次清洗使用的溶剂体积
|
||||||
temp: 清洗时的温度
|
temp: 清洗时的温度
|
||||||
repeats: 清洗操作的重复次数,默认为 1
|
repeats: 清洗操作的重复次数,默认为 1
|
||||||
|
|
||||||
@@ -24,103 +175,265 @@ def generate_clean_vessel_protocol(
|
|||||||
List[Dict[str, Any]]: 容器清洗操作的动作序列
|
List[Dict[str, Any]]: 容器清洗操作的动作序列
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
ValueError: 当找不到必要的设备时抛出异常
|
ValueError: 当找不到必要的容器或设备时抛出异常
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
clean_vessel_protocol = generate_clean_vessel_protocol(G, "reactor", "water", 50.0, 25.0, 2)
|
clean_protocol = generate_clean_vessel_protocol(G, "main_reactor", "water", 100.0, 60.0, 2)
|
||||||
"""
|
"""
|
||||||
action_sequence = []
|
action_sequence = []
|
||||||
|
|
||||||
# 查找虚拟转移泵设备进行清洗操作
|
print(f"CLEAN_VESSEL: 开始生成容器清洗协议")
|
||||||
pump_nodes = [node for node in G.nodes()
|
print(f" - 目标容器: {vessel}")
|
||||||
if G.nodes[node].get('class') == 'virtual_transfer_pump']
|
print(f" - 清洗溶剂: {solvent}")
|
||||||
|
print(f" - 清洗体积: {volume} mL")
|
||||||
|
print(f" - 清洗温度: {temp}°C")
|
||||||
|
print(f" - 重复次数: {repeats}")
|
||||||
|
|
||||||
if not pump_nodes:
|
# 验证目标容器存在
|
||||||
raise ValueError("没有找到可用的转移泵设备进行容器清洗")
|
|
||||||
|
|
||||||
pump_id = pump_nodes[0]
|
|
||||||
|
|
||||||
# 验证容器是否存在
|
|
||||||
if vessel not in G.nodes():
|
if vessel not in G.nodes():
|
||||||
raise ValueError(f"容器 {vessel} 不存在于图中")
|
raise ValueError(f"目标容器 '{vessel}' 不存在于系统中")
|
||||||
|
|
||||||
# 查找溶剂容器
|
# 查找溶剂容器
|
||||||
solvent_vessel = f"flask_{solvent}"
|
try:
|
||||||
if solvent_vessel not in G.nodes():
|
solvent_vessel = find_solvent_vessel(G, solvent)
|
||||||
raise ValueError(f"溶剂容器 {solvent_vessel} 不存在于图中")
|
print(f"CLEAN_VESSEL: 找到溶剂容器: {solvent_vessel}")
|
||||||
|
except ValueError as e:
|
||||||
|
raise ValueError(f"无法找到溶剂容器: {str(e)}")
|
||||||
|
|
||||||
# 查找废液容器
|
# 查找废液容器
|
||||||
waste_vessel = "flask_waste"
|
try:
|
||||||
if waste_vessel not in G.nodes():
|
waste_vessel = find_waste_vessel(G)
|
||||||
raise ValueError(f"废液容器 {waste_vessel} 不存在于图中")
|
print(f"CLEAN_VESSEL: 找到废液容器: {waste_vessel}")
|
||||||
|
except ValueError as e:
|
||||||
|
raise ValueError(f"无法找到废液容器: {str(e)}")
|
||||||
|
|
||||||
# 查找加热设备(如果需要加热)
|
# 查找加热设备(可选)
|
||||||
heatchill_nodes = [node for node in G.nodes()
|
heatchill_id = find_connected_heatchill(G, vessel)
|
||||||
if G.nodes[node].get('class') == 'virtual_heatchill']
|
if heatchill_id:
|
||||||
|
print(f"CLEAN_VESSEL: 找到加热设备: {heatchill_id}")
|
||||||
|
else:
|
||||||
|
print(f"CLEAN_VESSEL: 未找到加热设备,将在室温下清洗")
|
||||||
|
|
||||||
heatchill_id = heatchill_nodes[0] if heatchill_nodes else None
|
# 第一步:如果需要加热且有加热设备,启动加热
|
||||||
|
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):
|
for repeat in range(repeats):
|
||||||
# 1. 如果需要加热,先设置温度
|
print(f"CLEAN_VESSEL: 执行第 {repeat + 1} 次清洗")
|
||||||
if temp > 25.0 and heatchill_id:
|
|
||||||
action_sequence.append({
|
|
||||||
"device_id": heatchill_id,
|
|
||||||
"action_name": "heat_chill_start",
|
|
||||||
"action_kwargs": {
|
|
||||||
"vessel": vessel,
|
|
||||||
"temp": temp,
|
|
||||||
"purpose": "cleaning"
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
# 2. 使用transfer操作:从溶剂容器转移清洗溶剂到目标容器
|
# 2a. 使用 pump_protocol 将溶剂转移到目标容器
|
||||||
action_sequence.append({
|
print(f"CLEAN_VESSEL: 将 {volume} mL {solvent} 转移到 {vessel}")
|
||||||
"device_id": pump_id,
|
try:
|
||||||
"action_name": "transfer",
|
# 调用成熟的 pump_protocol 算法
|
||||||
"action_kwargs": {
|
add_solvent_actions = generate_pump_protocol(
|
||||||
"from_vessel": solvent_vessel,
|
G=G,
|
||||||
"to_vessel": vessel,
|
from_vessel=solvent_vessel,
|
||||||
"volume": volume,
|
to_vessel=vessel,
|
||||||
"amount": f"cleaning with {solvent} - cycle {repeat + 1}",
|
volume=volume,
|
||||||
"time": 0.0,
|
flowrate=2.5, # 适中的流速,避免飞溅
|
||||||
"viscous": False,
|
transfer_flowrate=2.5
|
||||||
"rinsing_solvent": "",
|
)
|
||||||
"rinsing_volume": 0.0,
|
action_sequence.extend(add_solvent_actions)
|
||||||
"rinsing_repeats": 0,
|
except Exception as e:
|
||||||
"solid": False
|
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)
|
||||||
|
|
||||||
# 3. 等待清洗作用时间(可选,可以添加wait操作)
|
|
||||||
# 这里省略wait操作,直接进行下一步
|
|
||||||
|
|
||||||
# 4. 将清洗后的溶剂转移到废液容器
|
|
||||||
action_sequence.append({
|
|
||||||
"device_id": pump_id,
|
|
||||||
"action_name": "transfer",
|
|
||||||
"action_kwargs": {
|
|
||||||
"from_vessel": vessel,
|
|
||||||
"to_vessel": waste_vessel,
|
|
||||||
"volume": volume,
|
|
||||||
"amount": f"waste from cleaning {vessel} - cycle {repeat + 1}",
|
|
||||||
"time": 0.0,
|
|
||||||
"viscous": False,
|
|
||||||
"rinsing_solvent": "",
|
|
||||||
"rinsing_volume": 0.0,
|
|
||||||
"rinsing_repeats": 0,
|
|
||||||
"solid": False
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
# 5. 如果加热了,停止加热
|
|
||||||
if temp > 25.0 and heatchill_id:
|
|
||||||
action_sequence.append({
|
|
||||||
"device_id": heatchill_id,
|
|
||||||
"action_name": "heat_chill_stop",
|
|
||||||
"action_kwargs": {
|
|
||||||
"vessel": vessel
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return action_sequence
|
# 第三步:如果加热了,停止加热
|
||||||
|
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
|
||||||
@@ -1,5 +1,47 @@
|
|||||||
from typing import List, Dict, Any
|
from typing import List, Dict, Any
|
||||||
import networkx as nx
|
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(
|
def generate_dissolve_protocol(
|
||||||
G: nx.DiGraph,
|
G: nx.DiGraph,
|
||||||
@@ -9,154 +51,309 @@ def generate_dissolve_protocol(
|
|||||||
amount: str = "",
|
amount: str = "",
|
||||||
temp: float = 25.0,
|
temp: float = 25.0,
|
||||||
time: float = 0.0,
|
time: float = 0.0,
|
||||||
stir_speed: float = 0.0
|
stir_speed: float = 300.0
|
||||||
) -> List[Dict[str, Any]]:
|
) -> List[Dict[str, Any]]:
|
||||||
"""
|
"""
|
||||||
生成溶解操作的协议序列
|
生成溶解操作的协议序列,复用 pump_protocol 的成熟算法
|
||||||
|
|
||||||
|
溶解流程:
|
||||||
|
1. 溶剂转移:将溶剂从溶剂瓶转移到目标容器
|
||||||
|
2. 启动加热搅拌:设置温度和搅拌
|
||||||
|
3. 等待溶解:监控溶解过程
|
||||||
|
4. 停止加热搅拌:完成溶解
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
G: 有向图,节点为设备和容器
|
G: 有向图,节点为设备和容器,边为流体管道
|
||||||
vessel: 装有要溶解物质的容器名称
|
vessel: 目标容器(要进行溶解的容器)
|
||||||
solvent: 用于溶解物质的溶剂名称
|
solvent: 溶剂名称(用于查找对应的溶剂瓶)
|
||||||
volume: 溶剂的体积,可选参数
|
volume: 溶剂体积 (mL)
|
||||||
amount: 要溶解物质的量,可选参数
|
amount: 要溶解的物质描述
|
||||||
temp: 溶解时的温度,可选参数
|
temp: 溶解温度 (°C),默认25°C(室温)
|
||||||
time: 溶解的时间,可选参数
|
time: 溶解时间 (秒),默认0(立即完成)
|
||||||
stir_speed: 搅拌速度,可选参数
|
stir_speed: 搅拌速度 (RPM),默认300 RPM
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
List[Dict[str, Any]]: 溶解操作的动作序列
|
List[Dict[str, Any]]: 溶解操作的动作序列
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
ValueError: 当找不到必要的设备时抛出异常
|
ValueError: 当找不到必要的设备或容器时
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
dissolve_protocol = generate_dissolve_protocol(G, "reactor", "water", 100.0, "NaCl 5g", 60.0, 300.0, 500.0)
|
dissolve_actions = generate_dissolve_protocol(G, "reaction_mixture", "DMF", 10.0, "NaCl 2g", 60.0, 600.0, 400.0)
|
||||||
"""
|
"""
|
||||||
action_sequence = []
|
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():
|
if vessel not in G.nodes():
|
||||||
raise ValueError(f"容器 {vessel} 不存在于图中")
|
raise ValueError(f"目标容器 '{vessel}' 不存在于系统中")
|
||||||
|
|
||||||
# 查找溶剂容器
|
# 查找溶剂瓶
|
||||||
solvent_vessel = f"flask_{solvent}"
|
try:
|
||||||
if solvent_vessel not in G.nodes():
|
solvent_vessel = find_solvent_vessel(G, solvent)
|
||||||
# 如果没有找到特定溶剂容器,查找可用的源容器
|
print(f"DISSOLVE: 找到溶剂瓶: {solvent_vessel}")
|
||||||
available_vessels = [node for node in G.nodes()
|
except ValueError as e:
|
||||||
if node.startswith('flask_') and
|
raise ValueError(f"无法找到溶剂 '{solvent}': {str(e)}")
|
||||||
G.nodes[node].get('type') == 'container']
|
|
||||||
if available_vessels:
|
|
||||||
solvent_vessel = available_vessels[0]
|
|
||||||
else:
|
|
||||||
raise ValueError(f"没有找到溶剂容器 {solvent}")
|
|
||||||
|
|
||||||
# 查找转移泵设备
|
# 验证是否存在从溶剂瓶到目标容器的路径
|
||||||
pump_nodes = [node for node in G.nodes()
|
try:
|
||||||
if G.nodes[node].get('class') == 'virtual_transfer_pump']
|
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}' 没有可用路径")
|
||||||
|
|
||||||
if not pump_nodes:
|
# 查找加热搅拌器
|
||||||
raise ValueError("没有找到可用的转移泵设备")
|
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)}")
|
||||||
|
|
||||||
pump_id = pump_nodes[0]
|
# === 第一步:启动加热搅拌(在添加溶剂前) ===
|
||||||
|
if heatchill_id and (temp > 25.0 or time > 0):
|
||||||
# 查找加热设备(如果需要加热)
|
print(f"DISSOLVE: 启动加热搅拌器,温度: {temp}°C")
|
||||||
heatchill_nodes = [node for node in G.nodes()
|
|
||||||
if G.nodes[node].get('class') == 'virtual_heatchill']
|
if time > 0:
|
||||||
|
# 如果指定了时间,使用定时加热搅拌
|
||||||
heatchill_id = heatchill_nodes[0] if heatchill_nodes else None
|
heatchill_action = {
|
||||||
|
"device_id": heatchill_id,
|
||||||
# 查找搅拌设备(如果需要搅拌)
|
"action_name": "heat_chill",
|
||||||
stirrer_nodes = [node for node in G.nodes()
|
|
||||||
if G.nodes[node].get('class') == 'virtual_stirrer']
|
|
||||||
|
|
||||||
stirrer_id = stirrer_nodes[0] if stirrer_nodes else None
|
|
||||||
|
|
||||||
# 步骤1:如果需要加热,先设置温度
|
|
||||||
if temp > 25.0 and heatchill_id:
|
|
||||||
action_sequence.append({
|
|
||||||
"device_id": heatchill_id,
|
|
||||||
"action_name": "heat_chill_start",
|
|
||||||
"action_kwargs": {
|
|
||||||
"vessel": vessel,
|
|
||||||
"temp": temp,
|
|
||||||
"purpose": "dissolution"
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
# 步骤2:添加溶剂到容器中
|
|
||||||
if volume > 0:
|
|
||||||
action_sequence.append({
|
|
||||||
"device_id": pump_id,
|
|
||||||
"action_name": "transfer",
|
|
||||||
"action_kwargs": {
|
|
||||||
"from_vessel": solvent_vessel,
|
|
||||||
"to_vessel": vessel,
|
|
||||||
"volume": volume,
|
|
||||||
"amount": f"solvent {solvent} for dissolving {amount}",
|
|
||||||
"time": 0.0,
|
|
||||||
"viscous": False,
|
|
||||||
"rinsing_solvent": "",
|
|
||||||
"rinsing_volume": 0.0,
|
|
||||||
"rinsing_repeats": 0,
|
|
||||||
"solid": False
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
# 步骤3:如果需要搅拌,开始搅拌
|
|
||||||
if stir_speed > 0 and stirrer_id:
|
|
||||||
action_sequence.append({
|
|
||||||
"device_id": stirrer_id,
|
|
||||||
"action_name": "start_stir",
|
|
||||||
"action_kwargs": {
|
|
||||||
"vessel": vessel,
|
|
||||||
"stir_speed": stir_speed,
|
|
||||||
"purpose": f"dissolving {amount} in {solvent}"
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
# 步骤4:如果指定了溶解时间,等待溶解完成
|
|
||||||
if time > 0:
|
|
||||||
# 这里可以添加等待操作,或者使用搅拌操作来模拟溶解时间
|
|
||||||
if stirrer_id and stir_speed > 0:
|
|
||||||
# 停止之前的搅拌,使用定时搅拌
|
|
||||||
action_sequence.append({
|
|
||||||
"device_id": stirrer_id,
|
|
||||||
"action_name": "stop_stir",
|
|
||||||
"action_kwargs": {
|
"action_kwargs": {
|
||||||
"vessel": vessel
|
"vessel": vessel,
|
||||||
}
|
"temp": temp,
|
||||||
})
|
"time": time,
|
||||||
|
"stir": True,
|
||||||
# 开始定时搅拌
|
|
||||||
action_sequence.append({
|
|
||||||
"device_id": stirrer_id,
|
|
||||||
"action_name": "stir",
|
|
||||||
"action_kwargs": {
|
|
||||||
"stir_time": time,
|
|
||||||
"stir_speed": stir_speed,
|
"stir_speed": stir_speed,
|
||||||
"settling_time": 10.0 # 搅拌后静置10秒
|
"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}
|
||||||
})
|
})
|
||||||
|
|
||||||
# 步骤5:如果加热了,停止加热
|
# === 第二步:添加溶剂到目标容器 ===
|
||||||
if temp > 25.0 and heatchill_id:
|
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_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,
|
"device_id": heatchill_id,
|
||||||
"action_name": "heat_chill_stop",
|
"action_name": "heat_chill_stop",
|
||||||
"action_kwargs": {
|
"action_kwargs": {
|
||||||
"vessel": vessel
|
"vessel": vessel
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
action_sequence.append(stop_action)
|
||||||
|
|
||||||
# 步骤6:如果还在搅拌,停止搅拌(除非已经用定时搅拌)
|
print(f"DISSOLVE: 生成了 {len(action_sequence)} 个动作")
|
||||||
if stir_speed > 0 and stirrer_id and time == 0:
|
print(f"DISSOLVE: 溶解协议生成完成")
|
||||||
action_sequence.append({
|
|
||||||
"device_id": stirrer_id,
|
return action_sequence
|
||||||
"action_name": "stop_stir",
|
|
||||||
"action_kwargs": {
|
|
||||||
"vessel": vessel
|
# 便捷函数:常用溶解方案
|
||||||
|
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 = []
|
||||||
|
|
||||||
return 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()
|
||||||
165
unilabos/compile/dry_protocol.py
Normal file
165
unilabos/compile/dry_protocol.py
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
import networkx as nx
|
||||||
|
from typing import List, Dict, Any
|
||||||
|
|
||||||
|
|
||||||
|
def find_connected_heater(G: nx.DiGraph, vessel: str) -> str:
|
||||||
|
"""
|
||||||
|
查找与容器相连的加热器
|
||||||
|
|
||||||
|
Args:
|
||||||
|
G: 网络图
|
||||||
|
vessel: 容器名称
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: 加热器ID,如果没有则返回None
|
||||||
|
"""
|
||||||
|
print(f"DRY: 正在查找与容器 '{vessel}' 相连的加热器...")
|
||||||
|
|
||||||
|
# 查找所有加热器节点
|
||||||
|
heater_nodes = [node for node in G.nodes()
|
||||||
|
if ('heater' in node.lower() or
|
||||||
|
'heat' in node.lower() or
|
||||||
|
G.nodes[node].get('class') == 'virtual_heatchill' or
|
||||||
|
G.nodes[node].get('type') == 'heater')]
|
||||||
|
|
||||||
|
print(f"DRY: 找到的加热器节点: {heater_nodes}")
|
||||||
|
|
||||||
|
# 检查是否有加热器与目标容器相连
|
||||||
|
for heater in heater_nodes:
|
||||||
|
if G.has_edge(heater, vessel) or G.has_edge(vessel, heater):
|
||||||
|
print(f"DRY: 找到与容器 '{vessel}' 相连的加热器: {heater}")
|
||||||
|
return heater
|
||||||
|
|
||||||
|
# 如果没有直接连接,查找距离最近的加热器
|
||||||
|
for heater in heater_nodes:
|
||||||
|
try:
|
||||||
|
path = nx.shortest_path(G, source=heater, target=vessel)
|
||||||
|
if len(path) <= 3: # 最多2个中间节点
|
||||||
|
print(f"DRY: 找到距离较近的加热器: {heater}, 路径: {' → '.join(path)}")
|
||||||
|
return heater
|
||||||
|
except nx.NetworkXNoPath:
|
||||||
|
continue
|
||||||
|
|
||||||
|
print(f"DRY: 未找到与容器 '{vessel}' 相连的加热器")
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def generate_dry_protocol(
|
||||||
|
G: nx.DiGraph,
|
||||||
|
compound: str,
|
||||||
|
vessel: str,
|
||||||
|
**kwargs # 接收其他可能的参数但不使用
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
生成干燥协议序列
|
||||||
|
|
||||||
|
Args:
|
||||||
|
G: 有向图,节点为容器和设备
|
||||||
|
compound: 化合物名称(从XDL传入)
|
||||||
|
vessel: 目标容器(从XDL传入)
|
||||||
|
**kwargs: 其他可选参数,但不使用
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List[Dict[str, Any]]: 动作序列
|
||||||
|
"""
|
||||||
|
action_sequence = []
|
||||||
|
|
||||||
|
# 默认参数
|
||||||
|
dry_temp = 60.0 # 默认干燥温度 60°C
|
||||||
|
dry_time = 3600.0 # 默认干燥时间 1小时(3600秒)
|
||||||
|
|
||||||
|
print(f"DRY: 开始生成干燥协议")
|
||||||
|
print(f" - 化合物: {compound}")
|
||||||
|
print(f" - 容器: {vessel}")
|
||||||
|
print(f" - 干燥温度: {dry_temp}°C")
|
||||||
|
print(f" - 干燥时间: {dry_time/60:.0f} 分钟")
|
||||||
|
|
||||||
|
# 1. 验证目标容器存在
|
||||||
|
if vessel not in G.nodes():
|
||||||
|
print(f"DRY: 警告 - 容器 '{vessel}' 不存在于系统中,跳过干燥")
|
||||||
|
return action_sequence
|
||||||
|
|
||||||
|
# 2. 查找相连的加热器
|
||||||
|
heater_id = find_connected_heater(G, vessel)
|
||||||
|
|
||||||
|
if heater_id is None:
|
||||||
|
print(f"DRY: 警告 - 未找到与容器 '{vessel}' 相连的加热器,跳过干燥")
|
||||||
|
# 添加一个等待动作,表示干燥过程(模拟)
|
||||||
|
action_sequence.append({
|
||||||
|
"action_name": "wait",
|
||||||
|
"action_kwargs": {
|
||||||
|
"time": 60.0, # 等待1分钟
|
||||||
|
"description": f"模拟干燥 {compound} (无加热器可用)"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return action_sequence
|
||||||
|
|
||||||
|
# 3. 启动加热器进行干燥
|
||||||
|
print(f"DRY: 启动加热器 {heater_id} 进行干燥")
|
||||||
|
|
||||||
|
# 3.1 启动加热
|
||||||
|
action_sequence.append({
|
||||||
|
"device_id": heater_id,
|
||||||
|
"action_name": "heat_chill_start",
|
||||||
|
"action_kwargs": {
|
||||||
|
"vessel": vessel,
|
||||||
|
"temp": dry_temp,
|
||||||
|
"purpose": f"干燥 {compound}"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
# 3.2 等待温度稳定
|
||||||
|
action_sequence.append({
|
||||||
|
"action_name": "wait",
|
||||||
|
"action_kwargs": {
|
||||||
|
"time": 60.0,
|
||||||
|
"description": f"等待温度稳定到 {dry_temp}°C"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
# 3.3 保持干燥温度
|
||||||
|
action_sequence.append({
|
||||||
|
"device_id": heater_id,
|
||||||
|
"action_name": "heat_chill",
|
||||||
|
"action_kwargs": {
|
||||||
|
"vessel": vessel,
|
||||||
|
"temp": dry_temp,
|
||||||
|
"time": dry_time,
|
||||||
|
"purpose": f"干燥 {compound},保持温度 {dry_temp}°C"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
# 3.4 停止加热
|
||||||
|
action_sequence.append({
|
||||||
|
"device_id": heater_id,
|
||||||
|
"action_name": "heat_chill_stop",
|
||||||
|
"action_kwargs": {
|
||||||
|
"vessel": vessel,
|
||||||
|
"purpose": f"干燥完成,停止加热"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
# 3.5 等待冷却
|
||||||
|
action_sequence.append({
|
||||||
|
"action_name": "wait",
|
||||||
|
"action_kwargs": {
|
||||||
|
"time": 300.0, # 等待5分钟冷却
|
||||||
|
"description": f"等待 {compound} 冷却"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
print(f"DRY: 协议生成完成,共 {len(action_sequence)} 个动作")
|
||||||
|
print(f"DRY: 预计总时间: {(dry_time + 360)/60:.0f} 分钟")
|
||||||
|
|
||||||
|
return action_sequence
|
||||||
|
|
||||||
|
|
||||||
|
# 测试函数
|
||||||
|
def test_dry_protocol():
|
||||||
|
"""测试干燥协议"""
|
||||||
|
print("=== DRY PROTOCOL 测试 ===")
|
||||||
|
print("测试完成")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
test_dry_protocol()
|
||||||
@@ -1,143 +1,537 @@
|
|||||||
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:
|
||||||
|
"""
|
||||||
|
根据气体名称查找对应的气源,支持多种匹配模式:
|
||||||
|
1. 容器名称匹配
|
||||||
|
2. 气体类型匹配(data.gas_type)
|
||||||
|
3. 默认气源
|
||||||
|
"""
|
||||||
|
print(f"EVACUATE_REFILL: 正在查找气体 '{gas}' 的气源...")
|
||||||
|
|
||||||
|
# 第一步:通过容器名称匹配
|
||||||
|
gas_source_patterns = [
|
||||||
|
f"gas_source_{gas}",
|
||||||
|
f"gas_{gas}",
|
||||||
|
f"flask_{gas}",
|
||||||
|
f"{gas}_source",
|
||||||
|
f"source_{gas}",
|
||||||
|
f"reagent_bottle_{gas}",
|
||||||
|
f"bottle_{gas}"
|
||||||
|
]
|
||||||
|
|
||||||
|
for pattern in gas_source_patterns:
|
||||||
|
if pattern in G.nodes():
|
||||||
|
print(f"EVACUATE_REFILL: 通过名称匹配找到气源: {pattern}")
|
||||||
|
return pattern
|
||||||
|
|
||||||
|
# 第二步:通过气体类型匹配 (data.gas_type)
|
||||||
|
for node_id in G.nodes():
|
||||||
|
node_data = G.nodes[node_id]
|
||||||
|
node_class = node_data.get('class', '') or ''
|
||||||
|
|
||||||
|
# 检查是否是气源设备
|
||||||
|
if ('gas_source' in node_class or
|
||||||
|
'gas' in node_id.lower() or
|
||||||
|
node_id.startswith('flask_')):
|
||||||
|
|
||||||
|
# 检查 data.gas_type
|
||||||
|
data = node_data.get('data', {})
|
||||||
|
gas_type = data.get('gas_type', '')
|
||||||
|
|
||||||
|
if gas_type.lower() == gas.lower():
|
||||||
|
print(f"EVACUATE_REFILL: 通过气体类型匹配找到气源: {node_id} (gas_type: {gas_type})")
|
||||||
|
return node_id
|
||||||
|
|
||||||
|
# 检查 config.gas_type
|
||||||
|
config = node_data.get('config', {})
|
||||||
|
config_gas_type = config.get('gas_type', '')
|
||||||
|
|
||||||
|
if config_gas_type.lower() == gas.lower():
|
||||||
|
print(f"EVACUATE_REFILL: 通过配置气体类型匹配找到气源: {node_id} (config.gas_type: {config_gas_type})")
|
||||||
|
return node_id
|
||||||
|
|
||||||
|
# 第三步:查找所有可用的气源设备
|
||||||
|
available_gas_sources = []
|
||||||
|
for node_id in G.nodes():
|
||||||
|
node_data = G.nodes[node_id]
|
||||||
|
node_class = node_data.get('class', '') or ''
|
||||||
|
|
||||||
|
if ('gas_source' in node_class or
|
||||||
|
'gas' in node_id.lower() or
|
||||||
|
(node_id.startswith('flask_') and any(g in node_id.lower() for g in ['air', 'nitrogen', 'argon']))):
|
||||||
|
|
||||||
|
data = node_data.get('data', {})
|
||||||
|
gas_type = data.get('gas_type', 'unknown')
|
||||||
|
available_gas_sources.append(f"{node_id} (gas_type: {gas_type})")
|
||||||
|
|
||||||
|
print(f"EVACUATE_REFILL: 可用气源列表: {available_gas_sources}")
|
||||||
|
|
||||||
|
# 第四步:如果找不到特定气体,使用默认的第一个气源
|
||||||
|
default_gas_sources = [
|
||||||
|
node for node in G.nodes()
|
||||||
|
if ((G.nodes[node].get('class') or '').startswith('virtual_gas_source')
|
||||||
|
or 'gas_source' in node)
|
||||||
|
]
|
||||||
|
|
||||||
|
if default_gas_sources:
|
||||||
|
default_source = default_gas_sources[0]
|
||||||
|
print(f"EVACUATE_REFILL: ⚠️ 未找到特定气体 '{gas}',使用默认气源: {default_source}")
|
||||||
|
return default_source
|
||||||
|
|
||||||
|
raise ValueError(f"找不到气体 '{gas}' 对应的气源。可用气源: {available_gas_sources}")
|
||||||
|
|
||||||
|
|
||||||
|
def find_gas_source_by_any_match(G: nx.DiGraph, gas: str) -> str:
|
||||||
|
"""
|
||||||
|
增强版气源查找,支持各种匹配方式的别名函数
|
||||||
|
"""
|
||||||
|
return find_gas_source(G, gas)
|
||||||
|
|
||||||
|
|
||||||
|
def get_gas_source_type(G: nx.DiGraph, gas_source: str) -> str:
|
||||||
|
"""获取气源的气体类型"""
|
||||||
|
if gas_source not in G.nodes():
|
||||||
|
return "unknown"
|
||||||
|
|
||||||
|
node_data = G.nodes[gas_source]
|
||||||
|
data = node_data.get('data', {})
|
||||||
|
config = node_data.get('config', {})
|
||||||
|
|
||||||
|
# 检查多个可能的字段
|
||||||
|
gas_type = (data.get('gas_type') or
|
||||||
|
config.get('gas_type') or
|
||||||
|
data.get('gas') or
|
||||||
|
config.get('gas') or
|
||||||
|
"air") # 默认为空气
|
||||||
|
|
||||||
|
return gas_type
|
||||||
|
|
||||||
|
|
||||||
|
def find_vessels_by_gas_type(G: nx.DiGraph, gas: str) -> List[str]:
|
||||||
|
"""
|
||||||
|
根据气体类型查找所有匹配的容器/气源
|
||||||
|
"""
|
||||||
|
matching_vessels = []
|
||||||
|
|
||||||
|
for node_id in G.nodes():
|
||||||
|
node_data = G.nodes[node_id]
|
||||||
|
|
||||||
|
# 检查容器名称匹配
|
||||||
|
if gas.lower() in node_id.lower():
|
||||||
|
matching_vessels.append(f"{node_id} (名称匹配)")
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 检查气体类型匹配
|
||||||
|
data = node_data.get('data', {})
|
||||||
|
config = node_data.get('config', {})
|
||||||
|
|
||||||
|
gas_type = data.get('gas_type', '') or config.get('gas_type', '')
|
||||||
|
if gas_type.lower() == gas.lower():
|
||||||
|
matching_vessels.append(f"{node_id} (gas_type: {gas_type})")
|
||||||
|
|
||||||
|
return matching_vessels
|
||||||
|
|
||||||
|
|
||||||
|
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}")
|
||||||
"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}})
|
|
||||||
|
|
||||||
# 关闭真空泵阀门、打开气源阀门
|
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):
|
||||||
"string": "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):
|
||||||
"string": "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": {
|
})
|
||||||
"string": "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 # 适中旋转速度
|
||||||
|
)
|
||||||
|
|||||||
@@ -1,5 +1,89 @@
|
|||||||
from typing import List, Dict, Any
|
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_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(
|
def generate_filter_protocol(
|
||||||
G: nx.DiGraph,
|
G: nx.DiGraph,
|
||||||
@@ -12,59 +96,209 @@ def generate_filter_protocol(
|
|||||||
volume: float = 0.0
|
volume: float = 0.0
|
||||||
) -> List[Dict[str, Any]]:
|
) -> List[Dict[str, Any]]:
|
||||||
"""
|
"""
|
||||||
生成过滤操作的协议序列
|
生成过滤操作的协议序列,复用 pump_protocol 的成熟算法
|
||||||
|
|
||||||
|
过滤流程:
|
||||||
|
1. 液体转移:将待过滤溶液从源容器转移到过滤器
|
||||||
|
2. 启动加热搅拌:设置温度和搅拌
|
||||||
|
3. 执行过滤:通过过滤器分离固液
|
||||||
|
4. (可选) 继续或停止加热搅拌
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
G: 有向图,节点为设备和容器
|
G: 有向图,节点为设备和容器,边为流体管道
|
||||||
vessel: 过滤容器
|
vessel: 包含待过滤溶液的容器名称
|
||||||
filtrate_vessel: 滤液容器(可选)
|
filtrate_vessel: 滤液收集容器(可选,自动查找)
|
||||||
stir: 是否搅拌
|
stir: 是否在过滤过程中搅拌
|
||||||
stir_speed: 搅拌速度(可选)
|
stir_speed: 搅拌速度 (RPM)
|
||||||
temp: 温度(可选,摄氏度)
|
temp: 过滤温度 (°C)
|
||||||
continue_heatchill: 是否继续加热冷却
|
continue_heatchill: 过滤后是否继续加热搅拌
|
||||||
volume: 过滤体积(可选)
|
volume: 预期过滤体积 (mL),0表示全部过滤
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
List[Dict[str, Any]]: 过滤操作的动作序列
|
List[Dict[str, Any]]: 过滤操作的动作序列
|
||||||
|
|
||||||
Raises:
|
|
||||||
ValueError: 当找不到过滤设备时抛出异常
|
|
||||||
|
|
||||||
Examples:
|
|
||||||
filter_protocol = generate_filter_protocol(G, "reactor", "filtrate_vessel", stir=True, volume=100.0)
|
|
||||||
"""
|
"""
|
||||||
action_sequence = []
|
action_sequence = []
|
||||||
|
|
||||||
# 查找过滤设备
|
print(f"FILTER: 开始生成过滤协议")
|
||||||
filter_nodes = [node for node in G.nodes()
|
print(f" - 源容器: {vessel}")
|
||||||
if G.nodes[node].get('class') == 'virtual_filter']
|
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 not filter_nodes:
|
# 验证源容器存在
|
||||||
raise ValueError("没有找到可用的过滤设备")
|
|
||||||
|
|
||||||
# 使用第一个可用的过滤器
|
|
||||||
filter_id = filter_nodes[0]
|
|
||||||
|
|
||||||
# 验证容器是否存在
|
|
||||||
if vessel not in G.nodes():
|
if vessel not in G.nodes():
|
||||||
raise ValueError(f"过滤容器 {vessel} 不存在于图中")
|
raise ValueError(f"源容器 '{vessel}' 不存在于系统中")
|
||||||
|
|
||||||
if filtrate_vessel and filtrate_vessel not in G.nodes():
|
# 获取源容器中的液体体积
|
||||||
raise ValueError(f"滤液容器 {filtrate_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_sequence.append({
|
||||||
|
"action_name": "wait",
|
||||||
|
"action_kwargs": {"time": 5}
|
||||||
|
})
|
||||||
|
|
||||||
|
# === 第三步:执行过滤操作(完全按照 Filter.action 参数) ===
|
||||||
|
print(f"FILTER: 执行过滤操作")
|
||||||
|
filter_action = {
|
||||||
"device_id": filter_id,
|
"device_id": filter_id,
|
||||||
"action_name": "filter_sample",
|
"action_name": "filter",
|
||||||
"action_kwargs": {
|
"action_kwargs": {
|
||||||
"vessel": vessel,
|
"vessel": filter_vessel_id,
|
||||||
"filtrate_vessel": filtrate_vessel,
|
"filtrate_vessel": actual_filtrate_vessel,
|
||||||
"stir": stir,
|
"stir": stir,
|
||||||
"stir_speed": stir_speed,
|
"stir_speed": stir_speed,
|
||||||
"temp": temp,
|
"temp": temp,
|
||||||
"continue_heatchill": continue_heatchill,
|
"continue_heatchill": continue_heatchill,
|
||||||
"volume": volume
|
"volume": transfer_volume
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
action_sequence.append(filter_action)
|
||||||
|
|
||||||
|
# 过滤后等待
|
||||||
|
action_sequence.append({
|
||||||
|
"action_name": "wait",
|
||||||
|
"action_kwargs": {"time": 10}
|
||||||
})
|
})
|
||||||
|
|
||||||
return action_sequence
|
# === 第四步:如果不继续加热搅拌,停止加热器 ===
|
||||||
|
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)
|
||||||
@@ -1,5 +1,72 @@
|
|||||||
from typing import List, Dict, Any
|
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_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(
|
def generate_filter_through_protocol(
|
||||||
G: nx.DiGraph,
|
G: nx.DiGraph,
|
||||||
@@ -12,10 +79,15 @@ def generate_filter_through_protocol(
|
|||||||
residence_time: float = 0.0
|
residence_time: float = 0.0
|
||||||
) -> List[Dict[str, Any]]:
|
) -> List[Dict[str, Any]]:
|
||||||
"""
|
"""
|
||||||
生成通过过滤介质过滤的协议序列
|
生成通过过滤介质过滤的协议序列,复用 pump_protocol 的成熟算法
|
||||||
|
|
||||||
|
过滤流程:
|
||||||
|
1. 液体转移:将样品从源容器转移到过滤介质
|
||||||
|
2. 重力过滤:液体通过过滤介质自动流到目标容器
|
||||||
|
3. 洗脱操作:将洗脱溶剂通过过滤介质洗脱目标物质
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
G: 有向图,节点为设备和容器
|
G: 有向图,节点为设备和容器,边为流体管道
|
||||||
from_vessel: 源容器的名称,即物质起始所在的容器
|
from_vessel: 源容器的名称,即物质起始所在的容器
|
||||||
to_vessel: 目标容器的名称,物质过滤后要到达的容器
|
to_vessel: 目标容器的名称,物质过滤后要到达的容器
|
||||||
filter_through: 过滤时所通过的介质,如滤纸、柱子等
|
filter_through: 过滤时所通过的介质,如滤纸、柱子等
|
||||||
@@ -28,123 +100,288 @@ def generate_filter_through_protocol(
|
|||||||
List[Dict[str, Any]]: 过滤操作的动作序列
|
List[Dict[str, Any]]: 过滤操作的动作序列
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
ValueError: 当找不到必要的设备时抛出异常
|
ValueError: 当找不到必要的设备或容器时
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
filter_through_protocol = generate_filter_through_protocol(
|
filter_through_actions = generate_filter_through_protocol(
|
||||||
G, "reactor", "collection_flask", "celite", "ethanol", 50.0, 2, 60.0
|
G, "reaction_mixture", "collection_bottle_1", "celite", "ethanol", 20.0, 2, 30.0
|
||||||
)
|
)
|
||||||
"""
|
"""
|
||||||
action_sequence = []
|
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():
|
if from_vessel not in G.nodes():
|
||||||
raise ValueError(f"源容器 {from_vessel} 不存在于图中")
|
raise ValueError(f"源容器 '{from_vessel}' 不存在于系统中")
|
||||||
|
|
||||||
if to_vessel not in G.nodes():
|
if to_vessel not in G.nodes():
|
||||||
raise ValueError(f"目标容器 {to_vessel} 不存在于图中")
|
raise ValueError(f"目标容器 '{to_vessel}' 不存在于系统中")
|
||||||
|
|
||||||
# 查找转移泵设备(用于液体转移)
|
# 获取源容器中的液体体积
|
||||||
pump_nodes = [node for node in G.nodes()
|
source_volume = get_vessel_liquid_volume(G, from_vessel)
|
||||||
if G.nodes[node].get('class') == 'virtual_transfer_pump']
|
print(f"FILTER_THROUGH: 源容器 {from_vessel} 中有 {source_volume} mL 液体")
|
||||||
|
|
||||||
if not pump_nodes:
|
# 查找过滤介质容器
|
||||||
raise ValueError("没有找到可用的转移泵设备")
|
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)}")
|
||||||
|
|
||||||
pump_id = pump_nodes[0]
|
# 查找洗脱溶剂容器(如果需要)
|
||||||
|
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)}")
|
||||||
|
|
||||||
# 查找过滤设备(可选,如果有专门的过滤设备)
|
# === 第一步:将样品从源容器转移到过滤介质 ===
|
||||||
filter_nodes = [node for node in G.nodes()
|
transfer_volume = source_volume if source_volume > 0 else 100.0 # 默认100mL
|
||||||
if G.nodes[node].get('class') == 'virtual_filter']
|
print(f"FILTER_THROUGH: 将 {transfer_volume} mL 样品从 {from_vessel} 转移到 {filter_through_vessel}")
|
||||||
|
|
||||||
filter_id = filter_nodes[0] if filter_nodes else None
|
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)}")
|
||||||
|
|
||||||
# 查找洗脱溶剂容器(如果需要洗脱)
|
# === 第二步:等待样品通过过滤介质(停留时间) ===
|
||||||
eluting_vessel = None
|
if residence_time > 0:
|
||||||
if eluting_solvent and eluting_volume > 0:
|
print(f"FILTER_THROUGH: 等待样品在过滤介质中停留 {residence_time}s")
|
||||||
eluting_vessel = f"flask_{eluting_solvent}"
|
|
||||||
if eluting_vessel not in G.nodes():
|
|
||||||
# 查找可用的溶剂容器
|
|
||||||
available_vessels = [node for node in G.nodes()
|
|
||||||
if node.startswith('flask_') and
|
|
||||||
G.nodes[node].get('type') == 'container']
|
|
||||||
if available_vessels:
|
|
||||||
eluting_vessel = available_vessels[0]
|
|
||||||
else:
|
|
||||||
raise ValueError(f"没有找到洗脱溶剂容器 {eluting_solvent}")
|
|
||||||
|
|
||||||
# 步骤1:将样品从源容器转移到过滤装置(模拟通过过滤介质)
|
|
||||||
# 这里我们将过滤过程分解为多个转移步骤来模拟通过介质的过程
|
|
||||||
|
|
||||||
# 首先转移样品(模拟样品通过过滤介质)
|
|
||||||
action_sequence.append({
|
|
||||||
"device_id": pump_id,
|
|
||||||
"action_name": "transfer",
|
|
||||||
"action_kwargs": {
|
|
||||||
"from_vessel": from_vessel,
|
|
||||||
"to_vessel": to_vessel,
|
|
||||||
"volume": 0.0, # 转移所有液体,体积由系统确定
|
|
||||||
"amount": f"通过 {filter_through} 过滤",
|
|
||||||
"time": residence_time if residence_time > 0 else 0.0,
|
|
||||||
"viscous": False,
|
|
||||||
"rinsing_solvent": "",
|
|
||||||
"rinsing_volume": 0.0,
|
|
||||||
"rinsing_repeats": 0,
|
|
||||||
"solid": True # 通过过滤介质可能涉及固体分离
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
# 步骤2:如果有专门的过滤设备,使用过滤设备处理
|
|
||||||
if filter_id:
|
|
||||||
action_sequence.append({
|
action_sequence.append({
|
||||||
"device_id": filter_id,
|
"action_name": "wait",
|
||||||
"action_name": "filter_sample",
|
"action_kwargs": {"time": residence_time}
|
||||||
"action_kwargs": {
|
})
|
||||||
"vessel": to_vessel,
|
else:
|
||||||
"filtrate_vessel": to_vessel,
|
# 即使没有指定停留时间,也等待一段时间让液体通过
|
||||||
"stir": False,
|
default_wait_time = max(10, transfer_volume / 10) # 根据体积估算等待时间
|
||||||
"stir_speed": 0.0,
|
print(f"FILTER_THROUGH: 等待样品通过过滤介质 {default_wait_time}s")
|
||||||
"temp": 25.0,
|
action_sequence.append({
|
||||||
"continue_heatchill": False,
|
"action_name": "wait",
|
||||||
"volume": 0.0
|
"action_kwargs": {"time": default_wait_time}
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
# 步骤3:洗脱操作(如果指定了洗脱溶剂和重复次数)
|
# === 第三步:洗脱操作(如果指定了洗脱参数) ===
|
||||||
if eluting_solvent and eluting_volume > 0 and eluting_repeats > 0 and eluting_vessel:
|
if eluting_solvent and eluting_volume > 0 and eluting_repeats > 0 and eluting_vessel:
|
||||||
for repeat in range(eluting_repeats):
|
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_sequence.append({
|
||||||
"device_id": pump_id,
|
"action_name": "wait",
|
||||||
"action_name": "transfer",
|
"action_kwargs": {"time": eluting_wait_time}
|
||||||
"action_kwargs": {
|
|
||||||
"from_vessel": eluting_vessel,
|
|
||||||
"to_vessel": to_vessel,
|
|
||||||
"volume": eluting_volume,
|
|
||||||
"amount": f"洗脱溶剂 {eluting_solvent} - 第 {repeat + 1} 次",
|
|
||||||
"time": 0.0,
|
|
||||||
"viscous": False,
|
|
||||||
"rinsing_solvent": "",
|
|
||||||
"rinsing_volume": 0.0,
|
|
||||||
"rinsing_repeats": 0,
|
|
||||||
"solid": False
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
# 如果有过滤设备,再次过滤洗脱液
|
# 洗脱间隔等待
|
||||||
if filter_id:
|
if repeat_idx < eluting_repeats - 1: # 不是最后一次洗脱
|
||||||
action_sequence.append({
|
action_sequence.append({
|
||||||
"device_id": filter_id,
|
"action_name": "wait",
|
||||||
"action_name": "filter_sample",
|
"action_kwargs": {"time": 10}
|
||||||
"action_kwargs": {
|
|
||||||
"vessel": to_vessel,
|
|
||||||
"filtrate_vessel": to_vessel,
|
|
||||||
"stir": False,
|
|
||||||
"stir_speed": 0.0,
|
|
||||||
"temp": 25.0,
|
|
||||||
"continue_heatchill": False,
|
|
||||||
"volume": eluting_volume
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
return action_sequence
|
# === 第四步:最终等待,确保所有液体完全通过 ===
|
||||||
|
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()
|
||||||
@@ -1,33 +1,61 @@
|
|||||||
from typing import List, Dict, Any
|
from typing import List, Dict, Any, Optional
|
||||||
import networkx as nx
|
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(
|
def generate_heat_chill_protocol(
|
||||||
G: nx.DiGraph,
|
G: nx.DiGraph,
|
||||||
vessel: str,
|
vessel: str,
|
||||||
temp: float,
|
temp: float,
|
||||||
time: float,
|
time: float,
|
||||||
stir: bool,
|
stir: bool = False,
|
||||||
stir_speed: float,
|
stir_speed: float = 300.0,
|
||||||
purpose: str
|
purpose: str = "加热/冷却操作"
|
||||||
) -> List[Dict[str, Any]]:
|
) -> List[Dict[str, Any]]:
|
||||||
"""
|
"""
|
||||||
生成加热/冷却操作的协议序列 - 严格按照 HeatChill.action
|
生成加热/冷却操作的协议序列 - 带时间限制的完整操作
|
||||||
"""
|
"""
|
||||||
action_sequence = []
|
action_sequence = []
|
||||||
|
|
||||||
# 查找加热/冷却设备
|
print(f"HEATCHILL: 开始生成加热/冷却协议")
|
||||||
heatchill_nodes = [node for node in G.nodes()
|
print(f" - 容器: {vessel}")
|
||||||
if G.nodes[node].get('class') == 'virtual_heatchill']
|
print(f" - 目标温度: {temp}°C")
|
||||||
|
print(f" - 持续时间: {time}秒")
|
||||||
if not heatchill_nodes:
|
print(f" - 使用内置搅拌: {stir}, 速度: {stir_speed} RPM")
|
||||||
raise ValueError("没有找到可用的加热/冷却设备")
|
print(f" - 目的: {purpose}")
|
||||||
|
|
||||||
heatchill_id = heatchill_nodes[0]
|
|
||||||
|
|
||||||
|
# 1. 验证容器存在
|
||||||
if vessel not in G.nodes():
|
if vessel not in G.nodes():
|
||||||
raise ValueError(f"容器 {vessel} 不存在于图中")
|
raise ValueError(f"容器 '{vessel}' 不存在于系统中")
|
||||||
|
|
||||||
action_sequence.append({
|
# 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,
|
"device_id": heatchill_id,
|
||||||
"action_name": "heat_chill",
|
"action_name": "heat_chill",
|
||||||
"action_kwargs": {
|
"action_kwargs": {
|
||||||
@@ -36,10 +64,13 @@ def generate_heat_chill_protocol(
|
|||||||
"time": time,
|
"time": time,
|
||||||
"stir": stir,
|
"stir": stir,
|
||||||
"stir_speed": stir_speed,
|
"stir_speed": stir_speed,
|
||||||
"purpose": purpose
|
"status": "start"
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
|
||||||
|
action_sequence.append(heatchill_action)
|
||||||
|
|
||||||
|
print(f"HEATCHILL: 生成了 {len(action_sequence)} 个动作")
|
||||||
return action_sequence
|
return action_sequence
|
||||||
|
|
||||||
|
|
||||||
@@ -47,25 +78,31 @@ def generate_heat_chill_start_protocol(
|
|||||||
G: nx.DiGraph,
|
G: nx.DiGraph,
|
||||||
vessel: str,
|
vessel: str,
|
||||||
temp: float,
|
temp: float,
|
||||||
purpose: str
|
purpose: str = "开始加热/冷却"
|
||||||
) -> List[Dict[str, Any]]:
|
) -> List[Dict[str, Any]]:
|
||||||
"""
|
"""
|
||||||
生成开始加热/冷却操作的协议序列 - 严格按照 HeatChillStart.action
|
生成开始加热/冷却操作的协议序列
|
||||||
"""
|
"""
|
||||||
action_sequence = []
|
action_sequence = []
|
||||||
|
|
||||||
heatchill_nodes = [node for node in G.nodes()
|
print(f"HEATCHILL_START: 开始生成加热/冷却启动协议")
|
||||||
if G.nodes[node].get('class') == 'virtual_heatchill']
|
print(f" - 容器: {vessel}")
|
||||||
|
print(f" - 目标温度: {temp}°C")
|
||||||
if not heatchill_nodes:
|
print(f" - 目的: {purpose}")
|
||||||
raise ValueError("没有找到可用的加热/冷却设备")
|
|
||||||
|
|
||||||
heatchill_id = heatchill_nodes[0]
|
|
||||||
|
|
||||||
|
# 1. 验证容器存在
|
||||||
if vessel not in G.nodes():
|
if vessel not in G.nodes():
|
||||||
raise ValueError(f"容器 {vessel} 不存在于图中")
|
raise ValueError(f"容器 '{vessel}' 不存在于系统中")
|
||||||
|
|
||||||
action_sequence.append({
|
# 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,
|
"device_id": heatchill_id,
|
||||||
"action_name": "heat_chill_start",
|
"action_name": "heat_chill_start",
|
||||||
"action_kwargs": {
|
"action_kwargs": {
|
||||||
@@ -73,8 +110,11 @@ def generate_heat_chill_start_protocol(
|
|||||||
"temp": temp,
|
"temp": temp,
|
||||||
"purpose": purpose
|
"purpose": purpose
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
|
||||||
|
action_sequence.append(heatchill_start_action)
|
||||||
|
|
||||||
|
print(f"HEATCHILL_START: 生成了 {len(action_sequence)} 个动作")
|
||||||
return action_sequence
|
return action_sequence
|
||||||
|
|
||||||
|
|
||||||
@@ -84,34 +124,250 @@ def generate_heat_chill_stop_protocol(
|
|||||||
) -> List[Dict[str, Any]]:
|
) -> List[Dict[str, Any]]:
|
||||||
"""
|
"""
|
||||||
生成停止加热/冷却操作的协议序列
|
生成停止加热/冷却操作的协议序列
|
||||||
|
|
||||||
Args:
|
|
||||||
G: 有向图,节点为设备和容器
|
|
||||||
vessel: 容器名称
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
List[Dict[str, Any]]: 停止加热/冷却操作的动作序列
|
|
||||||
"""
|
"""
|
||||||
action_sequence = []
|
action_sequence = []
|
||||||
|
|
||||||
# 查找加热/冷却设备
|
print(f"HEATCHILL_STOP: 开始生成加热/冷却停止协议")
|
||||||
heatchill_nodes = [node for node in G.nodes()
|
print(f" - 容器: {vessel}")
|
||||||
if G.nodes[node].get('class') == 'virtual_heatchill']
|
|
||||||
|
|
||||||
if not heatchill_nodes:
|
|
||||||
raise ValueError("没有找到可用的加热/冷却设备")
|
|
||||||
|
|
||||||
heatchill_id = heatchill_nodes[0]
|
|
||||||
|
|
||||||
|
# 1. 验证容器存在
|
||||||
if vessel not in G.nodes():
|
if vessel not in G.nodes():
|
||||||
raise ValueError(f"容器 {vessel} 不存在于图中")
|
raise ValueError(f"容器 '{vessel}' 不存在于系统中")
|
||||||
|
|
||||||
action_sequence.append({
|
# 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,
|
"device_id": heatchill_id,
|
||||||
"action_name": "heat_chill_stop",
|
"action_name": "heat_chill_stop",
|
||||||
"action_kwargs": {
|
"action_kwargs": {
|
||||||
"vessel": vessel
|
"vessel": vessel
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
|
||||||
return action_sequence
|
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()
|
||||||
366
unilabos/compile/hydrogenate_protocol.py
Normal file
366
unilabos/compile/hydrogenate_protocol.py
Normal file
@@ -0,0 +1,366 @@
|
|||||||
|
import networkx as nx
|
||||||
|
from typing import List, Dict, Any, Optional
|
||||||
|
|
||||||
|
|
||||||
|
def parse_temperature(temp_str: str) -> float:
|
||||||
|
"""
|
||||||
|
解析温度字符串,支持多种格式
|
||||||
|
|
||||||
|
Args:
|
||||||
|
temp_str: 温度字符串(如 "45 °C", "45°C", "45")
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
float: 温度值(摄氏度)
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# 移除常见的温度单位和符号
|
||||||
|
temp_clean = temp_str.replace("°C", "").replace("°", "").replace("C", "").strip()
|
||||||
|
return float(temp_clean)
|
||||||
|
except ValueError:
|
||||||
|
print(f"HYDROGENATE: 无法解析温度 '{temp_str}',使用默认温度 25°C")
|
||||||
|
return 25.0
|
||||||
|
|
||||||
|
|
||||||
|
def parse_time(time_str: str) -> float:
|
||||||
|
"""
|
||||||
|
解析时间字符串,支持多种格式
|
||||||
|
|
||||||
|
Args:
|
||||||
|
time_str: 时间字符串(如 "2 h", "120 min", "7200 s")
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
float: 时间值(秒)
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
time_clean = time_str.lower().strip()
|
||||||
|
|
||||||
|
# 处理小时
|
||||||
|
if "h" in time_clean:
|
||||||
|
hours = float(time_clean.replace("h", "").strip())
|
||||||
|
return hours * 3600.0
|
||||||
|
|
||||||
|
# 处理分钟
|
||||||
|
if "min" in time_clean:
|
||||||
|
minutes = float(time_clean.replace("min", "").strip())
|
||||||
|
return minutes * 60.0
|
||||||
|
|
||||||
|
# 处理秒
|
||||||
|
if "s" in time_clean:
|
||||||
|
seconds = float(time_clean.replace("s", "").strip())
|
||||||
|
return seconds
|
||||||
|
|
||||||
|
# 默认按小时处理
|
||||||
|
return float(time_clean) * 3600.0
|
||||||
|
|
||||||
|
except ValueError:
|
||||||
|
print(f"HYDROGENATE: 无法解析时间 '{time_str}',使用默认时间 2小时")
|
||||||
|
return 7200.0 # 2小时
|
||||||
|
|
||||||
|
|
||||||
|
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 'gas' in device_id.lower():
|
||||||
|
device_type = "gas"
|
||||||
|
elif 'h2' in device_id.lower() or 'hydrogen' 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 find_connected_device(G: nx.DiGraph, vessel: str, device_type: str) -> str:
|
||||||
|
"""
|
||||||
|
查找与容器相连的指定类型设备
|
||||||
|
|
||||||
|
Args:
|
||||||
|
G: 网络图
|
||||||
|
vessel: 容器名称
|
||||||
|
device_type: 设备类型 ('heater', 'stirrer', 'gas_source')
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: 设备ID,如果没有则返回None
|
||||||
|
"""
|
||||||
|
print(f"HYDROGENATE: 正在查找与容器 '{vessel}' 相连的 {device_type}...")
|
||||||
|
|
||||||
|
# 根据设备类型定义搜索关键词
|
||||||
|
if device_type == 'heater':
|
||||||
|
keywords = ['heater', 'heat', 'heatchill']
|
||||||
|
device_class = 'virtual_heatchill'
|
||||||
|
elif device_type == 'stirrer':
|
||||||
|
keywords = ['stirrer', 'stir']
|
||||||
|
device_class = 'virtual_stirrer'
|
||||||
|
elif device_type == 'gas_source':
|
||||||
|
keywords = ['gas', 'h2', 'hydrogen']
|
||||||
|
device_class = 'virtual_gas_source'
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# 查找设备节点
|
||||||
|
device_nodes = []
|
||||||
|
for node in G.nodes():
|
||||||
|
node_data = G.nodes[node]
|
||||||
|
node_name = node.lower()
|
||||||
|
node_class = node_data.get('class', '').lower()
|
||||||
|
|
||||||
|
# 通过名称匹配
|
||||||
|
if any(keyword in node_name for keyword in keywords):
|
||||||
|
device_nodes.append(node)
|
||||||
|
# 通过类型匹配
|
||||||
|
elif device_class in node_class:
|
||||||
|
device_nodes.append(node)
|
||||||
|
|
||||||
|
print(f"HYDROGENATE: 找到的{device_type}节点: {device_nodes}")
|
||||||
|
|
||||||
|
# 检查是否有设备与目标容器相连
|
||||||
|
for device in device_nodes:
|
||||||
|
if G.has_edge(device, vessel) or G.has_edge(vessel, device):
|
||||||
|
print(f"HYDROGENATE: 找到与容器 '{vessel}' 相连的{device_type}: {device}")
|
||||||
|
return device
|
||||||
|
|
||||||
|
# 如果没有直接连接,查找距离最近的设备
|
||||||
|
for device in device_nodes:
|
||||||
|
try:
|
||||||
|
path = nx.shortest_path(G, source=device, target=vessel)
|
||||||
|
if len(path) <= 3: # 最多2个中间节点
|
||||||
|
print(f"HYDROGENATE: 找到距离较近的{device_type}: {device}")
|
||||||
|
return device
|
||||||
|
except nx.NetworkXNoPath:
|
||||||
|
continue
|
||||||
|
|
||||||
|
print(f"HYDROGENATE: 未找到与容器 '{vessel}' 相连的{device_type}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def generate_hydrogenate_protocol(
|
||||||
|
G: nx.DiGraph,
|
||||||
|
temp: str,
|
||||||
|
time: str,
|
||||||
|
vessel: str,
|
||||||
|
**kwargs # 接收其他可能的参数但不使用
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
生成氢化反应协议序列
|
||||||
|
|
||||||
|
Args:
|
||||||
|
G: 有向图,节点为容器和设备
|
||||||
|
temp: 反应温度(如 "45 °C")
|
||||||
|
time: 反应时间(如 "2 h")
|
||||||
|
vessel: 反应容器
|
||||||
|
**kwargs: 其他可选参数,但不使用
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List[Dict[str, Any]]: 动作序列
|
||||||
|
"""
|
||||||
|
action_sequence = []
|
||||||
|
|
||||||
|
# 解析参数
|
||||||
|
temperature = parse_temperature(temp)
|
||||||
|
reaction_time = parse_time(time)
|
||||||
|
|
||||||
|
print(f"HYDROGENATE: 开始生成氢化反应协议")
|
||||||
|
print(f" - 反应温度: {temperature}°C")
|
||||||
|
print(f" - 反应时间: {reaction_time/3600:.1f} 小时")
|
||||||
|
print(f" - 反应容器: {vessel}")
|
||||||
|
|
||||||
|
# 1. 验证目标容器存在
|
||||||
|
if vessel not in G.nodes():
|
||||||
|
print(f"HYDROGENATE: 警告 - 容器 '{vessel}' 不存在于系统中,跳过氢化反应")
|
||||||
|
return action_sequence
|
||||||
|
|
||||||
|
# 2. 查找相连的设备
|
||||||
|
heater_id = find_connected_device(G, vessel, 'heater')
|
||||||
|
stirrer_id = find_connected_device(G, vessel, 'stirrer')
|
||||||
|
gas_source_id = find_connected_device(G, vessel, 'gas_source')
|
||||||
|
|
||||||
|
# 3. 启动搅拌器
|
||||||
|
if stirrer_id:
|
||||||
|
print(f"HYDROGENATE: 启动搅拌器 {stirrer_id}")
|
||||||
|
action_sequence.append({
|
||||||
|
"device_id": stirrer_id,
|
||||||
|
"action_name": "start_stir",
|
||||||
|
"action_kwargs": {
|
||||||
|
"vessel": vessel,
|
||||||
|
"stir_speed": 300.0,
|
||||||
|
"purpose": "氢化反应: 开始搅拌"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
print(f"HYDROGENATE: 警告 - 未找到搅拌器,继续执行")
|
||||||
|
|
||||||
|
# 4. 启动气源(氢气)- 修复版本
|
||||||
|
if gas_source_id:
|
||||||
|
print(f"HYDROGENATE: 启动气源 {gas_source_id} (氢气)")
|
||||||
|
action_sequence.append({
|
||||||
|
"device_id": gas_source_id,
|
||||||
|
"action_name": "set_status", # 修改为 set_status
|
||||||
|
"action_kwargs": {
|
||||||
|
"string": "ON" # 修改参数格式
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
# 查找相关的电磁阀
|
||||||
|
gas_solenoid = find_associated_solenoid_valve(G, gas_source_id)
|
||||||
|
if gas_solenoid:
|
||||||
|
print(f"HYDROGENATE: 开启气源电磁阀 {gas_solenoid}")
|
||||||
|
action_sequence.append({
|
||||||
|
"device_id": gas_solenoid,
|
||||||
|
"action_name": "set_valve_position",
|
||||||
|
"action_kwargs": {
|
||||||
|
"command": "OPEN"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
print(f"HYDROGENATE: 警告 - 未找到气源,继续执行")
|
||||||
|
|
||||||
|
# 5. 等待气体稳定
|
||||||
|
action_sequence.append({
|
||||||
|
"action_name": "wait",
|
||||||
|
"action_kwargs": {
|
||||||
|
"time": 30.0,
|
||||||
|
"description": "等待氢气环境稳定"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
# 6. 启动加热器
|
||||||
|
if heater_id:
|
||||||
|
print(f"HYDROGENATE: 启动加热器 {heater_id} 到 {temperature}°C")
|
||||||
|
action_sequence.append({
|
||||||
|
"device_id": heater_id,
|
||||||
|
"action_name": "heat_chill_start",
|
||||||
|
"action_kwargs": {
|
||||||
|
"vessel": vessel,
|
||||||
|
"temp": temperature,
|
||||||
|
"purpose": f"氢化反应: 加热到 {temperature}°C"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
# 等待温度稳定
|
||||||
|
action_sequence.append({
|
||||||
|
"action_name": "wait",
|
||||||
|
"action_kwargs": {
|
||||||
|
"time": 120.0,
|
||||||
|
"description": f"等待温度稳定到 {temperature}°C"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
# 保持反应温度
|
||||||
|
action_sequence.append({
|
||||||
|
"device_id": heater_id,
|
||||||
|
"action_name": "heat_chill",
|
||||||
|
"action_kwargs": {
|
||||||
|
"vessel": vessel,
|
||||||
|
"temp": temperature,
|
||||||
|
"time": reaction_time,
|
||||||
|
"purpose": f"氢化反应: 保持 {temperature}°C,反应 {reaction_time/3600:.1f} 小时"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
print(f"HYDROGENATE: 警告 - 未找到加热器,使用室温反应")
|
||||||
|
# 室温反应,只等待时间
|
||||||
|
action_sequence.append({
|
||||||
|
"action_name": "wait",
|
||||||
|
"action_kwargs": {
|
||||||
|
"time": reaction_time,
|
||||||
|
"description": f"室温氢化反应 {reaction_time/3600:.1f} 小时"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
# 7. 停止加热
|
||||||
|
if heater_id:
|
||||||
|
action_sequence.append({
|
||||||
|
"device_id": heater_id,
|
||||||
|
"action_name": "heat_chill_stop",
|
||||||
|
"action_kwargs": {
|
||||||
|
"vessel": vessel,
|
||||||
|
"purpose": "氢化反应完成,停止加热"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
# 8. 等待冷却
|
||||||
|
action_sequence.append({
|
||||||
|
"action_name": "wait",
|
||||||
|
"action_kwargs": {
|
||||||
|
"time": 300.0,
|
||||||
|
"description": "等待反应混合物冷却"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
# 9. 停止气源 - 修复版本
|
||||||
|
if gas_source_id:
|
||||||
|
# 先关闭电磁阀
|
||||||
|
gas_solenoid = find_associated_solenoid_valve(G, gas_source_id)
|
||||||
|
if gas_solenoid:
|
||||||
|
print(f"HYDROGENATE: 关闭气源电磁阀 {gas_solenoid}")
|
||||||
|
action_sequence.append({
|
||||||
|
"device_id": gas_solenoid,
|
||||||
|
"action_name": "set_valve_position",
|
||||||
|
"action_kwargs": {
|
||||||
|
"command": "CLOSED"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
# 再关闭气源
|
||||||
|
action_sequence.append({
|
||||||
|
"device_id": gas_source_id,
|
||||||
|
"action_name": "set_status", # 修改为 set_status
|
||||||
|
"action_kwargs": {
|
||||||
|
"string": "OFF" # 修改参数格式
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
# 10. 停止搅拌
|
||||||
|
if stirrer_id:
|
||||||
|
action_sequence.append({
|
||||||
|
"device_id": stirrer_id,
|
||||||
|
"action_name": "stop_stir",
|
||||||
|
"action_kwargs": {
|
||||||
|
"vessel": vessel,
|
||||||
|
"purpose": "氢化反应完成,停止搅拌"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
print(f"HYDROGENATE: 协议生成完成,共 {len(action_sequence)} 个动作")
|
||||||
|
print(f"HYDROGENATE: 预计总时间: {(reaction_time + 450)/3600:.1f} 小时")
|
||||||
|
|
||||||
|
return action_sequence
|
||||||
|
|
||||||
|
|
||||||
|
# 测试函数
|
||||||
|
def test_hydrogenate_protocol():
|
||||||
|
"""测试氢化反应协议"""
|
||||||
|
print("=== HYDROGENATE PROTOCOL 测试 ===")
|
||||||
|
|
||||||
|
# 测试温度解析
|
||||||
|
test_temps = ["45 °C", "45°C", "45", "25 C", "invalid"]
|
||||||
|
for temp in test_temps:
|
||||||
|
parsed = parse_temperature(temp)
|
||||||
|
print(f"温度 '{temp}' -> {parsed}°C")
|
||||||
|
|
||||||
|
# 测试时间解析
|
||||||
|
test_times = ["2 h", "120 min", "7200 s", "2", "invalid"]
|
||||||
|
for time in test_times:
|
||||||
|
parsed = parse_time(time)
|
||||||
|
print(f"时间 '{time}' -> {parsed/3600:.1f} 小时")
|
||||||
|
|
||||||
|
print("测试完成")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
test_hydrogenate_protocol()
|
||||||
@@ -8,7 +8,8 @@ def is_integrated_pump(node_name):
|
|||||||
|
|
||||||
def find_connected_pump(G, valve_node):
|
def find_connected_pump(G, valve_node):
|
||||||
for neighbor in G.neighbors(valve_node):
|
for neighbor in G.neighbors(valve_node):
|
||||||
if "pump" in G.nodes[neighbor]["class"]:
|
node_class = G.nodes[neighbor].get("class") or "" # 防止 None
|
||||||
|
if "pump" in node_class:
|
||||||
return neighbor
|
return neighbor
|
||||||
raise ValueError(f"未找到与阀 {valve_node} 唯一相连的泵节点")
|
raise ValueError(f"未找到与阀 {valve_node} 唯一相连的泵节点")
|
||||||
|
|
||||||
|
|||||||
281
unilabos/compile/recrystallize_protocol.py
Normal file
281
unilabos/compile/recrystallize_protocol.py
Normal file
@@ -0,0 +1,281 @@
|
|||||||
|
import networkx as nx
|
||||||
|
from typing import List, Dict, Any, Tuple
|
||||||
|
from .pump_protocol import generate_pump_protocol_with_rinsing
|
||||||
|
|
||||||
|
|
||||||
|
def parse_ratio(ratio_str: str) -> Tuple[float, float]:
|
||||||
|
"""
|
||||||
|
解析比例字符串,支持多种格式
|
||||||
|
|
||||||
|
Args:
|
||||||
|
ratio_str: 比例字符串(如 "1:1", "3:7", "50:50")
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Tuple[float, float]: 比例元组 (ratio1, ratio2)
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# 处理 "1:1", "3:7", "50:50" 等格式
|
||||||
|
if ":" in ratio_str:
|
||||||
|
parts = ratio_str.split(":")
|
||||||
|
if len(parts) == 2:
|
||||||
|
ratio1 = float(parts[0])
|
||||||
|
ratio2 = float(parts[1])
|
||||||
|
return ratio1, ratio2
|
||||||
|
|
||||||
|
# 处理 "1-1", "3-7" 等格式
|
||||||
|
if "-" in ratio_str:
|
||||||
|
parts = ratio_str.split("-")
|
||||||
|
if len(parts) == 2:
|
||||||
|
ratio1 = float(parts[0])
|
||||||
|
ratio2 = float(parts[1])
|
||||||
|
return ratio1, ratio2
|
||||||
|
|
||||||
|
# 处理 "1,1", "3,7" 等格式
|
||||||
|
if "," in ratio_str:
|
||||||
|
parts = ratio_str.split(",")
|
||||||
|
if len(parts) == 2:
|
||||||
|
ratio1 = float(parts[0])
|
||||||
|
ratio2 = float(parts[1])
|
||||||
|
return ratio1, ratio2
|
||||||
|
|
||||||
|
# 默认 1:1
|
||||||
|
print(f"RECRYSTALLIZE: 无法解析比例 '{ratio_str}',使用默认比例 1:1")
|
||||||
|
return 1.0, 1.0
|
||||||
|
|
||||||
|
except ValueError:
|
||||||
|
print(f"RECRYSTALLIZE: 比例解析错误 '{ratio_str}',使用默认比例 1:1")
|
||||||
|
return 1.0, 1.0
|
||||||
|
|
||||||
|
|
||||||
|
def find_solvent_vessel(G: nx.DiGraph, solvent: str) -> str:
|
||||||
|
"""
|
||||||
|
查找溶剂容器
|
||||||
|
|
||||||
|
Args:
|
||||||
|
G: 网络图
|
||||||
|
solvent: 溶剂名称
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: 溶剂容器ID
|
||||||
|
"""
|
||||||
|
print(f"RECRYSTALLIZE: 正在查找溶剂 '{solvent}' 的容器...")
|
||||||
|
|
||||||
|
# 构建可能的容器名称
|
||||||
|
possible_names = [
|
||||||
|
f"flask_{solvent}",
|
||||||
|
f"bottle_{solvent}",
|
||||||
|
f"reagent_{solvent}",
|
||||||
|
f"reagent_bottle_{solvent}",
|
||||||
|
f"{solvent}_flask",
|
||||||
|
f"{solvent}_bottle",
|
||||||
|
f"{solvent}",
|
||||||
|
f"vessel_{solvent}",
|
||||||
|
]
|
||||||
|
|
||||||
|
# 第一步:通过容器名称匹配
|
||||||
|
for vessel_name in possible_names:
|
||||||
|
if vessel_name in G.nodes():
|
||||||
|
print(f"RECRYSTALLIZE: 通过名称匹配找到容器: {vessel_name}")
|
||||||
|
return vessel_name
|
||||||
|
|
||||||
|
# 第二步:通过模糊匹配
|
||||||
|
for node_id in G.nodes():
|
||||||
|
if G.nodes[node_id].get('type') == 'container':
|
||||||
|
node_name = G.nodes[node_id].get('name', '').lower()
|
||||||
|
|
||||||
|
if solvent.lower() in node_id.lower() or solvent.lower() in node_name:
|
||||||
|
print(f"RECRYSTALLIZE: 通过模糊匹配找到容器: {node_id}")
|
||||||
|
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', '')).lower()
|
||||||
|
reagent_name = vessel_data.get('reagent_name', '').lower()
|
||||||
|
|
||||||
|
if solvent.lower() in liquid_type or solvent.lower() in reagent_name:
|
||||||
|
print(f"RECRYSTALLIZE: 通过液体类型匹配找到容器: {node_id}")
|
||||||
|
return node_id
|
||||||
|
|
||||||
|
raise ValueError(f"找不到溶剂 '{solvent}' 对应的容器")
|
||||||
|
|
||||||
|
|
||||||
|
def generate_recrystallize_protocol(
|
||||||
|
G: nx.DiGraph,
|
||||||
|
ratio: str,
|
||||||
|
solvent1: str,
|
||||||
|
solvent2: str,
|
||||||
|
vessel: str,
|
||||||
|
volume: float,
|
||||||
|
**kwargs # 接收其他可能的参数但不使用
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
生成重结晶协议序列
|
||||||
|
|
||||||
|
Args:
|
||||||
|
G: 有向图,节点为容器和设备
|
||||||
|
ratio: 溶剂比例(如 "1:1", "3:7")
|
||||||
|
solvent1: 第一种溶剂名称
|
||||||
|
solvent2: 第二种溶剂名称
|
||||||
|
vessel: 目标容器
|
||||||
|
volume: 总体积 (mL)
|
||||||
|
**kwargs: 其他可选参数,但不使用
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List[Dict[str, Any]]: 动作序列
|
||||||
|
"""
|
||||||
|
action_sequence = []
|
||||||
|
|
||||||
|
print(f"RECRYSTALLIZE: 开始生成重结晶协议")
|
||||||
|
print(f" - 比例: {ratio}")
|
||||||
|
print(f" - 溶剂1: {solvent1}")
|
||||||
|
print(f" - 溶剂2: {solvent2}")
|
||||||
|
print(f" - 容器: {vessel}")
|
||||||
|
print(f" - 总体积: {volume} mL")
|
||||||
|
|
||||||
|
# 1. 验证目标容器存在
|
||||||
|
if vessel not in G.nodes():
|
||||||
|
raise ValueError(f"目标容器 '{vessel}' 不存在于系统中")
|
||||||
|
|
||||||
|
# 2. 解析比例
|
||||||
|
ratio1, ratio2 = parse_ratio(ratio)
|
||||||
|
total_ratio = ratio1 + ratio2
|
||||||
|
|
||||||
|
# 3. 计算各溶剂体积
|
||||||
|
volume1 = volume * (ratio1 / total_ratio)
|
||||||
|
volume2 = volume * (ratio2 / total_ratio)
|
||||||
|
|
||||||
|
print(f"RECRYSTALLIZE: 解析比例: {ratio1}:{ratio2}")
|
||||||
|
print(f"RECRYSTALLIZE: {solvent1} 体积: {volume1:.2f} mL")
|
||||||
|
print(f"RECRYSTALLIZE: {solvent2} 体积: {volume2:.2f} mL")
|
||||||
|
|
||||||
|
# 4. 查找溶剂容器
|
||||||
|
try:
|
||||||
|
solvent1_vessel = find_solvent_vessel(G, solvent1)
|
||||||
|
print(f"RECRYSTALLIZE: 找到溶剂1容器: {solvent1_vessel}")
|
||||||
|
except ValueError as e:
|
||||||
|
raise ValueError(f"无法找到溶剂1 '{solvent1}': {str(e)}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
solvent2_vessel = find_solvent_vessel(G, solvent2)
|
||||||
|
print(f"RECRYSTALLIZE: 找到溶剂2容器: {solvent2_vessel}")
|
||||||
|
except ValueError as e:
|
||||||
|
raise ValueError(f"无法找到溶剂2 '{solvent2}': {str(e)}")
|
||||||
|
|
||||||
|
# 5. 验证路径存在
|
||||||
|
try:
|
||||||
|
path1 = nx.shortest_path(G, source=solvent1_vessel, target=vessel)
|
||||||
|
print(f"RECRYSTALLIZE: 溶剂1路径: {' → '.join(path1)}")
|
||||||
|
except nx.NetworkXNoPath:
|
||||||
|
raise ValueError(f"从溶剂1容器 '{solvent1_vessel}' 到目标容器 '{vessel}' 没有可用路径")
|
||||||
|
|
||||||
|
try:
|
||||||
|
path2 = nx.shortest_path(G, source=solvent2_vessel, target=vessel)
|
||||||
|
print(f"RECRYSTALLIZE: 溶剂2路径: {' → '.join(path2)}")
|
||||||
|
except nx.NetworkXNoPath:
|
||||||
|
raise ValueError(f"从溶剂2容器 '{solvent2_vessel}' 到目标容器 '{vessel}' 没有可用路径")
|
||||||
|
|
||||||
|
# 6. 添加第一种溶剂
|
||||||
|
print(f"RECRYSTALLIZE: 开始添加溶剂1 {volume1:.2f} mL")
|
||||||
|
|
||||||
|
try:
|
||||||
|
pump_actions1 = generate_pump_protocol_with_rinsing(
|
||||||
|
G=G,
|
||||||
|
from_vessel=solvent1_vessel,
|
||||||
|
to_vessel=vessel,
|
||||||
|
volume=volume1,
|
||||||
|
amount="",
|
||||||
|
time=0.0,
|
||||||
|
viscous=False,
|
||||||
|
rinsing_solvent="", # 重结晶不需要清洗
|
||||||
|
rinsing_volume=0.0,
|
||||||
|
rinsing_repeats=0,
|
||||||
|
solid=False,
|
||||||
|
flowrate=2.0, # 正常流速
|
||||||
|
transfer_flowrate=0.5
|
||||||
|
)
|
||||||
|
|
||||||
|
action_sequence.extend(pump_actions1)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
raise ValueError(f"生成溶剂1泵协议时出错: {str(e)}")
|
||||||
|
|
||||||
|
# 7. 等待溶剂1稳定
|
||||||
|
action_sequence.append({
|
||||||
|
"action_name": "wait",
|
||||||
|
"action_kwargs": {
|
||||||
|
"time": 10.0,
|
||||||
|
"description": f"等待溶剂1 {solvent1} 稳定"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
# 8. 添加第二种溶剂
|
||||||
|
print(f"RECRYSTALLIZE: 开始添加溶剂2 {volume2:.2f} mL")
|
||||||
|
|
||||||
|
try:
|
||||||
|
pump_actions2 = generate_pump_protocol_with_rinsing(
|
||||||
|
G=G,
|
||||||
|
from_vessel=solvent2_vessel,
|
||||||
|
to_vessel=vessel,
|
||||||
|
volume=volume2,
|
||||||
|
amount="",
|
||||||
|
time=0.0,
|
||||||
|
viscous=False,
|
||||||
|
rinsing_solvent="", # 重结晶不需要清洗
|
||||||
|
rinsing_volume=0.0,
|
||||||
|
rinsing_repeats=0,
|
||||||
|
solid=False,
|
||||||
|
flowrate=2.0, # 正常流速
|
||||||
|
transfer_flowrate=0.5
|
||||||
|
)
|
||||||
|
|
||||||
|
action_sequence.extend(pump_actions2)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
raise ValueError(f"生成溶剂2泵协议时出错: {str(e)}")
|
||||||
|
|
||||||
|
# 9. 等待溶剂2稳定
|
||||||
|
action_sequence.append({
|
||||||
|
"action_name": "wait",
|
||||||
|
"action_kwargs": {
|
||||||
|
"time": 10.0,
|
||||||
|
"description": f"等待溶剂2 {solvent2} 稳定"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
# 10. 等待重结晶完成
|
||||||
|
action_sequence.append({
|
||||||
|
"action_name": "wait",
|
||||||
|
"action_kwargs": {
|
||||||
|
"time": 600.0, # 等待10分钟进行重结晶
|
||||||
|
"description": f"等待重结晶完成({solvent1}:{solvent2} = {ratio})"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
print(f"RECRYSTALLIZE: 协议生成完成,共 {len(action_sequence)} 个动作")
|
||||||
|
print(f"RECRYSTALLIZE: 预计总时间: {620/60:.1f} 分钟")
|
||||||
|
|
||||||
|
return action_sequence
|
||||||
|
|
||||||
|
|
||||||
|
# 测试函数
|
||||||
|
def test_recrystallize_protocol():
|
||||||
|
"""测试重结晶协议"""
|
||||||
|
print("=== RECRYSTALLIZE PROTOCOL 测试 ===")
|
||||||
|
|
||||||
|
# 测试比例解析
|
||||||
|
test_ratios = ["1:1", "3:7", "50:50", "1-1", "2,8", "invalid"]
|
||||||
|
for ratio in test_ratios:
|
||||||
|
r1, r2 = parse_ratio(ratio)
|
||||||
|
print(f"比例 '{ratio}' -> {r1}:{r2}")
|
||||||
|
|
||||||
|
print("测试完成")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
test_recrystallize_protocol()
|
||||||
180
unilabos/compile/reset_handling_protocol.py
Normal file
180
unilabos/compile/reset_handling_protocol.py
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
import networkx as nx
|
||||||
|
from typing import List, Dict, Any
|
||||||
|
from .pump_protocol import generate_pump_protocol_with_rinsing
|
||||||
|
|
||||||
|
|
||||||
|
def find_solvent_vessel(G: nx.DiGraph, solvent: str) -> str:
|
||||||
|
"""
|
||||||
|
查找溶剂容器,支持多种匹配模式
|
||||||
|
|
||||||
|
Args:
|
||||||
|
G: 网络图
|
||||||
|
solvent: 溶剂名称(如 "methanol", "ethanol", "water")
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: 溶剂容器ID
|
||||||
|
"""
|
||||||
|
print(f"RESET_HANDLING: 正在查找溶剂 '{solvent}' 的容器...")
|
||||||
|
|
||||||
|
# 构建可能的容器名称
|
||||||
|
possible_names = [
|
||||||
|
f"flask_{solvent}", # flask_methanol
|
||||||
|
f"bottle_{solvent}", # bottle_methanol
|
||||||
|
f"reagent_{solvent}", # reagent_methanol
|
||||||
|
f"reagent_bottle_{solvent}", # reagent_bottle_methanol
|
||||||
|
f"{solvent}_flask", # methanol_flask
|
||||||
|
f"{solvent}_bottle", # methanol_bottle
|
||||||
|
f"{solvent}", # methanol
|
||||||
|
f"vessel_{solvent}", # vessel_methanol
|
||||||
|
]
|
||||||
|
|
||||||
|
# 第一步:通过容器名称匹配
|
||||||
|
for vessel_name in possible_names:
|
||||||
|
if vessel_name in G.nodes():
|
||||||
|
print(f"RESET_HANDLING: 通过名称匹配找到容器: {vessel_name}")
|
||||||
|
return vessel_name
|
||||||
|
|
||||||
|
# 第二步:通过模糊匹配
|
||||||
|
for node_id in G.nodes():
|
||||||
|
if G.nodes[node_id].get('type') == 'container':
|
||||||
|
node_name = G.nodes[node_id].get('name', '').lower()
|
||||||
|
|
||||||
|
# 检查是否包含溶剂名称
|
||||||
|
if solvent.lower() in node_id.lower() or solvent.lower() in node_name:
|
||||||
|
print(f"RESET_HANDLING: 通过模糊匹配找到容器: {node_id}")
|
||||||
|
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', '')).lower()
|
||||||
|
reagent_name = vessel_data.get('reagent_name', '').lower()
|
||||||
|
|
||||||
|
if solvent.lower() in liquid_type or solvent.lower() in reagent_name:
|
||||||
|
print(f"RESET_HANDLING: 通过液体类型匹配找到容器: {node_id}")
|
||||||
|
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', {})
|
||||||
|
liquids = vessel_data.get('liquid', [])
|
||||||
|
liquid_types = [liquid.get('liquid_type', '') or liquid.get('name', '')
|
||||||
|
for liquid in liquids if isinstance(liquid, dict)]
|
||||||
|
|
||||||
|
available_containers.append({
|
||||||
|
'id': node_id,
|
||||||
|
'name': G.nodes[node_id].get('name', ''),
|
||||||
|
'liquids': liquid_types,
|
||||||
|
'reagent_name': vessel_data.get('reagent_name', '')
|
||||||
|
})
|
||||||
|
|
||||||
|
print(f"RESET_HANDLING: 可用容器列表:")
|
||||||
|
for container in available_containers:
|
||||||
|
print(f" - {container['id']}: {container['name']}")
|
||||||
|
print(f" 液体: {container['liquids']}")
|
||||||
|
print(f" 试剂: {container['reagent_name']}")
|
||||||
|
|
||||||
|
raise ValueError(f"找不到溶剂 '{solvent}' 对应的容器。尝试了: {possible_names}")
|
||||||
|
|
||||||
|
|
||||||
|
def generate_reset_handling_protocol(
|
||||||
|
G: nx.DiGraph,
|
||||||
|
solvent: str,
|
||||||
|
**kwargs # 接收其他可能的参数但不使用
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
生成重置处理协议序列
|
||||||
|
|
||||||
|
Args:
|
||||||
|
G: 有向图,节点为容器和设备
|
||||||
|
solvent: 溶剂名称(从XDL传入)
|
||||||
|
**kwargs: 其他可选参数,但不使用
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List[Dict[str, Any]]: 动作序列
|
||||||
|
"""
|
||||||
|
action_sequence = []
|
||||||
|
|
||||||
|
# 固定参数
|
||||||
|
target_vessel = "main_reactor" # 默认目标容器
|
||||||
|
volume = 100.0 # 默认体积 100 mL
|
||||||
|
|
||||||
|
print(f"RESET_HANDLING: 开始生成重置处理协议")
|
||||||
|
print(f" - 溶剂: {solvent}")
|
||||||
|
print(f" - 目标容器: {target_vessel}")
|
||||||
|
print(f" - 体积: {volume} mL")
|
||||||
|
|
||||||
|
# 1. 验证目标容器存在
|
||||||
|
if target_vessel not in G.nodes():
|
||||||
|
raise ValueError(f"目标容器 '{target_vessel}' 不存在于系统中")
|
||||||
|
|
||||||
|
# 2. 查找溶剂容器
|
||||||
|
try:
|
||||||
|
solvent_vessel = find_solvent_vessel(G, solvent)
|
||||||
|
print(f"RESET_HANDLING: 找到溶剂容器: {solvent_vessel}")
|
||||||
|
except ValueError as e:
|
||||||
|
raise ValueError(f"无法找到溶剂 '{solvent}': {str(e)}")
|
||||||
|
|
||||||
|
# 3. 验证路径存在
|
||||||
|
try:
|
||||||
|
path = nx.shortest_path(G, source=solvent_vessel, target=target_vessel)
|
||||||
|
print(f"RESET_HANDLING: 找到路径: {' → '.join(path)}")
|
||||||
|
except nx.NetworkXNoPath:
|
||||||
|
raise ValueError(f"从溶剂容器 '{solvent_vessel}' 到目标容器 '{target_vessel}' 没有可用路径")
|
||||||
|
|
||||||
|
# 4. 使用pump_protocol转移溶剂
|
||||||
|
print(f"RESET_HANDLING: 开始转移溶剂 {volume} mL")
|
||||||
|
|
||||||
|
try:
|
||||||
|
pump_actions = generate_pump_protocol_with_rinsing(
|
||||||
|
G=G,
|
||||||
|
from_vessel=solvent_vessel,
|
||||||
|
to_vessel=target_vessel,
|
||||||
|
volume=volume,
|
||||||
|
amount="",
|
||||||
|
time=0.0,
|
||||||
|
viscous=False,
|
||||||
|
rinsing_solvent="", # 重置处理不需要清洗
|
||||||
|
rinsing_volume=0.0,
|
||||||
|
rinsing_repeats=0,
|
||||||
|
solid=False,
|
||||||
|
flowrate=2.5, # 正常流速
|
||||||
|
transfer_flowrate=0.5 # 正常转移流速
|
||||||
|
)
|
||||||
|
|
||||||
|
action_sequence.extend(pump_actions)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
raise ValueError(f"生成泵协议时出错: {str(e)}")
|
||||||
|
|
||||||
|
# 5. 等待溶剂稳定
|
||||||
|
action_sequence.append({
|
||||||
|
"action_name": "wait",
|
||||||
|
"action_kwargs": {
|
||||||
|
"time": 10.0,
|
||||||
|
"description": f"等待溶剂 {solvent} 稳定"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
print(f"RESET_HANDLING: 协议生成完成,共 {len(action_sequence)} 个动作")
|
||||||
|
print(f"RESET_HANDLING: 已添加 {volume} mL {solvent} 到 {target_vessel}")
|
||||||
|
|
||||||
|
return action_sequence
|
||||||
|
|
||||||
|
|
||||||
|
# 测试函数
|
||||||
|
def test_reset_handling_protocol():
|
||||||
|
"""测试重置处理协议"""
|
||||||
|
print("=== RESET HANDLING PROTOCOL 测试 ===")
|
||||||
|
print("测试完成")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
test_reset_handling_protocol()
|
||||||
@@ -1,5 +1,87 @@
|
|||||||
from typing import List, Dict, Any
|
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):
|
||||||
|
# 支持两种格式:新格式 (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(
|
def generate_run_column_protocol(
|
||||||
G: nx.DiGraph,
|
G: nx.DiGraph,
|
||||||
@@ -11,92 +93,220 @@ def generate_run_column_protocol(
|
|||||||
生成柱层析分离的协议序列
|
生成柱层析分离的协议序列
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
G: 有向图,节点为设备和容器
|
G: 有向图,节点为设备和容器,边为流体管道
|
||||||
from_vessel: 源容器的名称,即样品起始所在的容器
|
from_vessel: 源容器的名称,即样品起始所在的容器
|
||||||
to_vessel: 目标容器的名称,分离后的样品要到达的容器
|
to_vessel: 目标容器的名称,分离后的样品要到达的容器
|
||||||
column: 所使用的柱子的名称
|
column: 所使用的柱子的名称
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
List[Dict[str, Any]]: 柱层析分离操作的动作序列
|
List[Dict[str, Any]]: 柱层析分离操作的动作序列
|
||||||
|
|
||||||
Raises:
|
|
||||||
ValueError: 当找不到必要的设备时抛出异常
|
|
||||||
|
|
||||||
Examples:
|
|
||||||
run_column_protocol = generate_run_column_protocol(G, "reactor", "collection_flask", "silica_column")
|
|
||||||
"""
|
"""
|
||||||
action_sequence = []
|
action_sequence = []
|
||||||
|
|
||||||
# 验证容器是否存在
|
print(f"RUN_COLUMN: 开始生成柱层析协议")
|
||||||
|
print(f" - 源容器: {from_vessel}")
|
||||||
|
print(f" - 目标容器: {to_vessel}")
|
||||||
|
print(f" - 柱子: {column}")
|
||||||
|
|
||||||
|
# 验证源容器和目标容器存在
|
||||||
if from_vessel not in G.nodes():
|
if from_vessel not in G.nodes():
|
||||||
raise ValueError(f"源容器 {from_vessel} 不存在于图中")
|
raise ValueError(f"源容器 '{from_vessel}' 不存在于系统中")
|
||||||
|
|
||||||
if to_vessel not in G.nodes():
|
if to_vessel not in G.nodes():
|
||||||
raise ValueError(f"目标容器 {to_vessel} 不存在于图中")
|
raise ValueError(f"目标容器 '{to_vessel}' 不存在于系统中")
|
||||||
|
|
||||||
# 查找转移泵设备(用于样品转移)
|
|
||||||
pump_nodes = [node for node in G.nodes()
|
|
||||||
if G.nodes[node].get('class') == 'virtual_transfer_pump']
|
|
||||||
|
|
||||||
if not pump_nodes:
|
|
||||||
raise ValueError("没有找到可用的转移泵设备")
|
|
||||||
|
|
||||||
pump_id = pump_nodes[0]
|
|
||||||
|
|
||||||
# 查找柱层析设备
|
# 查找柱层析设备
|
||||||
|
column_device_id = None
|
||||||
column_nodes = [node for node in G.nodes()
|
column_nodes = [node for node in G.nodes()
|
||||||
if G.nodes[node].get('class') == 'virtual_column']
|
if (G.nodes[node].get('class') or '') == 'virtual_column']
|
||||||
|
|
||||||
if not column_nodes:
|
if column_nodes:
|
||||||
raise ValueError("没有找到可用的柱层析设备")
|
column_device_id = column_nodes[0]
|
||||||
|
print(f"RUN_COLUMN: 找到柱层析设备: {column_device_id}")
|
||||||
|
else:
|
||||||
|
print(f"RUN_COLUMN: 警告 - 未找到柱层析设备")
|
||||||
|
|
||||||
column_id = column_nodes[0]
|
# 获取源容器中的液体体积
|
||||||
|
source_volume = get_vessel_liquid_volume(G, from_vessel)
|
||||||
|
print(f"RUN_COLUMN: 源容器 {from_vessel} 中有 {source_volume} mL 液体")
|
||||||
|
|
||||||
# 步骤1:将样品从源容器转移到柱子上
|
# === 第一步:样品转移到柱子(如果柱子是容器) ===
|
||||||
action_sequence.append({
|
if column in G.nodes() and G.nodes[column].get('type') == 'container':
|
||||||
"device_id": pump_id,
|
print(f"RUN_COLUMN: 样品转移 - {source_volume} mL 从 {from_vessel} 到 {column}")
|
||||||
"action_name": "transfer",
|
|
||||||
"action_kwargs": {
|
try:
|
||||||
"from_vessel": from_vessel,
|
sample_transfer_actions = generate_pump_protocol(
|
||||||
"to_vessel": column_id, # 将样品转移到柱子设备
|
G=G,
|
||||||
"volume": 0.0, # 转移所有液体,体积由系统确定
|
from_vessel=from_vessel,
|
||||||
"amount": f"样品上柱 - 使用 {column}",
|
to_vessel=column,
|
||||||
"time": 0.0,
|
volume=source_volume if source_volume > 0 else 100.0,
|
||||||
"viscous": False,
|
flowrate=2.0
|
||||||
"rinsing_solvent": "",
|
)
|
||||||
"rinsing_volume": 0.0,
|
action_sequence.extend(sample_transfer_actions)
|
||||||
"rinsing_repeats": 0,
|
except Exception as e:
|
||||||
"solid": False
|
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}
|
||||||
|
})
|
||||||
|
|
||||||
# 步骤2:运行柱层析分离
|
# === 第三步:从柱子转移到目标容器(如果需要) ===
|
||||||
action_sequence.append({
|
if column in G.nodes() and column != to_vessel:
|
||||||
"device_id": column_id,
|
print(f"RUN_COLUMN: 产物转移 - 从 {column} 到 {to_vessel}")
|
||||||
"action_name": "run_column",
|
|
||||||
"action_kwargs": {
|
try:
|
||||||
"from_vessel": from_vessel,
|
product_transfer_actions = generate_pump_protocol(
|
||||||
"to_vessel": to_vessel,
|
G=G,
|
||||||
"column": column
|
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)}")
|
||||||
|
|
||||||
# 步骤3:将分离后的产物从柱子转移到目标容器
|
print(f"RUN_COLUMN: 生成了 {len(action_sequence)} 个动作")
|
||||||
action_sequence.append({
|
return action_sequence
|
||||||
"device_id": pump_id,
|
|
||||||
"action_name": "transfer",
|
|
||||||
"action_kwargs": {
|
# 便捷函数:常用柱层析方案
|
||||||
"from_vessel": column_id, # 从柱子设备转移
|
def generate_flash_column_protocol(
|
||||||
"to_vessel": to_vessel,
|
G: nx.DiGraph,
|
||||||
"volume": 0.0, # 转移所有液体,体积由系统确定
|
from_vessel: str,
|
||||||
"amount": f"收集分离产物 - 来自 {column}",
|
to_vessel: str,
|
||||||
"time": 0.0,
|
column_material: str = "silica_gel",
|
||||||
"viscous": False,
|
mobile_phase: str = "ethyl_acetate",
|
||||||
"rinsing_solvent": "",
|
mobile_phase_volume: float = 100.0
|
||||||
"rinsing_volume": 0.0,
|
) -> List[Dict[str, Any]]:
|
||||||
"rinsing_repeats": 0,
|
"""快速柱层析:高流速分离"""
|
||||||
"solid": False
|
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]
|
||||||
|
|
||||||
return action_sequence
|
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()
|
||||||
@@ -1,6 +1,28 @@
|
|||||||
from typing import List, Dict, Any
|
from typing import List, Dict, Any
|
||||||
import networkx as nx
|
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(
|
def generate_stir_protocol(
|
||||||
G: nx.DiGraph,
|
G: nx.DiGraph,
|
||||||
stir_time: float,
|
stir_time: float,
|
||||||
@@ -8,37 +30,24 @@ def generate_stir_protocol(
|
|||||||
settling_time: float
|
settling_time: float
|
||||||
) -> List[Dict[str, Any]]:
|
) -> List[Dict[str, Any]]:
|
||||||
"""
|
"""
|
||||||
生成搅拌操作的协议序列
|
生成搅拌操作的协议序列 - 定时搅拌 + 沉降
|
||||||
|
|
||||||
Args:
|
|
||||||
G: 有向图,节点为设备和容器
|
|
||||||
stir_time: 搅拌时间 (秒)
|
|
||||||
stir_speed: 搅拌速度 (rpm)
|
|
||||||
settling_time: 沉降时间 (秒)
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
List[Dict[str, Any]]: 搅拌操作的动作序列
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
ValueError: 当找不到搅拌设备时抛出异常
|
|
||||||
|
|
||||||
Examples:
|
|
||||||
stir_protocol = generate_stir_protocol(G, 300.0, 500.0, 60.0)
|
|
||||||
"""
|
"""
|
||||||
action_sequence = []
|
action_sequence = []
|
||||||
|
|
||||||
|
print(f"STIR: 开始生成搅拌协议")
|
||||||
|
print(f" - 搅拌时间: {stir_time}秒")
|
||||||
|
print(f" - 搅拌速度: {stir_speed} RPM")
|
||||||
|
print(f" - 沉降时间: {settling_time}秒")
|
||||||
|
|
||||||
# 查找搅拌设备
|
# 查找搅拌设备
|
||||||
stirrer_nodes = [node for node in G.nodes()
|
try:
|
||||||
if G.nodes[node].get('class') == 'virtual_stirrer']
|
stirrer_id = find_connected_stirrer(G)
|
||||||
|
print(f"STIR: 找到搅拌设备: {stirrer_id}")
|
||||||
if not stirrer_nodes:
|
except ValueError as e:
|
||||||
raise ValueError("没有找到可用的搅拌设备")
|
raise ValueError(f"无法找到搅拌设备: {str(e)}")
|
||||||
|
|
||||||
# 使用第一个可用的搅拌器
|
|
||||||
stirrer_id = stirrer_nodes[0]
|
|
||||||
|
|
||||||
# 执行搅拌操作
|
# 执行搅拌操作
|
||||||
action_sequence.append({
|
stir_action = {
|
||||||
"device_id": stirrer_id,
|
"device_id": stirrer_id,
|
||||||
"action_name": "stir",
|
"action_name": "stir",
|
||||||
"action_kwargs": {
|
"action_kwargs": {
|
||||||
@@ -46,8 +55,11 @@ def generate_stir_protocol(
|
|||||||
"stir_speed": stir_speed,
|
"stir_speed": stir_speed,
|
||||||
"settling_time": settling_time
|
"settling_time": settling_time
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
|
||||||
|
action_sequence.append(stir_action)
|
||||||
|
|
||||||
|
print(f"STIR: 生成了 {len(action_sequence)} 个动作")
|
||||||
return action_sequence
|
return action_sequence
|
||||||
|
|
||||||
|
|
||||||
@@ -58,33 +70,28 @@ def generate_start_stir_protocol(
|
|||||||
purpose: str
|
purpose: str
|
||||||
) -> List[Dict[str, Any]]:
|
) -> List[Dict[str, Any]]:
|
||||||
"""
|
"""
|
||||||
生成开始搅拌操作的协议序列
|
生成开始搅拌操作的协议序列 - 持续搅拌
|
||||||
|
|
||||||
Args:
|
|
||||||
G: 有向图,节点为设备和容器
|
|
||||||
vessel: 搅拌容器
|
|
||||||
stir_speed: 搅拌速度 (rpm)
|
|
||||||
purpose: 搅拌目的
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
List[Dict[str, Any]]: 开始搅拌操作的动作序列
|
|
||||||
"""
|
"""
|
||||||
action_sequence = []
|
action_sequence = []
|
||||||
|
|
||||||
# 查找搅拌设备
|
print(f"START_STIR: 开始生成启动搅拌协议")
|
||||||
stirrer_nodes = [node for node in G.nodes()
|
print(f" - 容器: {vessel}")
|
||||||
if G.nodes[node].get('class') == 'virtual_stirrer']
|
print(f" - 搅拌速度: {stir_speed} RPM")
|
||||||
|
print(f" - 目的: {purpose}")
|
||||||
|
|
||||||
if not stirrer_nodes:
|
# 验证容器存在
|
||||||
raise ValueError("没有找到可用的搅拌设备")
|
|
||||||
|
|
||||||
stirrer_id = stirrer_nodes[0]
|
|
||||||
|
|
||||||
# 验证容器是否存在
|
|
||||||
if vessel not in G.nodes():
|
if vessel not in G.nodes():
|
||||||
raise ValueError(f"容器 {vessel} 不存在于图中")
|
raise ValueError(f"容器 '{vessel}' 不存在于系统中")
|
||||||
|
|
||||||
action_sequence.append({
|
# 查找搅拌设备
|
||||||
|
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,
|
"device_id": stirrer_id,
|
||||||
"action_name": "start_stir",
|
"action_name": "start_stir",
|
||||||
"action_kwargs": {
|
"action_kwargs": {
|
||||||
@@ -92,8 +99,11 @@ def generate_start_stir_protocol(
|
|||||||
"stir_speed": stir_speed,
|
"stir_speed": stir_speed,
|
||||||
"purpose": purpose
|
"purpose": purpose
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
|
||||||
|
action_sequence.append(start_stir_action)
|
||||||
|
|
||||||
|
print(f"START_STIR: 生成了 {len(action_sequence)} 个动作")
|
||||||
return action_sequence
|
return action_sequence
|
||||||
|
|
||||||
|
|
||||||
@@ -103,35 +113,54 @@ def generate_stop_stir_protocol(
|
|||||||
) -> List[Dict[str, Any]]:
|
) -> List[Dict[str, Any]]:
|
||||||
"""
|
"""
|
||||||
生成停止搅拌操作的协议序列
|
生成停止搅拌操作的协议序列
|
||||||
|
|
||||||
Args:
|
|
||||||
G: 有向图,节点为设备和容器
|
|
||||||
vessel: 搅拌容器
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
List[Dict[str, Any]]: 停止搅拌操作的动作序列
|
|
||||||
"""
|
"""
|
||||||
action_sequence = []
|
action_sequence = []
|
||||||
|
|
||||||
# 查找搅拌设备
|
print(f"STOP_STIR: 开始生成停止搅拌协议")
|
||||||
stirrer_nodes = [node for node in G.nodes()
|
print(f" - 容器: {vessel}")
|
||||||
if G.nodes[node].get('class') == 'virtual_stirrer']
|
|
||||||
|
|
||||||
if not stirrer_nodes:
|
# 验证容器存在
|
||||||
raise ValueError("没有找到可用的搅拌设备")
|
|
||||||
|
|
||||||
stirrer_id = stirrer_nodes[0]
|
|
||||||
|
|
||||||
# 验证容器是否存在
|
|
||||||
if vessel not in G.nodes():
|
if vessel not in G.nodes():
|
||||||
raise ValueError(f"容器 {vessel} 不存在于图中")
|
raise ValueError(f"容器 '{vessel}' 不存在于系统中")
|
||||||
|
|
||||||
action_sequence.append({
|
# 查找搅拌设备
|
||||||
|
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,
|
"device_id": stirrer_id,
|
||||||
"action_name": "stop_stir",
|
"action_name": "stop_stir",
|
||||||
"action_kwargs": {
|
"action_kwargs": {
|
||||||
"vessel": vessel
|
"vessel": vessel
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
|
||||||
return action_sequence
|
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)
|
||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
import rtde_control
|
try:
|
||||||
import dashboard_client
|
import rtde_control
|
||||||
|
import dashboard_client
|
||||||
|
import rtde_receive
|
||||||
|
except ImportError as ex:
|
||||||
|
print("Import Error, Please Install Packages in ur_arm_task.py First!", ex)
|
||||||
import time
|
import time
|
||||||
import json
|
import json
|
||||||
from unilabos.devices.agv.robotiq_gripper import RobotiqGripper
|
from unilabos.devices.agv.robotiq_gripper import RobotiqGripper
|
||||||
import rtde_receive
|
|
||||||
from std_msgs.msg import Float64MultiArray
|
from std_msgs.msg import Float64MultiArray
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
|||||||
@@ -234,71 +234,71 @@ class Laiyu:
|
|||||||
resp_reset = self.reset()
|
resp_reset = self.reset()
|
||||||
return actual_mass_mg
|
return actual_mass_mg
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
|
||||||
|
'''
|
||||||
|
样例:对单个粉筒进行称量
|
||||||
|
'''
|
||||||
|
|
||||||
|
modbus = Laiyu(port="COM25")
|
||||||
|
|
||||||
|
mass_test = modbus.add_powder_tube(1, 'h12', 6.0)
|
||||||
|
print(f"实际出料质量:{mass_test}mg")
|
||||||
|
|
||||||
|
|
||||||
'''
|
'''
|
||||||
样例:对单个粉筒进行称量
|
样例: 对一份excel文件记录的化合物进行称量
|
||||||
'''
|
'''
|
||||||
|
|
||||||
modbus = Laiyu(port="COM25")
|
excel_file = r"C:\auto\laiyu\test1.xlsx"
|
||||||
|
# 定义输出文件路径,用于记录实际加样多少
|
||||||
|
output_file = r"C:\auto\laiyu\test_output.xlsx"
|
||||||
|
|
||||||
mass_test = modbus.add_powder_tube(1, 'h12', 6.0)
|
# 定义物料名称和料筒位置关系
|
||||||
print(f"实际出料质量:{mass_test}mg")
|
compound_positions = {
|
||||||
|
'XPhos': '1',
|
||||||
|
'Cu(OTf)2': '2',
|
||||||
|
'CuSO4': '3',
|
||||||
|
'PPh3': '4',
|
||||||
|
}
|
||||||
|
|
||||||
|
# read excel file
|
||||||
|
# excel_file = r"C:\auto\laiyu\test.xlsx"
|
||||||
|
df = pd.read_excel(excel_file, sheet_name='Sheet1')
|
||||||
|
# 读取Excel文件中的数据
|
||||||
|
# 遍历每一行数据
|
||||||
|
for index, row in df.iterrows():
|
||||||
|
# 获取物料名称和质量
|
||||||
|
copper_name = row['copper']
|
||||||
|
copper_mass = row['copper_mass']
|
||||||
|
ligand_name = row['ligand']
|
||||||
|
ligand_mass = row['ligand_mass']
|
||||||
|
target_tube_position = row['position']
|
||||||
|
# 获取物料位置 from compound_positions
|
||||||
|
copper_position = compound_positions.get(copper_name)
|
||||||
|
ligand_position = compound_positions.get(ligand_name)
|
||||||
|
# 判断物料位置是否存在
|
||||||
|
if copper_position is None:
|
||||||
|
print(f"物料位置不存在:{copper_name}")
|
||||||
|
continue
|
||||||
|
if ligand_position is None:
|
||||||
|
print(f"物料位置不存在:{ligand_name}")
|
||||||
|
continue
|
||||||
|
# 加铜
|
||||||
|
copper_actual_mass = modbus.add_powder_tube(int(copper_position), target_tube_position, copper_mass)
|
||||||
|
time.sleep(1)
|
||||||
|
# 加配体
|
||||||
|
ligand_actual_mass = modbus.add_powder_tube(int(ligand_position), target_tube_position, ligand_mass)
|
||||||
|
time.sleep(1)
|
||||||
|
# 保存至df
|
||||||
|
df.at[index, 'copper_actual_mass'] = copper_actual_mass
|
||||||
|
df.at[index, 'ligand_actual_mass'] = ligand_actual_mass
|
||||||
|
|
||||||
'''
|
# 保存修改后的数据到新的Excel文件
|
||||||
样例: 对一份excel文件记录的化合物进行称量
|
df.to_excel(output_file, index=False)
|
||||||
'''
|
print(f"已保存到文件:{output_file}")
|
||||||
|
|
||||||
excel_file = r"C:\auto\laiyu\test1.xlsx"
|
# 关闭串口
|
||||||
# 定义输出文件路径,用于记录实际加样多少
|
modbus.ser.close()
|
||||||
output_file = r"C:\auto\laiyu\test_output.xlsx"
|
print("串口已关闭")
|
||||||
|
|
||||||
# 定义物料名称和料筒位置关系
|
|
||||||
compound_positions = {
|
|
||||||
'XPhos': '1',
|
|
||||||
'Cu(OTf)2': '2',
|
|
||||||
'CuSO4': '3',
|
|
||||||
'PPh3': '4',
|
|
||||||
}
|
|
||||||
|
|
||||||
# read excel file
|
|
||||||
# excel_file = r"C:\auto\laiyu\test.xlsx"
|
|
||||||
df = pd.read_excel(excel_file, sheet_name='Sheet1')
|
|
||||||
# 读取Excel文件中的数据
|
|
||||||
# 遍历每一行数据
|
|
||||||
for index, row in df.iterrows():
|
|
||||||
# 获取物料名称和质量
|
|
||||||
copper_name = row['copper']
|
|
||||||
copper_mass = row['copper_mass']
|
|
||||||
ligand_name = row['ligand']
|
|
||||||
ligand_mass = row['ligand_mass']
|
|
||||||
target_tube_position = row['position']
|
|
||||||
# 获取物料位置 from compound_positions
|
|
||||||
copper_position = compound_positions.get(copper_name)
|
|
||||||
ligand_position = compound_positions.get(ligand_name)
|
|
||||||
# 判断物料位置是否存在
|
|
||||||
if copper_position is None:
|
|
||||||
print(f"物料位置不存在:{copper_name}")
|
|
||||||
continue
|
|
||||||
if ligand_position is None:
|
|
||||||
print(f"物料位置不存在:{ligand_name}")
|
|
||||||
continue
|
|
||||||
# 加铜
|
|
||||||
copper_actual_mass = modbus.add_powder_tube(int(copper_position), target_tube_position, copper_mass)
|
|
||||||
time.sleep(1)
|
|
||||||
# 加配体
|
|
||||||
ligand_actual_mass = modbus.add_powder_tube(int(ligand_position), target_tube_position, ligand_mass)
|
|
||||||
time.sleep(1)
|
|
||||||
# 保存至df
|
|
||||||
df.at[index, 'copper_actual_mass'] = copper_actual_mass
|
|
||||||
df.at[index, 'ligand_actual_mass'] = ligand_actual_mass
|
|
||||||
|
|
||||||
# 保存修改后的数据到新的Excel文件
|
|
||||||
df.to_excel(output_file, index=False)
|
|
||||||
print(f"已保存到文件:{output_file}")
|
|
||||||
|
|
||||||
# 关闭串口
|
|
||||||
modbus.ser.close()
|
|
||||||
print("串口已关闭")
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import traceback
|
||||||
from typing import List, Sequence, Optional, Literal, Union, Iterator
|
from typing import List, Sequence, Optional, Literal, Union, Iterator
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
@@ -117,7 +118,7 @@ class LiquidHandlerAbstract(LiquidHandler):
|
|||||||
pass # This mode is not verified.
|
pass # This mode is not verified.
|
||||||
else:
|
else:
|
||||||
if len(asp_vols) != len(targets):
|
if len(asp_vols) != len(targets):
|
||||||
raise ValueError("Length of `vols` must match `targets`.")
|
raise ValueError(f"Length of `asp_vols` {len(asp_vols)} must match `targets` {len(targets)}.")
|
||||||
tip = next(self.current_tip)
|
tip = next(self.current_tip)
|
||||||
await self.pick_up_tips(tip)
|
await self.pick_up_tips(tip)
|
||||||
|
|
||||||
@@ -160,6 +161,7 @@ class LiquidHandlerAbstract(LiquidHandler):
|
|||||||
await self.discard_tips()
|
await self.discard_tips()
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
traceback.print_exc()
|
||||||
raise RuntimeError(f"Liquid addition failed: {e}") from e
|
raise RuntimeError(f"Liquid addition failed: {e}") from e
|
||||||
|
|
||||||
# ---------------------------------------------------------------
|
# ---------------------------------------------------------------
|
||||||
@@ -183,7 +185,7 @@ class LiquidHandlerAbstract(LiquidHandler):
|
|||||||
spread: Literal["wide", "tight", "custom"] = "wide",
|
spread: Literal["wide", "tight", "custom"] = "wide",
|
||||||
is_96_well: bool = False,
|
is_96_well: bool = False,
|
||||||
mix_stage: Optional[Literal["none", "before", "after", "both"]] = "none",
|
mix_stage: Optional[Literal["none", "before", "after", "both"]] = "none",
|
||||||
mix_times: Optional[List(int)] = None,
|
mix_times: Optional[List[int]] = None,
|
||||||
mix_vol: Optional[int] = None,
|
mix_vol: Optional[int] = None,
|
||||||
mix_rate: Optional[int] = None,
|
mix_rate: Optional[int] = None,
|
||||||
mix_liquid_height: Optional[float] = None,
|
mix_liquid_height: Optional[float] = None,
|
||||||
|
|||||||
1046
unilabos/devices/liquid_handling/prcxi/prcxi.py
Normal file
1046
unilabos/devices/liquid_handling/prcxi/prcxi.py
Normal file
File diff suppressed because it is too large
Load Diff
@@ -3,7 +3,11 @@ import sys
|
|||||||
import io
|
import io
|
||||||
# sys.path.insert(0, r'C:\kui\winprep_cli\winprep_c_Uni-lab\x64\Debug')
|
# sys.path.insert(0, r'C:\kui\winprep_cli\winprep_c_Uni-lab\x64\Debug')
|
||||||
|
|
||||||
import winprep_c
|
try:
|
||||||
|
import winprep_c
|
||||||
|
except ImportError as e:
|
||||||
|
print("Error importing winprep_c:", e)
|
||||||
|
print("Please ensure that the winprep_c module is correctly installed and accessible.")
|
||||||
from queue import Queue
|
from queue import Queue
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ except Exception as e:
|
|||||||
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "..")))
|
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "..")))
|
||||||
from unilabos.utils.pywinauto_util import connect_application, get_process_pid_by_name, get_ui_path_with_window_specification, print_wrapper_identifiers
|
from unilabos.utils.pywinauto_util import connect_application, get_process_pid_by_name, get_ui_path_with_window_specification, print_wrapper_identifiers
|
||||||
from unilabos.device_comms.universal_driver import UniversalDriver, SingleRunningExecutor
|
from unilabos.device_comms.universal_driver import UniversalDriver, SingleRunningExecutor
|
||||||
from unilabos.devices.template_driver import universal_driver as ud
|
from unilabos.device_comms import universal_driver as ud
|
||||||
print(f"使用文件DEBUG运行: {e}")
|
print(f"使用文件DEBUG运行: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
|
|||||||
import time
|
import time
|
||||||
|
|
||||||
class RamanObj:
|
class RamanObj:
|
||||||
def __init__(self, port_laser,port_ccd, baudrate_laser=9600, baudrate_ccd=921600):
|
def __init__(self, port_laser, port_ccd, baudrate_laser=9600, baudrate_ccd=921600):
|
||||||
self.port_laser = port_laser
|
self.port_laser = port_laser
|
||||||
self.port_ccd = port_ccd
|
self.port_ccd = port_ccd
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ class MoveitInterface:
|
|||||||
tf_buffer: Buffer
|
tf_buffer: Buffer
|
||||||
tf_listener: TransformListener
|
tf_listener: TransformListener
|
||||||
|
|
||||||
def __init__(self, moveit_type, joint_poses, rotation=None, device_config=None):
|
def __init__(self, moveit_type, joint_poses, rotation=None, device_config=None, **kwargs):
|
||||||
self.device_config = device_config
|
self.device_config = device_config
|
||||||
self.rotation = rotation
|
self.rotation = rotation
|
||||||
self.data_config = json.load(
|
self.data_config = json.load(
|
||||||
|
|||||||
@@ -1,158 +1,213 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
from typing import Dict, Any
|
import time as time_module
|
||||||
|
from typing import Dict, Any, Optional
|
||||||
|
|
||||||
|
|
||||||
class VirtualCentrifuge:
|
class VirtualCentrifuge:
|
||||||
"""Virtual centrifuge device for CentrifugeProtocol testing"""
|
"""Virtual centrifuge device - 简化版,只保留核心功能"""
|
||||||
|
|
||||||
def __init__(self, device_id: str = None, config: Dict[str, Any] = None, **kwargs):
|
def __init__(self, device_id: Optional[str] = None, config: Optional[Dict[str, Any]] = None, **kwargs):
|
||||||
# 处理可能的不同调用方式
|
# 处理可能的不同调用方式
|
||||||
if device_id is None and 'id' in kwargs:
|
if device_id is None and "id" in kwargs:
|
||||||
device_id = kwargs.pop('id')
|
device_id = kwargs.pop("id")
|
||||||
if config is None and 'config' in kwargs:
|
if config is None and "config" in kwargs:
|
||||||
config = kwargs.pop('config')
|
config = kwargs.pop("config")
|
||||||
|
|
||||||
# 设置默认值
|
# 设置默认值
|
||||||
self.device_id = device_id or "unknown_centrifuge"
|
self.device_id = device_id or "unknown_centrifuge"
|
||||||
self.config = config or {}
|
self.config = config or {}
|
||||||
|
|
||||||
self.logger = logging.getLogger(f"VirtualCentrifuge.{self.device_id}")
|
self.logger = logging.getLogger(f"VirtualCentrifuge.{self.device_id}")
|
||||||
self.data = {}
|
self.data = {}
|
||||||
|
|
||||||
# 添加调试信息
|
|
||||||
print(f"=== VirtualCentrifuge {self.device_id} is being created! ===")
|
|
||||||
print(f"=== Config: {self.config} ===")
|
|
||||||
print(f"=== Kwargs: {kwargs} ===")
|
|
||||||
|
|
||||||
# 从config或kwargs中获取配置参数
|
# 从config或kwargs中获取配置参数
|
||||||
self.port = self.config.get('port') or kwargs.get('port', 'VIRTUAL')
|
self.port = self.config.get("port") or kwargs.get("port", "VIRTUAL")
|
||||||
self._max_speed = self.config.get('max_speed') or kwargs.get('max_speed', 15000.0)
|
self._max_speed = self.config.get("max_speed") or kwargs.get("max_speed", 15000.0)
|
||||||
self._max_temp = self.config.get('max_temp') or kwargs.get('max_temp', 40.0)
|
self._max_temp = self.config.get("max_temp") or kwargs.get("max_temp", 40.0)
|
||||||
self._min_temp = self.config.get('min_temp') or kwargs.get('min_temp', 4.0)
|
self._min_temp = self.config.get("min_temp") or kwargs.get("min_temp", 4.0)
|
||||||
|
|
||||||
# 处理其他kwargs参数,但跳过已知的配置参数
|
# 处理其他kwargs参数
|
||||||
skip_keys = {'port', 'max_speed', 'max_temp', 'min_temp'}
|
skip_keys = {"port", "max_speed", "max_temp", "min_temp"}
|
||||||
for key, value in kwargs.items():
|
for key, value in kwargs.items():
|
||||||
if key not in skip_keys and not hasattr(self, key):
|
if key not in skip_keys and not hasattr(self, key):
|
||||||
setattr(self, key, value)
|
setattr(self, key, value)
|
||||||
|
|
||||||
async def initialize(self) -> bool:
|
async def initialize(self) -> bool:
|
||||||
"""Initialize virtual centrifuge"""
|
"""Initialize virtual centrifuge"""
|
||||||
print(f"=== VirtualCentrifuge {self.device_id} initialize() called! ===")
|
|
||||||
self.logger.info(f"Initializing virtual centrifuge {self.device_id}")
|
self.logger.info(f"Initializing virtual centrifuge {self.device_id}")
|
||||||
|
|
||||||
|
# 只保留核心状态
|
||||||
self.data.update({
|
self.data.update({
|
||||||
"status": "Idle",
|
"status": "Idle",
|
||||||
|
"centrifuge_state": "Stopped", # Stopped, Running, Completed, Error
|
||||||
"current_speed": 0.0,
|
"current_speed": 0.0,
|
||||||
"target_speed": 0.0,
|
"target_speed": 0.0,
|
||||||
"current_temp": 25.0,
|
"current_temp": 25.0,
|
||||||
"target_temp": 25.0,
|
"target_temp": 25.0,
|
||||||
"max_speed": self._max_speed,
|
|
||||||
"max_temp": self._max_temp,
|
|
||||||
"min_temp": self._min_temp,
|
|
||||||
"centrifuge_state": "Stopped",
|
|
||||||
"time_remaining": 0.0,
|
"time_remaining": 0.0,
|
||||||
"progress": 0.0,
|
"progress": 0.0,
|
||||||
"message": ""
|
"message": "Ready for centrifugation"
|
||||||
})
|
})
|
||||||
return True
|
return True
|
||||||
|
|
||||||
async def cleanup(self) -> bool:
|
async def cleanup(self) -> bool:
|
||||||
"""Cleanup virtual centrifuge"""
|
"""Cleanup virtual centrifuge"""
|
||||||
self.logger.info(f"Cleaning up virtual centrifuge {self.device_id}")
|
self.logger.info(f"Cleaning up virtual centrifuge {self.device_id}")
|
||||||
|
|
||||||
|
self.data.update({
|
||||||
|
"status": "Offline",
|
||||||
|
"centrifuge_state": "Offline",
|
||||||
|
"current_speed": 0.0,
|
||||||
|
"current_temp": 25.0,
|
||||||
|
"message": "System offline"
|
||||||
|
})
|
||||||
return True
|
return True
|
||||||
|
|
||||||
async def centrifuge(self, vessel: str, speed: float, time: float, temp: float = 25.0) -> bool:
|
async def centrifuge(
|
||||||
"""Execute centrifuge action - matches Centrifuge action"""
|
self,
|
||||||
self.logger.info(f"Centrifuge: vessel={vessel}, speed={speed} RPM, time={time}s, temp={temp}°C")
|
vessel: str,
|
||||||
|
speed: float,
|
||||||
|
time: float,
|
||||||
|
temp: float = 25.0
|
||||||
|
) -> bool:
|
||||||
|
"""Execute centrifuge action - 简化的离心流程"""
|
||||||
|
self.logger.info(f"Centrifuge: vessel={vessel}, speed={speed} rpm, time={time}s, temp={temp}°C")
|
||||||
|
|
||||||
# 验证参数
|
# 验证参数
|
||||||
if speed > self._max_speed:
|
if speed > self._max_speed or speed < 100.0:
|
||||||
self.logger.error(f"Speed {speed} exceeds maximum {self._max_speed}")
|
error_msg = f"离心速度 {speed} rpm 超出范围 (100-{self._max_speed} rpm)"
|
||||||
self.data["message"] = f"速度 {speed} 超过最大值 {self._max_speed}"
|
self.logger.error(error_msg)
|
||||||
|
self.data.update({
|
||||||
|
"status": f"Error: {error_msg}",
|
||||||
|
"centrifuge_state": "Error",
|
||||||
|
"message": error_msg
|
||||||
|
})
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if temp > self._max_temp or temp < self._min_temp:
|
if temp > self._max_temp or temp < self._min_temp:
|
||||||
self.logger.error(f"Temperature {temp} outside range {self._min_temp}-{self._max_temp}")
|
error_msg = f"温度 {temp}°C 超出范围 ({self._min_temp}-{self._max_temp}°C)"
|
||||||
self.data["message"] = f"温度 {temp} 超出范围 {self._min_temp}-{self._max_temp}"
|
self.logger.error(error_msg)
|
||||||
|
self.data.update({
|
||||||
|
"status": f"Error: {error_msg}",
|
||||||
|
"centrifuge_state": "Error",
|
||||||
|
"message": error_msg
|
||||||
|
})
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# 开始离心
|
# 开始离心
|
||||||
self.data.update({
|
self.data.update({
|
||||||
"status": "Running",
|
"status": f"离心中: {vessel}",
|
||||||
"centrifuge_state": "Centrifuging",
|
"centrifuge_state": "Running",
|
||||||
"target_speed": speed,
|
|
||||||
"current_speed": speed,
|
"current_speed": speed,
|
||||||
"target_temp": temp,
|
"target_speed": speed,
|
||||||
"current_temp": temp,
|
"current_temp": temp,
|
||||||
|
"target_temp": temp,
|
||||||
"time_remaining": time,
|
"time_remaining": time,
|
||||||
"vessel": vessel,
|
|
||||||
"progress": 0.0,
|
"progress": 0.0,
|
||||||
"message": f"离心中: {vessel} at {speed} RPM"
|
"message": f"Centrifuging {vessel} at {speed} rpm, {temp}°C"
|
||||||
})
|
})
|
||||||
|
|
||||||
# 模拟离心过程
|
try:
|
||||||
simulation_time = min(time, 5.0) # 最多等待5秒用于测试
|
# 离心过程 - 实时更新进度
|
||||||
await asyncio.sleep(simulation_time)
|
start_time = time_module.time()
|
||||||
|
total_time = time
|
||||||
# 离心完成
|
|
||||||
self.data.update({
|
while True:
|
||||||
"status": "Idle",
|
current_time = time_module.time()
|
||||||
"centrifuge_state": "Stopped",
|
elapsed = current_time - start_time
|
||||||
"current_speed": 0.0,
|
remaining = max(0, total_time - elapsed)
|
||||||
"target_speed": 0.0,
|
progress = min(100.0, (elapsed / total_time) * 100)
|
||||||
"time_remaining": 0.0,
|
|
||||||
"progress": 100.0,
|
# 更新状态
|
||||||
"message": f"离心完成: {vessel}"
|
self.data.update({
|
||||||
})
|
"time_remaining": remaining,
|
||||||
|
"progress": progress,
|
||||||
self.logger.info(f"Centrifuge completed for vessel {vessel}")
|
"status": f"离心中: {vessel} | {speed} rpm | {temp}°C | {progress:.1f}% | 剩余: {remaining:.0f}s",
|
||||||
return True
|
"message": f"Centrifuging: {progress:.1f}% complete, {remaining:.0f}s remaining"
|
||||||
|
})
|
||||||
# 状态属性
|
|
||||||
|
# 时间到了,退出循环
|
||||||
|
if remaining <= 0:
|
||||||
|
break
|
||||||
|
|
||||||
|
# 每秒更新一次
|
||||||
|
await asyncio.sleep(1.0)
|
||||||
|
|
||||||
|
# 离心完成
|
||||||
|
self.data.update({
|
||||||
|
"status": f"离心完成: {vessel} | {speed} rpm | {time}s",
|
||||||
|
"centrifuge_state": "Completed",
|
||||||
|
"progress": 100.0,
|
||||||
|
"time_remaining": 0.0,
|
||||||
|
"current_speed": 0.0, # 停止旋转
|
||||||
|
"current_temp": 25.0, # 恢复室温
|
||||||
|
"message": f"Centrifugation completed: {vessel} at {speed} rpm for {time}s"
|
||||||
|
})
|
||||||
|
|
||||||
|
self.logger.info(f"Centrifugation completed: {vessel} at {speed} rpm for {time}s")
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
# 出错处理
|
||||||
|
self.logger.error(f"Error during centrifugation: {str(e)}")
|
||||||
|
|
||||||
|
self.data.update({
|
||||||
|
"status": f"离心错误: {str(e)}",
|
||||||
|
"centrifuge_state": "Error",
|
||||||
|
"current_speed": 0.0,
|
||||||
|
"current_temp": 25.0,
|
||||||
|
"progress": 0.0,
|
||||||
|
"time_remaining": 0.0,
|
||||||
|
"message": f"Centrifugation failed: {str(e)}"
|
||||||
|
})
|
||||||
|
return False
|
||||||
|
|
||||||
|
# === 核心状态属性 ===
|
||||||
@property
|
@property
|
||||||
def status(self) -> str:
|
def status(self) -> str:
|
||||||
return self.data.get("status", "Unknown")
|
return self.data.get("status", "Unknown")
|
||||||
|
|
||||||
@property
|
|
||||||
def current_speed(self) -> float:
|
|
||||||
return self.data.get("current_speed", 0.0)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def target_speed(self) -> float:
|
|
||||||
return self.data.get("target_speed", 0.0)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def current_temp(self) -> float:
|
|
||||||
return self.data.get("current_temp", 25.0)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def target_temp(self) -> float:
|
|
||||||
return self.data.get("target_temp", 25.0)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def max_speed(self) -> float:
|
|
||||||
return self.data.get("max_speed", self._max_speed)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def max_temp(self) -> float:
|
|
||||||
return self.data.get("max_temp", self._max_temp)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def min_temp(self) -> float:
|
|
||||||
return self.data.get("min_temp", self._min_temp)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def centrifuge_state(self) -> str:
|
def centrifuge_state(self) -> str:
|
||||||
return self.data.get("centrifuge_state", "Unknown")
|
return self.data.get("centrifuge_state", "Unknown")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def current_speed(self) -> float:
|
||||||
|
return self.data.get("current_speed", 0.0)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def target_speed(self) -> float:
|
||||||
|
return self.data.get("target_speed", 0.0)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def current_temp(self) -> float:
|
||||||
|
return self.data.get("current_temp", 25.0)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def target_temp(self) -> float:
|
||||||
|
return self.data.get("target_temp", 25.0)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def max_speed(self) -> float:
|
||||||
|
return self._max_speed
|
||||||
|
|
||||||
|
@property
|
||||||
|
def max_temp(self) -> float:
|
||||||
|
return self._max_temp
|
||||||
|
|
||||||
|
@property
|
||||||
|
def min_temp(self) -> float:
|
||||||
|
return self._min_temp
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def time_remaining(self) -> float:
|
def time_remaining(self) -> float:
|
||||||
return self.data.get("time_remaining", 0.0)
|
return self.data.get("time_remaining", 0.0)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def progress(self) -> float:
|
def progress(self) -> float:
|
||||||
return self.data.get("progress", 0.0)
|
return self.data.get("progress", 0.0)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def message(self) -> str:
|
def message(self) -> str:
|
||||||
return self.data.get("message", "")
|
return self.data.get("message", "")
|
||||||
@@ -1,151 +1,221 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
from typing import Dict, Any
|
import time as time_module
|
||||||
|
from typing import Dict, Any, Optional
|
||||||
|
|
||||||
|
|
||||||
class VirtualFilter:
|
class VirtualFilter:
|
||||||
"""Virtual filter device for FilterProtocol testing"""
|
"""Virtual filter device - 完全按照 Filter.action 规范"""
|
||||||
|
|
||||||
def __init__(self, device_id: str = None, config: Dict[str, Any] = None, **kwargs):
|
def __init__(self, device_id: Optional[str] = None, config: Optional[Dict[str, Any]] = None, **kwargs):
|
||||||
# 处理可能的不同调用方式
|
|
||||||
if device_id is None and 'id' in kwargs:
|
if device_id is None and 'id' in kwargs:
|
||||||
device_id = kwargs.pop('id')
|
device_id = kwargs.pop('id')
|
||||||
if config is None and 'config' in kwargs:
|
if config is None and 'config' in kwargs:
|
||||||
config = kwargs.pop('config')
|
config = kwargs.pop('config')
|
||||||
|
|
||||||
# 设置默认值
|
|
||||||
self.device_id = device_id or "unknown_filter"
|
self.device_id = device_id or "unknown_filter"
|
||||||
self.config = config or {}
|
self.config = config or {}
|
||||||
|
|
||||||
self.logger = logging.getLogger(f"VirtualFilter.{self.device_id}")
|
self.logger = logging.getLogger(f"VirtualFilter.{self.device_id}")
|
||||||
self.data = {}
|
self.data = {}
|
||||||
|
|
||||||
# 添加调试信息
|
|
||||||
print(f"=== VirtualFilter {self.device_id} is being created! ===")
|
|
||||||
print(f"=== Config: {self.config} ===")
|
|
||||||
print(f"=== Kwargs: {kwargs} ===")
|
|
||||||
|
|
||||||
# 从config或kwargs中获取配置参数
|
# 从config或kwargs中获取配置参数
|
||||||
self.port = self.config.get('port') or kwargs.get('port', 'VIRTUAL')
|
self.port = self.config.get('port') or kwargs.get('port', 'VIRTUAL')
|
||||||
self._max_temp = self.config.get('max_temp') or kwargs.get('max_temp', 100.0)
|
self._max_temp = self.config.get('max_temp') or kwargs.get('max_temp', 100.0)
|
||||||
self._max_stir_speed = self.config.get('max_stir_speed') or kwargs.get('max_stir_speed', 1000.0)
|
self._max_stir_speed = self.config.get('max_stir_speed') or kwargs.get('max_stir_speed', 1000.0)
|
||||||
|
self._max_volume = self.config.get('max_volume') or kwargs.get('max_volume', 500.0)
|
||||||
|
|
||||||
# 处理其他kwargs参数,但跳过已知的配置参数
|
# 处理其他kwargs参数
|
||||||
skip_keys = {'port', 'max_temp', 'max_stir_speed'}
|
skip_keys = {'port', 'max_temp', 'max_stir_speed', 'max_volume'}
|
||||||
for key, value in kwargs.items():
|
for key, value in kwargs.items():
|
||||||
if key not in skip_keys and not hasattr(self, key):
|
if key not in skip_keys and not hasattr(self, key):
|
||||||
setattr(self, key, value)
|
setattr(self, key, value)
|
||||||
|
|
||||||
async def initialize(self) -> bool:
|
async def initialize(self) -> bool:
|
||||||
"""Initialize virtual filter"""
|
"""Initialize virtual filter"""
|
||||||
print(f"=== VirtualFilter {self.device_id} initialize() called! ===")
|
|
||||||
self.logger.info(f"Initializing virtual filter {self.device_id}")
|
self.logger.info(f"Initializing virtual filter {self.device_id}")
|
||||||
|
|
||||||
|
# 按照 Filter.action 的 feedback 字段初始化
|
||||||
self.data.update({
|
self.data.update({
|
||||||
"status": "Idle",
|
"status": "Idle",
|
||||||
"filter_state": "Ready",
|
"progress": 0.0, # Filter.action feedback
|
||||||
"current_temp": 25.0,
|
"current_temp": 25.0, # Filter.action feedback
|
||||||
"target_temp": 25.0,
|
"filtered_volume": 0.0, # Filter.action feedback
|
||||||
"max_temp": self._max_temp,
|
"current_status": "Ready for filtration", # Filter.action feedback
|
||||||
"stir_speed": 0.0,
|
"message": "Ready for filtration"
|
||||||
"max_stir_speed": self._max_stir_speed,
|
|
||||||
"filtered_volume": 0.0,
|
|
||||||
"progress": 0.0,
|
|
||||||
"message": ""
|
|
||||||
})
|
})
|
||||||
return True
|
return True
|
||||||
|
|
||||||
async def cleanup(self) -> bool:
|
async def cleanup(self) -> bool:
|
||||||
"""Cleanup virtual filter"""
|
"""Cleanup virtual filter"""
|
||||||
self.logger.info(f"Cleaning up virtual filter {self.device_id}")
|
self.logger.info(f"Cleaning up virtual filter {self.device_id}")
|
||||||
|
|
||||||
|
self.data.update({
|
||||||
|
"status": "Offline",
|
||||||
|
"current_status": "System offline",
|
||||||
|
"message": "System offline"
|
||||||
|
})
|
||||||
return True
|
return True
|
||||||
|
|
||||||
async def filter_sample(self, vessel: str, filtrate_vessel: str = "", stir: bool = False,
|
async def filter(
|
||||||
stir_speed: float = 300.0, temp: float = 25.0,
|
self,
|
||||||
continue_heatchill: bool = False, volume: float = 0.0) -> bool:
|
vessel: str,
|
||||||
"""Execute filter action - matches Filter action"""
|
filtrate_vessel: str = "",
|
||||||
self.logger.info(f"Filter: vessel={vessel}, filtrate_vessel={filtrate_vessel}, stir={stir}, volume={volume}")
|
stir: bool = False,
|
||||||
|
stir_speed: float = 300.0,
|
||||||
|
temp: float = 25.0,
|
||||||
|
continue_heatchill: bool = False,
|
||||||
|
volume: float = 0.0
|
||||||
|
) -> bool:
|
||||||
|
"""Execute filter action - 完全按照 Filter.action 参数"""
|
||||||
|
self.logger.info(f"Filter: vessel={vessel}, filtrate_vessel={filtrate_vessel}")
|
||||||
|
self.logger.info(f" stir={stir}, stir_speed={stir_speed}, temp={temp}")
|
||||||
|
self.logger.info(f" continue_heatchill={continue_heatchill}, volume={volume}")
|
||||||
|
|
||||||
# 验证参数
|
# 验证参数
|
||||||
if temp > self._max_temp:
|
if temp > self._max_temp or temp < 4.0:
|
||||||
self.logger.error(f"Temperature {temp} exceeds maximum {self._max_temp}")
|
error_msg = f"温度 {temp}°C 超出范围 (4-{self._max_temp}°C)"
|
||||||
self.data["message"] = f"温度 {temp} 超过最大值 {self._max_temp}"
|
self.logger.error(error_msg)
|
||||||
|
self.data.update({
|
||||||
|
"status": f"Error: {error_msg}",
|
||||||
|
"current_status": f"Error: {error_msg}",
|
||||||
|
"message": error_msg
|
||||||
|
})
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if stir and stir_speed > self._max_stir_speed:
|
if stir and stir_speed > self._max_stir_speed:
|
||||||
self.logger.error(f"Stir speed {stir_speed} exceeds maximum {self._max_stir_speed}")
|
error_msg = f"搅拌速度 {stir_speed} RPM 超出范围 (0-{self._max_stir_speed} RPM)"
|
||||||
self.data["message"] = f"搅拌速度 {stir_speed} 超过最大值 {self._max_stir_speed}"
|
self.logger.error(error_msg)
|
||||||
|
self.data.update({
|
||||||
|
"status": f"Error: {error_msg}",
|
||||||
|
"current_status": f"Error: {error_msg}",
|
||||||
|
"message": error_msg
|
||||||
|
})
|
||||||
|
return False
|
||||||
|
|
||||||
|
if volume > self._max_volume:
|
||||||
|
error_msg = f"过滤体积 {volume} mL 超出范围 (0-{self._max_volume} mL)"
|
||||||
|
self.logger.error(error_msg)
|
||||||
|
self.data.update({
|
||||||
|
"status": f"Error: {error_msg}",
|
||||||
|
"current_status": f"Error: {error_msg}",
|
||||||
|
"message": error_msg
|
||||||
|
})
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# 开始过滤
|
# 开始过滤
|
||||||
|
filter_volume = volume if volume > 0 else 50.0
|
||||||
|
|
||||||
self.data.update({
|
self.data.update({
|
||||||
"status": "Running",
|
"status": f"过滤中: {vessel}",
|
||||||
"filter_state": "Filtering",
|
|
||||||
"target_temp": temp,
|
|
||||||
"current_temp": temp,
|
"current_temp": temp,
|
||||||
"stir_speed": stir_speed if stir else 0.0,
|
"filtered_volume": 0.0,
|
||||||
"vessel": vessel,
|
|
||||||
"filtrate_vessel": filtrate_vessel,
|
|
||||||
"target_volume": volume,
|
|
||||||
"progress": 0.0,
|
"progress": 0.0,
|
||||||
"message": f"过滤中: {vessel}"
|
"current_status": f"Filtering {vessel} → {filtrate_vessel}",
|
||||||
|
"message": f"Starting filtration: {vessel} → {filtrate_vessel}"
|
||||||
})
|
})
|
||||||
|
|
||||||
# 模拟过滤过程
|
try:
|
||||||
simulation_time = min(volume / 10.0 if volume > 0 else 5.0, 10.0)
|
# 过滤过程 - 实时更新进度
|
||||||
await asyncio.sleep(simulation_time)
|
start_time = time_module.time()
|
||||||
|
# 根据体积和搅拌估算过滤时间
|
||||||
# 过滤完成
|
base_time = filter_volume / 5.0 # 5mL/s 基础速度
|
||||||
filtered_vol = volume if volume > 0 else 50.0 # 默认过滤量
|
if stir:
|
||||||
self.data.update({
|
base_time *= 0.8 # 搅拌加速过滤
|
||||||
"status": "Idle",
|
if temp > 50.0:
|
||||||
"filter_state": "Ready",
|
base_time *= 0.7 # 高温加速过滤
|
||||||
"current_temp": 25.0 if not continue_heatchill else temp,
|
filter_time = max(base_time, 10.0) # 最少10秒
|
||||||
"target_temp": 25.0 if not continue_heatchill else temp,
|
|
||||||
"stir_speed": 0.0 if not stir else stir_speed,
|
while True:
|
||||||
"filtered_volume": filtered_vol,
|
current_time = time_module.time()
|
||||||
"progress": 100.0,
|
elapsed = current_time - start_time
|
||||||
"message": f"过滤完成: {filtered_vol}mL"
|
remaining = max(0, filter_time - elapsed)
|
||||||
})
|
progress = min(100.0, (elapsed / filter_time) * 100)
|
||||||
|
current_filtered = (progress / 100.0) * filter_volume
|
||||||
self.logger.info(f"Filter completed: {filtered_vol}mL from {vessel}")
|
|
||||||
return True
|
# 更新状态 - 按照 Filter.action feedback 字段
|
||||||
|
status_msg = f"过滤中: {vessel}"
|
||||||
|
if stir:
|
||||||
|
status_msg += f" | 搅拌: {stir_speed} RPM"
|
||||||
|
status_msg += f" | {temp}°C | {progress:.1f}% | 已过滤: {current_filtered:.1f}mL"
|
||||||
|
|
||||||
|
self.data.update({
|
||||||
|
"progress": progress, # Filter.action feedback
|
||||||
|
"current_temp": temp, # Filter.action feedback
|
||||||
|
"filtered_volume": current_filtered, # Filter.action feedback
|
||||||
|
"current_status": f"Filtering: {progress:.1f}% complete", # Filter.action feedback
|
||||||
|
"status": status_msg,
|
||||||
|
"message": f"Filtering: {progress:.1f}% complete, {current_filtered:.1f}mL filtered"
|
||||||
|
})
|
||||||
|
|
||||||
|
if remaining <= 0:
|
||||||
|
break
|
||||||
|
|
||||||
|
await asyncio.sleep(1.0)
|
||||||
|
|
||||||
|
# 过滤完成
|
||||||
|
final_temp = temp if continue_heatchill else 25.0
|
||||||
|
final_status = f"过滤完成: {vessel} | {filter_volume}mL → {filtrate_vessel}"
|
||||||
|
if continue_heatchill:
|
||||||
|
final_status += " | 继续加热搅拌"
|
||||||
|
|
||||||
|
self.data.update({
|
||||||
|
"status": final_status,
|
||||||
|
"progress": 100.0, # Filter.action feedback
|
||||||
|
"current_temp": final_temp, # Filter.action feedback
|
||||||
|
"filtered_volume": filter_volume, # Filter.action feedback
|
||||||
|
"current_status": f"Filtration completed: {filter_volume}mL", # Filter.action feedback
|
||||||
|
"message": f"Filtration completed: {filter_volume}mL filtered from {vessel}"
|
||||||
|
})
|
||||||
|
|
||||||
|
self.logger.info(f"Filtration completed: {filter_volume}mL from {vessel} to {filtrate_vessel}")
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"Error during filtration: {str(e)}")
|
||||||
|
self.data.update({
|
||||||
|
"status": f"过滤错误: {str(e)}",
|
||||||
|
"current_status": f"Filtration failed: {str(e)}",
|
||||||
|
"message": f"Filtration failed: {str(e)}"
|
||||||
|
})
|
||||||
|
return False
|
||||||
|
|
||||||
# 状态属性
|
# === 核心状态属性 - 按照 Filter.action feedback 字段 ===
|
||||||
@property
|
@property
|
||||||
def status(self) -> str:
|
def status(self) -> str:
|
||||||
return self.data.get("status", "Unknown")
|
return self.data.get("status", "Unknown")
|
||||||
|
|
||||||
@property
|
|
||||||
def filter_state(self) -> str:
|
|
||||||
return self.data.get("filter_state", "Unknown")
|
|
||||||
|
|
||||||
@property
|
|
||||||
def current_temp(self) -> float:
|
|
||||||
return self.data.get("current_temp", 25.0)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def target_temp(self) -> float:
|
|
||||||
return self.data.get("target_temp", 25.0)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def max_temp(self) -> float:
|
|
||||||
return self.data.get("max_temp", self._max_temp)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def stir_speed(self) -> float:
|
|
||||||
return self.data.get("stir_speed", 0.0)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def max_stir_speed(self) -> float:
|
|
||||||
return self.data.get("max_stir_speed", self._max_stir_speed)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def filtered_volume(self) -> float:
|
|
||||||
return self.data.get("filtered_volume", 0.0)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def progress(self) -> float:
|
def progress(self) -> float:
|
||||||
|
"""Filter.action feedback 字段"""
|
||||||
return self.data.get("progress", 0.0)
|
return self.data.get("progress", 0.0)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def current_temp(self) -> float:
|
||||||
|
"""Filter.action feedback 字段"""
|
||||||
|
return self.data.get("current_temp", 25.0)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def filtered_volume(self) -> float:
|
||||||
|
"""Filter.action feedback 字段"""
|
||||||
|
return self.data.get("filtered_volume", 0.0)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def current_status(self) -> str:
|
||||||
|
"""Filter.action feedback 字段"""
|
||||||
|
return self.data.get("current_status", "")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def message(self) -> str:
|
def message(self) -> str:
|
||||||
return self.data.get("message", "")
|
return self.data.get("message", "")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def max_temp(self) -> float:
|
||||||
|
return self._max_temp
|
||||||
|
|
||||||
|
@property
|
||||||
|
def max_stir_speed(self) -> float:
|
||||||
|
return self._max_stir_speed
|
||||||
|
|
||||||
|
@property
|
||||||
|
def max_volume(self) -> float:
|
||||||
|
return self._max_volume
|
||||||
46
unilabos/devices/virtual/virtual_gas_source.py
Normal file
46
unilabos/devices/virtual/virtual_gas_source.py
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import time
|
||||||
|
from typing import Dict, Any, Optional
|
||||||
|
|
||||||
|
|
||||||
|
class VirtualGasSource:
|
||||||
|
"""Virtual gas source for testing"""
|
||||||
|
|
||||||
|
def __init__(self, device_id: Optional[str] = None, config: Optional[Dict[str, Any]] = None, **kwargs):
|
||||||
|
self.device_id = device_id or "unknown_gas_source"
|
||||||
|
self.config = config or {}
|
||||||
|
self.data = {}
|
||||||
|
self._status = "OPEN"
|
||||||
|
|
||||||
|
async def initialize(self) -> bool:
|
||||||
|
"""Initialize virtual gas source"""
|
||||||
|
self.data.update({
|
||||||
|
"status": self._status
|
||||||
|
})
|
||||||
|
return True
|
||||||
|
|
||||||
|
async def cleanup(self) -> bool:
|
||||||
|
"""Cleanup virtual gas source"""
|
||||||
|
return True
|
||||||
|
|
||||||
|
@property
|
||||||
|
def status(self) -> str:
|
||||||
|
return self._status
|
||||||
|
|
||||||
|
def get_status(self) -> str:
|
||||||
|
return self._status
|
||||||
|
|
||||||
|
def set_status(self, string):
|
||||||
|
self._status = string
|
||||||
|
time.sleep(5)
|
||||||
|
|
||||||
|
def open(self):
|
||||||
|
self._status = "OPEN"
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
self._status = "CLOSED"
|
||||||
|
|
||||||
|
def is_open(self):
|
||||||
|
return self._status
|
||||||
|
|
||||||
|
def is_closed(self):
|
||||||
|
return not self._status
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
|
import time as time_module # 重命名time模块,避免与参数冲突
|
||||||
from typing import Dict, Any
|
from typing import Dict, Any
|
||||||
|
|
||||||
class VirtualHeatChill:
|
class VirtualHeatChill:
|
||||||
@@ -19,18 +20,13 @@ class VirtualHeatChill:
|
|||||||
self.logger = logging.getLogger(f"VirtualHeatChill.{self.device_id}")
|
self.logger = logging.getLogger(f"VirtualHeatChill.{self.device_id}")
|
||||||
self.data = {}
|
self.data = {}
|
||||||
|
|
||||||
# 添加调试信息
|
|
||||||
print(f"=== VirtualHeatChill {self.device_id} is being created! ===")
|
|
||||||
print(f"=== Config: {self.config} ===")
|
|
||||||
print(f"=== Kwargs: {kwargs} ===")
|
|
||||||
|
|
||||||
# 从config或kwargs中获取配置参数
|
# 从config或kwargs中获取配置参数
|
||||||
self.port = self.config.get('port') or kwargs.get('port', 'VIRTUAL')
|
self.port = self.config.get('port') or kwargs.get('port', 'VIRTUAL')
|
||||||
self._max_temp = self.config.get('max_temp') or kwargs.get('max_temp', 200.0)
|
self._max_temp = self.config.get('max_temp') or kwargs.get('max_temp', 200.0)
|
||||||
self._min_temp = self.config.get('min_temp') or kwargs.get('min_temp', -80.0)
|
self._min_temp = self.config.get('min_temp') or kwargs.get('min_temp', -80.0)
|
||||||
self._max_stir_speed = self.config.get('max_stir_speed') or kwargs.get('max_stir_speed', 1000.0)
|
self._max_stir_speed = self.config.get('max_stir_speed') or kwargs.get('max_stir_speed', 1000.0)
|
||||||
|
|
||||||
# 处理其他kwargs参数,但跳过已知的配置参数
|
# 处理其他kwargs参数
|
||||||
skip_keys = {'port', 'max_temp', 'min_temp', 'max_stir_speed'}
|
skip_keys = {'port', 'max_temp', 'min_temp', 'max_stir_speed'}
|
||||||
for key, value in kwargs.items():
|
for key, value in kwargs.items():
|
||||||
if key not in skip_keys and not hasattr(self, key):
|
if key not in skip_keys and not hasattr(self, key):
|
||||||
@@ -38,70 +34,177 @@ class VirtualHeatChill:
|
|||||||
|
|
||||||
async def initialize(self) -> bool:
|
async def initialize(self) -> bool:
|
||||||
"""Initialize virtual heat chill"""
|
"""Initialize virtual heat chill"""
|
||||||
print(f"=== VirtualHeatChill {self.device_id} initialize() called! ===")
|
|
||||||
self.logger.info(f"Initializing virtual heat chill {self.device_id}")
|
self.logger.info(f"Initializing virtual heat chill {self.device_id}")
|
||||||
|
|
||||||
|
# 初始化状态信息
|
||||||
self.data.update({
|
self.data.update({
|
||||||
"status": "Idle"
|
"status": "Idle",
|
||||||
|
"operation_mode": "Idle",
|
||||||
|
"is_stirring": False,
|
||||||
|
"stir_speed": 0.0,
|
||||||
|
"remaining_time": 0.0,
|
||||||
})
|
})
|
||||||
return True
|
return True
|
||||||
|
|
||||||
async def cleanup(self) -> bool:
|
async def cleanup(self) -> bool:
|
||||||
"""Cleanup virtual heat chill"""
|
"""Cleanup virtual heat chill"""
|
||||||
self.logger.info(f"Cleaning up virtual heat chill {self.device_id}")
|
self.logger.info(f"Cleaning up virtual heat chill {self.device_id}")
|
||||||
|
self.data.update({
|
||||||
|
"status": "Offline",
|
||||||
|
"operation_mode": "Offline",
|
||||||
|
"is_stirring": False,
|
||||||
|
"stir_speed": 0.0,
|
||||||
|
"remaining_time": 0.0
|
||||||
|
})
|
||||||
return True
|
return True
|
||||||
|
|
||||||
async def heat_chill(self, vessel: str, temp: float, time: float, stir: bool,
|
async def heat_chill(self, vessel: str, temp: float, time: float, stir: bool,
|
||||||
stir_speed: float, purpose: str) -> bool:
|
stir_speed: float, purpose: str) -> bool:
|
||||||
"""Execute heat chill action - matches HeatChill action exactly"""
|
"""Execute heat chill action - 按实际时间运行,实时更新剩余时间"""
|
||||||
self.logger.info(f"HeatChill: vessel={vessel}, temp={temp}°C, time={time}s, stir={stir}, stir_speed={stir_speed}, purpose={purpose}")
|
self.logger.info(f"HeatChill: vessel={vessel}, temp={temp}°C, time={time}s, stir={stir}, stir_speed={stir_speed}")
|
||||||
|
|
||||||
# 验证参数
|
# 验证参数
|
||||||
if temp > self._max_temp or temp < self._min_temp:
|
if temp > self._max_temp or temp < self._min_temp:
|
||||||
self.logger.error(f"Temperature {temp} outside range {self._min_temp}-{self._max_temp}")
|
error_msg = f"温度 {temp}°C 超出范围 ({self._min_temp}°C - {self._max_temp}°C)"
|
||||||
self.data["status"] = f"温度 {temp} 超出范围"
|
self.logger.error(error_msg)
|
||||||
|
self.data.update({
|
||||||
|
"status": f"Error: {error_msg}",
|
||||||
|
"operation_mode": "Error"
|
||||||
|
})
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if stir and stir_speed > self._max_stir_speed:
|
if stir and stir_speed > self._max_stir_speed:
|
||||||
self.logger.error(f"Stir speed {stir_speed} exceeds maximum {self._max_stir_speed}")
|
error_msg = f"搅拌速度 {stir_speed} RPM 超出最大值 {self._max_stir_speed} RPM"
|
||||||
self.data["status"] = f"搅拌速度 {stir_speed} 超出范围"
|
self.logger.error(error_msg)
|
||||||
|
self.data.update({
|
||||||
|
"status": f"Error: {error_msg}",
|
||||||
|
"operation_mode": "Error"
|
||||||
|
})
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# 开始加热/冷却
|
# 确定操作模式
|
||||||
|
if temp > 25.0:
|
||||||
|
operation_mode = "Heating"
|
||||||
|
status_action = "加热"
|
||||||
|
elif temp < 25.0:
|
||||||
|
operation_mode = "Cooling"
|
||||||
|
status_action = "冷却"
|
||||||
|
else:
|
||||||
|
operation_mode = "Maintaining"
|
||||||
|
status_action = "保温"
|
||||||
|
|
||||||
|
# **修复**: 使用重命名的time模块
|
||||||
|
start_time = time_module.time()
|
||||||
|
total_time = time
|
||||||
|
|
||||||
|
# 开始操作
|
||||||
|
stir_info = f" | 搅拌: {stir_speed} RPM" if stir else ""
|
||||||
self.data.update({
|
self.data.update({
|
||||||
"status": f"加热/冷却中: {vessel} 至 {temp}°C"
|
"status": f"运行中: {status_action} {vessel} 至 {temp}°C | 剩余: {total_time:.0f}s{stir_info}",
|
||||||
|
"operation_mode": operation_mode,
|
||||||
|
"is_stirring": stir,
|
||||||
|
"stir_speed": stir_speed if stir else 0.0,
|
||||||
|
"remaining_time": total_time,
|
||||||
})
|
})
|
||||||
|
|
||||||
# 模拟加热/冷却时间
|
# **修复**: 在等待过程中每秒更新剩余时间
|
||||||
simulation_time = min(time, 10.0) # 最多等待10秒用于测试
|
while True:
|
||||||
await asyncio.sleep(simulation_time)
|
current_time = time_module.time() # 使用重命名的time模块
|
||||||
|
elapsed = current_time - start_time
|
||||||
|
remaining = max(0, total_time - elapsed)
|
||||||
|
|
||||||
|
# 更新剩余时间和状态
|
||||||
|
self.data.update({
|
||||||
|
"remaining_time": remaining,
|
||||||
|
"status": f"运行中: {status_action} {vessel} 至 {temp}°C | 剩余: {remaining:.0f}s{stir_info}"
|
||||||
|
})
|
||||||
|
|
||||||
|
# 如果时间到了,退出循环
|
||||||
|
if remaining <= 0:
|
||||||
|
break
|
||||||
|
|
||||||
|
# 等待1秒后再次检查
|
||||||
|
await asyncio.sleep(1.0)
|
||||||
|
|
||||||
# 加热/冷却完成
|
# 操作完成
|
||||||
self.data["status"] = f"完成: {vessel} 已达到 {temp}°C"
|
final_stir_info = f" | 搅拌: {stir_speed} RPM" if stir else ""
|
||||||
|
self.data.update({
|
||||||
|
"status": f"完成: {vessel} 已达到 {temp}°C | 用时: {total_time:.0f}s{final_stir_info}",
|
||||||
|
"operation_mode": "Completed",
|
||||||
|
"remaining_time": 0.0,
|
||||||
|
"is_stirring": False,
|
||||||
|
"stir_speed": 0.0
|
||||||
|
})
|
||||||
|
|
||||||
self.logger.info(f"HeatChill completed for vessel {vessel} at {temp}°C")
|
self.logger.info(f"HeatChill completed for vessel {vessel} at {temp}°C after {total_time}s")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
async def heat_chill_start(self, vessel: str, temp: float, purpose: str) -> bool:
|
async def heat_chill_start(self, vessel: str, temp: float, purpose: str) -> bool:
|
||||||
"""Start heat chill - matches HeatChillStart action exactly"""
|
"""Start continuous heat chill"""
|
||||||
self.logger.info(f"HeatChillStart: vessel={vessel}, temp={temp}°C, purpose={purpose}")
|
self.logger.info(f"HeatChillStart: vessel={vessel}, temp={temp}°C")
|
||||||
|
|
||||||
# 验证参数
|
# 验证参数
|
||||||
if temp > self._max_temp or temp < self._min_temp:
|
if temp > self._max_temp or temp < self._min_temp:
|
||||||
self.logger.error(f"Temperature {temp} outside range {self._min_temp}-{self._max_temp}")
|
error_msg = f"温度 {temp}°C 超出范围 ({self._min_temp}°C - {self._max_temp}°C)"
|
||||||
self.data["status"] = f"温度 {temp} 超出范围"
|
self.logger.error(error_msg)
|
||||||
|
self.data.update({
|
||||||
|
"status": f"Error: {error_msg}",
|
||||||
|
"operation_mode": "Error"
|
||||||
|
})
|
||||||
return False
|
return False
|
||||||
|
|
||||||
self.data["status"] = f"开始加热/冷却: {vessel} 至 {temp}°C"
|
# 确定操作模式
|
||||||
|
if temp > 25.0:
|
||||||
|
operation_mode = "Heating"
|
||||||
|
status_action = "持续加热"
|
||||||
|
elif temp < 25.0:
|
||||||
|
operation_mode = "Cooling"
|
||||||
|
status_action = "持续冷却"
|
||||||
|
else:
|
||||||
|
operation_mode = "Maintaining"
|
||||||
|
status_action = "恒温保持"
|
||||||
|
|
||||||
|
self.data.update({
|
||||||
|
"status": f"启动: {status_action} {vessel} 至 {temp}°C | 持续运行",
|
||||||
|
"operation_mode": operation_mode,
|
||||||
|
"is_stirring": False,
|
||||||
|
"stir_speed": 0.0,
|
||||||
|
"remaining_time": -1.0, # -1 表示持续运行
|
||||||
|
})
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
async def heat_chill_stop(self, vessel: str) -> bool:
|
async def heat_chill_stop(self, vessel: str) -> bool:
|
||||||
"""Stop heat chill - matches HeatChillStop action exactly"""
|
"""Stop heat chill"""
|
||||||
self.logger.info(f"HeatChillStop: vessel={vessel}")
|
self.logger.info(f"HeatChillStop: vessel={vessel}")
|
||||||
|
|
||||||
self.data["status"] = f"停止加热/冷却: {vessel}"
|
self.data.update({
|
||||||
|
"status": f"已停止: {vessel} 温控停止",
|
||||||
|
"operation_mode": "Stopped",
|
||||||
|
"is_stirring": False,
|
||||||
|
"stir_speed": 0.0,
|
||||||
|
"remaining_time": 0.0,
|
||||||
|
})
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# 状态属性 - 只保留 action 中定义的 feedback
|
# 状态属性
|
||||||
@property
|
@property
|
||||||
def status(self) -> str:
|
def status(self) -> str:
|
||||||
return self.data.get("status", "Idle")
|
return self.data.get("status", "Idle")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def operation_mode(self) -> str:
|
||||||
|
return self.data.get("operation_mode", "Idle")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_stirring(self) -> bool:
|
||||||
|
return self.data.get("is_stirring", False)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def stir_speed(self) -> float:
|
||||||
|
return self.data.get("stir_speed", 0.0)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def remaining_time(self) -> float:
|
||||||
|
return self.data.get("remaining_time", 0.0)
|
||||||
231
unilabos/devices/virtual/virtual_multiway_valve.py
Normal file
231
unilabos/devices/virtual/virtual_multiway_valve.py
Normal file
@@ -0,0 +1,231 @@
|
|||||||
|
import time
|
||||||
|
from typing import Union, Dict, Optional
|
||||||
|
|
||||||
|
|
||||||
|
class VirtualMultiwayValve:
|
||||||
|
"""
|
||||||
|
虚拟九通阀门 - 0号位连接transfer pump,1-8号位连接其他设备
|
||||||
|
"""
|
||||||
|
def __init__(self, port: str = "VIRTUAL", positions: int = 8):
|
||||||
|
self.port = port
|
||||||
|
self.max_positions = positions # 1-8号位
|
||||||
|
self.total_positions = positions + 1 # 0-8号位,共9个位置
|
||||||
|
|
||||||
|
# 状态属性
|
||||||
|
self._status = "Idle"
|
||||||
|
self._valve_state = "Ready"
|
||||||
|
self._current_position = 0 # 默认在0号位(transfer pump位置)
|
||||||
|
self._target_position = 0
|
||||||
|
|
||||||
|
# 位置映射说明
|
||||||
|
self.position_map = {
|
||||||
|
0: "transfer_pump", # 0号位连接转移泵
|
||||||
|
1: "port_1", # 1号位
|
||||||
|
2: "port_2", # 2号位
|
||||||
|
3: "port_3", # 3号位
|
||||||
|
4: "port_4", # 4号位
|
||||||
|
5: "port_5", # 5号位
|
||||||
|
6: "port_6", # 6号位
|
||||||
|
7: "port_7", # 7号位
|
||||||
|
8: "port_8" # 8号位
|
||||||
|
}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def status(self) -> str:
|
||||||
|
return self._status
|
||||||
|
|
||||||
|
@property
|
||||||
|
def valve_state(self) -> str:
|
||||||
|
return self._valve_state
|
||||||
|
|
||||||
|
@property
|
||||||
|
def current_position(self) -> int:
|
||||||
|
return self._current_position
|
||||||
|
|
||||||
|
@property
|
||||||
|
def target_position(self) -> int:
|
||||||
|
return self._target_position
|
||||||
|
|
||||||
|
def get_current_position(self) -> int:
|
||||||
|
"""获取当前阀门位置"""
|
||||||
|
return self._current_position
|
||||||
|
|
||||||
|
def get_current_port(self) -> str:
|
||||||
|
"""获取当前连接的端口名称"""
|
||||||
|
return self.position_map.get(self._current_position, "unknown")
|
||||||
|
|
||||||
|
def set_position(self, command: Union[int, str]):
|
||||||
|
"""
|
||||||
|
设置阀门位置 - 支持0-8位置
|
||||||
|
|
||||||
|
Args:
|
||||||
|
command: 目标位置 (0-8) 或位置字符串
|
||||||
|
0: transfer pump位置
|
||||||
|
1-8: 其他设备位置
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# 如果是字符串形式的位置,先转换为数字
|
||||||
|
if isinstance(command, str):
|
||||||
|
pos = int(command)
|
||||||
|
else:
|
||||||
|
pos = int(command)
|
||||||
|
|
||||||
|
if pos < 0 or pos > self.max_positions:
|
||||||
|
raise ValueError(f"Position must be between 0 and {self.max_positions}")
|
||||||
|
|
||||||
|
self._status = "Busy"
|
||||||
|
self._valve_state = "Moving"
|
||||||
|
self._target_position = pos
|
||||||
|
|
||||||
|
# 模拟阀门切换时间
|
||||||
|
switch_time = abs(self._current_position - pos) * 0.5 # 每个位置0.5秒
|
||||||
|
time.sleep(switch_time)
|
||||||
|
|
||||||
|
self._current_position = pos
|
||||||
|
self._status = "Idle"
|
||||||
|
self._valve_state = "Ready"
|
||||||
|
|
||||||
|
current_port = self.get_current_port()
|
||||||
|
return f"Position set to {pos} ({current_port})"
|
||||||
|
|
||||||
|
except ValueError as e:
|
||||||
|
self._status = "Error"
|
||||||
|
self._valve_state = "Error"
|
||||||
|
return f"Error: {str(e)}"
|
||||||
|
|
||||||
|
def set_to_pump_position(self):
|
||||||
|
"""切换到transfer pump位置(0号位)"""
|
||||||
|
return self.set_position(0)
|
||||||
|
|
||||||
|
def set_to_port(self, port_number: int):
|
||||||
|
"""
|
||||||
|
切换到指定端口位置
|
||||||
|
|
||||||
|
Args:
|
||||||
|
port_number: 端口号 (1-8)
|
||||||
|
"""
|
||||||
|
if port_number < 1 or port_number > self.max_positions:
|
||||||
|
raise ValueError(f"Port number must be between 1 and {self.max_positions}")
|
||||||
|
return self.set_position(port_number)
|
||||||
|
|
||||||
|
def open(self):
|
||||||
|
"""打开阀门 - 设置到transfer pump位置(0号位)"""
|
||||||
|
return self.set_to_pump_position()
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
"""关闭阀门 - 对于多通阀门,设置到一个"关闭"状态"""
|
||||||
|
self._status = "Busy"
|
||||||
|
self._valve_state = "Closing"
|
||||||
|
time.sleep(0.5)
|
||||||
|
|
||||||
|
# 可以选择保持当前位置或设置特殊关闭状态
|
||||||
|
self._status = "Idle"
|
||||||
|
self._valve_state = "Closed"
|
||||||
|
|
||||||
|
return f"Valve closed at position {self._current_position}"
|
||||||
|
|
||||||
|
def get_valve_position(self) -> int:
|
||||||
|
"""获取阀门位置 - 兼容性方法"""
|
||||||
|
return self._current_position
|
||||||
|
|
||||||
|
def is_at_position(self, position: int) -> bool:
|
||||||
|
"""检查是否在指定位置"""
|
||||||
|
return self._current_position == position
|
||||||
|
|
||||||
|
def is_at_pump_position(self) -> bool:
|
||||||
|
"""检查是否在transfer pump位置"""
|
||||||
|
return self._current_position == 0
|
||||||
|
|
||||||
|
def is_at_port(self, port_number: int) -> bool:
|
||||||
|
"""检查是否在指定端口位置"""
|
||||||
|
return self._current_position == port_number
|
||||||
|
|
||||||
|
def get_available_positions(self) -> list:
|
||||||
|
"""获取可用位置列表"""
|
||||||
|
return list(range(0, self.max_positions + 1))
|
||||||
|
|
||||||
|
def get_available_ports(self) -> Dict[int, str]:
|
||||||
|
"""获取可用端口映射"""
|
||||||
|
return self.position_map.copy()
|
||||||
|
|
||||||
|
def reset(self):
|
||||||
|
"""重置阀门到transfer pump位置(0号位)"""
|
||||||
|
return self.set_position(0)
|
||||||
|
|
||||||
|
def switch_between_pump_and_port(self, port_number: int):
|
||||||
|
"""
|
||||||
|
在transfer pump位置和指定端口之间切换
|
||||||
|
|
||||||
|
Args:
|
||||||
|
port_number: 目标端口号 (1-8)
|
||||||
|
"""
|
||||||
|
if self._current_position == 0:
|
||||||
|
# 当前在pump位置,切换到指定端口
|
||||||
|
return self.set_to_port(port_number)
|
||||||
|
else:
|
||||||
|
# 当前在某个端口,切换到pump位置
|
||||||
|
return self.set_to_pump_position()
|
||||||
|
|
||||||
|
def get_flow_path(self) -> str:
|
||||||
|
"""获取当前流路路径描述"""
|
||||||
|
current_port = self.get_current_port()
|
||||||
|
if self._current_position == 0:
|
||||||
|
return f"Transfer pump connected (position {self._current_position})"
|
||||||
|
else:
|
||||||
|
return f"Port {self._current_position} connected ({current_port})"
|
||||||
|
|
||||||
|
def get_info(self) -> dict:
|
||||||
|
"""获取阀门详细信息"""
|
||||||
|
return {
|
||||||
|
"port": self.port,
|
||||||
|
"max_positions": self.max_positions,
|
||||||
|
"total_positions": self.total_positions,
|
||||||
|
"current_position": self._current_position,
|
||||||
|
"current_port": self.get_current_port(),
|
||||||
|
"target_position": self._target_position,
|
||||||
|
"status": self._status,
|
||||||
|
"valve_state": self._valve_state,
|
||||||
|
"flow_path": self.get_flow_path(),
|
||||||
|
"position_map": self.position_map
|
||||||
|
}
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"VirtualMultiwayValve(Position: {self._current_position}/{self.max_positions}, Port: {self.get_current_port()}, Status: {self._status})"
|
||||||
|
|
||||||
|
def set_valve_position(self, command: Union[int, str]):
|
||||||
|
"""
|
||||||
|
设置阀门位置 - 兼容pump_protocol调用
|
||||||
|
这是set_position的别名方法,用于兼容pump_protocol.py
|
||||||
|
|
||||||
|
Args:
|
||||||
|
command: 目标位置 (0-8) 或位置字符串
|
||||||
|
"""
|
||||||
|
return self.set_position(command)
|
||||||
|
|
||||||
|
|
||||||
|
# 使用示例
|
||||||
|
if __name__ == "__main__":
|
||||||
|
valve = VirtualMultiwayValve()
|
||||||
|
|
||||||
|
print("=== 虚拟九通阀门测试 ===")
|
||||||
|
print(f"初始状态: {valve}")
|
||||||
|
print(f"当前流路: {valve.get_flow_path()}")
|
||||||
|
|
||||||
|
# 切换到试剂瓶1(1号位)
|
||||||
|
print(f"\n切换到1号位: {valve.set_position(1)}")
|
||||||
|
print(f"当前状态: {valve}")
|
||||||
|
|
||||||
|
# 切换到transfer pump位置(0号位)
|
||||||
|
print(f"\n切换到pump位置: {valve.set_to_pump_position()}")
|
||||||
|
print(f"当前状态: {valve}")
|
||||||
|
|
||||||
|
# 切换到试剂瓶2(2号位)
|
||||||
|
print(f"\n切换到2号位: {valve.set_to_port(2)}")
|
||||||
|
print(f"当前状态: {valve}")
|
||||||
|
|
||||||
|
# 显示所有可用位置
|
||||||
|
print(f"\n可用位置: {valve.get_available_positions()}")
|
||||||
|
print(f"端口映射: {valve.get_available_ports()}")
|
||||||
|
|
||||||
|
# 获取详细信息
|
||||||
|
print(f"\n详细信息: {valve.get_info()}")
|
||||||
228
unilabos/devices/virtual/virtual_rotavap.py
Normal file
228
unilabos/devices/virtual/virtual_rotavap.py
Normal file
@@ -0,0 +1,228 @@
|
|||||||
|
import asyncio
|
||||||
|
import logging
|
||||||
|
import time as time_module
|
||||||
|
from typing import Dict, Any, Optional
|
||||||
|
|
||||||
|
|
||||||
|
class VirtualRotavap:
|
||||||
|
"""Virtual rotary evaporator device - 简化版,只保留核心功能"""
|
||||||
|
|
||||||
|
def __init__(self, device_id: Optional[str] = None, config: Optional[Dict[str, Any]] = None, **kwargs):
|
||||||
|
# 处理可能的不同调用方式
|
||||||
|
if device_id is None and "id" in kwargs:
|
||||||
|
device_id = kwargs.pop("id")
|
||||||
|
if config is None and "config" in kwargs:
|
||||||
|
config = kwargs.pop("config")
|
||||||
|
|
||||||
|
# 设置默认值
|
||||||
|
self.device_id = device_id or "unknown_rotavap"
|
||||||
|
self.config = config or {}
|
||||||
|
|
||||||
|
self.logger = logging.getLogger(f"VirtualRotavap.{self.device_id}")
|
||||||
|
self.data = {}
|
||||||
|
|
||||||
|
# 从config或kwargs中获取配置参数
|
||||||
|
self.port = self.config.get("port") or kwargs.get("port", "VIRTUAL")
|
||||||
|
self._max_temp = self.config.get("max_temp") or kwargs.get("max_temp", 180.0)
|
||||||
|
self._max_rotation_speed = self.config.get("max_rotation_speed") or kwargs.get("max_rotation_speed", 280.0)
|
||||||
|
|
||||||
|
# 处理其他kwargs参数
|
||||||
|
skip_keys = {"port", "max_temp", "max_rotation_speed"}
|
||||||
|
for key, value in kwargs.items():
|
||||||
|
if key not in skip_keys and not hasattr(self, key):
|
||||||
|
setattr(self, key, value)
|
||||||
|
|
||||||
|
async def initialize(self) -> bool:
|
||||||
|
"""Initialize virtual rotary evaporator"""
|
||||||
|
self.logger.info(f"Initializing virtual rotary evaporator {self.device_id}")
|
||||||
|
|
||||||
|
# 只保留核心状态
|
||||||
|
self.data.update({
|
||||||
|
"status": "Idle",
|
||||||
|
"rotavap_state": "Ready", # Ready, Evaporating, Completed, Error
|
||||||
|
"current_temp": 25.0,
|
||||||
|
"target_temp": 25.0,
|
||||||
|
"rotation_speed": 0.0,
|
||||||
|
"vacuum_pressure": 1.0, # 大气压
|
||||||
|
"evaporated_volume": 0.0,
|
||||||
|
"progress": 0.0,
|
||||||
|
"remaining_time": 0.0,
|
||||||
|
"message": "Ready for evaporation"
|
||||||
|
})
|
||||||
|
return True
|
||||||
|
|
||||||
|
async def cleanup(self) -> bool:
|
||||||
|
"""Cleanup virtual rotary evaporator"""
|
||||||
|
self.logger.info(f"Cleaning up virtual rotary evaporator {self.device_id}")
|
||||||
|
|
||||||
|
self.data.update({
|
||||||
|
"status": "Offline",
|
||||||
|
"rotavap_state": "Offline",
|
||||||
|
"current_temp": 25.0,
|
||||||
|
"rotation_speed": 0.0,
|
||||||
|
"vacuum_pressure": 1.0,
|
||||||
|
"message": "System offline"
|
||||||
|
})
|
||||||
|
return True
|
||||||
|
|
||||||
|
async def evaporate(
|
||||||
|
self,
|
||||||
|
vessel: str,
|
||||||
|
pressure: float = 0.1,
|
||||||
|
temp: float = 60.0,
|
||||||
|
time: float = 1800.0, # 30分钟默认
|
||||||
|
stir_speed: float = 100.0
|
||||||
|
) -> bool:
|
||||||
|
"""Execute evaporate action - 简化的蒸发流程"""
|
||||||
|
self.logger.info(f"Evaporate: vessel={vessel}, pressure={pressure} bar, temp={temp}°C, time={time}s, rotation={stir_speed} RPM")
|
||||||
|
|
||||||
|
# 验证参数
|
||||||
|
if temp > self._max_temp or temp < 10.0:
|
||||||
|
error_msg = f"温度 {temp}°C 超出范围 (10-{self._max_temp}°C)"
|
||||||
|
self.logger.error(error_msg)
|
||||||
|
self.data.update({
|
||||||
|
"status": f"Error: {error_msg}",
|
||||||
|
"rotavap_state": "Error",
|
||||||
|
"message": error_msg
|
||||||
|
})
|
||||||
|
return False
|
||||||
|
|
||||||
|
if stir_speed > self._max_rotation_speed or stir_speed < 10.0:
|
||||||
|
error_msg = f"旋转速度 {stir_speed} RPM 超出范围 (10-{self._max_rotation_speed} RPM)"
|
||||||
|
self.logger.error(error_msg)
|
||||||
|
self.data.update({
|
||||||
|
"status": f"Error: {error_msg}",
|
||||||
|
"rotavap_state": "Error",
|
||||||
|
"message": error_msg
|
||||||
|
})
|
||||||
|
return False
|
||||||
|
|
||||||
|
if pressure < 0.01 or pressure > 1.0:
|
||||||
|
error_msg = f"真空度 {pressure} bar 超出范围 (0.01-1.0 bar)"
|
||||||
|
self.logger.error(error_msg)
|
||||||
|
self.data.update({
|
||||||
|
"status": f"Error: {error_msg}",
|
||||||
|
"rotavap_state": "Error",
|
||||||
|
"message": error_msg
|
||||||
|
})
|
||||||
|
return False
|
||||||
|
|
||||||
|
# 开始蒸发
|
||||||
|
self.data.update({
|
||||||
|
"status": f"蒸发中: {vessel}",
|
||||||
|
"rotavap_state": "Evaporating",
|
||||||
|
"current_temp": temp,
|
||||||
|
"target_temp": temp,
|
||||||
|
"rotation_speed": stir_speed,
|
||||||
|
"vacuum_pressure": pressure,
|
||||||
|
"remaining_time": time,
|
||||||
|
"progress": 0.0,
|
||||||
|
"evaporated_volume": 0.0,
|
||||||
|
"message": f"Evaporating {vessel} at {temp}°C, {pressure} bar, {stir_speed} RPM"
|
||||||
|
})
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 蒸发过程 - 实时更新进度
|
||||||
|
start_time = time_module.time()
|
||||||
|
total_time = time
|
||||||
|
|
||||||
|
while True:
|
||||||
|
current_time = time_module.time()
|
||||||
|
elapsed = current_time - start_time
|
||||||
|
remaining = max(0, total_time - elapsed)
|
||||||
|
progress = min(100.0, (elapsed / total_time) * 100)
|
||||||
|
|
||||||
|
# 模拟蒸发体积
|
||||||
|
evaporated_vol = progress * 0.8 # 假设最多蒸发80mL
|
||||||
|
|
||||||
|
# 更新状态
|
||||||
|
self.data.update({
|
||||||
|
"remaining_time": remaining,
|
||||||
|
"progress": progress,
|
||||||
|
"evaporated_volume": evaporated_vol,
|
||||||
|
"status": f"蒸发中: {vessel} | {temp}°C | {pressure} bar | {progress:.1f}% | 剩余: {remaining:.0f}s",
|
||||||
|
"message": f"Evaporating: {progress:.1f}% complete, {remaining:.0f}s remaining"
|
||||||
|
})
|
||||||
|
|
||||||
|
# 时间到了,退出循环
|
||||||
|
if remaining <= 0:
|
||||||
|
break
|
||||||
|
|
||||||
|
# 每秒更新一次
|
||||||
|
await asyncio.sleep(1.0)
|
||||||
|
|
||||||
|
# 蒸发完成
|
||||||
|
final_evaporated = 80.0
|
||||||
|
self.data.update({
|
||||||
|
"status": f"蒸发完成: {vessel} | 蒸发量: {final_evaporated:.1f}mL",
|
||||||
|
"rotavap_state": "Completed",
|
||||||
|
"evaporated_volume": final_evaporated,
|
||||||
|
"progress": 100.0,
|
||||||
|
"remaining_time": 0.0,
|
||||||
|
"current_temp": 25.0, # 冷却下来
|
||||||
|
"rotation_speed": 0.0, # 停止旋转
|
||||||
|
"vacuum_pressure": 1.0, # 恢复大气压
|
||||||
|
"message": f"Evaporation completed: {final_evaporated}mL evaporated from {vessel}"
|
||||||
|
})
|
||||||
|
|
||||||
|
self.logger.info(f"Evaporation completed: {final_evaporated}mL evaporated from {vessel}")
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
# 出错处理
|
||||||
|
self.logger.error(f"Error during evaporation: {str(e)}")
|
||||||
|
|
||||||
|
self.data.update({
|
||||||
|
"status": f"蒸发错误: {str(e)}",
|
||||||
|
"rotavap_state": "Error",
|
||||||
|
"current_temp": 25.0,
|
||||||
|
"rotation_speed": 0.0,
|
||||||
|
"vacuum_pressure": 1.0,
|
||||||
|
"message": f"Evaporation failed: {str(e)}"
|
||||||
|
})
|
||||||
|
return False
|
||||||
|
|
||||||
|
# === 核心状态属性 ===
|
||||||
|
@property
|
||||||
|
def status(self) -> str:
|
||||||
|
return self.data.get("status", "Unknown")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def rotavap_state(self) -> str:
|
||||||
|
return self.data.get("rotavap_state", "Unknown")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def current_temp(self) -> float:
|
||||||
|
return self.data.get("current_temp", 25.0)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def rotation_speed(self) -> float:
|
||||||
|
return self.data.get("rotation_speed", 0.0)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def vacuum_pressure(self) -> float:
|
||||||
|
return self.data.get("vacuum_pressure", 1.0)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def evaporated_volume(self) -> float:
|
||||||
|
return self.data.get("evaporated_volume", 0.0)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def progress(self) -> float:
|
||||||
|
return self.data.get("progress", 0.0)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def message(self) -> str:
|
||||||
|
return self.data.get("message", "")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def max_temp(self) -> float:
|
||||||
|
return self._max_temp
|
||||||
|
|
||||||
|
@property
|
||||||
|
def max_rotation_speed(self) -> float:
|
||||||
|
return self._max_rotation_speed
|
||||||
|
|
||||||
|
@property
|
||||||
|
def remaining_time(self) -> float:
|
||||||
|
return self.data.get("remaining_time", 0.0)
|
||||||
184
unilabos/devices/virtual/virtual_separator.py
Normal file
184
unilabos/devices/virtual/virtual_separator.py
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
import asyncio
|
||||||
|
import logging
|
||||||
|
from typing import Dict, Any, Optional
|
||||||
|
|
||||||
|
|
||||||
|
class VirtualSeparator:
|
||||||
|
"""Virtual separator device for SeparateProtocol testing"""
|
||||||
|
|
||||||
|
def __init__(self, device_id: Optional[str] = None, config: Optional[Dict[str, Any]] = None, **kwargs):
|
||||||
|
# 处理可能的不同调用方式
|
||||||
|
if device_id is None and "id" in kwargs:
|
||||||
|
device_id = kwargs.pop("id")
|
||||||
|
if config is None and "config" in kwargs:
|
||||||
|
config = kwargs.pop("config")
|
||||||
|
|
||||||
|
# 设置默认值
|
||||||
|
self.device_id = device_id or "unknown_separator"
|
||||||
|
self.config = config or {}
|
||||||
|
|
||||||
|
self.logger = logging.getLogger(f"VirtualSeparator.{self.device_id}")
|
||||||
|
self.data = {}
|
||||||
|
|
||||||
|
# 添加调试信息
|
||||||
|
print(f"=== VirtualSeparator {self.device_id} is being created! ===")
|
||||||
|
print(f"=== Config: {self.config} ===")
|
||||||
|
print(f"=== Kwargs: {kwargs} ===")
|
||||||
|
|
||||||
|
# 从config或kwargs中获取配置参数
|
||||||
|
self.port = self.config.get("port") or kwargs.get("port", "VIRTUAL")
|
||||||
|
self._volume = self.config.get("volume") or kwargs.get("volume", 250.0)
|
||||||
|
self._has_phases = self.config.get("has_phases") or kwargs.get("has_phases", True)
|
||||||
|
|
||||||
|
# 处理其他kwargs参数,但跳过已知的配置参数
|
||||||
|
skip_keys = {"port", "volume", "has_phases"}
|
||||||
|
for key, value in kwargs.items():
|
||||||
|
if key not in skip_keys and not hasattr(self, key):
|
||||||
|
setattr(self, key, value)
|
||||||
|
|
||||||
|
async def initialize(self) -> bool:
|
||||||
|
"""Initialize virtual separator"""
|
||||||
|
print(f"=== VirtualSeparator {self.device_id} initialize() called! ===")
|
||||||
|
self.logger.info(f"Initializing virtual separator {self.device_id}")
|
||||||
|
self.data.update(
|
||||||
|
{
|
||||||
|
"status": "Ready",
|
||||||
|
"separator_state": "Ready",
|
||||||
|
"volume": self._volume,
|
||||||
|
"has_phases": self._has_phases,
|
||||||
|
"phase_separation": False,
|
||||||
|
"stir_speed": 0.0,
|
||||||
|
"settling_time": 0.0,
|
||||||
|
"progress": 0.0,
|
||||||
|
"message": "",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return True
|
||||||
|
|
||||||
|
async def cleanup(self) -> bool:
|
||||||
|
"""Cleanup virtual separator"""
|
||||||
|
self.logger.info(f"Cleaning up virtual separator {self.device_id}")
|
||||||
|
return True
|
||||||
|
|
||||||
|
async def separate(
|
||||||
|
self,
|
||||||
|
purpose: str,
|
||||||
|
product_phase: str,
|
||||||
|
from_vessel: str,
|
||||||
|
separation_vessel: str,
|
||||||
|
to_vessel: str,
|
||||||
|
waste_phase_to_vessel: str = "",
|
||||||
|
solvent: str = "",
|
||||||
|
solvent_volume: float = 50.0,
|
||||||
|
through: str = "",
|
||||||
|
repeats: int = 1,
|
||||||
|
stir_time: float = 30.0,
|
||||||
|
stir_speed: float = 300.0,
|
||||||
|
settling_time: float = 300.0,
|
||||||
|
) -> bool:
|
||||||
|
"""Execute separate action - matches Separate action"""
|
||||||
|
self.logger.info(f"Separate: purpose={purpose}, product_phase={product_phase}, from_vessel={from_vessel}")
|
||||||
|
|
||||||
|
# 验证参数
|
||||||
|
if product_phase not in ["top", "bottom"]:
|
||||||
|
self.logger.error(f"Invalid product_phase {product_phase}, must be 'top' or 'bottom'")
|
||||||
|
self.data["message"] = f"产物相位 {product_phase} 无效,必须是 'top' 或 'bottom'"
|
||||||
|
return False
|
||||||
|
|
||||||
|
if purpose not in ["wash", "extract"]:
|
||||||
|
self.logger.error(f"Invalid purpose {purpose}, must be 'wash' or 'extract'")
|
||||||
|
self.data["message"] = f"分离目的 {purpose} 无效,必须是 'wash' 或 'extract'"
|
||||||
|
return False
|
||||||
|
|
||||||
|
# 开始分离
|
||||||
|
self.data.update(
|
||||||
|
{
|
||||||
|
"status": "Running",
|
||||||
|
"separator_state": "Separating",
|
||||||
|
"purpose": purpose,
|
||||||
|
"product_phase": product_phase,
|
||||||
|
"from_vessel": from_vessel,
|
||||||
|
"separation_vessel": separation_vessel,
|
||||||
|
"to_vessel": to_vessel,
|
||||||
|
"waste_phase_to_vessel": waste_phase_to_vessel,
|
||||||
|
"solvent": solvent,
|
||||||
|
"solvent_volume": solvent_volume,
|
||||||
|
"repeats": repeats,
|
||||||
|
"stir_speed": stir_speed,
|
||||||
|
"settling_time": settling_time,
|
||||||
|
"phase_separation": True,
|
||||||
|
"progress": 0.0,
|
||||||
|
"message": f"正在分离: {from_vessel} -> {to_vessel}",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# 模拟分离过程
|
||||||
|
total_time = (stir_time + settling_time) * repeats
|
||||||
|
simulation_time = min(total_time / 60.0, 15.0) # 最多模拟15秒
|
||||||
|
|
||||||
|
for repeat in range(repeats):
|
||||||
|
# 搅拌阶段
|
||||||
|
for progress in range(0, 51, 10):
|
||||||
|
await asyncio.sleep(simulation_time / (repeats * 10))
|
||||||
|
overall_progress = ((repeat * 100) + (progress * 0.5)) / repeats
|
||||||
|
self.data["progress"] = overall_progress
|
||||||
|
self.data["message"] = f"第{repeat+1}次分离 - 搅拌中 ({progress}%)"
|
||||||
|
|
||||||
|
# 静置分相阶段
|
||||||
|
for progress in range(50, 101, 10):
|
||||||
|
await asyncio.sleep(simulation_time / (repeats * 10))
|
||||||
|
overall_progress = ((repeat * 100) + (progress * 0.5)) / repeats
|
||||||
|
self.data["progress"] = overall_progress
|
||||||
|
self.data["message"] = f"第{repeat+1}次分离 - 静置分相中 ({progress}%)"
|
||||||
|
|
||||||
|
# 分离完成
|
||||||
|
self.data.update(
|
||||||
|
{
|
||||||
|
"status": "Ready",
|
||||||
|
"separator_state": "Ready",
|
||||||
|
"phase_separation": False,
|
||||||
|
"stir_speed": 0.0,
|
||||||
|
"progress": 100.0,
|
||||||
|
"message": f"分离完成: {repeats}次分离操作",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
self.logger.info(f"Separation completed: {repeats} cycles from {from_vessel} to {to_vessel}")
|
||||||
|
return True
|
||||||
|
|
||||||
|
# 状态属性
|
||||||
|
@property
|
||||||
|
def status(self) -> str:
|
||||||
|
return self.data.get("status", "Unknown")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def separator_state(self) -> str:
|
||||||
|
return self.data.get("separator_state", "Unknown")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def volume(self) -> float:
|
||||||
|
return self.data.get("volume", self._volume)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def has_phases(self) -> bool:
|
||||||
|
return self.data.get("has_phases", self._has_phases)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def phase_separation(self) -> bool:
|
||||||
|
return self.data.get("phase_separation", False)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def stir_speed(self) -> float:
|
||||||
|
return self.data.get("stir_speed", 0.0)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def settling_time(self) -> float:
|
||||||
|
return self.data.get("settling_time", 0.0)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def progress(self) -> float:
|
||||||
|
return self.data.get("progress", 0.0)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def message(self) -> str:
|
||||||
|
return self.data.get("message", "")
|
||||||
147
unilabos/devices/virtual/virtual_solenoid_valve.py
Normal file
147
unilabos/devices/virtual/virtual_solenoid_valve.py
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
import time
|
||||||
|
import asyncio
|
||||||
|
from typing import Union
|
||||||
|
|
||||||
|
|
||||||
|
class VirtualSolenoidValve:
|
||||||
|
"""
|
||||||
|
虚拟电磁阀门 - 简单的开关型阀门,只有开启和关闭两个状态
|
||||||
|
"""
|
||||||
|
def __init__(self, device_id: str = None, config: dict = None, **kwargs):
|
||||||
|
# 从配置中获取参数,提供默认值
|
||||||
|
if config is None:
|
||||||
|
config = {}
|
||||||
|
|
||||||
|
self.device_id = device_id
|
||||||
|
self.port = config.get("port", "VIRTUAL")
|
||||||
|
self.voltage = config.get("voltage", 12.0)
|
||||||
|
self.response_time = config.get("response_time", 0.1)
|
||||||
|
|
||||||
|
# 状态属性
|
||||||
|
self._status = "Idle"
|
||||||
|
self._valve_state = "Closed" # "Open" or "Closed"
|
||||||
|
self._is_open = False
|
||||||
|
|
||||||
|
async def initialize(self) -> bool:
|
||||||
|
"""初始化设备"""
|
||||||
|
self._status = "Idle"
|
||||||
|
return True
|
||||||
|
|
||||||
|
async def cleanup(self) -> bool:
|
||||||
|
"""清理资源"""
|
||||||
|
return True
|
||||||
|
|
||||||
|
@property
|
||||||
|
def status(self) -> str:
|
||||||
|
return self._status
|
||||||
|
|
||||||
|
@property
|
||||||
|
def valve_state(self) -> str:
|
||||||
|
return self._valve_state
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_open(self) -> bool:
|
||||||
|
return self._is_open
|
||||||
|
|
||||||
|
def get_valve_position(self) -> str:
|
||||||
|
"""获取阀门位置状态"""
|
||||||
|
return "OPEN" if self._is_open else "CLOSED"
|
||||||
|
|
||||||
|
async def set_valve_position(self, command: str = None, **kwargs):
|
||||||
|
"""
|
||||||
|
设置阀门位置 - ROS动作接口
|
||||||
|
|
||||||
|
Args:
|
||||||
|
command: "OPEN"/"CLOSED" 或其他控制命令
|
||||||
|
"""
|
||||||
|
if command is None:
|
||||||
|
return {"success": False, "message": "Missing command parameter"}
|
||||||
|
|
||||||
|
print(f"SOLENOID_VALVE: {self.device_id} 接收到命令: {command}")
|
||||||
|
|
||||||
|
self._status = "Busy"
|
||||||
|
|
||||||
|
# 模拟阀门响应时间
|
||||||
|
await asyncio.sleep(self.response_time)
|
||||||
|
|
||||||
|
# 处理不同的命令格式
|
||||||
|
if isinstance(command, str):
|
||||||
|
cmd_upper = command.upper()
|
||||||
|
if cmd_upper in ["OPEN", "ON", "TRUE", "1"]:
|
||||||
|
self._is_open = True
|
||||||
|
self._valve_state = "Open"
|
||||||
|
result_msg = f"Valve {self.device_id} opened"
|
||||||
|
elif cmd_upper in ["CLOSED", "CLOSE", "OFF", "FALSE", "0"]:
|
||||||
|
self._is_open = False
|
||||||
|
self._valve_state = "Closed"
|
||||||
|
result_msg = f"Valve {self.device_id} closed"
|
||||||
|
else:
|
||||||
|
# 可能是端口名称,处理路径设置
|
||||||
|
# 对于简单电磁阀,任何非关闭命令都视为开启
|
||||||
|
self._is_open = True
|
||||||
|
self._valve_state = "Open"
|
||||||
|
result_msg = f"Valve {self.device_id} set to position: {command}"
|
||||||
|
else:
|
||||||
|
self._status = "Error"
|
||||||
|
return {"success": False, "message": "Invalid command type"}
|
||||||
|
|
||||||
|
self._status = "Idle"
|
||||||
|
print(f"SOLENOID_VALVE: {result_msg}")
|
||||||
|
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"message": result_msg,
|
||||||
|
"valve_position": self.get_valve_position()
|
||||||
|
}
|
||||||
|
|
||||||
|
async def open(self, **kwargs):
|
||||||
|
"""打开电磁阀 - ROS动作接口"""
|
||||||
|
return await self.set_valve_position(command="OPEN")
|
||||||
|
|
||||||
|
async def close(self, **kwargs):
|
||||||
|
"""关闭电磁阀 - ROS动作接口"""
|
||||||
|
return await self.set_valve_position(command="CLOSED")
|
||||||
|
|
||||||
|
async def set_state(self, command: Union[bool, str], **kwargs):
|
||||||
|
"""
|
||||||
|
设置阀门状态 - 兼容 SendCmd 类型
|
||||||
|
|
||||||
|
Args:
|
||||||
|
command: True/False 或 "open"/"close"
|
||||||
|
"""
|
||||||
|
if isinstance(command, bool):
|
||||||
|
cmd_str = "OPEN" if command else "CLOSED"
|
||||||
|
elif isinstance(command, str):
|
||||||
|
cmd_str = command
|
||||||
|
else:
|
||||||
|
return {"success": False, "message": "Invalid command type"}
|
||||||
|
|
||||||
|
return await self.set_valve_position(command=cmd_str)
|
||||||
|
|
||||||
|
def toggle(self):
|
||||||
|
"""切换阀门状态"""
|
||||||
|
if self._is_open:
|
||||||
|
return self.close()
|
||||||
|
else:
|
||||||
|
return self.open()
|
||||||
|
|
||||||
|
def is_closed(self) -> bool:
|
||||||
|
"""检查阀门是否关闭"""
|
||||||
|
return not self._is_open
|
||||||
|
|
||||||
|
def get_state(self) -> dict:
|
||||||
|
"""获取阀门完整状态"""
|
||||||
|
return {
|
||||||
|
"device_id": self.device_id,
|
||||||
|
"port": self.port,
|
||||||
|
"voltage": self.voltage,
|
||||||
|
"response_time": self.response_time,
|
||||||
|
"is_open": self._is_open,
|
||||||
|
"valve_state": self._valve_state,
|
||||||
|
"status": self._status,
|
||||||
|
"position": self.get_valve_position()
|
||||||
|
}
|
||||||
|
|
||||||
|
async def reset(self):
|
||||||
|
"""重置阀门到关闭状态"""
|
||||||
|
return await self.close()
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
|
import time as time_module
|
||||||
from typing import Dict, Any
|
from typing import Dict, Any
|
||||||
|
|
||||||
class VirtualStirrer:
|
class VirtualStirrer:
|
||||||
"""Virtual stirrer device for StirProtocol testing"""
|
"""Virtual stirrer device for StirProtocol testing - 功能完整版"""
|
||||||
|
|
||||||
def __init__(self, device_id: str = None, config: Dict[str, Any] = None, **kwargs):
|
def __init__(self, device_id: str = None, config: Dict[str, Any] = None, **kwargs):
|
||||||
# 处理可能的不同调用方式
|
# 处理可能的不同调用方式
|
||||||
@@ -19,86 +20,196 @@ class VirtualStirrer:
|
|||||||
self.logger = logging.getLogger(f"VirtualStirrer.{self.device_id}")
|
self.logger = logging.getLogger(f"VirtualStirrer.{self.device_id}")
|
||||||
self.data = {}
|
self.data = {}
|
||||||
|
|
||||||
# 添加调试信息
|
|
||||||
print(f"=== VirtualStirrer {self.device_id} is being created! ===")
|
|
||||||
print(f"=== Config: {self.config} ===")
|
|
||||||
print(f"=== Kwargs: {kwargs} ===")
|
|
||||||
|
|
||||||
# 从config或kwargs中获取配置参数
|
# 从config或kwargs中获取配置参数
|
||||||
self.port = self.config.get('port') or kwargs.get('port', 'VIRTUAL')
|
self.port = self.config.get('port') or kwargs.get('port', 'VIRTUAL')
|
||||||
self._max_temp = self.config.get('max_temp') or kwargs.get('max_temp', 100.0)
|
self._max_speed = self.config.get('max_speed') or kwargs.get('max_speed', 1500.0)
|
||||||
self._max_speed = self.config.get('max_speed') or kwargs.get('max_speed', 1000.0)
|
self._min_speed = self.config.get('min_speed') or kwargs.get('min_speed', 50.0)
|
||||||
|
|
||||||
# 处理其他kwargs参数,但跳过已知的配置参数
|
# 处理其他kwargs参数
|
||||||
skip_keys = {'port', 'max_temp', 'max_speed'}
|
skip_keys = {'port', 'max_speed', 'min_speed'}
|
||||||
for key, value in kwargs.items():
|
for key, value in kwargs.items():
|
||||||
if key not in skip_keys and not hasattr(self, key):
|
if key not in skip_keys and not hasattr(self, key):
|
||||||
setattr(self, key, value)
|
setattr(self, key, value)
|
||||||
|
|
||||||
async def initialize(self) -> bool:
|
async def initialize(self) -> bool:
|
||||||
"""Initialize virtual stirrer"""
|
"""Initialize virtual stirrer"""
|
||||||
print(f"=== VirtualStirrer {self.device_id} initialize() called! ===")
|
|
||||||
self.logger.info(f"Initializing virtual stirrer {self.device_id}")
|
self.logger.info(f"Initializing virtual stirrer {self.device_id}")
|
||||||
|
|
||||||
|
# 初始化状态信息
|
||||||
self.data.update({
|
self.data.update({
|
||||||
"status": "Idle"
|
"status": "Idle",
|
||||||
|
"operation_mode": "Idle", # 操作模式: Idle, Stirring, Settling, Completed, Error
|
||||||
|
"current_vessel": "", # 当前搅拌的容器
|
||||||
|
"current_speed": 0.0, # 当前搅拌速度
|
||||||
|
"is_stirring": False, # 是否正在搅拌
|
||||||
|
"remaining_time": 0.0, # 剩余时间
|
||||||
})
|
})
|
||||||
return True
|
return True
|
||||||
|
|
||||||
async def cleanup(self) -> bool:
|
async def cleanup(self) -> bool:
|
||||||
"""Cleanup virtual stirrer"""
|
"""Cleanup virtual stirrer"""
|
||||||
self.logger.info(f"Cleaning up virtual stirrer {self.device_id}")
|
self.logger.info(f"Cleaning up virtual stirrer {self.device_id}")
|
||||||
|
self.data.update({
|
||||||
|
"status": "Offline",
|
||||||
|
"operation_mode": "Offline",
|
||||||
|
"current_vessel": "",
|
||||||
|
"current_speed": 0.0,
|
||||||
|
"is_stirring": False,
|
||||||
|
"remaining_time": 0.0,
|
||||||
|
})
|
||||||
return True
|
return True
|
||||||
|
|
||||||
async def stir(self, stir_time: float, stir_speed: float, settling_time: float) -> bool:
|
async def stir(self, stir_time: float, stir_speed: float, settling_time: float) -> bool:
|
||||||
"""Execute stir action - matches Stir action exactly"""
|
"""Execute stir action - 定时搅拌 + 沉降"""
|
||||||
self.logger.info(f"Stir: speed={stir_speed} RPM, time={stir_time}s, settling={settling_time}s")
|
self.logger.info(f"Stir: speed={stir_speed} RPM, time={stir_time}s, settling={settling_time}s")
|
||||||
|
|
||||||
# 验证参数
|
# 验证参数
|
||||||
if stir_speed > self._max_speed:
|
if stir_speed > self._max_speed or stir_speed < self._min_speed:
|
||||||
self.logger.error(f"Stir speed {stir_speed} exceeds maximum {self._max_speed}")
|
error_msg = f"搅拌速度 {stir_speed} RPM 超出范围 ({self._min_speed} - {self._max_speed} RPM)"
|
||||||
self.data["status"] = f"搅拌速度 {stir_speed} 超出范围"
|
self.logger.error(error_msg)
|
||||||
|
self.data.update({
|
||||||
|
"status": f"Error: {error_msg}",
|
||||||
|
"operation_mode": "Error"
|
||||||
|
})
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# 开始搅拌
|
# === 第一阶段:搅拌 ===
|
||||||
self.data["status"] = f"搅拌中: {stir_speed} RPM, {stir_time}s"
|
start_time = time_module.time()
|
||||||
|
total_stir_time = stir_time
|
||||||
|
|
||||||
# 模拟搅拌时间
|
self.data.update({
|
||||||
simulation_time = min(stir_time, 10.0) # 最多等待10秒用于测试
|
"status": f"搅拌中: {stir_speed} RPM | 剩余: {total_stir_time:.0f}s",
|
||||||
await asyncio.sleep(simulation_time)
|
"operation_mode": "Stirring",
|
||||||
|
"current_speed": stir_speed,
|
||||||
|
"is_stirring": True,
|
||||||
|
"remaining_time": total_stir_time,
|
||||||
|
})
|
||||||
|
|
||||||
# 搅拌完成,开始沉降
|
# 搅拌过程 - 实时更新剩余时间
|
||||||
|
while True:
|
||||||
|
current_time = time_module.time()
|
||||||
|
elapsed = current_time - start_time
|
||||||
|
remaining = max(0, total_stir_time - elapsed)
|
||||||
|
|
||||||
|
# 更新状态
|
||||||
|
self.data.update({
|
||||||
|
"remaining_time": remaining,
|
||||||
|
"status": f"搅拌中: {stir_speed} RPM | 剩余: {remaining:.0f}s"
|
||||||
|
})
|
||||||
|
|
||||||
|
# 搅拌时间到了
|
||||||
|
if remaining <= 0:
|
||||||
|
break
|
||||||
|
|
||||||
|
await asyncio.sleep(1.0)
|
||||||
|
|
||||||
|
# === 第二阶段:沉降(如果需要)===
|
||||||
if settling_time > 0:
|
if settling_time > 0:
|
||||||
self.data["status"] = f"沉降中: {settling_time}s"
|
start_settling_time = time_module.time()
|
||||||
settling_simulation = min(settling_time, 5.0) # 最多等待5秒
|
total_settling_time = settling_time
|
||||||
await asyncio.sleep(settling_simulation)
|
|
||||||
|
self.data.update({
|
||||||
|
"status": f"沉降中: 停止搅拌 | 剩余: {total_settling_time:.0f}s",
|
||||||
|
"operation_mode": "Settling",
|
||||||
|
"current_speed": 0.0,
|
||||||
|
"is_stirring": False,
|
||||||
|
"remaining_time": total_settling_time,
|
||||||
|
})
|
||||||
|
|
||||||
|
# 沉降过程 - 实时更新剩余时间
|
||||||
|
while True:
|
||||||
|
current_time = time_module.time()
|
||||||
|
elapsed = current_time - start_settling_time
|
||||||
|
remaining = max(0, total_settling_time - elapsed)
|
||||||
|
|
||||||
|
# 更新状态
|
||||||
|
self.data.update({
|
||||||
|
"remaining_time": remaining,
|
||||||
|
"status": f"沉降中: 停止搅拌 | 剩余: {remaining:.0f}s"
|
||||||
|
})
|
||||||
|
|
||||||
|
# 沉降时间到了
|
||||||
|
if remaining <= 0:
|
||||||
|
break
|
||||||
|
|
||||||
|
await asyncio.sleep(1.0)
|
||||||
|
|
||||||
# 操作完成
|
# === 操作完成 ===
|
||||||
self.data["status"] = "搅拌完成"
|
settling_info = f" | 沉降: {settling_time:.0f}s" if settling_time > 0 else ""
|
||||||
|
self.data.update({
|
||||||
|
"status": f"完成: 搅拌 {stir_speed} RPM, {stir_time:.0f}s{settling_info}",
|
||||||
|
"operation_mode": "Completed",
|
||||||
|
"current_speed": 0.0,
|
||||||
|
"is_stirring": False,
|
||||||
|
"remaining_time": 0.0,
|
||||||
|
})
|
||||||
|
|
||||||
self.logger.info(f"Stir completed: {stir_speed} RPM for {stir_time}s")
|
self.logger.info(f"Stir completed: {stir_speed} RPM for {stir_time}s + settling {settling_time}s")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
async def start_stir(self, vessel: str, stir_speed: float, purpose: str) -> bool:
|
async def start_stir(self, vessel: str, stir_speed: float, purpose: str) -> bool:
|
||||||
"""Start stir action - matches StartStir action exactly"""
|
"""Start stir action - 开始持续搅拌"""
|
||||||
self.logger.info(f"StartStir: vessel={vessel}, speed={stir_speed} RPM, purpose={purpose}")
|
self.logger.info(f"StartStir: vessel={vessel}, speed={stir_speed} RPM, purpose={purpose}")
|
||||||
|
|
||||||
# 验证参数
|
# 验证参数
|
||||||
if stir_speed > self._max_speed:
|
if stir_speed > self._max_speed or stir_speed < self._min_speed:
|
||||||
self.logger.error(f"Stir speed {stir_speed} exceeds maximum {self._max_speed}")
|
error_msg = f"搅拌速度 {stir_speed} RPM 超出范围 ({self._min_speed} - {self._max_speed} RPM)"
|
||||||
self.data["status"] = f"搅拌速度 {stir_speed} 超出范围"
|
self.logger.error(error_msg)
|
||||||
|
self.data.update({
|
||||||
|
"status": f"Error: {error_msg}",
|
||||||
|
"operation_mode": "Error"
|
||||||
|
})
|
||||||
return False
|
return False
|
||||||
|
|
||||||
self.data["status"] = f"开始搅拌: {vessel} at {stir_speed} RPM"
|
self.data.update({
|
||||||
|
"status": f"启动: 持续搅拌 {vessel} at {stir_speed} RPM | {purpose}",
|
||||||
|
"operation_mode": "Stirring",
|
||||||
|
"current_vessel": vessel,
|
||||||
|
"current_speed": stir_speed,
|
||||||
|
"is_stirring": True,
|
||||||
|
"remaining_time": -1.0, # -1 表示持续运行
|
||||||
|
})
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
async def stop_stir(self, vessel: str) -> bool:
|
async def stop_stir(self, vessel: str) -> bool:
|
||||||
"""Stop stir action - matches StopStir action exactly"""
|
"""Stop stir action - 停止搅拌"""
|
||||||
self.logger.info(f"StopStir: vessel={vessel}")
|
self.logger.info(f"StopStir: vessel={vessel}")
|
||||||
|
|
||||||
self.data["status"] = f"停止搅拌: {vessel}"
|
current_speed = self.data.get("current_speed", 0.0)
|
||||||
|
|
||||||
|
self.data.update({
|
||||||
|
"status": f"已停止: {vessel} 搅拌停止 | 之前速度: {current_speed} RPM",
|
||||||
|
"operation_mode": "Stopped",
|
||||||
|
"current_vessel": "",
|
||||||
|
"current_speed": 0.0,
|
||||||
|
"is_stirring": False,
|
||||||
|
"remaining_time": 0.0,
|
||||||
|
})
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# 状态属性 - 只保留 action 中定义的 feedback
|
# 状态属性
|
||||||
@property
|
@property
|
||||||
def status(self) -> str:
|
def status(self) -> str:
|
||||||
return self.data.get("status", "Idle")
|
return self.data.get("status", "Idle")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def operation_mode(self) -> str:
|
||||||
|
return self.data.get("operation_mode", "Idle")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def current_vessel(self) -> str:
|
||||||
|
return self.data.get("current_vessel", "")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def current_speed(self) -> float:
|
||||||
|
return self.data.get("current_speed", 0.0)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_stirring(self) -> bool:
|
||||||
|
return self.data.get("is_stirring", False)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def remaining_time(self) -> float:
|
||||||
|
return self.data.get("remaining_time", 0.0)
|
||||||
@@ -1,149 +1,328 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
|
import time
|
||||||
|
from enum import Enum
|
||||||
|
from typing import Union, Optional
|
||||||
import logging
|
import logging
|
||||||
from typing import Dict, Any, Optional
|
|
||||||
|
|
||||||
|
class VirtualPumpMode(Enum):
|
||||||
|
Normal = 0
|
||||||
|
AccuratePos = 1
|
||||||
|
AccuratePosVel = 2
|
||||||
|
|
||||||
|
|
||||||
class VirtualTransferPump:
|
class VirtualTransferPump:
|
||||||
"""Virtual pump device specifically for Transfer protocol"""
|
"""虚拟转移泵类 - 模拟泵的基本功能,无需实际硬件"""
|
||||||
|
|
||||||
def __init__(self, device_id: str = None, config: Dict[str, Any] = None, **kwargs):
|
def __init__(self, device_id: str = None, config: dict = None, **kwargs):
|
||||||
# 处理可能的不同调用方式
|
"""
|
||||||
if device_id is None and 'id' in kwargs:
|
初始化虚拟转移泵
|
||||||
device_id = kwargs.pop('id')
|
|
||||||
if config is None and 'config' in kwargs:
|
|
||||||
config = kwargs.pop('config')
|
|
||||||
|
|
||||||
# 设置默认值
|
Args:
|
||||||
self.device_id = device_id or "unknown_transfer_pump"
|
device_id: 设备ID
|
||||||
self.config = config or {}
|
config: 配置字典,包含max_volume, port等参数
|
||||||
|
**kwargs: 其他参数,确保兼容性
|
||||||
|
"""
|
||||||
|
self.device_id = device_id or "virtual_transfer_pump"
|
||||||
|
|
||||||
|
# 从config或kwargs中获取参数,确保类型正确
|
||||||
|
if config:
|
||||||
|
self.max_volume = float(config.get('max_volume', 25.0))
|
||||||
|
self.port = config.get('port', 'VIRTUAL')
|
||||||
|
else:
|
||||||
|
self.max_volume = float(kwargs.get('max_volume', 25.0))
|
||||||
|
self.port = kwargs.get('port', 'VIRTUAL')
|
||||||
|
|
||||||
|
self._transfer_rate = float(kwargs.get('transfer_rate', 0))
|
||||||
|
self.mode = kwargs.get('mode', VirtualPumpMode.Normal)
|
||||||
|
|
||||||
|
# 状态变量 - 确保都是正确类型
|
||||||
|
self._status = "Idle"
|
||||||
|
self._position = 0.0 # float
|
||||||
|
self._max_velocity = 5.0 # float
|
||||||
|
self._current_volume = 0.0 # float
|
||||||
|
|
||||||
self.logger = logging.getLogger(f"VirtualTransferPump.{self.device_id}")
|
self.logger = logging.getLogger(f"VirtualTransferPump.{self.device_id}")
|
||||||
self.data = {}
|
|
||||||
|
|
||||||
# 添加调试信息
|
|
||||||
print(f"=== VirtualTransferPump {self.device_id} is being created! ===")
|
|
||||||
print(f"=== Config: {self.config} ===")
|
|
||||||
print(f"=== Kwargs: {kwargs} ===")
|
|
||||||
|
|
||||||
# 从config或kwargs中获取配置参数
|
|
||||||
self.port = self.config.get('port') or kwargs.get('port', 'VIRTUAL')
|
|
||||||
self._max_volume = self.config.get('max_volume') or kwargs.get('max_volume', 50.0)
|
|
||||||
self._transfer_rate = self.config.get('transfer_rate') or kwargs.get('transfer_rate', 5.0)
|
|
||||||
self._current_volume = 0.0
|
|
||||||
self.is_running = False
|
|
||||||
|
|
||||||
async def initialize(self) -> bool:
|
async def initialize(self) -> bool:
|
||||||
"""Initialize virtual transfer pump"""
|
"""初始化虚拟泵"""
|
||||||
print(f"=== VirtualTransferPump {self.device_id} initialize() called! ===")
|
self.logger.info(f"Initializing virtual pump {self.device_id}")
|
||||||
self.logger.info(f"Initializing virtual transfer pump {self.device_id}")
|
self._status = "Idle"
|
||||||
self.data.update({
|
self._position = 0.0
|
||||||
"status": "Idle",
|
self._current_volume = 0.0
|
||||||
"current_volume": 0.0,
|
|
||||||
"max_volume": self._max_volume,
|
|
||||||
"transfer_rate": self._transfer_rate,
|
|
||||||
"from_vessel": "",
|
|
||||||
"to_vessel": "",
|
|
||||||
"progress": 0.0,
|
|
||||||
"transferred_volume": 0.0,
|
|
||||||
"current_status": "Ready"
|
|
||||||
})
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
async def cleanup(self) -> bool:
|
async def cleanup(self) -> bool:
|
||||||
"""Cleanup virtual transfer pump"""
|
"""清理虚拟泵"""
|
||||||
self.logger.info(f"Cleaning up virtual transfer pump {self.device_id}")
|
self.logger.info(f"Cleaning up virtual pump {self.device_id}")
|
||||||
|
self._status = "Idle"
|
||||||
return True
|
return True
|
||||||
|
|
||||||
async def transfer(self, 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) -> bool:
|
|
||||||
"""Execute liquid transfer - matches Transfer action"""
|
|
||||||
self.logger.info(f"Transfer: {volume}mL from {from_vessel} to {to_vessel}")
|
|
||||||
|
|
||||||
# 计算转移时间
|
|
||||||
if time > 0:
|
|
||||||
transfer_time = time
|
|
||||||
else:
|
|
||||||
# 如果是粘性液体,降低转移速率
|
|
||||||
rate = self._transfer_rate * 0.5 if viscous else self._transfer_rate
|
|
||||||
transfer_time = volume / rate
|
|
||||||
|
|
||||||
self.data.update({
|
|
||||||
"status": "Running",
|
|
||||||
"from_vessel": from_vessel,
|
|
||||||
"to_vessel": to_vessel,
|
|
||||||
"current_status": "Transferring",
|
|
||||||
"progress": 0.0,
|
|
||||||
"transferred_volume": 0.0
|
|
||||||
})
|
|
||||||
|
|
||||||
# 模拟转移过程
|
|
||||||
steps = 10
|
|
||||||
step_time = transfer_time / steps
|
|
||||||
step_volume = volume / steps
|
|
||||||
|
|
||||||
for i in range(steps):
|
|
||||||
await asyncio.sleep(step_time)
|
|
||||||
progress = (i + 1) / steps * 100
|
|
||||||
transferred = (i + 1) * step_volume
|
|
||||||
|
|
||||||
self.data.update({
|
|
||||||
"progress": progress,
|
|
||||||
"transferred_volume": transferred,
|
|
||||||
"current_status": f"Transferring {progress:.1f}%"
|
|
||||||
})
|
|
||||||
|
|
||||||
self.logger.info(f"Transfer progress: {progress:.1f}% ({transferred:.1f}/{volume}mL)")
|
|
||||||
|
|
||||||
# 如果需要冲洗
|
|
||||||
if rinsing_solvent and rinsing_volume > 0 and rinsing_repeats > 0:
|
|
||||||
self.data["current_status"] = "Rinsing"
|
|
||||||
for repeat in range(rinsing_repeats):
|
|
||||||
self.logger.info(f"Rinsing cycle {repeat + 1}/{rinsing_repeats} with {rinsing_solvent}")
|
|
||||||
await asyncio.sleep(1) # 模拟冲洗时间
|
|
||||||
|
|
||||||
self.data.update({
|
|
||||||
"status": "Idle",
|
|
||||||
"current_status": "Transfer completed",
|
|
||||||
"progress": 100.0,
|
|
||||||
"transferred_volume": volume
|
|
||||||
})
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
# 添加所有在virtual_device.yaml中定义的状态属性
|
|
||||||
@property
|
@property
|
||||||
def status(self) -> str:
|
def status(self) -> str:
|
||||||
return self.data.get("status", "Unknown")
|
return self._status
|
||||||
|
|
||||||
|
@property
|
||||||
|
def position(self) -> float:
|
||||||
|
"""当前柱塞位置 (ml)"""
|
||||||
|
return self._position
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_volume(self) -> float:
|
def current_volume(self) -> float:
|
||||||
return self.data.get("current_volume", 0.0)
|
"""当前注射器中的体积 (ml)"""
|
||||||
|
return self._current_volume
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def max_volume(self) -> float:
|
def max_velocity(self) -> float:
|
||||||
return self.data.get("max_volume", self._max_volume)
|
return self._max_velocity
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def transfer_rate(self) -> float:
|
def transfer_rate(self) -> float:
|
||||||
return self.data.get("transfer_rate", self._transfer_rate)
|
return self._transfer_rate
|
||||||
|
|
||||||
|
def set_max_velocity(self, velocity: float):
|
||||||
|
"""设置最大速度 (ml/s)"""
|
||||||
|
self._max_velocity = max(0.1, min(50.0, velocity)) # 限制在合理范围内
|
||||||
|
self.logger.info(f"Set max velocity to {self._max_velocity} ml/s")
|
||||||
|
|
||||||
@property
|
def get_status(self) -> str:
|
||||||
def from_vessel(self) -> str:
|
"""获取泵状态"""
|
||||||
return self.data.get("from_vessel", "")
|
return self._status
|
||||||
|
|
||||||
@property
|
async def _simulate_operation(self, duration: float):
|
||||||
def to_vessel(self) -> str:
|
"""模拟操作延时"""
|
||||||
return self.data.get("to_vessel", "")
|
self._status = "Busy"
|
||||||
|
await asyncio.sleep(duration)
|
||||||
|
self._status = "Idle"
|
||||||
|
|
||||||
@property
|
def _calculate_duration(self, volume: float, velocity: float = None) -> float:
|
||||||
def progress(self) -> float:
|
"""计算操作持续时间"""
|
||||||
return self.data.get("progress", 0.0)
|
if velocity is None:
|
||||||
|
velocity = self._max_velocity
|
||||||
|
return abs(volume) / velocity
|
||||||
|
|
||||||
@property
|
# 新的set_position方法 - 专门用于SetPumpPosition动作
|
||||||
def transferred_volume(self) -> float:
|
async def set_position(self, position: float, max_velocity: float = None):
|
||||||
return self.data.get("transferred_volume", 0.0)
|
"""
|
||||||
|
移动到绝对位置 - 专门用于SetPumpPosition动作
|
||||||
|
|
||||||
|
Args:
|
||||||
|
position (float): 目标位置 (ml)
|
||||||
|
max_velocity (float): 移动速度 (ml/s)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: 符合SetPumpPosition.action定义的结果
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# 验证并转换参数
|
||||||
|
target_position = float(position)
|
||||||
|
velocity = float(max_velocity) if max_velocity is not None else self._max_velocity
|
||||||
|
|
||||||
|
# 限制位置在有效范围内
|
||||||
|
target_position = max(0.0, min(float(self.max_volume), target_position))
|
||||||
|
|
||||||
|
# 计算移动距离和时间
|
||||||
|
volume_to_move = abs(target_position - self._position)
|
||||||
|
duration = self._calculate_duration(volume_to_move, velocity)
|
||||||
|
|
||||||
|
self.logger.info(f"SET_POSITION: Moving to {target_position} ml (current: {self._position} ml), velocity: {velocity} ml/s")
|
||||||
|
|
||||||
|
# 模拟移动过程
|
||||||
|
start_position = self._position
|
||||||
|
steps = 10 if duration > 0.1 else 1 # 如果移动距离很小,只用1步
|
||||||
|
step_duration = duration / steps if steps > 1 else duration
|
||||||
|
|
||||||
|
for i in range(steps + 1):
|
||||||
|
# 计算当前位置和进度
|
||||||
|
progress = (i / steps) * 100 if steps > 0 else 100
|
||||||
|
current_pos = start_position + (target_position - start_position) * (i / steps) if steps > 0 else target_position
|
||||||
|
|
||||||
|
# 更新状态
|
||||||
|
self._status = "Moving" if i < steps else "Idle"
|
||||||
|
self._position = current_pos
|
||||||
|
self._current_volume = current_pos
|
||||||
|
|
||||||
|
# 等待一小步时间
|
||||||
|
if i < steps and step_duration > 0:
|
||||||
|
await asyncio.sleep(step_duration)
|
||||||
|
|
||||||
|
# 确保最终位置准确
|
||||||
|
self._position = target_position
|
||||||
|
self._current_volume = target_position
|
||||||
|
self._status = "Idle"
|
||||||
|
|
||||||
|
self.logger.info(f"SET_POSITION: Reached position {self._position} ml, current volume: {self._current_volume} ml")
|
||||||
|
|
||||||
|
# 返回符合action定义的结果
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"message": f"Successfully moved to position {self._position} ml"
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
error_msg = f"Failed to set position: {str(e)}"
|
||||||
|
self.logger.error(error_msg)
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"message": error_msg
|
||||||
|
}
|
||||||
|
|
||||||
@property
|
# 其他泵操作方法
|
||||||
def current_status(self) -> str:
|
async def pull_plunger(self, volume: float, velocity: float = None):
|
||||||
return self.data.get("current_status", "Ready")
|
"""
|
||||||
|
拉取柱塞(吸液)
|
||||||
|
|
||||||
|
Args:
|
||||||
|
volume (float): 要拉取的体积 (ml)
|
||||||
|
velocity (float): 拉取速度 (ml/s)
|
||||||
|
"""
|
||||||
|
new_position = min(self.max_volume, self._position + volume)
|
||||||
|
actual_volume = new_position - self._position
|
||||||
|
|
||||||
|
if actual_volume <= 0:
|
||||||
|
self.logger.warning("Cannot pull - already at maximum volume")
|
||||||
|
return
|
||||||
|
|
||||||
|
duration = self._calculate_duration(actual_volume, velocity)
|
||||||
|
|
||||||
|
self.logger.info(f"Pulling {actual_volume} ml (from {self._position} to {new_position})")
|
||||||
|
|
||||||
|
await self._simulate_operation(duration)
|
||||||
|
|
||||||
|
self._position = new_position
|
||||||
|
self._current_volume = new_position
|
||||||
|
|
||||||
|
self.logger.info(f"Pulled {actual_volume} ml, current volume: {self._current_volume} ml")
|
||||||
|
|
||||||
|
async def push_plunger(self, volume: float, velocity: float = None):
|
||||||
|
"""
|
||||||
|
推出柱塞(排液)
|
||||||
|
|
||||||
|
Args:
|
||||||
|
volume (float): 要推出的体积 (ml)
|
||||||
|
velocity (float): 推出速度 (ml/s)
|
||||||
|
"""
|
||||||
|
new_position = max(0, self._position - volume)
|
||||||
|
actual_volume = self._position - new_position
|
||||||
|
|
||||||
|
if actual_volume <= 0:
|
||||||
|
self.logger.warning("Cannot push - already at minimum volume")
|
||||||
|
return
|
||||||
|
|
||||||
|
duration = self._calculate_duration(actual_volume, velocity)
|
||||||
|
|
||||||
|
self.logger.info(f"Pushing {actual_volume} ml (from {self._position} to {new_position})")
|
||||||
|
|
||||||
|
await self._simulate_operation(duration)
|
||||||
|
|
||||||
|
self._position = new_position
|
||||||
|
self._current_volume = new_position
|
||||||
|
|
||||||
|
self.logger.info(f"Pushed {actual_volume} ml, current volume: {self._current_volume} ml")
|
||||||
|
|
||||||
|
# 便捷操作方法
|
||||||
|
async def aspirate(self, volume: float, velocity: float = None):
|
||||||
|
"""吸液操作"""
|
||||||
|
await self.pull_plunger(volume, velocity)
|
||||||
|
|
||||||
|
async def dispense(self, volume: float, velocity: float = None):
|
||||||
|
"""排液操作"""
|
||||||
|
await self.push_plunger(volume, velocity)
|
||||||
|
|
||||||
|
async def transfer(self, volume: float, aspirate_velocity: float = None, dispense_velocity: float = None):
|
||||||
|
"""转移操作(先吸后排)"""
|
||||||
|
# 吸液
|
||||||
|
await self.aspirate(volume, aspirate_velocity)
|
||||||
|
|
||||||
|
# 短暂停顿
|
||||||
|
await asyncio.sleep(0.1)
|
||||||
|
|
||||||
|
# 排液
|
||||||
|
await self.dispense(volume, dispense_velocity)
|
||||||
|
|
||||||
|
async def empty_syringe(self, velocity: float = None):
|
||||||
|
"""清空注射器"""
|
||||||
|
await self.set_position(0, velocity)
|
||||||
|
|
||||||
|
async def fill_syringe(self, velocity: float = None):
|
||||||
|
"""充满注射器"""
|
||||||
|
await self.set_position(self.max_volume, velocity)
|
||||||
|
|
||||||
|
async def stop_operation(self):
|
||||||
|
"""停止当前操作"""
|
||||||
|
self._status = "Idle"
|
||||||
|
self.logger.info("Operation stopped")
|
||||||
|
|
||||||
|
# 状态查询方法
|
||||||
|
def get_position(self) -> float:
|
||||||
|
"""获取当前位置"""
|
||||||
|
return self._position
|
||||||
|
|
||||||
|
def get_current_volume(self) -> float:
|
||||||
|
"""获取当前体积"""
|
||||||
|
return self._current_volume
|
||||||
|
|
||||||
|
def get_remaining_capacity(self) -> float:
|
||||||
|
"""获取剩余容量"""
|
||||||
|
return self.max_volume - self._current_volume
|
||||||
|
|
||||||
|
def is_empty(self) -> bool:
|
||||||
|
"""检查是否为空"""
|
||||||
|
return self._current_volume <= 0.01 # 允许小量误差
|
||||||
|
|
||||||
|
def is_full(self) -> bool:
|
||||||
|
"""检查是否已满"""
|
||||||
|
return self._current_volume >= (self.max_volume - 0.01) # 允许小量误差
|
||||||
|
|
||||||
|
# 调试和状态信息
|
||||||
|
def get_pump_info(self) -> dict:
|
||||||
|
"""获取泵的详细信息"""
|
||||||
|
return {
|
||||||
|
"device_id": self.device_id,
|
||||||
|
"status": self._status,
|
||||||
|
"position": self._position,
|
||||||
|
"current_volume": self._current_volume,
|
||||||
|
"max_volume": self.max_volume,
|
||||||
|
"max_velocity": self._max_velocity,
|
||||||
|
"mode": self.mode.name,
|
||||||
|
"is_empty": self.is_empty(),
|
||||||
|
"is_full": self.is_full(),
|
||||||
|
"remaining_capacity": self.get_remaining_capacity()
|
||||||
|
}
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"VirtualTransferPump({self.device_id}: {self._current_volume:.2f}/{self.max_volume} ml, {self._status})"
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return self.__str__()
|
||||||
|
|
||||||
|
|
||||||
|
# 使用示例
|
||||||
|
async def demo():
|
||||||
|
"""虚拟泵使用示例"""
|
||||||
|
pump = VirtualTransferPump("demo_pump", {"max_volume": 50.0})
|
||||||
|
|
||||||
|
await pump.initialize()
|
||||||
|
|
||||||
|
print(f"Initial state: {pump}")
|
||||||
|
|
||||||
|
# 测试set_position方法
|
||||||
|
result = await pump.set_position(10.0, max_velocity=2.0)
|
||||||
|
print(f"Set position result: {result}")
|
||||||
|
print(f"After setting position to 10ml: {pump}")
|
||||||
|
|
||||||
|
# 吸液测试
|
||||||
|
await pump.aspirate(5.0, velocity=2.0)
|
||||||
|
print(f"After aspirating 5ml: {pump}")
|
||||||
|
|
||||||
|
# 清空测试
|
||||||
|
result = await pump.set_position(0.0)
|
||||||
|
print(f"Empty result: {result}")
|
||||||
|
print(f"After emptying: {pump}")
|
||||||
|
|
||||||
|
print("\nPump info:", pump.get_pump_info())
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
asyncio.run(demo())
|
||||||
|
|||||||
47
unilabos/devices/virtual/virtual_vacuum_pump.py
Normal file
47
unilabos/devices/virtual/virtual_vacuum_pump.py
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import asyncio
|
||||||
|
import time
|
||||||
|
from typing import Dict, Any, Optional
|
||||||
|
|
||||||
|
|
||||||
|
class VirtualVacuumPump:
|
||||||
|
"""Virtual vacuum pump for testing"""
|
||||||
|
|
||||||
|
def __init__(self, device_id: Optional[str] = None, config: Optional[Dict[str, Any]] = None, **kwargs):
|
||||||
|
self.device_id = device_id or "unknown_vacuum_pump"
|
||||||
|
self.config = config or {}
|
||||||
|
self.data = {}
|
||||||
|
self._status = "OPEN"
|
||||||
|
|
||||||
|
async def initialize(self) -> bool:
|
||||||
|
"""Initialize virtual vacuum pump"""
|
||||||
|
self.data.update({
|
||||||
|
"status": self._status
|
||||||
|
})
|
||||||
|
return True
|
||||||
|
|
||||||
|
async def cleanup(self) -> bool:
|
||||||
|
"""Cleanup virtual vacuum pump"""
|
||||||
|
return True
|
||||||
|
|
||||||
|
@property
|
||||||
|
def status(self) -> str:
|
||||||
|
return self._status
|
||||||
|
|
||||||
|
def get_status(self) -> str:
|
||||||
|
return self._status
|
||||||
|
|
||||||
|
def set_status(self, string):
|
||||||
|
self._status = string
|
||||||
|
time.sleep(5)
|
||||||
|
|
||||||
|
def open(self):
|
||||||
|
self._status = "OPEN"
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
self._status = "CLOSED"
|
||||||
|
|
||||||
|
def is_open(self):
|
||||||
|
return self._status
|
||||||
|
|
||||||
|
def is_closed(self):
|
||||||
|
return not self._status
|
||||||
@@ -1,11 +1,9 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
import socket
|
|
||||||
import json
|
|
||||||
import base64
|
import base64
|
||||||
import argparse
|
import json
|
||||||
import sys
|
import socket
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
|
||||||
@@ -96,17 +94,20 @@ class ZhidaClient:
|
|||||||
def abort(self) -> dict:
|
def abort(self) -> dict:
|
||||||
return self._send_command({"command": "abort"})
|
return self._send_command({"command": "abort"})
|
||||||
|
|
||||||
"""
|
|
||||||
a,b,c
|
|
||||||
1,2,4
|
|
||||||
2,4,5
|
|
||||||
"""
|
|
||||||
|
|
||||||
client = ZhidaClient()
|
if __name__ == "__main__":
|
||||||
# 连接
|
|
||||||
client.connect()
|
"""
|
||||||
# 获取状态
|
a,b,c
|
||||||
print(client.status)
|
1,2,4
|
||||||
|
2,4,5
|
||||||
|
"""
|
||||||
|
|
||||||
|
client = ZhidaClient()
|
||||||
|
# 连接
|
||||||
|
client.connect()
|
||||||
|
# 获取状态
|
||||||
|
print(client.status)
|
||||||
|
|
||||||
|
|
||||||
# 命令格式:python zhida.py <subcommand> [options]
|
# 命令格式:python zhida.py <subcommand> [options]
|
||||||
|
|||||||
@@ -33,19 +33,19 @@ class CleanProtocol(BaseModel):
|
|||||||
|
|
||||||
|
|
||||||
class SeparateProtocol(BaseModel):
|
class SeparateProtocol(BaseModel):
|
||||||
purpose: str # 'wash' or 'extract'. 'wash' means that product phase will not be the added solvent phase, 'extract' means product phase will be the added solvent phase. If no solvent is added just use 'extract'.
|
purpose: str
|
||||||
product_phase: str # 'top' or 'bottom'. Phase that product will be in.
|
product_phase: str
|
||||||
from_vessel: str #Contents of from_vessel are transferred to separation_vessel and separation is performed.
|
from_vessel: str
|
||||||
separation_vessel: str # Vessel in which separation of phases will be carried out.
|
separation_vessel: str
|
||||||
to_vessel: str # Vessel to send product phase to.
|
to_vessel: str
|
||||||
waste_phase_to_vessel: str # Optional. Vessel to send waste phase to.
|
waste_phase_to_vessel: str
|
||||||
solvent: str # Optional. Solvent to add to separation vessel after contents of from_vessel has been transferred to create two phases.
|
solvent: str
|
||||||
solvent_volume: float # Optional. Volume of solvent to add.
|
solvent_volume: float
|
||||||
through: str # Optional. Solid chemical to send product phase through on way to to_vessel, e.g. 'celite'.
|
through: str
|
||||||
repeats: int # Optional. Number of separations to perform.
|
repeats: int
|
||||||
stir_time: float # Optional. Time stir for after adding solvent, before separation of phases.
|
stir_time: float
|
||||||
stir_speed: float # Optional. Speed to stir at after adding solvent, before separation of phases.
|
stir_speed: float
|
||||||
settling_time: float # Optional. Time
|
settling_time: float
|
||||||
|
|
||||||
|
|
||||||
class EvaporateProtocol(BaseModel):
|
class EvaporateProtocol(BaseModel):
|
||||||
@@ -67,6 +67,7 @@ class AGVTransferProtocol(BaseModel):
|
|||||||
to_repo: dict
|
to_repo: dict
|
||||||
from_repo_position: str
|
from_repo_position: str
|
||||||
to_repo_position: str
|
to_repo_position: str
|
||||||
|
|
||||||
#=============新添加的新的协议================
|
#=============新添加的新的协议================
|
||||||
class AddProtocol(BaseModel):
|
class AddProtocol(BaseModel):
|
||||||
vessel: str
|
vessel: str
|
||||||
@@ -84,16 +85,16 @@ class CentrifugeProtocol(BaseModel):
|
|||||||
vessel: str
|
vessel: str
|
||||||
speed: float
|
speed: float
|
||||||
time: float
|
time: float
|
||||||
temp: float # 移除默认值
|
temp: float
|
||||||
|
|
||||||
class FilterProtocol(BaseModel):
|
class FilterProtocol(BaseModel):
|
||||||
vessel: str
|
vessel: str
|
||||||
filtrate_vessel: str # 移除默认值
|
filtrate_vessel: str
|
||||||
stir: bool # 移除默认值
|
stir: bool
|
||||||
stir_speed: float # 移除默认值
|
stir_speed: float
|
||||||
temp: float # 移除默认值
|
temp: float
|
||||||
continue_heatchill: bool # 移除默认值
|
continue_heatchill: bool
|
||||||
volume: float # 移除默认值
|
volume: float
|
||||||
|
|
||||||
class HeatChillProtocol(BaseModel):
|
class HeatChillProtocol(BaseModel):
|
||||||
vessel: str
|
vessel: str
|
||||||
@@ -137,45 +138,80 @@ class TransferProtocol(BaseModel):
|
|||||||
solid: bool = False
|
solid: bool = False
|
||||||
|
|
||||||
class CleanVesselProtocol(BaseModel):
|
class CleanVesselProtocol(BaseModel):
|
||||||
vessel: str # 要清洗的容器名称
|
vessel: str
|
||||||
solvent: str # 用于清洗容器的溶剂名称
|
solvent: str
|
||||||
volume: float # 清洗溶剂的体积,可选参数
|
volume: float
|
||||||
temp: float # 清洗时的温度,可选参数
|
temp: float
|
||||||
repeats: int = 1 # 清洗操作的重复次数,默认为 1
|
repeats: int = 1
|
||||||
|
|
||||||
class DissolveProtocol(BaseModel):
|
class DissolveProtocol(BaseModel):
|
||||||
vessel: str # 装有要溶解物质的容器名称
|
vessel: str
|
||||||
solvent: str # 用于溶解物质的溶剂名称
|
solvent: str
|
||||||
volume: float # 溶剂的体积,可选参数
|
volume: float
|
||||||
amount: str = "" # 要溶解物质的量,可选参数
|
amount: str = ""
|
||||||
temp: float = 25.0 # 溶解时的温度,可选参数
|
temp: float = 25.0
|
||||||
time: float = 0.0 # 溶解的时间,可选参数
|
time: float = 0.0
|
||||||
stir_speed: float = 0.0 # 搅拌速度,可选参数
|
stir_speed: float = 0.0
|
||||||
|
|
||||||
class FilterThroughProtocol(BaseModel):
|
class FilterThroughProtocol(BaseModel):
|
||||||
from_vessel: str # 源容器的名称,即物质起始所在的容器
|
from_vessel: str
|
||||||
to_vessel: str # 目标容器的名称,物质过滤后要到达的容器
|
to_vessel: str
|
||||||
filter_through: str # 过滤时所通过的介质,如滤纸、柱子等
|
filter_through: str
|
||||||
eluting_solvent: str = "" # 洗脱溶剂的名称,可选参数
|
eluting_solvent: str = ""
|
||||||
eluting_volume: float = 0.0 # 洗脱溶剂的体积,可选参数
|
eluting_volume: float = 0.0
|
||||||
eluting_repeats: int = 0 # 洗脱操作的重复次数,默认为 0
|
eluting_repeats: int = 0
|
||||||
residence_time: float = 0.0 # 物质在过滤介质中的停留时间,可选参数
|
residence_time: float = 0.0
|
||||||
|
|
||||||
class RunColumnProtocol(BaseModel):
|
class RunColumnProtocol(BaseModel):
|
||||||
from_vessel: str # 源容器的名称,即样品起始所在的容器
|
from_vessel: str
|
||||||
to_vessel: str # 目标容器的名称,分离后的样品要到达的容器
|
to_vessel: str
|
||||||
column: str # 所使用的柱子的名称
|
column: str
|
||||||
|
|
||||||
class WashSolidProtocol(BaseModel):
|
class WashSolidProtocol(BaseModel):
|
||||||
vessel: str # 装有固体物质的容器名称
|
vessel: str
|
||||||
solvent: str # 用于清洗固体的溶剂名称
|
solvent: str
|
||||||
volume: float # 清洗溶剂的体积
|
volume: float
|
||||||
filtrate_vessel: str = "" # 滤液要收集到的容器名称,可选参数
|
filtrate_vessel: str = ""
|
||||||
temp: float = 25.0 # 清洗时的温度,可选参数
|
temp: float = 25.0
|
||||||
stir: bool = False # 是否在清洗过程中搅拌,默认为 False
|
stir: bool = False
|
||||||
stir_speed: float = 0.0 # 搅拌速度,可选参数
|
stir_speed: float = 0.0
|
||||||
time: float = 0.0 # 清洗的时间,可选参数
|
time: float = 0.0
|
||||||
repeats: int = 1 # 清洗操作的重复次数,默认为 1
|
repeats: int = 1
|
||||||
|
|
||||||
__all__ = ["Point3D", "PumpTransferProtocol", "CleanProtocol", "SeparateProtocol", "EvaporateProtocol", "EvacuateAndRefillProtocol", "AGVTransferProtocol", "CentrifugeProtocol", "AddProtocol", "FilterProtocol", "HeatChillProtocol", "HeatChillStartProtocol", "HeatChillStopProtocol", "StirProtocol", "StartStirProtocol", "StopStirProtocol", "TransferProtocol", "CleanVesselProtocol", "DissolveProtocol", "FilterThroughProtocol", "RunColumnProtocol", "WashSolidProtocol"]
|
class AdjustPHProtocol(BaseModel):
|
||||||
|
vessel: str = Field(..., description="目标容器")
|
||||||
|
ph_value: float = Field(..., description="目标pH值") # 改为 ph_value
|
||||||
|
reagent: str = Field(..., description="酸碱试剂名称")
|
||||||
|
# 移除其他可选参数,使用默认值
|
||||||
|
|
||||||
|
class ResetHandlingProtocol(BaseModel):
|
||||||
|
solvent: str = Field(..., description="溶剂名称")
|
||||||
|
|
||||||
|
class DryProtocol(BaseModel):
|
||||||
|
compound: str = Field(..., description="化合物名称")
|
||||||
|
vessel: str = Field(..., description="目标容器")
|
||||||
|
|
||||||
|
class RecrystallizeProtocol(BaseModel):
|
||||||
|
ratio: str = Field(..., description="溶剂比例(如 '1:1', '3:7')")
|
||||||
|
solvent1: str = Field(..., description="第一种溶剂名称")
|
||||||
|
solvent2: str = Field(..., description="第二种溶剂名称")
|
||||||
|
vessel: str = Field(..., description="目标容器")
|
||||||
|
volume: float = Field(..., description="总体积 (mL)")
|
||||||
|
|
||||||
|
class HydrogenateProtocol(BaseModel):
|
||||||
|
temp: str = Field(..., description="反应温度(如 '45 °C')")
|
||||||
|
time: str = Field(..., description="反应时间(如 '2 h')")
|
||||||
|
vessel: str = Field(..., description="反应容器")
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"Point3D", "PumpTransferProtocol", "CleanProtocol", "SeparateProtocol",
|
||||||
|
"EvaporateProtocol", "EvacuateAndRefillProtocol", "AGVTransferProtocol",
|
||||||
|
"CentrifugeProtocol", "AddProtocol", "FilterProtocol",
|
||||||
|
"HeatChillProtocol", "HeatChillStartProtocol", "HeatChillStopProtocol",
|
||||||
|
"StirProtocol", "StartStirProtocol", "StopStirProtocol",
|
||||||
|
"TransferProtocol", "CleanVesselProtocol", "DissolveProtocol",
|
||||||
|
"FilterThroughProtocol", "RunColumnProtocol", "WashSolidProtocol",
|
||||||
|
"AdjustPHProtocol", "ResetHandlingProtocol", "DryProtocol",
|
||||||
|
"RecrystallizeProtocol", "HydrogenateProtocol"
|
||||||
|
]
|
||||||
# End Protocols
|
# End Protocols
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
io_snrd:
|
#io_snrd:
|
||||||
description: IO Board with 16 IOs
|
# description: IO Board with 16 IOs
|
||||||
class:
|
# class:
|
||||||
module: ilabos.device_comms.SRND_16_IO:SRND_16_IO
|
# module: unilabos.device_comms.SRND_16_IO:SRND_16_IO
|
||||||
type: python
|
# type: python
|
||||||
hardware_interface:
|
# hardware_interface:
|
||||||
name: modbus_client
|
# name: modbus_client
|
||||||
extra_info: []
|
# extra_info: []
|
||||||
read: read_io_coil
|
# read: read_io_coil
|
||||||
write: write_io_coil
|
# write: write_io_coil
|
||||||
@@ -1,7 +1,102 @@
|
|||||||
serial:
|
serial:
|
||||||
description: Serial communication interface, used when sharing same serial port for multiple devices
|
|
||||||
class:
|
class:
|
||||||
|
action_value_mappings:
|
||||||
|
auto-handle_serial_request:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
request: null
|
||||||
|
response: null
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: handle_serial_request的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
request:
|
||||||
|
type: string
|
||||||
|
response:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- request
|
||||||
|
- response
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: handle_serial_request参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-read_data:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default: {}
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: read_data的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: read_data参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-send_command:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
command: null
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: send_command的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
command:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- command
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: send_command参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
module: unilabos.ros.nodes.presets.serial_node:ROS2SerialNode
|
module: unilabos.ros.nodes.presets.serial_node:ROS2SerialNode
|
||||||
|
status_types: {}
|
||||||
type: ros2
|
type: ros2
|
||||||
schema:
|
description: Serial communication interface, used when sharing same serial port
|
||||||
properties: {}
|
for multiple devices
|
||||||
|
handles: []
|
||||||
|
icon: ''
|
||||||
|
init_param_schema:
|
||||||
|
config:
|
||||||
|
properties:
|
||||||
|
baudrate:
|
||||||
|
default: 9600
|
||||||
|
type: integer
|
||||||
|
device_id:
|
||||||
|
type: string
|
||||||
|
port:
|
||||||
|
type: string
|
||||||
|
resource_tracker:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- device_id
|
||||||
|
- port
|
||||||
|
type: object
|
||||||
|
data:
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
|||||||
69
unilabos/registry/devices/camera.yaml
Normal file
69
unilabos/registry/devices/camera.yaml
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
camera:
|
||||||
|
class:
|
||||||
|
action_value_mappings:
|
||||||
|
auto-destroy_node:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default: {}
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: destroy_node的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: destroy_node参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-timer_callback:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default: {}
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: timer_callback的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: timer_callback参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
module: unilabos.ros.nodes.presets.camera:VideoPublisher
|
||||||
|
status_types: {}
|
||||||
|
type: ros2
|
||||||
|
description: ''
|
||||||
|
handles: []
|
||||||
|
icon: ''
|
||||||
|
init_param_schema:
|
||||||
|
config:
|
||||||
|
properties:
|
||||||
|
camera_index:
|
||||||
|
default: 0
|
||||||
|
type: string
|
||||||
|
device_id:
|
||||||
|
default: video_publisher
|
||||||
|
type: string
|
||||||
|
period:
|
||||||
|
default: 0.1
|
||||||
|
type: number
|
||||||
|
resource_tracker:
|
||||||
|
type: string
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
data:
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
@@ -1,67 +1,433 @@
|
|||||||
# 光学表征设备:红外、紫外可见、拉曼等
|
|
||||||
raman_home_made:
|
|
||||||
description: Raman spectroscopy device
|
|
||||||
class:
|
|
||||||
module: unilabos.devices.raman_uv.home_made_raman:RamanObj
|
|
||||||
type: python
|
|
||||||
status_types:
|
|
||||||
status: String
|
|
||||||
action_value_mappings:
|
|
||||||
raman_cmd:
|
|
||||||
type: SendCmd
|
|
||||||
goal:
|
|
||||||
command: command
|
|
||||||
feedback: {}
|
|
||||||
result:
|
|
||||||
success: success
|
|
||||||
schema:
|
|
||||||
properties:
|
|
||||||
status:
|
|
||||||
type: string
|
|
||||||
required:
|
|
||||||
- status
|
|
||||||
additionalProperties: false
|
|
||||||
type: object
|
|
||||||
hplc.agilent:
|
hplc.agilent:
|
||||||
description: HPLC device
|
|
||||||
class:
|
class:
|
||||||
module: unilabos.devices.hplc.AgilentHPLC:HPLCDriver
|
|
||||||
type: python
|
|
||||||
status_types:
|
|
||||||
device_status: String
|
|
||||||
could_run: Bool
|
|
||||||
driver_init_ok: Bool
|
|
||||||
is_running: Bool
|
|
||||||
finish_status: String
|
|
||||||
status_text: String
|
|
||||||
action_value_mappings:
|
action_value_mappings:
|
||||||
|
auto-check_status:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default: {}
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: check_status的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: check_status参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-extract_data_from_txt:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
file_path: null
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: extract_data_from_txt的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
file_path:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- file_path
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: extract_data_from_txt参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-start_sequence:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
params: null
|
||||||
|
resource: null
|
||||||
|
wf_name: null
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: start_sequence的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
params:
|
||||||
|
type: string
|
||||||
|
resource:
|
||||||
|
type: object
|
||||||
|
wf_name:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- wf_name
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: start_sequence参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-try_close_sub_device:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
device_name: null
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: try_close_sub_device的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
device_name:
|
||||||
|
type: string
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: try_close_sub_device参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-try_open_sub_device:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
device_name: null
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: try_open_sub_device的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
device_name:
|
||||||
|
type: string
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: try_open_sub_device参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
execute_command_from_outer:
|
execute_command_from_outer:
|
||||||
type: SendCmd
|
feedback: {}
|
||||||
goal:
|
goal:
|
||||||
command: command
|
command: command
|
||||||
feedback: {}
|
goal_default:
|
||||||
|
command: ''
|
||||||
|
handles: []
|
||||||
result:
|
result:
|
||||||
success: success
|
success: success
|
||||||
schema:
|
schema:
|
||||||
properties:
|
description: ROS Action SendCmd 的 JSON Schema
|
||||||
device_status:
|
properties:
|
||||||
type: string
|
feedback:
|
||||||
could_run:
|
description: Action 反馈 - 执行过程中从服务器发送到客户端
|
||||||
type: boolean
|
properties:
|
||||||
driver_init_ok:
|
status:
|
||||||
type: boolean
|
type: string
|
||||||
is_running:
|
required:
|
||||||
type: boolean
|
- status
|
||||||
finish_status:
|
title: SendCmd_Feedback
|
||||||
type: string
|
type: object
|
||||||
status_text:
|
goal:
|
||||||
type: string
|
description: Action 目标 - 从客户端发送到服务器
|
||||||
required:
|
properties:
|
||||||
- device_status
|
command:
|
||||||
- could_run
|
type: string
|
||||||
- driver_init_ok
|
required:
|
||||||
- is_running
|
- command
|
||||||
- finish_status
|
title: SendCmd_Goal
|
||||||
- status_text
|
type: object
|
||||||
additionalProperties: false
|
result:
|
||||||
type: object
|
description: Action 结果 - 完成后从服务器发送到客户端
|
||||||
|
properties:
|
||||||
|
return_info:
|
||||||
|
type: string
|
||||||
|
success:
|
||||||
|
type: boolean
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
|
- success
|
||||||
|
title: SendCmd_Result
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: SendCmd
|
||||||
|
type: object
|
||||||
|
type: SendCmd
|
||||||
|
module: unilabos.devices.hplc.AgilentHPLC:HPLCDriver
|
||||||
|
status_types:
|
||||||
|
could_run: bool
|
||||||
|
data_file: list
|
||||||
|
device_status: str
|
||||||
|
driver_init_ok: bool
|
||||||
|
finish_status: str
|
||||||
|
is_running: bool
|
||||||
|
status_text: str
|
||||||
|
success: bool
|
||||||
|
type: python
|
||||||
|
description: HPLC device
|
||||||
|
handles: []
|
||||||
|
icon: ''
|
||||||
|
init_param_schema:
|
||||||
|
config:
|
||||||
|
properties:
|
||||||
|
driver_debug:
|
||||||
|
default: false
|
||||||
|
type: string
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
data:
|
||||||
|
properties:
|
||||||
|
could_run:
|
||||||
|
type: boolean
|
||||||
|
data_file:
|
||||||
|
type: array
|
||||||
|
device_status:
|
||||||
|
type: string
|
||||||
|
driver_init_ok:
|
||||||
|
type: boolean
|
||||||
|
finish_status:
|
||||||
|
type: string
|
||||||
|
is_running:
|
||||||
|
type: boolean
|
||||||
|
status_text:
|
||||||
|
type: string
|
||||||
|
success:
|
||||||
|
type: boolean
|
||||||
|
required:
|
||||||
|
- status_text
|
||||||
|
- device_status
|
||||||
|
- could_run
|
||||||
|
- driver_init_ok
|
||||||
|
- is_running
|
||||||
|
- success
|
||||||
|
- finish_status
|
||||||
|
- data_file
|
||||||
|
type: object
|
||||||
|
raman_home_made:
|
||||||
|
class:
|
||||||
|
action_value_mappings:
|
||||||
|
auto-ccd_time:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
int_time: null
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: ccd_time的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
int_time:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- int_time
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: ccd_time参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-laser_on_power:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
output_voltage_laser: null
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: laser_on_power的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
output_voltage_laser:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- output_voltage_laser
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: laser_on_power参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-raman_cmd:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
command: null
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: raman_cmd的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
command:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- command
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: raman_cmd参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-raman_without_background:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
int_time: null
|
||||||
|
laser_power: null
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: raman_without_background的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
int_time:
|
||||||
|
type: string
|
||||||
|
laser_power:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- int_time
|
||||||
|
- laser_power
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: raman_without_background参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-raman_without_background_average:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
average: null
|
||||||
|
int_time: null
|
||||||
|
laser_power: null
|
||||||
|
sample_name: null
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: raman_without_background_average的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
average:
|
||||||
|
type: string
|
||||||
|
int_time:
|
||||||
|
type: string
|
||||||
|
laser_power:
|
||||||
|
type: string
|
||||||
|
sample_name:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- sample_name
|
||||||
|
- int_time
|
||||||
|
- laser_power
|
||||||
|
- average
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: raman_without_background_average参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
raman_cmd:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
command: command
|
||||||
|
goal_default:
|
||||||
|
command: ''
|
||||||
|
handles: []
|
||||||
|
result:
|
||||||
|
success: success
|
||||||
|
schema:
|
||||||
|
description: ROS Action SendCmd 的 JSON Schema
|
||||||
|
properties:
|
||||||
|
feedback:
|
||||||
|
description: Action 反馈 - 执行过程中从服务器发送到客户端
|
||||||
|
properties:
|
||||||
|
status:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- status
|
||||||
|
title: SendCmd_Feedback
|
||||||
|
type: object
|
||||||
|
goal:
|
||||||
|
description: Action 目标 - 从客户端发送到服务器
|
||||||
|
properties:
|
||||||
|
command:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- command
|
||||||
|
title: SendCmd_Goal
|
||||||
|
type: object
|
||||||
|
result:
|
||||||
|
description: Action 结果 - 完成后从服务器发送到客户端
|
||||||
|
properties:
|
||||||
|
return_info:
|
||||||
|
type: string
|
||||||
|
success:
|
||||||
|
type: boolean
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
|
- success
|
||||||
|
title: SendCmd_Result
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: SendCmd
|
||||||
|
type: object
|
||||||
|
type: SendCmd
|
||||||
|
module: unilabos.devices.raman_uv.home_made_raman:RamanObj
|
||||||
|
status_types: {}
|
||||||
|
type: python
|
||||||
|
description: Raman spectroscopy device
|
||||||
|
handles: []
|
||||||
|
icon: ''
|
||||||
|
init_param_schema:
|
||||||
|
config:
|
||||||
|
properties:
|
||||||
|
baudrate_ccd:
|
||||||
|
default: 921600
|
||||||
|
type: string
|
||||||
|
baudrate_laser:
|
||||||
|
default: 9600
|
||||||
|
type: string
|
||||||
|
port_ccd:
|
||||||
|
type: string
|
||||||
|
port_laser:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- port_laser
|
||||||
|
- port_ccd
|
||||||
|
type: object
|
||||||
|
data:
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
|||||||
@@ -1,9 +1,31 @@
|
|||||||
hotel.thermo_orbitor_rs2_hotel:
|
hotel.thermo_orbitor_rs2_hotel:
|
||||||
description: Thermo Orbitor RS2 Hotel
|
class:
|
||||||
class:
|
action_value_mappings: {}
|
||||||
module: unilabos.devices.resource_container.container:HotelContainer
|
module: unilabos.devices.resource_container.container:HotelContainer
|
||||||
|
status_types:
|
||||||
|
rotation: String
|
||||||
type: python
|
type: python
|
||||||
|
description: Thermo Orbitor RS2 Hotel
|
||||||
|
handles: []
|
||||||
|
icon: ''
|
||||||
|
init_param_schema:
|
||||||
|
config:
|
||||||
|
properties:
|
||||||
|
device_config:
|
||||||
|
type: object
|
||||||
|
rotation:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- rotation
|
||||||
|
- device_config
|
||||||
|
type: object
|
||||||
|
data:
|
||||||
|
properties:
|
||||||
|
rotation:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- rotation
|
||||||
|
type: object
|
||||||
model:
|
model:
|
||||||
type: device
|
|
||||||
mesh: thermo_orbitor_rs2_hotel
|
mesh: thermo_orbitor_rs2_hotel
|
||||||
|
type: device
|
||||||
|
|||||||
@@ -1,56 +1,582 @@
|
|||||||
laiyu_add_solid:
|
laiyu_add_solid:
|
||||||
description: Laiyu Add Solid
|
|
||||||
class:
|
class:
|
||||||
module: unilabos.devices.laiyu_add_solid.laiyu:Laiyu
|
|
||||||
type: python
|
|
||||||
status_types: {}
|
|
||||||
action_value_mappings:
|
action_value_mappings:
|
||||||
|
add_powder_tube:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
compound_mass: compound_mass
|
||||||
|
powder_tube_number: powder_tube_number
|
||||||
|
target_tube_position: target_tube_position
|
||||||
|
goal_default:
|
||||||
|
compound_mass: 0.0
|
||||||
|
powder_tube_number: 0
|
||||||
|
target_tube_position: ''
|
||||||
|
handles: []
|
||||||
|
result:
|
||||||
|
actual_mass_mg: actual_mass_mg
|
||||||
|
schema:
|
||||||
|
description: ROS Action SolidDispenseAddPowderTube 的 JSON Schema
|
||||||
|
properties:
|
||||||
|
feedback:
|
||||||
|
description: Action 反馈 - 执行过程中从服务器发送到客户端
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
title: SolidDispenseAddPowderTube_Feedback
|
||||||
|
type: object
|
||||||
|
goal:
|
||||||
|
description: Action 目标 - 从客户端发送到服务器
|
||||||
|
properties:
|
||||||
|
compound_mass:
|
||||||
|
type: number
|
||||||
|
powder_tube_number:
|
||||||
|
maximum: 2147483647
|
||||||
|
minimum: -2147483648
|
||||||
|
type: integer
|
||||||
|
target_tube_position:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- powder_tube_number
|
||||||
|
- target_tube_position
|
||||||
|
- compound_mass
|
||||||
|
title: SolidDispenseAddPowderTube_Goal
|
||||||
|
type: object
|
||||||
|
result:
|
||||||
|
description: Action 结果 - 完成后从服务器发送到客户端
|
||||||
|
properties:
|
||||||
|
actual_mass_mg:
|
||||||
|
type: number
|
||||||
|
return_info:
|
||||||
|
type: string
|
||||||
|
success:
|
||||||
|
type: boolean
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
|
- actual_mass_mg
|
||||||
|
- success
|
||||||
|
title: SolidDispenseAddPowderTube_Result
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: SolidDispenseAddPowderTube
|
||||||
|
type: object
|
||||||
|
type: SolidDispenseAddPowderTube
|
||||||
|
auto-add_powder_tube:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
compound_mass: null
|
||||||
|
powder_tube_number: null
|
||||||
|
target_tube_position: null
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: add_powder_tube的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
compound_mass:
|
||||||
|
type: string
|
||||||
|
powder_tube_number:
|
||||||
|
type: string
|
||||||
|
target_tube_position:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- powder_tube_number
|
||||||
|
- target_tube_position
|
||||||
|
- compound_mass
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: add_powder_tube参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-calculate_crc:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
data: null
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: calculate_crc的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
data:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- data
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: calculate_crc参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-discharge:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
float_in: null
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: discharge的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
float_in:
|
||||||
|
type: number
|
||||||
|
required:
|
||||||
|
- float_in
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: discharge参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-move_to_plate:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
string: null
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: move_to_plate的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
string:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- string
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: move_to_plate参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-move_to_xyz:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
x: null
|
||||||
|
y: null
|
||||||
|
z: null
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: move_to_xyz的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
x:
|
||||||
|
type: number
|
||||||
|
y:
|
||||||
|
type: number
|
||||||
|
z:
|
||||||
|
type: number
|
||||||
|
required:
|
||||||
|
- x
|
||||||
|
- y
|
||||||
|
- z
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: move_to_xyz参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-pick_powder_tube:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
int_input: null
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: pick_powder_tube的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
int_input:
|
||||||
|
type: integer
|
||||||
|
required:
|
||||||
|
- int_input
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: pick_powder_tube参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-put_powder_tube:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
int_input: null
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: put_powder_tube的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
int_input:
|
||||||
|
type: integer
|
||||||
|
required:
|
||||||
|
- int_input
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: put_powder_tube参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-reset:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default: {}
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: reset的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: reset参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-send_command:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
command: null
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: send_command的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
command:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- command
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: send_command参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
discharge:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
float_input: float_input
|
||||||
|
goal_default:
|
||||||
|
float_in: 0.0
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: ROS Action FloatSingleInput 的 JSON Schema
|
||||||
|
properties:
|
||||||
|
feedback:
|
||||||
|
description: Action 反馈 - 执行过程中从服务器发送到客户端
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
title: FloatSingleInput_Feedback
|
||||||
|
type: object
|
||||||
|
goal:
|
||||||
|
description: Action 目标 - 从客户端发送到服务器
|
||||||
|
properties:
|
||||||
|
float_in:
|
||||||
|
type: number
|
||||||
|
required:
|
||||||
|
- float_in
|
||||||
|
title: FloatSingleInput_Goal
|
||||||
|
type: object
|
||||||
|
result:
|
||||||
|
description: Action 结果 - 完成后从服务器发送到客户端
|
||||||
|
properties:
|
||||||
|
return_info:
|
||||||
|
type: string
|
||||||
|
success:
|
||||||
|
type: boolean
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
|
- success
|
||||||
|
title: FloatSingleInput_Result
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: FloatSingleInput
|
||||||
|
type: object
|
||||||
|
type: FloatSingleInput
|
||||||
|
move_to_plate:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
string: string
|
||||||
|
goal_default:
|
||||||
|
string: ''
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: ROS Action StrSingleInput 的 JSON Schema
|
||||||
|
properties:
|
||||||
|
feedback:
|
||||||
|
description: Action 反馈 - 执行过程中从服务器发送到客户端
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
title: StrSingleInput_Feedback
|
||||||
|
type: object
|
||||||
|
goal:
|
||||||
|
description: Action 目标 - 从客户端发送到服务器
|
||||||
|
properties:
|
||||||
|
string:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- string
|
||||||
|
title: StrSingleInput_Goal
|
||||||
|
type: object
|
||||||
|
result:
|
||||||
|
description: Action 结果 - 完成后从服务器发送到客户端
|
||||||
|
properties:
|
||||||
|
return_info:
|
||||||
|
type: string
|
||||||
|
success:
|
||||||
|
type: boolean
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
|
- success
|
||||||
|
title: StrSingleInput_Result
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: StrSingleInput
|
||||||
|
type: object
|
||||||
|
type: StrSingleInput
|
||||||
move_to_xyz:
|
move_to_xyz:
|
||||||
type: Point3DSeparateInput
|
feedback: {}
|
||||||
goal:
|
goal:
|
||||||
x: x
|
x: x
|
||||||
y: y
|
y: y
|
||||||
z: z
|
z: z
|
||||||
feedback: {}
|
goal_default:
|
||||||
|
x: 0.0
|
||||||
|
y: 0.0
|
||||||
|
z: 0.0
|
||||||
|
handles: []
|
||||||
result: {}
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: ROS Action Point3DSeparateInput 的 JSON Schema
|
||||||
|
properties:
|
||||||
|
feedback:
|
||||||
|
description: Action 反馈 - 执行过程中从服务器发送到客户端
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
title: Point3DSeparateInput_Feedback
|
||||||
|
type: object
|
||||||
|
goal:
|
||||||
|
description: Action 目标 - 从客户端发送到服务器
|
||||||
|
properties:
|
||||||
|
x:
|
||||||
|
type: number
|
||||||
|
y:
|
||||||
|
type: number
|
||||||
|
z:
|
||||||
|
type: number
|
||||||
|
required:
|
||||||
|
- x
|
||||||
|
- y
|
||||||
|
- z
|
||||||
|
title: Point3DSeparateInput_Goal
|
||||||
|
type: object
|
||||||
|
result:
|
||||||
|
description: Action 结果 - 完成后从服务器发送到客户端
|
||||||
|
properties:
|
||||||
|
return_info:
|
||||||
|
type: string
|
||||||
|
success:
|
||||||
|
type: boolean
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
|
- success
|
||||||
|
title: Point3DSeparateInput_Result
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: Point3DSeparateInput
|
||||||
|
type: object
|
||||||
|
type: Point3DSeparateInput
|
||||||
pick_powder_tube:
|
pick_powder_tube:
|
||||||
type: IntSingleInput
|
feedback: {}
|
||||||
goal:
|
goal:
|
||||||
int_input: int_input
|
int_input: int_input
|
||||||
feedback: {}
|
goal_default:
|
||||||
|
int_input: 0
|
||||||
|
handles: []
|
||||||
result: {}
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: ROS Action IntSingleInput 的 JSON Schema
|
||||||
|
properties:
|
||||||
|
feedback:
|
||||||
|
description: Action 反馈 - 执行过程中从服务器发送到客户端
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
title: IntSingleInput_Feedback
|
||||||
|
type: object
|
||||||
|
goal:
|
||||||
|
description: Action 目标 - 从客户端发送到服务器
|
||||||
|
properties:
|
||||||
|
int_input:
|
||||||
|
maximum: 2147483647
|
||||||
|
minimum: -2147483648
|
||||||
|
type: integer
|
||||||
|
required:
|
||||||
|
- int_input
|
||||||
|
title: IntSingleInput_Goal
|
||||||
|
type: object
|
||||||
|
result:
|
||||||
|
description: Action 结果 - 完成后从服务器发送到客户端
|
||||||
|
properties:
|
||||||
|
return_info:
|
||||||
|
type: string
|
||||||
|
success:
|
||||||
|
type: boolean
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
|
- success
|
||||||
|
title: IntSingleInput_Result
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: IntSingleInput
|
||||||
|
type: object
|
||||||
|
type: IntSingleInput
|
||||||
put_powder_tube:
|
put_powder_tube:
|
||||||
type: IntSingleInput
|
feedback: {}
|
||||||
goal:
|
goal:
|
||||||
int_input: int_input
|
int_input: int_input
|
||||||
feedback: {}
|
goal_default:
|
||||||
|
int_input: 0
|
||||||
|
handles: []
|
||||||
result: {}
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: ROS Action IntSingleInput 的 JSON Schema
|
||||||
|
properties:
|
||||||
|
feedback:
|
||||||
|
description: Action 反馈 - 执行过程中从服务器发送到客户端
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
title: IntSingleInput_Feedback
|
||||||
|
type: object
|
||||||
|
goal:
|
||||||
|
description: Action 目标 - 从客户端发送到服务器
|
||||||
|
properties:
|
||||||
|
int_input:
|
||||||
|
maximum: 2147483647
|
||||||
|
minimum: -2147483648
|
||||||
|
type: integer
|
||||||
|
required:
|
||||||
|
- int_input
|
||||||
|
title: IntSingleInput_Goal
|
||||||
|
type: object
|
||||||
|
result:
|
||||||
|
description: Action 结果 - 完成后从服务器发送到客户端
|
||||||
|
properties:
|
||||||
|
return_info:
|
||||||
|
type: string
|
||||||
|
success:
|
||||||
|
type: boolean
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
|
- success
|
||||||
|
title: IntSingleInput_Result
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: IntSingleInput
|
||||||
|
type: object
|
||||||
|
type: IntSingleInput
|
||||||
reset:
|
reset:
|
||||||
type: EmptyIn
|
feedback: {}
|
||||||
goal: {}
|
goal: {}
|
||||||
feedback: {}
|
goal_default: {}
|
||||||
|
handles: []
|
||||||
result: {}
|
result: {}
|
||||||
add_powder_tube:
|
schema:
|
||||||
type: SolidDispenseAddPowderTube
|
description: ROS Action EmptyIn 的 JSON Schema
|
||||||
goal:
|
properties:
|
||||||
powder_tube_number: powder_tube_number
|
feedback:
|
||||||
target_tube_position: target_tube_position
|
description: Action 反馈 - 执行过程中从服务器发送到客户端
|
||||||
compound_mass: compound_mass
|
properties: {}
|
||||||
feedback: {}
|
required: []
|
||||||
result:
|
title: EmptyIn_Feedback
|
||||||
actual_mass_mg: actual_mass_mg
|
type: object
|
||||||
move_to_plate:
|
goal:
|
||||||
type: StrSingleInput
|
description: Action 目标 - 从客户端发送到服务器
|
||||||
goal:
|
properties: {}
|
||||||
string: string
|
required: []
|
||||||
feedback: {}
|
title: EmptyIn_Goal
|
||||||
result: {}
|
type: object
|
||||||
discharge:
|
result:
|
||||||
type: FloatSingleInput
|
description: Action 结果 - 完成后从服务器发送到客户端
|
||||||
goal:
|
properties:
|
||||||
float_input: float_input
|
return_info:
|
||||||
feedback: {}
|
type: string
|
||||||
result: {}
|
required:
|
||||||
|
- return_info
|
||||||
schema:
|
title: EmptyIn_Result
|
||||||
properties: {}
|
type: object
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: EmptyIn
|
||||||
|
type: object
|
||||||
|
type: EmptyIn
|
||||||
|
module: unilabos.devices.laiyu_add_solid.laiyu:Laiyu
|
||||||
|
status_types:
|
||||||
|
status: str
|
||||||
|
type: python
|
||||||
|
description: Laiyu Add Solid
|
||||||
|
handles: []
|
||||||
|
icon: ''
|
||||||
|
init_param_schema:
|
||||||
|
config:
|
||||||
|
properties:
|
||||||
|
baudrate:
|
||||||
|
default: 115200
|
||||||
|
type: string
|
||||||
|
port:
|
||||||
|
type: string
|
||||||
|
timeout:
|
||||||
|
default: 0.5
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- port
|
||||||
|
type: object
|
||||||
|
data:
|
||||||
|
properties:
|
||||||
|
status:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- status
|
||||||
|
type: object
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,56 +1,858 @@
|
|||||||
moveit.toyo_xyz:
|
|
||||||
description: Toyo XYZ
|
|
||||||
class:
|
|
||||||
module: unilabos.devices.ros_dev.moveit_interface:MoveitInterface
|
|
||||||
type: python
|
|
||||||
action_value_mappings:
|
|
||||||
set_position:
|
|
||||||
type: SendCmd
|
|
||||||
goal:
|
|
||||||
command: command
|
|
||||||
feedback: { }
|
|
||||||
result: { }
|
|
||||||
pick_and_place:
|
|
||||||
type: SendCmd
|
|
||||||
goal:
|
|
||||||
command: command
|
|
||||||
feedback: { }
|
|
||||||
result: { }
|
|
||||||
set_status:
|
|
||||||
type: SendCmd
|
|
||||||
goal:
|
|
||||||
command: command
|
|
||||||
feedback: { }
|
|
||||||
result: { }
|
|
||||||
|
|
||||||
model:
|
|
||||||
type: device
|
|
||||||
mesh: toyo_xyz
|
|
||||||
|
|
||||||
moveit.arm_slider:
|
moveit.arm_slider:
|
||||||
description: Arm with Slider
|
|
||||||
model:
|
|
||||||
type: device
|
|
||||||
mesh: arm_slider
|
|
||||||
class:
|
class:
|
||||||
module: unilabos.devices.ros_dev.moveit_interface:MoveitInterface
|
|
||||||
type: python
|
|
||||||
action_value_mappings:
|
action_value_mappings:
|
||||||
set_position:
|
auto-check_tf_update_actions:
|
||||||
type: SendCmd
|
|
||||||
goal:
|
|
||||||
command: command
|
|
||||||
feedback: {}
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default: {}
|
||||||
|
handles: []
|
||||||
result: {}
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: check_tf_update_actions的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: check_tf_update_actions参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-moveit_joint_task:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
joint_names: null
|
||||||
|
joint_positions: null
|
||||||
|
move_group: null
|
||||||
|
retry: 10
|
||||||
|
speed: 1
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: moveit_joint_task的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
joint_names:
|
||||||
|
type: string
|
||||||
|
joint_positions:
|
||||||
|
type: string
|
||||||
|
move_group:
|
||||||
|
type: string
|
||||||
|
retry:
|
||||||
|
default: 10
|
||||||
|
type: string
|
||||||
|
speed:
|
||||||
|
default: 1
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- move_group
|
||||||
|
- joint_positions
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: moveit_joint_task参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-moveit_task:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
cartesian: false
|
||||||
|
move_group: null
|
||||||
|
offsets:
|
||||||
|
- 0
|
||||||
|
- 0
|
||||||
|
- 0
|
||||||
|
position: null
|
||||||
|
quaternion: null
|
||||||
|
retry: 10
|
||||||
|
speed: 1
|
||||||
|
target_link: null
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: moveit_task的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
cartesian:
|
||||||
|
default: false
|
||||||
|
type: string
|
||||||
|
move_group:
|
||||||
|
type: string
|
||||||
|
offsets:
|
||||||
|
default:
|
||||||
|
- 0
|
||||||
|
- 0
|
||||||
|
- 0
|
||||||
|
type: string
|
||||||
|
position:
|
||||||
|
type: string
|
||||||
|
quaternion:
|
||||||
|
type: string
|
||||||
|
retry:
|
||||||
|
default: 10
|
||||||
|
type: string
|
||||||
|
speed:
|
||||||
|
default: 1
|
||||||
|
type: string
|
||||||
|
target_link:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- move_group
|
||||||
|
- position
|
||||||
|
- quaternion
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: moveit_task参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-pick_and_place:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
command: null
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: pick_and_place的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
command:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- command
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: pick_and_place参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-post_init:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
ros_node: null
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: post_init的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
ros_node:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- ros_node
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: post_init参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-resource_manager:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
parent_link: null
|
||||||
|
resource: null
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: resource_manager的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
parent_link:
|
||||||
|
type: string
|
||||||
|
resource:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- resource
|
||||||
|
- parent_link
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: resource_manager参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-set_position:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
command: null
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: set_position的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
command:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- command
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: set_position参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-set_status:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
command: null
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: set_status的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
command:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- command
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: set_status参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-wait_for_resource_action:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default: {}
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: wait_for_resource_action的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: wait_for_resource_action参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
pick_and_place:
|
pick_and_place:
|
||||||
type: SendCmd
|
feedback: {}
|
||||||
goal:
|
goal:
|
||||||
command: command
|
command: command
|
||||||
feedback: {}
|
goal_default:
|
||||||
|
command: ''
|
||||||
|
handles: []
|
||||||
result: {}
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: ROS Action SendCmd 的 JSON Schema
|
||||||
|
properties:
|
||||||
|
feedback:
|
||||||
|
description: Action 反馈 - 执行过程中从服务器发送到客户端
|
||||||
|
properties:
|
||||||
|
status:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- status
|
||||||
|
title: SendCmd_Feedback
|
||||||
|
type: object
|
||||||
|
goal:
|
||||||
|
description: Action 目标 - 从客户端发送到服务器
|
||||||
|
properties:
|
||||||
|
command:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- command
|
||||||
|
title: SendCmd_Goal
|
||||||
|
type: object
|
||||||
|
result:
|
||||||
|
description: Action 结果 - 完成后从服务器发送到客户端
|
||||||
|
properties:
|
||||||
|
return_info:
|
||||||
|
type: string
|
||||||
|
success:
|
||||||
|
type: boolean
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
|
- success
|
||||||
|
title: SendCmd_Result
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: SendCmd
|
||||||
|
type: object
|
||||||
|
type: SendCmd
|
||||||
|
set_position:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
command: command
|
||||||
|
goal_default:
|
||||||
|
command: ''
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: ROS Action SendCmd 的 JSON Schema
|
||||||
|
properties:
|
||||||
|
feedback:
|
||||||
|
description: Action 反馈 - 执行过程中从服务器发送到客户端
|
||||||
|
properties:
|
||||||
|
status:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- status
|
||||||
|
title: SendCmd_Feedback
|
||||||
|
type: object
|
||||||
|
goal:
|
||||||
|
description: Action 目标 - 从客户端发送到服务器
|
||||||
|
properties:
|
||||||
|
command:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- command
|
||||||
|
title: SendCmd_Goal
|
||||||
|
type: object
|
||||||
|
result:
|
||||||
|
description: Action 结果 - 完成后从服务器发送到客户端
|
||||||
|
properties:
|
||||||
|
return_info:
|
||||||
|
type: string
|
||||||
|
success:
|
||||||
|
type: boolean
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
|
- success
|
||||||
|
title: SendCmd_Result
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: SendCmd
|
||||||
|
type: object
|
||||||
|
type: SendCmd
|
||||||
set_status:
|
set_status:
|
||||||
type: SendCmd
|
feedback: {}
|
||||||
goal:
|
goal:
|
||||||
command: command
|
command: command
|
||||||
feedback: {}
|
goal_default:
|
||||||
|
command: ''
|
||||||
|
handles: []
|
||||||
result: {}
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: ROS Action SendCmd 的 JSON Schema
|
||||||
|
properties:
|
||||||
|
feedback:
|
||||||
|
description: Action 反馈 - 执行过程中从服务器发送到客户端
|
||||||
|
properties:
|
||||||
|
status:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- status
|
||||||
|
title: SendCmd_Feedback
|
||||||
|
type: object
|
||||||
|
goal:
|
||||||
|
description: Action 目标 - 从客户端发送到服务器
|
||||||
|
properties:
|
||||||
|
command:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- command
|
||||||
|
title: SendCmd_Goal
|
||||||
|
type: object
|
||||||
|
result:
|
||||||
|
description: Action 结果 - 完成后从服务器发送到客户端
|
||||||
|
properties:
|
||||||
|
return_info:
|
||||||
|
type: string
|
||||||
|
success:
|
||||||
|
type: boolean
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
|
- success
|
||||||
|
title: SendCmd_Result
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: SendCmd
|
||||||
|
type: object
|
||||||
|
type: SendCmd
|
||||||
|
module: unilabos.devices.ros_dev.moveit_interface:MoveitInterface
|
||||||
|
status_types: {}
|
||||||
|
type: python
|
||||||
|
description: Arm with Slider
|
||||||
|
handles: []
|
||||||
|
icon: ''
|
||||||
|
init_param_schema:
|
||||||
|
config:
|
||||||
|
properties:
|
||||||
|
device_config:
|
||||||
|
type: string
|
||||||
|
joint_poses:
|
||||||
|
type: string
|
||||||
|
moveit_type:
|
||||||
|
type: string
|
||||||
|
rotation:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- moveit_type
|
||||||
|
- joint_poses
|
||||||
|
type: object
|
||||||
|
data:
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
model:
|
||||||
|
mesh: arm_slider
|
||||||
|
type: device
|
||||||
|
moveit.toyo_xyz:
|
||||||
|
class:
|
||||||
|
action_value_mappings:
|
||||||
|
auto-check_tf_update_actions:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default: {}
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: check_tf_update_actions的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: check_tf_update_actions参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-moveit_joint_task:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
joint_names: null
|
||||||
|
joint_positions: null
|
||||||
|
move_group: null
|
||||||
|
retry: 10
|
||||||
|
speed: 1
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: moveit_joint_task的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
joint_names:
|
||||||
|
type: string
|
||||||
|
joint_positions:
|
||||||
|
type: string
|
||||||
|
move_group:
|
||||||
|
type: string
|
||||||
|
retry:
|
||||||
|
default: 10
|
||||||
|
type: string
|
||||||
|
speed:
|
||||||
|
default: 1
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- move_group
|
||||||
|
- joint_positions
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: moveit_joint_task参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-moveit_task:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
cartesian: false
|
||||||
|
move_group: null
|
||||||
|
offsets:
|
||||||
|
- 0
|
||||||
|
- 0
|
||||||
|
- 0
|
||||||
|
position: null
|
||||||
|
quaternion: null
|
||||||
|
retry: 10
|
||||||
|
speed: 1
|
||||||
|
target_link: null
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: moveit_task的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
cartesian:
|
||||||
|
default: false
|
||||||
|
type: string
|
||||||
|
move_group:
|
||||||
|
type: string
|
||||||
|
offsets:
|
||||||
|
default:
|
||||||
|
- 0
|
||||||
|
- 0
|
||||||
|
- 0
|
||||||
|
type: string
|
||||||
|
position:
|
||||||
|
type: string
|
||||||
|
quaternion:
|
||||||
|
type: string
|
||||||
|
retry:
|
||||||
|
default: 10
|
||||||
|
type: string
|
||||||
|
speed:
|
||||||
|
default: 1
|
||||||
|
type: string
|
||||||
|
target_link:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- move_group
|
||||||
|
- position
|
||||||
|
- quaternion
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: moveit_task参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-pick_and_place:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
command: null
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: pick_and_place的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
command:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- command
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: pick_and_place参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-post_init:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
ros_node: null
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: post_init的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
ros_node:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- ros_node
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: post_init参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-resource_manager:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
parent_link: null
|
||||||
|
resource: null
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: resource_manager的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
parent_link:
|
||||||
|
type: string
|
||||||
|
resource:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- resource
|
||||||
|
- parent_link
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: resource_manager参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-set_position:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
command: null
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: set_position的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
command:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- command
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: set_position参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-set_status:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
command: null
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: set_status的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
command:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- command
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: set_status参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-wait_for_resource_action:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default: {}
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: wait_for_resource_action的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: wait_for_resource_action参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
pick_and_place:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
command: command
|
||||||
|
goal_default:
|
||||||
|
command: ''
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: ROS Action SendCmd 的 JSON Schema
|
||||||
|
properties:
|
||||||
|
feedback:
|
||||||
|
description: Action 反馈 - 执行过程中从服务器发送到客户端
|
||||||
|
properties:
|
||||||
|
status:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- status
|
||||||
|
title: SendCmd_Feedback
|
||||||
|
type: object
|
||||||
|
goal:
|
||||||
|
description: Action 目标 - 从客户端发送到服务器
|
||||||
|
properties:
|
||||||
|
command:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- command
|
||||||
|
title: SendCmd_Goal
|
||||||
|
type: object
|
||||||
|
result:
|
||||||
|
description: Action 结果 - 完成后从服务器发送到客户端
|
||||||
|
properties:
|
||||||
|
return_info:
|
||||||
|
type: string
|
||||||
|
success:
|
||||||
|
type: boolean
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
|
- success
|
||||||
|
title: SendCmd_Result
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: SendCmd
|
||||||
|
type: object
|
||||||
|
type: SendCmd
|
||||||
|
set_position:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
command: command
|
||||||
|
goal_default:
|
||||||
|
command: ''
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: ROS Action SendCmd 的 JSON Schema
|
||||||
|
properties:
|
||||||
|
feedback:
|
||||||
|
description: Action 反馈 - 执行过程中从服务器发送到客户端
|
||||||
|
properties:
|
||||||
|
status:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- status
|
||||||
|
title: SendCmd_Feedback
|
||||||
|
type: object
|
||||||
|
goal:
|
||||||
|
description: Action 目标 - 从客户端发送到服务器
|
||||||
|
properties:
|
||||||
|
command:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- command
|
||||||
|
title: SendCmd_Goal
|
||||||
|
type: object
|
||||||
|
result:
|
||||||
|
description: Action 结果 - 完成后从服务器发送到客户端
|
||||||
|
properties:
|
||||||
|
return_info:
|
||||||
|
type: string
|
||||||
|
success:
|
||||||
|
type: boolean
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
|
- success
|
||||||
|
title: SendCmd_Result
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: SendCmd
|
||||||
|
type: object
|
||||||
|
type: SendCmd
|
||||||
|
set_status:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
command: command
|
||||||
|
goal_default:
|
||||||
|
command: ''
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: ROS Action SendCmd 的 JSON Schema
|
||||||
|
properties:
|
||||||
|
feedback:
|
||||||
|
description: Action 反馈 - 执行过程中从服务器发送到客户端
|
||||||
|
properties:
|
||||||
|
status:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- status
|
||||||
|
title: SendCmd_Feedback
|
||||||
|
type: object
|
||||||
|
goal:
|
||||||
|
description: Action 目标 - 从客户端发送到服务器
|
||||||
|
properties:
|
||||||
|
command:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- command
|
||||||
|
title: SendCmd_Goal
|
||||||
|
type: object
|
||||||
|
result:
|
||||||
|
description: Action 结果 - 完成后从服务器发送到客户端
|
||||||
|
properties:
|
||||||
|
return_info:
|
||||||
|
type: string
|
||||||
|
success:
|
||||||
|
type: boolean
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
|
- success
|
||||||
|
title: SendCmd_Result
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: SendCmd
|
||||||
|
type: object
|
||||||
|
type: SendCmd
|
||||||
|
module: unilabos.devices.ros_dev.moveit_interface:MoveitInterface
|
||||||
|
status_types: {}
|
||||||
|
type: python
|
||||||
|
description: Toyo XYZ
|
||||||
|
handles: []
|
||||||
|
icon: ''
|
||||||
|
init_param_schema:
|
||||||
|
config:
|
||||||
|
properties:
|
||||||
|
device_config:
|
||||||
|
type: string
|
||||||
|
joint_poses:
|
||||||
|
type: string
|
||||||
|
moveit_type:
|
||||||
|
type: string
|
||||||
|
rotation:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- moveit_type
|
||||||
|
- joint_poses
|
||||||
|
type: object
|
||||||
|
data:
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
model:
|
||||||
|
mesh: toyo_xyz
|
||||||
|
type: device
|
||||||
|
|||||||
@@ -1,73 +1,451 @@
|
|||||||
separator.homemade:
|
|
||||||
description: Separator device with homemade grbl controller
|
|
||||||
class:
|
|
||||||
module: unilabos.devices.separator.homemade_grbl_conductivity:SeparatorController
|
|
||||||
type: python
|
|
||||||
status_types:
|
|
||||||
sensordata: Float64
|
|
||||||
status: String
|
|
||||||
action_value_mappings:
|
|
||||||
stir:
|
|
||||||
type: Stir
|
|
||||||
goal:
|
|
||||||
stir_time: stir_time,
|
|
||||||
stir_speed: stir_speed
|
|
||||||
settling_time: settling_time
|
|
||||||
feedback:
|
|
||||||
status: status
|
|
||||||
result:
|
|
||||||
success: success
|
|
||||||
valve_open_cmd:
|
|
||||||
type: SendCmd
|
|
||||||
goal:
|
|
||||||
command: command
|
|
||||||
feedback:
|
|
||||||
status: status
|
|
||||||
result":
|
|
||||||
success: success
|
|
||||||
schema:
|
|
||||||
type: object
|
|
||||||
properties:
|
|
||||||
status:
|
|
||||||
type: string
|
|
||||||
description: The status of the device
|
|
||||||
sensordata:
|
|
||||||
type: number
|
|
||||||
description: 电导传感器数据
|
|
||||||
required:
|
|
||||||
- status
|
|
||||||
- sensordata
|
|
||||||
additionalProperties: false
|
|
||||||
|
|
||||||
rotavap.one:
|
rotavap.one:
|
||||||
description: Rotavap device
|
|
||||||
class:
|
class:
|
||||||
module: unilabos.devices.rotavap.rotavap_one:RotavapOne
|
|
||||||
type: python
|
|
||||||
status_types:
|
|
||||||
pump_time: Float64
|
|
||||||
rotate_time: Float64
|
|
||||||
action_value_mappings:
|
action_value_mappings:
|
||||||
|
auto-cmd_write:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
cmd: null
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: cmd_write的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
cmd:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- cmd
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: cmd_write参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-main_loop:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default: {}
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: main_loop的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: main_loop参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-set_pump_time:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
time: null
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: set_pump_time的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
time:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- time
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: set_pump_time参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-set_rotate_time:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
time: null
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: set_rotate_time的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
time:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- time
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: set_rotate_time参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-set_timer:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
command: null
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: set_timer的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
command:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- command
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: set_timer参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
set_timer:
|
set_timer:
|
||||||
type: SendCmd
|
feedback: {}
|
||||||
goal:
|
goal:
|
||||||
command: command
|
command: command
|
||||||
feedback: {}
|
goal_default:
|
||||||
|
command: ''
|
||||||
|
handles: []
|
||||||
result:
|
result:
|
||||||
success: success
|
success: success
|
||||||
schema:
|
schema:
|
||||||
type: object
|
description: ROS Action SendCmd 的 JSON Schema
|
||||||
properties:
|
properties:
|
||||||
temperature:
|
feedback:
|
||||||
type: number
|
description: Action 反馈 - 执行过程中从服务器发送到客户端
|
||||||
description: 旋蒸水浴温度
|
properties:
|
||||||
pump_time:
|
status:
|
||||||
type: number
|
type: string
|
||||||
description: The pump time of the device
|
required:
|
||||||
rotate_time:
|
- status
|
||||||
type: number
|
title: SendCmd_Feedback
|
||||||
description: The rotate time of the device
|
type: object
|
||||||
required:
|
goal:
|
||||||
- pump_time
|
description: Action 目标 - 从客户端发送到服务器
|
||||||
- rotate_time
|
properties:
|
||||||
additionalProperties: false
|
command:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- command
|
||||||
|
title: SendCmd_Goal
|
||||||
|
type: object
|
||||||
|
result:
|
||||||
|
description: Action 结果 - 完成后从服务器发送到客户端
|
||||||
|
properties:
|
||||||
|
return_info:
|
||||||
|
type: string
|
||||||
|
success:
|
||||||
|
type: boolean
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
|
- success
|
||||||
|
title: SendCmd_Result
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: SendCmd
|
||||||
|
type: object
|
||||||
|
type: SendCmd
|
||||||
|
module: unilabos.devices.rotavap.rotavap_one:RotavapOne
|
||||||
|
status_types: {}
|
||||||
|
type: python
|
||||||
|
description: Rotavap device
|
||||||
|
handles: []
|
||||||
|
icon: ''
|
||||||
|
init_param_schema:
|
||||||
|
config:
|
||||||
|
properties:
|
||||||
|
port:
|
||||||
|
type: string
|
||||||
|
rate:
|
||||||
|
default: 9600
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- port
|
||||||
|
type: object
|
||||||
|
data:
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
separator.homemade:
|
||||||
|
class:
|
||||||
|
action_value_mappings:
|
||||||
|
auto-read_sensor_loop:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default: {}
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: read_sensor_loop的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: read_sensor_loop参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-stir:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
settling_time: 10
|
||||||
|
stir_speed: 300
|
||||||
|
stir_time: 10
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: stir的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
settling_time:
|
||||||
|
default: 10
|
||||||
|
type: number
|
||||||
|
stir_speed:
|
||||||
|
default: 300
|
||||||
|
type: number
|
||||||
|
stir_time:
|
||||||
|
default: 10
|
||||||
|
type: number
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: stir参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-valve_open:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
condition: null
|
||||||
|
value: null
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: valve_open的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
condition:
|
||||||
|
type: string
|
||||||
|
value:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- condition
|
||||||
|
- value
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: valve_open参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-valve_open_cmd:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
command: null
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: valve_open_cmd的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
command:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- command
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: valve_open_cmd参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-write:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
data: null
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: write的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
data:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- data
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: write参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
stir:
|
||||||
|
feedback:
|
||||||
|
status: status
|
||||||
|
goal:
|
||||||
|
settling_time: settling_time
|
||||||
|
stir_speed: stir_speed
|
||||||
|
stir_time: stir_time,
|
||||||
|
goal_default:
|
||||||
|
settling_time: 0.0
|
||||||
|
stir_speed: 0.0
|
||||||
|
stir_time: 0.0
|
||||||
|
handles: []
|
||||||
|
result:
|
||||||
|
success: success
|
||||||
|
schema:
|
||||||
|
description: ROS Action Stir 的 JSON Schema
|
||||||
|
properties:
|
||||||
|
feedback:
|
||||||
|
description: Action 反馈 - 执行过程中从服务器发送到客户端
|
||||||
|
properties:
|
||||||
|
status:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- status
|
||||||
|
title: Stir_Feedback
|
||||||
|
type: object
|
||||||
|
goal:
|
||||||
|
description: Action 目标 - 从客户端发送到服务器
|
||||||
|
properties:
|
||||||
|
settling_time:
|
||||||
|
type: number
|
||||||
|
stir_speed:
|
||||||
|
type: number
|
||||||
|
stir_time:
|
||||||
|
type: number
|
||||||
|
required:
|
||||||
|
- stir_time
|
||||||
|
- stir_speed
|
||||||
|
- settling_time
|
||||||
|
title: Stir_Goal
|
||||||
|
type: object
|
||||||
|
result:
|
||||||
|
description: Action 结果 - 完成后从服务器发送到客户端
|
||||||
|
properties:
|
||||||
|
return_info:
|
||||||
|
type: string
|
||||||
|
success:
|
||||||
|
type: boolean
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
|
- success
|
||||||
|
title: Stir_Result
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: Stir
|
||||||
|
type: object
|
||||||
|
type: Stir
|
||||||
|
valve_open_cmd:
|
||||||
|
feedback:
|
||||||
|
status: status
|
||||||
|
goal:
|
||||||
|
command: command
|
||||||
|
goal_default:
|
||||||
|
command: ''
|
||||||
|
handles: []
|
||||||
|
result":
|
||||||
|
success: success
|
||||||
|
schema:
|
||||||
|
description: ROS Action SendCmd 的 JSON Schema
|
||||||
|
properties:
|
||||||
|
feedback:
|
||||||
|
description: Action 反馈 - 执行过程中从服务器发送到客户端
|
||||||
|
properties:
|
||||||
|
status:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- status
|
||||||
|
title: SendCmd_Feedback
|
||||||
|
type: object
|
||||||
|
goal:
|
||||||
|
description: Action 目标 - 从客户端发送到服务器
|
||||||
|
properties:
|
||||||
|
command:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- command
|
||||||
|
title: SendCmd_Goal
|
||||||
|
type: object
|
||||||
|
result:
|
||||||
|
description: Action 结果 - 完成后从服务器发送到客户端
|
||||||
|
properties:
|
||||||
|
return_info:
|
||||||
|
type: string
|
||||||
|
success:
|
||||||
|
type: boolean
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
|
- success
|
||||||
|
title: SendCmd_Result
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: SendCmd
|
||||||
|
type: object
|
||||||
|
type: SendCmd
|
||||||
|
module: unilabos.devices.separator.homemade_grbl_conductivity:SeparatorController
|
||||||
|
status_types: {}
|
||||||
|
type: python
|
||||||
|
description: Separator device with homemade grbl controller
|
||||||
|
handles: []
|
||||||
|
icon: ''
|
||||||
|
init_param_schema:
|
||||||
|
config:
|
||||||
|
properties:
|
||||||
|
baudrate_executor:
|
||||||
|
default: 115200
|
||||||
|
type: integer
|
||||||
|
baudrate_sensor:
|
||||||
|
default: 115200
|
||||||
|
type: integer
|
||||||
|
port_executor:
|
||||||
|
type: string
|
||||||
|
port_sensor:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- port_executor
|
||||||
|
- port_sensor
|
||||||
|
type: object
|
||||||
|
data:
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
|||||||
@@ -1,83 +1,877 @@
|
|||||||
syringe_pump_with_valve.runze:
|
solenoid_valve:
|
||||||
description: Runze Syringe pump with valve
|
|
||||||
class:
|
class:
|
||||||
module: unilabos.devices.pump_and_valve.runze_backbone:RunzeSyringePump
|
action_value_mappings:
|
||||||
|
auto-close:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default: {}
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: close的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: close参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-is_closed:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default: {}
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: is_closed的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: is_closed参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-is_open:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default: {}
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: is_open的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: is_open参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-open:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default: {}
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: open的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: open参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-read_data:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default: {}
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: read_data的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: read_data参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-send_command:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
command: null
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: send_command的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
command:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- command
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: send_command参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-set_valve_position:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
position: null
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: set_valve_position的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
position:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- position
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: set_valve_position参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
set_valve_position:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
string: position
|
||||||
|
goal_default:
|
||||||
|
string: ''
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: ROS Action StrSingleInput 的 JSON Schema
|
||||||
|
properties:
|
||||||
|
feedback:
|
||||||
|
description: Action 反馈 - 执行过程中从服务器发送到客户端
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
title: StrSingleInput_Feedback
|
||||||
|
type: object
|
||||||
|
goal:
|
||||||
|
description: Action 目标 - 从客户端发送到服务器
|
||||||
|
properties:
|
||||||
|
string:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- string
|
||||||
|
title: StrSingleInput_Goal
|
||||||
|
type: object
|
||||||
|
result:
|
||||||
|
description: Action 结果 - 完成后从服务器发送到客户端
|
||||||
|
properties:
|
||||||
|
return_info:
|
||||||
|
type: string
|
||||||
|
success:
|
||||||
|
type: boolean
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
|
- success
|
||||||
|
title: StrSingleInput_Result
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: StrSingleInput
|
||||||
|
type: object
|
||||||
|
type: StrSingleInput
|
||||||
|
module: unilabos.devices.pump_and_valve.solenoid_valve:SolenoidValve
|
||||||
|
status_types:
|
||||||
|
status: str
|
||||||
|
valve_position: str
|
||||||
type: python
|
type: python
|
||||||
|
description: Solenoid valve
|
||||||
|
handles: []
|
||||||
|
icon: ''
|
||||||
|
init_param_schema:
|
||||||
|
config:
|
||||||
|
properties:
|
||||||
|
io_device_port:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- io_device_port
|
||||||
|
type: object
|
||||||
|
data:
|
||||||
|
properties:
|
||||||
|
status:
|
||||||
|
type: string
|
||||||
|
valve_position:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- status
|
||||||
|
- valve_position
|
||||||
|
type: object
|
||||||
|
solenoid_valve.mock:
|
||||||
|
class:
|
||||||
|
action_value_mappings:
|
||||||
|
auto-close:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default: {}
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: close的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: close参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-is_closed:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default: {}
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: is_closed的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: is_closed参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-is_open:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default: {}
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: is_open的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: is_open参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-open:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default: {}
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: open的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: open参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-set_valve_position:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
position: null
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: set_valve_position的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
position:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- position
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: set_valve_position参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
close:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default: {}
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: ROS Action EmptyIn 的 JSON Schema
|
||||||
|
properties:
|
||||||
|
feedback:
|
||||||
|
description: Action 反馈 - 执行过程中从服务器发送到客户端
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
title: EmptyIn_Feedback
|
||||||
|
type: object
|
||||||
|
goal:
|
||||||
|
description: Action 目标 - 从客户端发送到服务器
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
title: EmptyIn_Goal
|
||||||
|
type: object
|
||||||
|
result:
|
||||||
|
description: Action 结果 - 完成后从服务器发送到客户端
|
||||||
|
properties:
|
||||||
|
return_info:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
|
title: EmptyIn_Result
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: EmptyIn
|
||||||
|
type: object
|
||||||
|
type: EmptyIn
|
||||||
|
open:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default: {}
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: ROS Action EmptyIn 的 JSON Schema
|
||||||
|
properties:
|
||||||
|
feedback:
|
||||||
|
description: Action 反馈 - 执行过程中从服务器发送到客户端
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
title: EmptyIn_Feedback
|
||||||
|
type: object
|
||||||
|
goal:
|
||||||
|
description: Action 目标 - 从客户端发送到服务器
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
title: EmptyIn_Goal
|
||||||
|
type: object
|
||||||
|
result:
|
||||||
|
description: Action 结果 - 完成后从服务器发送到客户端
|
||||||
|
properties:
|
||||||
|
return_info:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
|
title: EmptyIn_Result
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: EmptyIn
|
||||||
|
type: object
|
||||||
|
type: EmptyIn
|
||||||
|
module: unilabos.devices.pump_and_valve.solenoid_valve_mock:SolenoidValveMock
|
||||||
|
status_types:
|
||||||
|
status: str
|
||||||
|
valve_position: str
|
||||||
|
type: python
|
||||||
|
description: Mock solenoid valve
|
||||||
|
handles:
|
||||||
|
- data_type: fluid
|
||||||
|
handler_key: in
|
||||||
|
io_type: target
|
||||||
|
label: in
|
||||||
|
side: NORTH
|
||||||
|
- data_type: fluid
|
||||||
|
handler_key: out
|
||||||
|
io_type: source
|
||||||
|
label: out
|
||||||
|
side: SOUTH
|
||||||
|
icon: ''
|
||||||
|
init_param_schema:
|
||||||
|
config:
|
||||||
|
properties:
|
||||||
|
port:
|
||||||
|
default: COM6
|
||||||
|
type: string
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
data:
|
||||||
|
properties:
|
||||||
|
status:
|
||||||
|
type: string
|
||||||
|
valve_position:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- status
|
||||||
|
- valve_position
|
||||||
|
type: object
|
||||||
|
syringe_pump_with_valve.runze:
|
||||||
|
class:
|
||||||
|
action_value_mappings:
|
||||||
|
auto-close:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default: {}
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: close的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: close参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-initialize:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default: {}
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: initialize的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: initialize参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-pull_plunger:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
volume: null
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: pull_plunger的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
volume:
|
||||||
|
type: number
|
||||||
|
required:
|
||||||
|
- volume
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: pull_plunger参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-push_plunger:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
volume: null
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: push_plunger的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
volume:
|
||||||
|
type: number
|
||||||
|
required:
|
||||||
|
- volume
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: push_plunger参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-query_aux_input_status_1:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default: {}
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: query_aux_input_status_1的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: query_aux_input_status_1参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-query_aux_input_status_2:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default: {}
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: query_aux_input_status_2的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: query_aux_input_status_2参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-query_backlash_position:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default: {}
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: query_backlash_position的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: query_backlash_position参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-query_command_buffer_status:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default: {}
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: query_command_buffer_status的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: query_command_buffer_status参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-query_software_version:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default: {}
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: query_software_version的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: query_software_version参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-send_command:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
full_command: null
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: send_command的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
full_command:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- full_command
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: send_command参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-set_baudrate:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
baudrate: null
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: set_baudrate的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
baudrate:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- baudrate
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: set_baudrate参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-set_max_velocity:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
velocity: null
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: set_max_velocity的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
velocity:
|
||||||
|
type: number
|
||||||
|
required:
|
||||||
|
- velocity
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: set_max_velocity参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-set_position:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
max_velocity: null
|
||||||
|
position: null
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: set_position的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
max_velocity:
|
||||||
|
type: number
|
||||||
|
position:
|
||||||
|
type: number
|
||||||
|
required:
|
||||||
|
- position
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: set_position参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-set_valve_position:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
position: null
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: set_valve_position的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
position:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- position
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: set_valve_position参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-set_velocity_grade:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
velocity: null
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: set_velocity_grade的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
velocity:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- velocity
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: set_velocity_grade参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-stop_operation:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default: {}
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: stop_operation的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: stop_operation参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-wait_error:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default: {}
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: wait_error的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: wait_error参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
hardware_interface:
|
hardware_interface:
|
||||||
name: hardware_interface
|
name: hardware_interface
|
||||||
read: send_command
|
read: send_command
|
||||||
write: send_command
|
write: send_command
|
||||||
schema:
|
module: unilabos.devices.pump_and_valve.runze_backbone:RunzeSyringePump
|
||||||
type: object
|
|
||||||
properties:
|
|
||||||
status:
|
|
||||||
type: string
|
|
||||||
description: The status of the device
|
|
||||||
position:
|
|
||||||
type: number
|
|
||||||
description: The volume of the syringe
|
|
||||||
speed_max:
|
|
||||||
type: number
|
|
||||||
description: The speed of the syringe
|
|
||||||
valve_position:
|
|
||||||
type: string
|
|
||||||
description: The position of the valve
|
|
||||||
required:
|
|
||||||
- status
|
|
||||||
- position
|
|
||||||
- valve_position
|
|
||||||
additionalProperties: false
|
|
||||||
|
|
||||||
solenoid_valve.mock:
|
|
||||||
description: Mock solenoid valve
|
|
||||||
class:
|
|
||||||
module: unilabos.devices.pump_and_valve.solenoid_valve_mock:SolenoidValveMock
|
|
||||||
type: python
|
|
||||||
status_types:
|
status_types:
|
||||||
status: String
|
max_velocity: float
|
||||||
valve_position: String
|
mode: int
|
||||||
action_value_mappings:
|
plunger_position: String
|
||||||
open:
|
position: float
|
||||||
type: EmptyIn
|
status: str
|
||||||
goal: {}
|
valve_position: str
|
||||||
feedback: {}
|
velocity_end: String
|
||||||
result: {}
|
velocity_grade: String
|
||||||
close:
|
velocity_init: String
|
||||||
type: EmptyIn
|
type: python
|
||||||
goal: {}
|
description: Runze Syringe pump with valve
|
||||||
feedback: {}
|
handles: []
|
||||||
result: {}
|
icon: ''
|
||||||
handles:
|
|
||||||
input:
|
|
||||||
- handler_key: fluid-input
|
|
||||||
label: Fluid Input
|
|
||||||
data_type: fluid
|
|
||||||
output:
|
|
||||||
- handler_key: fluid-output
|
|
||||||
label: Fluid Output
|
|
||||||
data_type: fluid
|
|
||||||
init_param_schema:
|
init_param_schema:
|
||||||
type: object
|
config:
|
||||||
properties:
|
properties:
|
||||||
port:
|
address:
|
||||||
type: string
|
default: '1'
|
||||||
description: "通信端口"
|
type: string
|
||||||
default: "COM6"
|
max_volume:
|
||||||
required:
|
default: 25.0
|
||||||
- port
|
type: number
|
||||||
|
mode:
|
||||||
solenoid_valve:
|
type: string
|
||||||
description: Solenoid valve
|
port:
|
||||||
class:
|
type: string
|
||||||
module: unilabos.devices.pump_and_valve.solenoid_valve:SolenoidValve
|
required:
|
||||||
type: python
|
- port
|
||||||
status_types:
|
type: object
|
||||||
status: String
|
data:
|
||||||
valve_position: String
|
properties:
|
||||||
action_value_mappings:
|
max_velocity:
|
||||||
set_valve_position:
|
type: number
|
||||||
type: StrSingleInput
|
mode:
|
||||||
goal:
|
type: integer
|
||||||
string: position
|
plunger_position:
|
||||||
feedback: {}
|
type: string
|
||||||
result: {}
|
position:
|
||||||
|
type: number
|
||||||
|
status:
|
||||||
|
type: string
|
||||||
|
valve_position:
|
||||||
|
type: string
|
||||||
|
velocity_end:
|
||||||
|
type: string
|
||||||
|
velocity_grade:
|
||||||
|
type: string
|
||||||
|
velocity_init:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- status
|
||||||
|
- mode
|
||||||
|
- max_velocity
|
||||||
|
- velocity_grade
|
||||||
|
- velocity_init
|
||||||
|
- velocity_end
|
||||||
|
- valve_position
|
||||||
|
- position
|
||||||
|
- plunger_position
|
||||||
|
type: object
|
||||||
|
|||||||
@@ -1,29 +1,132 @@
|
|||||||
# 仙工智能底盘(知行使用)
|
|
||||||
agv.SEER:
|
agv.SEER:
|
||||||
description: SEER AGV
|
|
||||||
class:
|
class:
|
||||||
module: unilabos.devices.agv.agv_navigator:AgvNavigator
|
|
||||||
type: python
|
|
||||||
status_types:
|
|
||||||
pose: Float64MultiArray
|
|
||||||
status: String
|
|
||||||
action_value_mappings:
|
action_value_mappings:
|
||||||
|
auto-send:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
cmd: null
|
||||||
|
ex_data: ''
|
||||||
|
obj: receive_socket
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: send的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
cmd:
|
||||||
|
type: string
|
||||||
|
ex_data:
|
||||||
|
default: ''
|
||||||
|
type: string
|
||||||
|
obj:
|
||||||
|
default: receive_socket
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- cmd
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: send参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-send_nav_task:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
command: null
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: send_nav_task的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
command:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- command
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: send_nav_task参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
send_nav_task:
|
send_nav_task:
|
||||||
type: SendCmd
|
feedback: {}
|
||||||
goal:
|
goal:
|
||||||
command: command
|
command: command
|
||||||
feedback: {}
|
goal_default:
|
||||||
|
command: ''
|
||||||
|
handles: []
|
||||||
result:
|
result:
|
||||||
success: success
|
success: success
|
||||||
schema:
|
schema:
|
||||||
properties:
|
description: ROS Action SendCmd 的 JSON Schema
|
||||||
pose:
|
properties:
|
||||||
type: array
|
feedback:
|
||||||
items:
|
description: Action 反馈 - 执行过程中从服务器发送到客户端
|
||||||
type: number
|
properties:
|
||||||
status:
|
status:
|
||||||
type: string
|
type: string
|
||||||
required:
|
required:
|
||||||
|
- status
|
||||||
|
title: SendCmd_Feedback
|
||||||
|
type: object
|
||||||
|
goal:
|
||||||
|
description: Action 目标 - 从客户端发送到服务器
|
||||||
|
properties:
|
||||||
|
command:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- command
|
||||||
|
title: SendCmd_Goal
|
||||||
|
type: object
|
||||||
|
result:
|
||||||
|
description: Action 结果 - 完成后从服务器发送到客户端
|
||||||
|
properties:
|
||||||
|
return_info:
|
||||||
|
type: string
|
||||||
|
success:
|
||||||
|
type: boolean
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
|
- success
|
||||||
|
title: SendCmd_Result
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: SendCmd
|
||||||
|
type: object
|
||||||
|
type: SendCmd
|
||||||
|
module: unilabos.devices.agv.agv_navigator:AgvNavigator
|
||||||
|
status_types:
|
||||||
|
pose: list
|
||||||
|
status: str
|
||||||
|
type: python
|
||||||
|
description: SEER AGV
|
||||||
|
handles: []
|
||||||
|
icon: ''
|
||||||
|
init_param_schema:
|
||||||
|
config:
|
||||||
|
properties:
|
||||||
|
host:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- host
|
||||||
|
type: object
|
||||||
|
data:
|
||||||
|
properties:
|
||||||
|
pose:
|
||||||
|
type: array
|
||||||
|
status:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- pose
|
||||||
- status
|
- status
|
||||||
additionalProperties: false
|
type: object
|
||||||
type: object
|
|
||||||
|
|||||||
@@ -1,37 +1,199 @@
|
|||||||
robotic_arm.UR:
|
robotic_arm.UR:
|
||||||
description: UR robotic arm
|
|
||||||
class:
|
class:
|
||||||
module: unilabos.devices.agv.ur_arm_task:UrArmTask
|
|
||||||
type: python
|
|
||||||
status_types:
|
|
||||||
arm_pose: Float64MultiArray
|
|
||||||
gripper_pose: Float64
|
|
||||||
arm_status: String
|
|
||||||
gripper_status: String
|
|
||||||
action_value_mappings:
|
action_value_mappings:
|
||||||
|
auto-arm_init:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default: {}
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: arm_init的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: arm_init参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-load_pose_data:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
data: null
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: load_pose_data的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
data:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- data
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: load_pose_data参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-load_pose_file:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
file: null
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: load_pose_file的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
file:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- file
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: load_pose_file参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-move_pos_task:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
command: null
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: move_pos_task的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
command:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- command
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: move_pos_task参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-reload_pose:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default: {}
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: reload_pose的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: reload_pose参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
move_pos_task:
|
move_pos_task:
|
||||||
type: SendCmd
|
feedback: {}
|
||||||
goal:
|
goal:
|
||||||
command: command
|
command: command
|
||||||
feedback: {}
|
goal_default:
|
||||||
|
command: ''
|
||||||
|
handles: []
|
||||||
result:
|
result:
|
||||||
success: success
|
success: success
|
||||||
schema:
|
schema:
|
||||||
properties:
|
description: ROS Action SendCmd 的 JSON Schema
|
||||||
arm_pose:
|
properties:
|
||||||
type: array
|
feedback:
|
||||||
items:
|
description: Action 反馈 - 执行过程中从服务器发送到客户端
|
||||||
|
properties:
|
||||||
|
status:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- status
|
||||||
|
title: SendCmd_Feedback
|
||||||
|
type: object
|
||||||
|
goal:
|
||||||
|
description: Action 目标 - 从客户端发送到服务器
|
||||||
|
properties:
|
||||||
|
command:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- command
|
||||||
|
title: SendCmd_Goal
|
||||||
|
type: object
|
||||||
|
result:
|
||||||
|
description: Action 结果 - 完成后从服务器发送到客户端
|
||||||
|
properties:
|
||||||
|
return_info:
|
||||||
|
type: string
|
||||||
|
success:
|
||||||
|
type: boolean
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
|
- success
|
||||||
|
title: SendCmd_Result
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: SendCmd
|
||||||
|
type: object
|
||||||
|
type: SendCmd
|
||||||
|
module: unilabos.devices.agv.ur_arm_task:UrArmTask
|
||||||
|
status_types:
|
||||||
|
arm_pose: list
|
||||||
|
arm_status: str
|
||||||
|
gripper_pose: float
|
||||||
|
gripper_status: str
|
||||||
|
type: python
|
||||||
|
description: UR robotic arm
|
||||||
|
handles: []
|
||||||
|
icon: ''
|
||||||
|
init_param_schema:
|
||||||
|
config:
|
||||||
|
properties:
|
||||||
|
host:
|
||||||
|
type: string
|
||||||
|
retry:
|
||||||
|
default: 30
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- host
|
||||||
|
type: object
|
||||||
|
data:
|
||||||
|
properties:
|
||||||
|
arm_pose:
|
||||||
|
type: array
|
||||||
|
arm_status:
|
||||||
|
type: string
|
||||||
|
gripper_pose:
|
||||||
type: number
|
type: number
|
||||||
gripper_pose:
|
gripper_status:
|
||||||
type: number
|
type: string
|
||||||
arm_status:
|
required:
|
||||||
type: string
|
- arm_pose
|
||||||
description: 机械臂设备状态
|
- gripper_pose
|
||||||
gripper_status:
|
|
||||||
type: string
|
|
||||||
description: 机械爪设备状态
|
|
||||||
required:
|
|
||||||
- arm_status
|
- arm_status
|
||||||
- gripper_status
|
- gripper_status
|
||||||
additionalProperties: false
|
type: object
|
||||||
type: object
|
|
||||||
|
|||||||
@@ -1,37 +1,626 @@
|
|||||||
gripper.mock:
|
|
||||||
description: Mock gripper
|
|
||||||
class:
|
|
||||||
module: unilabos.devices.gripper.mock:MockGripper
|
|
||||||
type: python
|
|
||||||
status_types:
|
|
||||||
position: Float64
|
|
||||||
torque: Float64
|
|
||||||
status: String
|
|
||||||
action_value_mappings:
|
|
||||||
push_to:
|
|
||||||
type: GripperCommand
|
|
||||||
goal:
|
|
||||||
command.position: position
|
|
||||||
command.max_effort: torque
|
|
||||||
feedback:
|
|
||||||
position: position
|
|
||||||
effort: torque
|
|
||||||
result:
|
|
||||||
position: position
|
|
||||||
effort: torque
|
|
||||||
|
|
||||||
gripper.misumi_rz:
|
gripper.misumi_rz:
|
||||||
description: Misumi RZ gripper
|
|
||||||
class:
|
class:
|
||||||
module: unilabos.devices.motor:Grasp.EleGripper
|
|
||||||
type: python
|
|
||||||
status_types:
|
|
||||||
status: String
|
|
||||||
action_value_mappings:
|
action_value_mappings:
|
||||||
|
auto-data_loop:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default: {}
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: data_loop的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: data_loop参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-data_reader:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default: {}
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: data_reader的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: data_reader参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-gripper_move:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
force: null
|
||||||
|
pos: null
|
||||||
|
speed: null
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: gripper_move的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
force:
|
||||||
|
type: string
|
||||||
|
pos:
|
||||||
|
type: string
|
||||||
|
speed:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- pos
|
||||||
|
- speed
|
||||||
|
- force
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: gripper_move参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-init_gripper:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default: {}
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: init_gripper的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: init_gripper参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-modbus_crc:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
data: null
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: modbus_crc的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
data:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- data
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: modbus_crc参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-move_and_rotate:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
grasp_F: null
|
||||||
|
grasp_pos: null
|
||||||
|
grasp_v: null
|
||||||
|
spin_F: null
|
||||||
|
spin_pos: null
|
||||||
|
spin_v: null
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: move_and_rotate的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
grasp_F:
|
||||||
|
type: string
|
||||||
|
grasp_pos:
|
||||||
|
type: string
|
||||||
|
grasp_v:
|
||||||
|
type: string
|
||||||
|
spin_F:
|
||||||
|
type: string
|
||||||
|
spin_pos:
|
||||||
|
type: string
|
||||||
|
spin_v:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- spin_pos
|
||||||
|
- grasp_pos
|
||||||
|
- spin_v
|
||||||
|
- grasp_v
|
||||||
|
- spin_F
|
||||||
|
- grasp_F
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: move_and_rotate参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-node_gripper_move:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
cmd: null
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: node_gripper_move的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
cmd:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- cmd
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: node_gripper_move参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-node_rotate_move:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
cmd: null
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: node_rotate_move的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
cmd:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- cmd
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: node_rotate_move参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-read_address:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
address: null
|
||||||
|
data_len: null
|
||||||
|
id: null
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: read_address的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
address:
|
||||||
|
type: string
|
||||||
|
data_len:
|
||||||
|
type: string
|
||||||
|
id:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- id
|
||||||
|
- address
|
||||||
|
- data_len
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: read_address参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-rotate_move_abs:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
force: null
|
||||||
|
pos: null
|
||||||
|
speed: null
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: rotate_move_abs的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
force:
|
||||||
|
type: string
|
||||||
|
pos:
|
||||||
|
type: string
|
||||||
|
speed:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- pos
|
||||||
|
- speed
|
||||||
|
- force
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: rotate_move_abs参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-send_cmd:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
address: null
|
||||||
|
data: null
|
||||||
|
fun: null
|
||||||
|
id: null
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: send_cmd的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
address:
|
||||||
|
type: string
|
||||||
|
data:
|
||||||
|
type: string
|
||||||
|
fun:
|
||||||
|
type: string
|
||||||
|
id:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- id
|
||||||
|
- fun
|
||||||
|
- address
|
||||||
|
- data
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: send_cmd参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-wait_for_gripper:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default: {}
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: wait_for_gripper的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: wait_for_gripper参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-wait_for_gripper_init:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default: {}
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: wait_for_gripper_init的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: wait_for_gripper_init参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-wait_for_rotate:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default: {}
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: wait_for_rotate的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: wait_for_rotate参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
execute_command_from_outer:
|
execute_command_from_outer:
|
||||||
type: SendCmd
|
feedback: {}
|
||||||
goal:
|
goal:
|
||||||
command: command
|
command: command
|
||||||
feedback: {}
|
goal_default:
|
||||||
|
command: ''
|
||||||
|
handles: []
|
||||||
result:
|
result:
|
||||||
success: success
|
success: success
|
||||||
|
schema:
|
||||||
|
description: ROS Action SendCmd 的 JSON Schema
|
||||||
|
properties:
|
||||||
|
feedback:
|
||||||
|
description: Action 反馈 - 执行过程中从服务器发送到客户端
|
||||||
|
properties:
|
||||||
|
status:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- status
|
||||||
|
title: SendCmd_Feedback
|
||||||
|
type: object
|
||||||
|
goal:
|
||||||
|
description: Action 目标 - 从客户端发送到服务器
|
||||||
|
properties:
|
||||||
|
command:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- command
|
||||||
|
title: SendCmd_Goal
|
||||||
|
type: object
|
||||||
|
result:
|
||||||
|
description: Action 结果 - 完成后从服务器发送到客户端
|
||||||
|
properties:
|
||||||
|
return_info:
|
||||||
|
type: string
|
||||||
|
success:
|
||||||
|
type: boolean
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
|
- success
|
||||||
|
title: SendCmd_Result
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: SendCmd
|
||||||
|
type: object
|
||||||
|
type: SendCmd
|
||||||
|
module: unilabos.devices.motor.Grasp:EleGripper
|
||||||
|
status_types:
|
||||||
|
status: str
|
||||||
|
type: python
|
||||||
|
description: Misumi RZ gripper
|
||||||
|
handles: []
|
||||||
|
icon: ''
|
||||||
|
init_param_schema:
|
||||||
|
config:
|
||||||
|
properties:
|
||||||
|
baudrate:
|
||||||
|
default: 115200
|
||||||
|
type: string
|
||||||
|
id:
|
||||||
|
default: 9
|
||||||
|
type: string
|
||||||
|
port:
|
||||||
|
type: string
|
||||||
|
pos_error:
|
||||||
|
default: -11
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- port
|
||||||
|
type: object
|
||||||
|
data:
|
||||||
|
properties:
|
||||||
|
status:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- status
|
||||||
|
type: object
|
||||||
|
gripper.mock:
|
||||||
|
class:
|
||||||
|
action_value_mappings:
|
||||||
|
auto-edit_id:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
params: '{}'
|
||||||
|
resource:
|
||||||
|
Gripper1: {}
|
||||||
|
wf_name: gripper_run
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: edit_id的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
params:
|
||||||
|
default: '{}'
|
||||||
|
type: string
|
||||||
|
resource:
|
||||||
|
default:
|
||||||
|
Gripper1: {}
|
||||||
|
type: object
|
||||||
|
wf_name:
|
||||||
|
default: gripper_run
|
||||||
|
type: string
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: edit_id参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-push_to:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
position: null
|
||||||
|
torque: null
|
||||||
|
velocity: 0.0
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: push_to的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
position:
|
||||||
|
type: number
|
||||||
|
torque:
|
||||||
|
type: number
|
||||||
|
velocity:
|
||||||
|
default: 0.0
|
||||||
|
type: number
|
||||||
|
required:
|
||||||
|
- position
|
||||||
|
- torque
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: push_to参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
push_to:
|
||||||
|
feedback:
|
||||||
|
effort: torque
|
||||||
|
position: position
|
||||||
|
goal:
|
||||||
|
command.max_effort: torque
|
||||||
|
command.position: position
|
||||||
|
goal_default:
|
||||||
|
command:
|
||||||
|
max_effort: 0.0
|
||||||
|
position: 0.0
|
||||||
|
handles: []
|
||||||
|
result:
|
||||||
|
effort: torque
|
||||||
|
position: position
|
||||||
|
schema:
|
||||||
|
description: ROS Action GripperCommand 的 JSON Schema
|
||||||
|
properties:
|
||||||
|
feedback:
|
||||||
|
description: Action 反馈 - 执行过程中从服务器发送到客户端
|
||||||
|
properties:
|
||||||
|
effort:
|
||||||
|
type: number
|
||||||
|
position:
|
||||||
|
type: number
|
||||||
|
reached_goal:
|
||||||
|
type: boolean
|
||||||
|
stalled:
|
||||||
|
type: boolean
|
||||||
|
required:
|
||||||
|
- position
|
||||||
|
- effort
|
||||||
|
- stalled
|
||||||
|
- reached_goal
|
||||||
|
title: GripperCommand_Feedback
|
||||||
|
type: object
|
||||||
|
goal:
|
||||||
|
description: Action 目标 - 从客户端发送到服务器
|
||||||
|
properties:
|
||||||
|
command:
|
||||||
|
properties:
|
||||||
|
max_effort:
|
||||||
|
type: number
|
||||||
|
position:
|
||||||
|
type: number
|
||||||
|
required:
|
||||||
|
- position
|
||||||
|
- max_effort
|
||||||
|
title: GripperCommand
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- command
|
||||||
|
title: GripperCommand_Goal
|
||||||
|
type: object
|
||||||
|
result:
|
||||||
|
description: Action 结果 - 完成后从服务器发送到客户端
|
||||||
|
properties:
|
||||||
|
effort:
|
||||||
|
type: number
|
||||||
|
position:
|
||||||
|
type: number
|
||||||
|
reached_goal:
|
||||||
|
type: boolean
|
||||||
|
stalled:
|
||||||
|
type: boolean
|
||||||
|
required:
|
||||||
|
- position
|
||||||
|
- effort
|
||||||
|
- stalled
|
||||||
|
- reached_goal
|
||||||
|
title: GripperCommand_Result
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: GripperCommand
|
||||||
|
type: object
|
||||||
|
type: GripperCommand
|
||||||
|
module: unilabos.devices.gripper.mock:MockGripper
|
||||||
|
status_types:
|
||||||
|
position: float
|
||||||
|
status: str
|
||||||
|
torque: float
|
||||||
|
velocity: float
|
||||||
|
type: python
|
||||||
|
description: Mock gripper
|
||||||
|
handles: []
|
||||||
|
icon: ''
|
||||||
|
init_param_schema:
|
||||||
|
config:
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
data:
|
||||||
|
properties:
|
||||||
|
position:
|
||||||
|
type: number
|
||||||
|
status:
|
||||||
|
type: string
|
||||||
|
torque:
|
||||||
|
type: number
|
||||||
|
velocity:
|
||||||
|
type: number
|
||||||
|
required:
|
||||||
|
- position
|
||||||
|
- velocity
|
||||||
|
- torque
|
||||||
|
- status
|
||||||
|
type: object
|
||||||
|
|||||||
@@ -1,57 +1,693 @@
|
|||||||
linear_motion.grbl:
|
linear_motion.grbl:
|
||||||
description: Grbl CNC
|
|
||||||
class:
|
class:
|
||||||
module: unilabos.devices.cnc.grbl_sync:GrblCNC
|
|
||||||
type: python
|
|
||||||
action_value_mappings:
|
action_value_mappings:
|
||||||
move_through_points: &move_through_points
|
auto-initialize:
|
||||||
type: NavigateThroughPoses
|
feedback: {}
|
||||||
goal:
|
goal: {}
|
||||||
poses[].pose.position: positions[]
|
goal_default: {}
|
||||||
feedback:
|
handles: []
|
||||||
current_pose.pose.position: position
|
|
||||||
navigation_time.sec: time_spent
|
|
||||||
estimated_time_remaining.sec: time_remaining
|
|
||||||
number_of_poses_remaining: pose_number_remaining
|
|
||||||
result: {}
|
result: {}
|
||||||
set_spindle_speed:
|
schema:
|
||||||
type: SingleJointPosition
|
description: initialize的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: initialize参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-move_through_points:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
positions: null
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: move_through_points的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
positions:
|
||||||
|
type: array
|
||||||
|
required:
|
||||||
|
- positions
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: move_through_points参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-set_position:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
position: null
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: set_position的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
position:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- position
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: set_position参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-set_spindle_speed:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
max_velocity: 500
|
||||||
|
spindle_speed: null
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: set_spindle_speed的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
max_velocity:
|
||||||
|
default: 500
|
||||||
|
type: number
|
||||||
|
spindle_speed:
|
||||||
|
type: number
|
||||||
|
required:
|
||||||
|
- spindle_speed
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: set_spindle_speed参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-stop_operation:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default: {}
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: stop_operation的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: stop_operation参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-wait_error:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default: {}
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: wait_error的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: wait_error参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommandAsync
|
||||||
|
move_through_points:
|
||||||
|
feedback:
|
||||||
|
current_pose.pose.position: position
|
||||||
|
estimated_time_remaining.sec: time_remaining
|
||||||
|
navigation_time.sec: time_spent
|
||||||
|
number_of_poses_remaining: pose_number_remaining
|
||||||
goal:
|
goal:
|
||||||
position: spindle_speed
|
poses[].pose.position: positions[]
|
||||||
|
goal_default:
|
||||||
|
behavior_tree: ''
|
||||||
|
poses:
|
||||||
|
- header:
|
||||||
|
frame_id: ''
|
||||||
|
stamp:
|
||||||
|
nanosec: 0
|
||||||
|
sec: 0
|
||||||
|
pose:
|
||||||
|
orientation:
|
||||||
|
w: 1.0
|
||||||
|
x: 0.0
|
||||||
|
y: 0.0
|
||||||
|
z: 0.0
|
||||||
|
position:
|
||||||
|
x: 0.0
|
||||||
|
y: 0.0
|
||||||
|
z: 0.0
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: ROS Action NavigateThroughPoses 的 JSON Schema
|
||||||
|
properties:
|
||||||
|
feedback:
|
||||||
|
description: Action 反馈 - 执行过程中从服务器发送到客户端
|
||||||
|
properties:
|
||||||
|
current_pose:
|
||||||
|
properties:
|
||||||
|
header:
|
||||||
|
properties:
|
||||||
|
frame_id:
|
||||||
|
type: string
|
||||||
|
stamp:
|
||||||
|
properties:
|
||||||
|
nanosec:
|
||||||
|
maximum: 4294967295
|
||||||
|
minimum: 0
|
||||||
|
type: integer
|
||||||
|
sec:
|
||||||
|
maximum: 2147483647
|
||||||
|
minimum: -2147483648
|
||||||
|
type: integer
|
||||||
|
required:
|
||||||
|
- sec
|
||||||
|
- nanosec
|
||||||
|
title: Time
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- stamp
|
||||||
|
- frame_id
|
||||||
|
title: Header
|
||||||
|
type: object
|
||||||
|
pose:
|
||||||
|
properties:
|
||||||
|
orientation:
|
||||||
|
properties:
|
||||||
|
w:
|
||||||
|
type: number
|
||||||
|
x:
|
||||||
|
type: number
|
||||||
|
y:
|
||||||
|
type: number
|
||||||
|
z:
|
||||||
|
type: number
|
||||||
|
required:
|
||||||
|
- x
|
||||||
|
- y
|
||||||
|
- z
|
||||||
|
- w
|
||||||
|
title: Quaternion
|
||||||
|
type: object
|
||||||
|
position:
|
||||||
|
properties:
|
||||||
|
x:
|
||||||
|
type: number
|
||||||
|
y:
|
||||||
|
type: number
|
||||||
|
z:
|
||||||
|
type: number
|
||||||
|
required:
|
||||||
|
- x
|
||||||
|
- y
|
||||||
|
- z
|
||||||
|
title: Point
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- position
|
||||||
|
- orientation
|
||||||
|
title: Pose
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- header
|
||||||
|
- pose
|
||||||
|
title: PoseStamped
|
||||||
|
type: object
|
||||||
|
distance_remaining:
|
||||||
|
type: number
|
||||||
|
estimated_time_remaining:
|
||||||
|
properties:
|
||||||
|
nanosec:
|
||||||
|
maximum: 4294967295
|
||||||
|
minimum: 0
|
||||||
|
type: integer
|
||||||
|
sec:
|
||||||
|
maximum: 2147483647
|
||||||
|
minimum: -2147483648
|
||||||
|
type: integer
|
||||||
|
required:
|
||||||
|
- sec
|
||||||
|
- nanosec
|
||||||
|
title: Duration
|
||||||
|
type: object
|
||||||
|
navigation_time:
|
||||||
|
properties:
|
||||||
|
nanosec:
|
||||||
|
maximum: 4294967295
|
||||||
|
minimum: 0
|
||||||
|
type: integer
|
||||||
|
sec:
|
||||||
|
maximum: 2147483647
|
||||||
|
minimum: -2147483648
|
||||||
|
type: integer
|
||||||
|
required:
|
||||||
|
- sec
|
||||||
|
- nanosec
|
||||||
|
title: Duration
|
||||||
|
type: object
|
||||||
|
number_of_poses_remaining:
|
||||||
|
maximum: 32767
|
||||||
|
minimum: -32768
|
||||||
|
type: integer
|
||||||
|
number_of_recoveries:
|
||||||
|
maximum: 32767
|
||||||
|
minimum: -32768
|
||||||
|
type: integer
|
||||||
|
required:
|
||||||
|
- current_pose
|
||||||
|
- navigation_time
|
||||||
|
- estimated_time_remaining
|
||||||
|
- number_of_recoveries
|
||||||
|
- distance_remaining
|
||||||
|
- number_of_poses_remaining
|
||||||
|
title: NavigateThroughPoses_Feedback
|
||||||
|
type: object
|
||||||
|
goal:
|
||||||
|
description: Action 目标 - 从客户端发送到服务器
|
||||||
|
properties:
|
||||||
|
behavior_tree:
|
||||||
|
type: string
|
||||||
|
poses:
|
||||||
|
items:
|
||||||
|
properties:
|
||||||
|
header:
|
||||||
|
properties:
|
||||||
|
frame_id:
|
||||||
|
type: string
|
||||||
|
stamp:
|
||||||
|
properties:
|
||||||
|
nanosec:
|
||||||
|
maximum: 4294967295
|
||||||
|
minimum: 0
|
||||||
|
type: integer
|
||||||
|
sec:
|
||||||
|
maximum: 2147483647
|
||||||
|
minimum: -2147483648
|
||||||
|
type: integer
|
||||||
|
required:
|
||||||
|
- sec
|
||||||
|
- nanosec
|
||||||
|
title: Time
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- stamp
|
||||||
|
- frame_id
|
||||||
|
title: Header
|
||||||
|
type: object
|
||||||
|
pose:
|
||||||
|
properties:
|
||||||
|
orientation:
|
||||||
|
properties:
|
||||||
|
w:
|
||||||
|
type: number
|
||||||
|
x:
|
||||||
|
type: number
|
||||||
|
y:
|
||||||
|
type: number
|
||||||
|
z:
|
||||||
|
type: number
|
||||||
|
required:
|
||||||
|
- x
|
||||||
|
- y
|
||||||
|
- z
|
||||||
|
- w
|
||||||
|
title: Quaternion
|
||||||
|
type: object
|
||||||
|
position:
|
||||||
|
properties:
|
||||||
|
x:
|
||||||
|
type: number
|
||||||
|
y:
|
||||||
|
type: number
|
||||||
|
z:
|
||||||
|
type: number
|
||||||
|
required:
|
||||||
|
- x
|
||||||
|
- y
|
||||||
|
- z
|
||||||
|
title: Point
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- position
|
||||||
|
- orientation
|
||||||
|
title: Pose
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- header
|
||||||
|
- pose
|
||||||
|
title: PoseStamped
|
||||||
|
type: object
|
||||||
|
type: array
|
||||||
|
required:
|
||||||
|
- poses
|
||||||
|
- behavior_tree
|
||||||
|
title: NavigateThroughPoses_Goal
|
||||||
|
type: object
|
||||||
|
result:
|
||||||
|
description: Action 结果 - 完成后从服务器发送到客户端
|
||||||
|
properties:
|
||||||
|
result:
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
title: Empty
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- result
|
||||||
|
title: NavigateThroughPoses_Result
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: NavigateThroughPoses
|
||||||
|
type: object
|
||||||
|
type: NavigateThroughPoses
|
||||||
|
set_spindle_speed:
|
||||||
feedback:
|
feedback:
|
||||||
position: spindle_speed
|
position: spindle_speed
|
||||||
|
goal:
|
||||||
|
position: spindle_speed
|
||||||
|
goal_default:
|
||||||
|
max_velocity: 0.0
|
||||||
|
min_duration:
|
||||||
|
nanosec: 0
|
||||||
|
sec: 0
|
||||||
|
position: 0.0
|
||||||
|
handles: []
|
||||||
result: {}
|
result: {}
|
||||||
schema:
|
schema:
|
||||||
type: object
|
description: ROS Action SingleJointPosition 的 JSON Schema
|
||||||
properties:
|
properties:
|
||||||
position:
|
feedback:
|
||||||
type: array
|
description: Action 反馈 - 执行过程中从服务器发送到客户端
|
||||||
items:
|
properties:
|
||||||
|
error:
|
||||||
|
type: number
|
||||||
|
header:
|
||||||
|
properties:
|
||||||
|
frame_id:
|
||||||
|
type: string
|
||||||
|
stamp:
|
||||||
|
properties:
|
||||||
|
nanosec:
|
||||||
|
maximum: 4294967295
|
||||||
|
minimum: 0
|
||||||
|
type: integer
|
||||||
|
sec:
|
||||||
|
maximum: 2147483647
|
||||||
|
minimum: -2147483648
|
||||||
|
type: integer
|
||||||
|
required:
|
||||||
|
- sec
|
||||||
|
- nanosec
|
||||||
|
title: Time
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- stamp
|
||||||
|
- frame_id
|
||||||
|
title: Header
|
||||||
|
type: object
|
||||||
|
position:
|
||||||
|
type: number
|
||||||
|
velocity:
|
||||||
|
type: number
|
||||||
|
required:
|
||||||
|
- header
|
||||||
|
- position
|
||||||
|
- velocity
|
||||||
|
- error
|
||||||
|
title: SingleJointPosition_Feedback
|
||||||
|
type: object
|
||||||
|
goal:
|
||||||
|
description: Action 目标 - 从客户端发送到服务器
|
||||||
|
properties:
|
||||||
|
max_velocity:
|
||||||
|
type: number
|
||||||
|
min_duration:
|
||||||
|
properties:
|
||||||
|
nanosec:
|
||||||
|
maximum: 4294967295
|
||||||
|
minimum: 0
|
||||||
|
type: integer
|
||||||
|
sec:
|
||||||
|
maximum: 2147483647
|
||||||
|
minimum: -2147483648
|
||||||
|
type: integer
|
||||||
|
required:
|
||||||
|
- sec
|
||||||
|
- nanosec
|
||||||
|
title: Duration
|
||||||
|
type: object
|
||||||
|
position:
|
||||||
|
type: number
|
||||||
|
required:
|
||||||
|
- position
|
||||||
|
- min_duration
|
||||||
|
- max_velocity
|
||||||
|
title: SingleJointPosition_Goal
|
||||||
|
type: object
|
||||||
|
result:
|
||||||
|
description: Action 结果 - 完成后从服务器发送到客户端
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
title: SingleJointPosition_Result
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: SingleJointPosition
|
||||||
|
type: object
|
||||||
|
type: SingleJointPosition
|
||||||
|
module: unilabos.devices.cnc.grbl_sync:GrblCNC
|
||||||
|
status_types:
|
||||||
|
position: unilabos.messages:Point3D
|
||||||
|
spindle_speed: float
|
||||||
|
status: str
|
||||||
|
type: python
|
||||||
|
description: Grbl CNC
|
||||||
|
handles: []
|
||||||
|
icon: ''
|
||||||
|
init_param_schema:
|
||||||
|
config:
|
||||||
|
properties:
|
||||||
|
address:
|
||||||
|
default: '1'
|
||||||
|
type: string
|
||||||
|
limits:
|
||||||
|
default:
|
||||||
|
- -150
|
||||||
|
- 150
|
||||||
|
- -200
|
||||||
|
- 0
|
||||||
|
- -80
|
||||||
|
- 0
|
||||||
|
type: array
|
||||||
|
port:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- port
|
||||||
|
type: object
|
||||||
|
data:
|
||||||
|
properties:
|
||||||
|
position:
|
||||||
|
type: string
|
||||||
|
spindle_speed:
|
||||||
type: number
|
type: number
|
||||||
description: The position of the device
|
status:
|
||||||
spindle_speed:
|
type: string
|
||||||
type: number
|
required:
|
||||||
description: The spindle speed of the device
|
- status
|
||||||
required:
|
|
||||||
- position
|
- position
|
||||||
- spindle_speed
|
- spindle_speed
|
||||||
additionalProperties: false
|
type: object
|
||||||
|
|
||||||
|
|
||||||
motor.iCL42:
|
motor.iCL42:
|
||||||
description: iCL42 motor
|
|
||||||
class:
|
class:
|
||||||
module: unilabos.devices.motor.iCL42:iCL42Driver
|
|
||||||
type: python
|
|
||||||
status_types:
|
|
||||||
motor_position: Int64
|
|
||||||
is_executing_run: Bool
|
|
||||||
success: Bool
|
|
||||||
action_value_mappings:
|
action_value_mappings:
|
||||||
|
auto-execute_run_motor:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
mode: null
|
||||||
|
position: null
|
||||||
|
velocity: null
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: execute_run_motor的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
mode:
|
||||||
|
type: string
|
||||||
|
position:
|
||||||
|
type: number
|
||||||
|
velocity:
|
||||||
|
type: integer
|
||||||
|
required:
|
||||||
|
- mode
|
||||||
|
- position
|
||||||
|
- velocity
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: execute_run_motor参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-init_device:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default: {}
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: init_device的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: init_device参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-run_motor:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
mode: null
|
||||||
|
position: null
|
||||||
|
velocity: null
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: run_motor的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
mode:
|
||||||
|
type: string
|
||||||
|
position:
|
||||||
|
type: number
|
||||||
|
velocity:
|
||||||
|
type: integer
|
||||||
|
required:
|
||||||
|
- mode
|
||||||
|
- position
|
||||||
|
- velocity
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: run_motor参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
execute_command_from_outer:
|
execute_command_from_outer:
|
||||||
type: SendCmd
|
feedback: {}
|
||||||
goal:
|
goal:
|
||||||
command: command
|
command: command
|
||||||
feedback: {}
|
goal_default:
|
||||||
|
command: ''
|
||||||
|
handles: []
|
||||||
result:
|
result:
|
||||||
success: success
|
success: success
|
||||||
|
schema:
|
||||||
|
description: ROS Action SendCmd 的 JSON Schema
|
||||||
|
properties:
|
||||||
|
feedback:
|
||||||
|
description: Action 反馈 - 执行过程中从服务器发送到客户端
|
||||||
|
properties:
|
||||||
|
status:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- status
|
||||||
|
title: SendCmd_Feedback
|
||||||
|
type: object
|
||||||
|
goal:
|
||||||
|
description: Action 目标 - 从客户端发送到服务器
|
||||||
|
properties:
|
||||||
|
command:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- command
|
||||||
|
title: SendCmd_Goal
|
||||||
|
type: object
|
||||||
|
result:
|
||||||
|
description: Action 结果 - 完成后从服务器发送到客户端
|
||||||
|
properties:
|
||||||
|
return_info:
|
||||||
|
type: string
|
||||||
|
success:
|
||||||
|
type: boolean
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
|
- success
|
||||||
|
title: SendCmd_Result
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: SendCmd
|
||||||
|
type: object
|
||||||
|
type: SendCmd
|
||||||
|
module: unilabos.devices.motor.iCL42:iCL42Driver
|
||||||
|
status_types:
|
||||||
|
is_executing_run: bool
|
||||||
|
motor_position: int
|
||||||
|
success: bool
|
||||||
|
type: python
|
||||||
|
description: iCL42 motor
|
||||||
|
handles: []
|
||||||
|
icon: ''
|
||||||
|
init_param_schema:
|
||||||
|
config:
|
||||||
|
properties:
|
||||||
|
device_address:
|
||||||
|
default: 1
|
||||||
|
type: integer
|
||||||
|
device_com:
|
||||||
|
default: COM9
|
||||||
|
type: string
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
data:
|
||||||
|
properties:
|
||||||
|
is_executing_run:
|
||||||
|
type: boolean
|
||||||
|
motor_position:
|
||||||
|
type: integer
|
||||||
|
success:
|
||||||
|
type: boolean
|
||||||
|
required:
|
||||||
|
- motor_position
|
||||||
|
- is_executing_run
|
||||||
|
- success
|
||||||
|
type: object
|
||||||
|
|||||||
@@ -1,5 +1,311 @@
|
|||||||
lh_joint_publisher:
|
lh_joint_publisher:
|
||||||
class:
|
class:
|
||||||
|
action_value_mappings:
|
||||||
|
auto-check_tf_update_actions:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default: {}
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: check_tf_update_actions的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: check_tf_update_actions参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-find_resource_parent:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
resource_id: null
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: find_resource_parent的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
resource_id:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- resource_id
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: find_resource_parent参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-inverse_kinematics:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
parent_id: null
|
||||||
|
x: null
|
||||||
|
x_joint: null
|
||||||
|
y: null
|
||||||
|
y_joint: null
|
||||||
|
z: null
|
||||||
|
z_joint: null
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: inverse_kinematics的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
parent_id:
|
||||||
|
type: string
|
||||||
|
x:
|
||||||
|
type: string
|
||||||
|
x_joint:
|
||||||
|
type: object
|
||||||
|
y:
|
||||||
|
type: string
|
||||||
|
y_joint:
|
||||||
|
type: object
|
||||||
|
z:
|
||||||
|
type: string
|
||||||
|
z_joint:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- x
|
||||||
|
- y
|
||||||
|
- z
|
||||||
|
- parent_id
|
||||||
|
- x_joint
|
||||||
|
- y_joint
|
||||||
|
- z_joint
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: inverse_kinematics参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-lh_joint_action_callback:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
goal_handle: null
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: lh_joint_action_callback的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
goal_handle:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- goal_handle
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: lh_joint_action_callback参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-lh_joint_pub_callback:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default: {}
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: lh_joint_pub_callback的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: lh_joint_pub_callback参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-move_joints:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
option: null
|
||||||
|
resource_names: null
|
||||||
|
speed: 0.1
|
||||||
|
x: null
|
||||||
|
x_joint: null
|
||||||
|
y: null
|
||||||
|
y_joint: null
|
||||||
|
z: null
|
||||||
|
z_joint: null
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: move_joints的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
option:
|
||||||
|
type: string
|
||||||
|
resource_names:
|
||||||
|
type: string
|
||||||
|
speed:
|
||||||
|
default: 0.1
|
||||||
|
type: string
|
||||||
|
x:
|
||||||
|
type: string
|
||||||
|
x_joint:
|
||||||
|
type: string
|
||||||
|
y:
|
||||||
|
type: string
|
||||||
|
y_joint:
|
||||||
|
type: string
|
||||||
|
z:
|
||||||
|
type: string
|
||||||
|
z_joint:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- resource_names
|
||||||
|
- x
|
||||||
|
- y
|
||||||
|
- z
|
||||||
|
- option
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: move_joints参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-move_to:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
joint_positions: null
|
||||||
|
parent_id: null
|
||||||
|
speed: null
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: move_to的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
joint_positions:
|
||||||
|
type: string
|
||||||
|
parent_id:
|
||||||
|
type: string
|
||||||
|
speed:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- joint_positions
|
||||||
|
- speed
|
||||||
|
- parent_id
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: move_to参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-resource_move:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
channels: null
|
||||||
|
link_name: null
|
||||||
|
resource_id: null
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: resource_move的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
channels:
|
||||||
|
type: array
|
||||||
|
link_name:
|
||||||
|
type: string
|
||||||
|
resource_id:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- resource_id
|
||||||
|
- link_name
|
||||||
|
- channels
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: resource_move参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-send_resource_action:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
link_name: null
|
||||||
|
resource_id_list: null
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: send_resource_action的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
link_name:
|
||||||
|
type: string
|
||||||
|
resource_id_list:
|
||||||
|
type: array
|
||||||
|
required:
|
||||||
|
- resource_id_list
|
||||||
|
- link_name
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: send_resource_action参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
module: unilabos.devices.ros_dev.liquid_handler_joint_publisher:LiquidHandlerJointPublisher
|
module: unilabos.devices.ros_dev.liquid_handler_joint_publisher:LiquidHandlerJointPublisher
|
||||||
|
status_types: {}
|
||||||
type: ros2
|
type: ros2
|
||||||
|
description: ''
|
||||||
|
handles: []
|
||||||
|
icon: ''
|
||||||
|
init_param_schema:
|
||||||
|
config:
|
||||||
|
properties:
|
||||||
|
device_id:
|
||||||
|
default: lh_joint_publisher
|
||||||
|
type: string
|
||||||
|
rate:
|
||||||
|
default: 50
|
||||||
|
type: string
|
||||||
|
resource_tracker:
|
||||||
|
type: string
|
||||||
|
resources_config:
|
||||||
|
type: array
|
||||||
|
required:
|
||||||
|
- resources_config
|
||||||
|
- resource_tracker
|
||||||
|
type: object
|
||||||
|
data:
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
|||||||
@@ -1,65 +1,792 @@
|
|||||||
heaterstirrer.dalong:
|
chiller:
|
||||||
description: DaLong heater stirrer
|
|
||||||
class:
|
class:
|
||||||
module: unilabos.devices.heaterstirrer.dalong:HeaterStirrer_DaLong
|
|
||||||
type: python
|
|
||||||
status_types:
|
|
||||||
temp: Float64
|
|
||||||
temp_warning: Float64
|
|
||||||
stir_speed: Float64
|
|
||||||
action_value_mappings:
|
action_value_mappings:
|
||||||
set_temp_warning:
|
auto-build_modbus_frame:
|
||||||
type: SendCmd
|
|
||||||
goal:
|
|
||||||
command: temp
|
|
||||||
feedback: {}
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
device_address: null
|
||||||
|
function_code: null
|
||||||
|
register_address: null
|
||||||
|
value: null
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: build_modbus_frame的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
device_address:
|
||||||
|
type: integer
|
||||||
|
function_code:
|
||||||
|
type: integer
|
||||||
|
register_address:
|
||||||
|
type: integer
|
||||||
|
value:
|
||||||
|
type: integer
|
||||||
|
required:
|
||||||
|
- device_address
|
||||||
|
- function_code
|
||||||
|
- register_address
|
||||||
|
- value
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: build_modbus_frame参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-convert_temperature_to_modbus_value:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
decimal_points: 1
|
||||||
|
temperature: null
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: convert_temperature_to_modbus_value的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
decimal_points:
|
||||||
|
default: 1
|
||||||
|
type: integer
|
||||||
|
temperature:
|
||||||
|
type: number
|
||||||
|
required:
|
||||||
|
- temperature
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: convert_temperature_to_modbus_value参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-modbus_crc:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
data: null
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: modbus_crc的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
data:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- data
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: modbus_crc参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-set_temperature:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
command: null
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: set_temperature的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
command:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- command
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: set_temperature参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-stop:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default: {}
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: stop的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: stop参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
set_temperature:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
command: command
|
||||||
|
goal_default:
|
||||||
|
command: ''
|
||||||
|
handles: []
|
||||||
result:
|
result:
|
||||||
success: success
|
success: success
|
||||||
set_temp_target:
|
schema:
|
||||||
|
description: ROS Action SendCmd 的 JSON Schema
|
||||||
|
properties:
|
||||||
|
feedback:
|
||||||
|
description: Action 反馈 - 执行过程中从服务器发送到客户端
|
||||||
|
properties:
|
||||||
|
status:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- status
|
||||||
|
title: SendCmd_Feedback
|
||||||
|
type: object
|
||||||
|
goal:
|
||||||
|
description: Action 目标 - 从客户端发送到服务器
|
||||||
|
properties:
|
||||||
|
command:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- command
|
||||||
|
title: SendCmd_Goal
|
||||||
|
type: object
|
||||||
|
result:
|
||||||
|
description: Action 结果 - 完成后从服务器发送到客户端
|
||||||
|
properties:
|
||||||
|
return_info:
|
||||||
|
type: string
|
||||||
|
success:
|
||||||
|
type: boolean
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
|
- success
|
||||||
|
title: SendCmd_Result
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: SendCmd
|
||||||
|
type: object
|
||||||
type: SendCmd
|
type: SendCmd
|
||||||
goal:
|
module: unilabos.devices.temperature.chiller:Chiller
|
||||||
command: temp
|
status_types: {}
|
||||||
|
type: python
|
||||||
|
description: Chiller
|
||||||
|
handles: []
|
||||||
|
icon: ''
|
||||||
|
init_param_schema:
|
||||||
|
config:
|
||||||
|
properties:
|
||||||
|
port:
|
||||||
|
type: string
|
||||||
|
rate:
|
||||||
|
default: 9600
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- port
|
||||||
|
type: object
|
||||||
|
data:
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
heaterstirrer.dalong:
|
||||||
|
class:
|
||||||
|
action_value_mappings:
|
||||||
|
auto-close:
|
||||||
feedback: {}
|
feedback: {}
|
||||||
result:
|
goal: {}
|
||||||
success: success
|
goal_default: {}
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: close的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: close参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-heatchill:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
purpose: reaction
|
||||||
|
stir: true
|
||||||
|
stir_speed: 300
|
||||||
|
temp: null
|
||||||
|
time: 3600
|
||||||
|
vessel: null
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: heatchill的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
purpose:
|
||||||
|
default: reaction
|
||||||
|
type: string
|
||||||
|
stir:
|
||||||
|
default: true
|
||||||
|
type: boolean
|
||||||
|
stir_speed:
|
||||||
|
default: 300
|
||||||
|
type: number
|
||||||
|
temp:
|
||||||
|
type: number
|
||||||
|
time:
|
||||||
|
default: 3600
|
||||||
|
type: number
|
||||||
|
vessel:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- vessel
|
||||||
|
- temp
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: heatchill参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-set_stir_speed:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
speed: null
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: set_stir_speed的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
speed:
|
||||||
|
type: number
|
||||||
|
required:
|
||||||
|
- speed
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: set_stir_speed参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-set_temp_inner:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
temp: null
|
||||||
|
type: warning
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: set_temp_inner的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
temp:
|
||||||
|
type: number
|
||||||
|
type:
|
||||||
|
default: warning
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- temp
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: set_temp_inner参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-set_temp_target:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
temp: null
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: set_temp_target的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
temp:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- temp
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: set_temp_target参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-set_temp_warning:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
temp: null
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: set_temp_warning的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
temp:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- temp
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: set_temp_warning参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
heatchill:
|
heatchill:
|
||||||
type: HeatChill
|
|
||||||
goal:
|
|
||||||
vessel: vessel
|
|
||||||
temp: temp
|
|
||||||
time: time
|
|
||||||
purpose: purpose
|
|
||||||
feedback:
|
feedback:
|
||||||
status: status
|
status: status
|
||||||
result:
|
|
||||||
success: success
|
|
||||||
|
|
||||||
chiller:
|
|
||||||
description: Chiller
|
|
||||||
class:
|
|
||||||
module: unilabos.devices.temperature.chiller:Chiller
|
|
||||||
type: python
|
|
||||||
action_value_mappings:
|
|
||||||
set_temperature:
|
|
||||||
type: SendCmd
|
|
||||||
goal:
|
goal:
|
||||||
command: command
|
purpose: purpose
|
||||||
feedback: {}
|
temp: temp
|
||||||
|
time: time
|
||||||
|
vessel: vessel
|
||||||
|
goal_default:
|
||||||
|
purpose: ''
|
||||||
|
stir: false
|
||||||
|
stir_speed: 0.0
|
||||||
|
temp: 0.0
|
||||||
|
time: 0.0
|
||||||
|
vessel: ''
|
||||||
|
handles: []
|
||||||
result:
|
result:
|
||||||
success: success
|
success: success
|
||||||
tempsensor:
|
schema:
|
||||||
description: Temperature sensor
|
description: ROS Action HeatChill 的 JSON Schema
|
||||||
class:
|
properties:
|
||||||
module: unilabos.devices.temperature.sensor_node:TempSensorNode
|
feedback:
|
||||||
type: python
|
description: Action 反馈 - 执行过程中从服务器发送到客户端
|
||||||
|
properties:
|
||||||
|
status:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- status
|
||||||
|
title: HeatChill_Feedback
|
||||||
|
type: object
|
||||||
|
goal:
|
||||||
|
description: Action 目标 - 从客户端发送到服务器
|
||||||
|
properties:
|
||||||
|
purpose:
|
||||||
|
type: string
|
||||||
|
stir:
|
||||||
|
type: boolean
|
||||||
|
stir_speed:
|
||||||
|
type: number
|
||||||
|
temp:
|
||||||
|
type: number
|
||||||
|
time:
|
||||||
|
type: number
|
||||||
|
vessel:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- vessel
|
||||||
|
- temp
|
||||||
|
- time
|
||||||
|
- stir
|
||||||
|
- stir_speed
|
||||||
|
- purpose
|
||||||
|
title: HeatChill_Goal
|
||||||
|
type: object
|
||||||
|
result:
|
||||||
|
description: Action 结果 - 完成后从服务器发送到客户端
|
||||||
|
properties:
|
||||||
|
return_info:
|
||||||
|
type: string
|
||||||
|
success:
|
||||||
|
type: boolean
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
|
- success
|
||||||
|
title: HeatChill_Result
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: HeatChill
|
||||||
|
type: object
|
||||||
|
type: HeatChill
|
||||||
|
set_temp_target:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
command: temp
|
||||||
|
goal_default:
|
||||||
|
command: ''
|
||||||
|
handles: []
|
||||||
|
result:
|
||||||
|
success: success
|
||||||
|
schema:
|
||||||
|
description: ROS Action SendCmd 的 JSON Schema
|
||||||
|
properties:
|
||||||
|
feedback:
|
||||||
|
description: Action 反馈 - 执行过程中从服务器发送到客户端
|
||||||
|
properties:
|
||||||
|
status:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- status
|
||||||
|
title: SendCmd_Feedback
|
||||||
|
type: object
|
||||||
|
goal:
|
||||||
|
description: Action 目标 - 从客户端发送到服务器
|
||||||
|
properties:
|
||||||
|
command:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- command
|
||||||
|
title: SendCmd_Goal
|
||||||
|
type: object
|
||||||
|
result:
|
||||||
|
description: Action 结果 - 完成后从服务器发送到客户端
|
||||||
|
properties:
|
||||||
|
return_info:
|
||||||
|
type: string
|
||||||
|
success:
|
||||||
|
type: boolean
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
|
- success
|
||||||
|
title: SendCmd_Result
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: SendCmd
|
||||||
|
type: object
|
||||||
|
type: SendCmd
|
||||||
|
set_temp_warning:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
command: temp
|
||||||
|
goal_default:
|
||||||
|
command: ''
|
||||||
|
handles: []
|
||||||
|
result:
|
||||||
|
success: success
|
||||||
|
schema:
|
||||||
|
description: ROS Action SendCmd 的 JSON Schema
|
||||||
|
properties:
|
||||||
|
feedback:
|
||||||
|
description: Action 反馈 - 执行过程中从服务器发送到客户端
|
||||||
|
properties:
|
||||||
|
status:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- status
|
||||||
|
title: SendCmd_Feedback
|
||||||
|
type: object
|
||||||
|
goal:
|
||||||
|
description: Action 目标 - 从客户端发送到服务器
|
||||||
|
properties:
|
||||||
|
command:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- command
|
||||||
|
title: SendCmd_Goal
|
||||||
|
type: object
|
||||||
|
result:
|
||||||
|
description: Action 结果 - 完成后从服务器发送到客户端
|
||||||
|
properties:
|
||||||
|
return_info:
|
||||||
|
type: string
|
||||||
|
success:
|
||||||
|
type: boolean
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
|
- success
|
||||||
|
title: SendCmd_Result
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: SendCmd
|
||||||
|
type: object
|
||||||
|
type: SendCmd
|
||||||
|
module: unilabos.devices.heaterstirrer.dalong:HeaterStirrer_DaLong
|
||||||
status_types:
|
status_types:
|
||||||
value: Float64
|
status: str
|
||||||
warning: Float64
|
stir_speed: float
|
||||||
|
temp: float
|
||||||
|
temp_target: float
|
||||||
|
temp_warning: float
|
||||||
|
type: python
|
||||||
|
description: DaLong heater stirrer
|
||||||
|
handles: []
|
||||||
|
icon: ''
|
||||||
|
init_param_schema:
|
||||||
|
config:
|
||||||
|
properties:
|
||||||
|
baudrate:
|
||||||
|
default: 9600
|
||||||
|
type: integer
|
||||||
|
port:
|
||||||
|
default: COM6
|
||||||
|
type: string
|
||||||
|
temp_warning:
|
||||||
|
default: 50.0
|
||||||
|
type: string
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
data:
|
||||||
|
properties:
|
||||||
|
status:
|
||||||
|
type: string
|
||||||
|
stir_speed:
|
||||||
|
type: number
|
||||||
|
temp:
|
||||||
|
type: number
|
||||||
|
temp_target:
|
||||||
|
type: number
|
||||||
|
temp_warning:
|
||||||
|
type: number
|
||||||
|
required:
|
||||||
|
- status
|
||||||
|
- stir_speed
|
||||||
|
- temp
|
||||||
|
- temp_warning
|
||||||
|
- temp_target
|
||||||
|
type: object
|
||||||
|
tempsensor:
|
||||||
|
class:
|
||||||
action_value_mappings:
|
action_value_mappings:
|
||||||
|
auto-build_modbus_request:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
device_id: null
|
||||||
|
function_code: null
|
||||||
|
register_address: null
|
||||||
|
register_count: null
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: build_modbus_request的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
device_id:
|
||||||
|
type: string
|
||||||
|
function_code:
|
||||||
|
type: string
|
||||||
|
register_address:
|
||||||
|
type: string
|
||||||
|
register_count:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- device_id
|
||||||
|
- function_code
|
||||||
|
- register_address
|
||||||
|
- register_count
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: build_modbus_request参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-calculate_crc:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
data: null
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: calculate_crc的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
data:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- data
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: calculate_crc参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-read_modbus_response:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
response: null
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: read_modbus_response的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
response:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- response
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: read_modbus_response参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-send_prototype_command:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
command: null
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: send_prototype_command的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
command:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- command
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: send_prototype_command参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-set_warning:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
command: null
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: set_warning的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
command:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- command
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: set_warning参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
set_warning:
|
set_warning:
|
||||||
type: SendCmd
|
feedback: {}
|
||||||
goal:
|
goal:
|
||||||
command: command
|
command: command
|
||||||
feedback: {}
|
goal_default:
|
||||||
|
command: ''
|
||||||
|
handles: []
|
||||||
result:
|
result:
|
||||||
success: success
|
success: success
|
||||||
|
schema:
|
||||||
|
description: ROS Action SendCmd 的 JSON Schema
|
||||||
|
properties:
|
||||||
|
feedback:
|
||||||
|
description: Action 反馈 - 执行过程中从服务器发送到客户端
|
||||||
|
properties:
|
||||||
|
status:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- status
|
||||||
|
title: SendCmd_Feedback
|
||||||
|
type: object
|
||||||
|
goal:
|
||||||
|
description: Action 目标 - 从客户端发送到服务器
|
||||||
|
properties:
|
||||||
|
command:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- command
|
||||||
|
title: SendCmd_Goal
|
||||||
|
type: object
|
||||||
|
result:
|
||||||
|
description: Action 结果 - 完成后从服务器发送到客户端
|
||||||
|
properties:
|
||||||
|
return_info:
|
||||||
|
type: string
|
||||||
|
success:
|
||||||
|
type: boolean
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
|
- success
|
||||||
|
title: SendCmd_Result
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: SendCmd
|
||||||
|
type: object
|
||||||
|
type: SendCmd
|
||||||
|
module: unilabos.devices.temperature.sensor_node:TempSensorNode
|
||||||
|
status_types:
|
||||||
|
value: float
|
||||||
|
type: python
|
||||||
|
description: Temperature sensor
|
||||||
|
handles: []
|
||||||
|
icon: ''
|
||||||
|
init_param_schema:
|
||||||
|
config:
|
||||||
|
properties:
|
||||||
|
address:
|
||||||
|
type: string
|
||||||
|
baudrate:
|
||||||
|
default: 9600
|
||||||
|
type: string
|
||||||
|
port:
|
||||||
|
type: string
|
||||||
|
warning:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- port
|
||||||
|
- warning
|
||||||
|
- address
|
||||||
|
type: object
|
||||||
|
data:
|
||||||
|
properties:
|
||||||
|
value:
|
||||||
|
type: number
|
||||||
|
required:
|
||||||
|
- value
|
||||||
|
type: object
|
||||||
|
|||||||
@@ -1,97 +1,496 @@
|
|||||||
vacuum_pump.mock:
|
|
||||||
description: Mock vacuum pump
|
|
||||||
class:
|
|
||||||
module: unilabos.devices.pump_and_valve.vacuum_pump_mock:VacuumPumpMock
|
|
||||||
type: python
|
|
||||||
status_types:
|
|
||||||
status: String
|
|
||||||
action_value_mappings:
|
|
||||||
open:
|
|
||||||
type: EmptyIn
|
|
||||||
goal: {}
|
|
||||||
feedback: {}
|
|
||||||
result: {}
|
|
||||||
close:
|
|
||||||
type: EmptyIn
|
|
||||||
goal: {}
|
|
||||||
feedback: {}
|
|
||||||
result: {}
|
|
||||||
set_status:
|
|
||||||
type: StrSingleInput
|
|
||||||
goal:
|
|
||||||
string: string
|
|
||||||
feedback: {}
|
|
||||||
result: {}
|
|
||||||
handles:
|
|
||||||
input:
|
|
||||||
- handler_key: fluid-input
|
|
||||||
label: Fluid Input
|
|
||||||
data_type: fluid
|
|
||||||
io_type: target
|
|
||||||
data_source: handle
|
|
||||||
data_key: fluid_in
|
|
||||||
output:
|
|
||||||
- handler_key: fluid-output
|
|
||||||
label: Fluid Output
|
|
||||||
data_type: fluid
|
|
||||||
io_type: source
|
|
||||||
data_source: executor
|
|
||||||
data_key: fluid_out
|
|
||||||
init_param_schema:
|
|
||||||
type: object
|
|
||||||
properties:
|
|
||||||
port:
|
|
||||||
type: string
|
|
||||||
description: "通信端口"
|
|
||||||
default: "COM6"
|
|
||||||
required:
|
|
||||||
- port
|
|
||||||
|
|
||||||
gas_source.mock:
|
gas_source.mock:
|
||||||
description: Mock gas source
|
|
||||||
class:
|
class:
|
||||||
module: unilabos.devices.pump_and_valve.vacuum_pump_mock:VacuumPumpMock
|
|
||||||
type: python
|
|
||||||
status_types:
|
|
||||||
status: String
|
|
||||||
action_value_mappings:
|
action_value_mappings:
|
||||||
open:
|
auto-close:
|
||||||
type: EmptyIn
|
|
||||||
goal: {}
|
|
||||||
feedback: {}
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default: {}
|
||||||
|
handles: []
|
||||||
result: {}
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: close的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: close参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-is_closed:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default: {}
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: is_closed的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: is_closed参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-is_open:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default: {}
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: is_open的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: is_open参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-open:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default: {}
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: open的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: open参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-set_status:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
string: null
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: set_status的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
string:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- string
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: set_status参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
close:
|
close:
|
||||||
type: EmptyIn
|
|
||||||
goal: {}
|
|
||||||
feedback: {}
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default: {}
|
||||||
|
handles: []
|
||||||
result: {}
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: ROS Action EmptyIn 的 JSON Schema
|
||||||
|
properties:
|
||||||
|
feedback:
|
||||||
|
description: Action 反馈 - 执行过程中从服务器发送到客户端
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
title: EmptyIn_Feedback
|
||||||
|
type: object
|
||||||
|
goal:
|
||||||
|
description: Action 目标 - 从客户端发送到服务器
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
title: EmptyIn_Goal
|
||||||
|
type: object
|
||||||
|
result:
|
||||||
|
description: Action 结果 - 完成后从服务器发送到客户端
|
||||||
|
properties:
|
||||||
|
return_info:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
|
title: EmptyIn_Result
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: EmptyIn
|
||||||
|
type: object
|
||||||
|
type: EmptyIn
|
||||||
|
open:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default: {}
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: ROS Action EmptyIn 的 JSON Schema
|
||||||
|
properties:
|
||||||
|
feedback:
|
||||||
|
description: Action 反馈 - 执行过程中从服务器发送到客户端
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
title: EmptyIn_Feedback
|
||||||
|
type: object
|
||||||
|
goal:
|
||||||
|
description: Action 目标 - 从客户端发送到服务器
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
title: EmptyIn_Goal
|
||||||
|
type: object
|
||||||
|
result:
|
||||||
|
description: Action 结果 - 完成后从服务器发送到客户端
|
||||||
|
properties:
|
||||||
|
return_info:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
|
title: EmptyIn_Result
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: EmptyIn
|
||||||
|
type: object
|
||||||
|
type: EmptyIn
|
||||||
set_status:
|
set_status:
|
||||||
type: StrSingleInput
|
feedback: {}
|
||||||
goal:
|
goal:
|
||||||
string: string
|
string: string
|
||||||
feedback: {}
|
goal_default:
|
||||||
|
string: ''
|
||||||
|
handles: []
|
||||||
result: {}
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: ROS Action StrSingleInput 的 JSON Schema
|
||||||
|
properties:
|
||||||
|
feedback:
|
||||||
|
description: Action 反馈 - 执行过程中从服务器发送到客户端
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
title: StrSingleInput_Feedback
|
||||||
|
type: object
|
||||||
|
goal:
|
||||||
|
description: Action 目标 - 从客户端发送到服务器
|
||||||
|
properties:
|
||||||
|
string:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- string
|
||||||
|
title: StrSingleInput_Goal
|
||||||
|
type: object
|
||||||
|
result:
|
||||||
|
description: Action 结果 - 完成后从服务器发送到客户端
|
||||||
|
properties:
|
||||||
|
return_info:
|
||||||
|
type: string
|
||||||
|
success:
|
||||||
|
type: boolean
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
|
- success
|
||||||
|
title: StrSingleInput_Result
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: StrSingleInput
|
||||||
|
type: object
|
||||||
|
type: StrSingleInput
|
||||||
|
module: unilabos.devices.pump_and_valve.vacuum_pump_mock:VacuumPumpMock
|
||||||
|
status_types:
|
||||||
|
status: str
|
||||||
|
type: python
|
||||||
|
description: Mock gas source
|
||||||
handles:
|
handles:
|
||||||
input:
|
- data_key: fluid_out
|
||||||
- handler_key: fluid-input
|
data_source: executor
|
||||||
label: Fluid Input
|
data_type: fluid
|
||||||
data_type: fluid
|
handler_key: out
|
||||||
io_type: target
|
io_type: source
|
||||||
data_source: handle
|
label: out
|
||||||
data_key: fluid_in
|
icon: ''
|
||||||
output:
|
|
||||||
- handler_key: fluid-output
|
|
||||||
label: Fluid Output
|
|
||||||
data_type: fluid
|
|
||||||
io_type: source
|
|
||||||
data_source: executor
|
|
||||||
data_key: fluid_out
|
|
||||||
init_param_schema:
|
init_param_schema:
|
||||||
type: object
|
config:
|
||||||
properties:
|
properties:
|
||||||
port:
|
port:
|
||||||
type: string
|
default: COM6
|
||||||
description: "通信端口"
|
type: string
|
||||||
default: "COM6"
|
required: []
|
||||||
required:
|
type: object
|
||||||
- port
|
data:
|
||||||
|
properties:
|
||||||
|
status:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- status
|
||||||
|
type: object
|
||||||
|
vacuum_pump.mock:
|
||||||
|
class:
|
||||||
|
action_value_mappings:
|
||||||
|
auto-close:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default: {}
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: close的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: close参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-is_closed:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default: {}
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: is_closed的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: is_closed参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-is_open:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default: {}
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: is_open的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: is_open参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-open:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default: {}
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: open的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: open参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-set_status:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
string: null
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: set_status的参数schema
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
string:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- string
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: set_status参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
close:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default: {}
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: ROS Action EmptyIn 的 JSON Schema
|
||||||
|
properties:
|
||||||
|
feedback:
|
||||||
|
description: Action 反馈 - 执行过程中从服务器发送到客户端
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
title: EmptyIn_Feedback
|
||||||
|
type: object
|
||||||
|
goal:
|
||||||
|
description: Action 目标 - 从客户端发送到服务器
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
title: EmptyIn_Goal
|
||||||
|
type: object
|
||||||
|
result:
|
||||||
|
description: Action 结果 - 完成后从服务器发送到客户端
|
||||||
|
properties:
|
||||||
|
return_info:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
|
title: EmptyIn_Result
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: EmptyIn
|
||||||
|
type: object
|
||||||
|
type: EmptyIn
|
||||||
|
open:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default: {}
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: ROS Action EmptyIn 的 JSON Schema
|
||||||
|
properties:
|
||||||
|
feedback:
|
||||||
|
description: Action 反馈 - 执行过程中从服务器发送到客户端
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
title: EmptyIn_Feedback
|
||||||
|
type: object
|
||||||
|
goal:
|
||||||
|
description: Action 目标 - 从客户端发送到服务器
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
title: EmptyIn_Goal
|
||||||
|
type: object
|
||||||
|
result:
|
||||||
|
description: Action 结果 - 完成后从服务器发送到客户端
|
||||||
|
properties:
|
||||||
|
return_info:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
|
title: EmptyIn_Result
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: EmptyIn
|
||||||
|
type: object
|
||||||
|
type: EmptyIn
|
||||||
|
set_status:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
string: string
|
||||||
|
goal_default:
|
||||||
|
string: ''
|
||||||
|
handles: []
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: ROS Action StrSingleInput 的 JSON Schema
|
||||||
|
properties:
|
||||||
|
feedback:
|
||||||
|
description: Action 反馈 - 执行过程中从服务器发送到客户端
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
title: StrSingleInput_Feedback
|
||||||
|
type: object
|
||||||
|
goal:
|
||||||
|
description: Action 目标 - 从客户端发送到服务器
|
||||||
|
properties:
|
||||||
|
string:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- string
|
||||||
|
title: StrSingleInput_Goal
|
||||||
|
type: object
|
||||||
|
result:
|
||||||
|
description: Action 结果 - 完成后从服务器发送到客户端
|
||||||
|
properties:
|
||||||
|
return_info:
|
||||||
|
type: string
|
||||||
|
success:
|
||||||
|
type: boolean
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
|
- success
|
||||||
|
title: StrSingleInput_Result
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: StrSingleInput
|
||||||
|
type: object
|
||||||
|
type: StrSingleInput
|
||||||
|
module: unilabos.devices.pump_and_valve.vacuum_pump_mock:VacuumPumpMock
|
||||||
|
status_types:
|
||||||
|
status: str
|
||||||
|
type: python
|
||||||
|
description: Mock vacuum pump
|
||||||
|
handles:
|
||||||
|
- data_key: fluid_in
|
||||||
|
data_source: handle
|
||||||
|
data_type: fluid
|
||||||
|
handler_key: out
|
||||||
|
io_type: source
|
||||||
|
label: out
|
||||||
|
icon: ''
|
||||||
|
init_param_schema:
|
||||||
|
config:
|
||||||
|
properties:
|
||||||
|
port:
|
||||||
|
default: COM6
|
||||||
|
type: string
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
data:
|
||||||
|
properties:
|
||||||
|
status:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- status
|
||||||
|
type: object
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user