mirror of
https://github.com/deepmodeling/Uni-Lab-OS
synced 2026-03-29 18:13:16 +00:00
Compare commits
2 Commits
7b04f3fa50
...
v0.9.5
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7db3123547 | ||
|
|
6da7a20a7a |
132
.github/workflows/multi-platform-build.yml
vendored
132
.github/workflows/multi-platform-build.yml
vendored
@@ -1,132 +0,0 @@
|
|||||||
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
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
1. 用到的仪器
|
|
||||||
virtual_multiway_valve() 八通阀门
|
|
||||||
virtual_transfer_pump() 转移泵
|
|
||||||
virtual_centrifuge() 离心机
|
|
||||||
virtual_rotavap() 旋蒸仪
|
|
||||||
virtual_heatchill() 加热器
|
|
||||||
virtual_stirrer() 搅拌器
|
|
||||||
virtual_solenoid_valve() 电磁阀
|
|
||||||
vacuum_pump() vacuum_pump.mock 真空泵
|
|
||||||
gas_source() 气源
|
|
||||||
virtual_filter() 过滤器
|
|
||||||
virtual_column(√) 层析柱
|
|
||||||
separator() homemade_grbl_conductivity 分液漏斗
|
|
||||||
2. 用到的protocol
|
|
||||||
AddProtocol()
|
|
||||||
TransferProtocol() 应该用pump_protocol.py删掉transfer
|
|
||||||
StartStirProtocol()
|
|
||||||
StopStirProtocol()
|
|
||||||
StirProtocol()
|
|
||||||
RunColumnProtocol()
|
|
||||||
CentrifugeProtocol()
|
|
||||||
FilterProtocol()
|
|
||||||
CleanVesselProtocol()
|
|
||||||
DissolveProtocol()
|
|
||||||
FilterThroughProtocol()
|
|
||||||
WashSolidProtocol()
|
|
||||||
SeparateProtocol(√)
|
|
||||||
EvaporateProtocol(√)
|
|
||||||
HeatChillProtocol()
|
|
||||||
HeatChillStartProtocol()
|
|
||||||
HeatChillStopProtocol()
|
|
||||||
EvacuateAndRefillProtocol(√)
|
|
||||||
@@ -1,887 +0,0 @@
|
|||||||
{
|
|
||||||
"nodes": [
|
|
||||||
{
|
|
||||||
"id": "ComprehensiveProtocolStation",
|
|
||||||
"name": "综合协议测试工作站",
|
|
||||||
"children": [
|
|
||||||
"multiway_valve_1",
|
|
||||||
"multiway_valve_2",
|
|
||||||
"transfer_pump_1",
|
|
||||||
"transfer_pump_2",
|
|
||||||
"reagent_bottle_1",
|
|
||||||
"reagent_bottle_2",
|
|
||||||
"reagent_bottle_3",
|
|
||||||
"reagent_bottle_4",
|
|
||||||
"reagent_bottle_5",
|
|
||||||
"centrifuge_1",
|
|
||||||
"rotavap_1",
|
|
||||||
"main_reactor",
|
|
||||||
"heater_1",
|
|
||||||
"stirrer_1",
|
|
||||||
"stirrer_2",
|
|
||||||
"waste_bottle_1",
|
|
||||||
"waste_bottle_2",
|
|
||||||
"solenoid_valve_1",
|
|
||||||
"solenoid_valve_2",
|
|
||||||
"vacuum_pump_1",
|
|
||||||
"gas_source_1",
|
|
||||||
"filter_1",
|
|
||||||
"column_1",
|
|
||||||
"separator_1",
|
|
||||||
"collection_bottle_1",
|
|
||||||
"collection_bottle_2",
|
|
||||||
"collection_bottle_3"
|
|
||||||
],
|
|
||||||
"parent": null,
|
|
||||||
"type": "device",
|
|
||||||
"class": "workstation",
|
|
||||||
"position": {
|
|
||||||
"x": 600,
|
|
||||||
"y": 400,
|
|
||||||
"z": 0
|
|
||||||
},
|
|
||||||
"config": {
|
|
||||||
"protocol_type": [
|
|
||||||
"AddProtocol",
|
|
||||||
"TransferProtocol",
|
|
||||||
"StartStirProtocol",
|
|
||||||
"StopStirProtocol",
|
|
||||||
"StirProtocol",
|
|
||||||
"RunColumnProtocol",
|
|
||||||
"CentrifugeProtocol",
|
|
||||||
"FilterProtocol",
|
|
||||||
"CleanVesselProtocol",
|
|
||||||
"DissolveProtocol",
|
|
||||||
"FilterThroughProtocol",
|
|
||||||
"WashSolidProtocol",
|
|
||||||
"SeparateProtocol",
|
|
||||||
"EvaporateProtocol",
|
|
||||||
"HeatChillProtocol",
|
|
||||||
"HeatChillStartProtocol",
|
|
||||||
"HeatChillStopProtocol",
|
|
||||||
"EvacuateAndRefillProtocol"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"data": {}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "multiway_valve_1",
|
|
||||||
"name": "八通阀门1",
|
|
||||||
"children": [],
|
|
||||||
"parent": "ComprehensiveProtocolStation",
|
|
||||||
"type": "device",
|
|
||||||
"class": "virtual_multiway_valve",
|
|
||||||
"position": {
|
|
||||||
"x": 400,
|
|
||||||
"y": 300,
|
|
||||||
"z": 0
|
|
||||||
},
|
|
||||||
"config": {
|
|
||||||
"positions": 8,
|
|
||||||
"current_position": 1
|
|
||||||
},
|
|
||||||
"data": {
|
|
||||||
"valve_state": "Ready",
|
|
||||||
"current_position": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "multiway_valve_2",
|
|
||||||
"name": "八通阀门2",
|
|
||||||
"children": [],
|
|
||||||
"parent": "ComprehensiveProtocolStation",
|
|
||||||
"type": "device",
|
|
||||||
"class": "virtual_multiway_valve",
|
|
||||||
"position": {
|
|
||||||
"x": 800,
|
|
||||||
"y": 300,
|
|
||||||
"z": 0
|
|
||||||
},
|
|
||||||
"config": {
|
|
||||||
"positions": 8,
|
|
||||||
"current_position": 1
|
|
||||||
},
|
|
||||||
"data": {
|
|
||||||
"valve_state": "Ready",
|
|
||||||
"current_position": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "transfer_pump_1",
|
|
||||||
"name": "转移泵1",
|
|
||||||
"children": [],
|
|
||||||
"parent": "ComprehensiveProtocolStation",
|
|
||||||
"type": "device",
|
|
||||||
"class": "virtual_transfer_pump",
|
|
||||||
"position": {
|
|
||||||
"x": 350,
|
|
||||||
"y": 250,
|
|
||||||
"z": 0
|
|
||||||
},
|
|
||||||
"config": {
|
|
||||||
"max_volume": 25.0,
|
|
||||||
"transfer_rate": 10.0
|
|
||||||
},
|
|
||||||
"data": {
|
|
||||||
"status": "Idle",
|
|
||||||
"current_volume": 0.0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "transfer_pump_2",
|
|
||||||
"name": "转移泵2",
|
|
||||||
"children": [],
|
|
||||||
"parent": "ComprehensiveProtocolStation",
|
|
||||||
"type": "device",
|
|
||||||
"class": "virtual_transfer_pump",
|
|
||||||
"position": {
|
|
||||||
"x": 850,
|
|
||||||
"y": 250,
|
|
||||||
"z": 0
|
|
||||||
},
|
|
||||||
"config": {
|
|
||||||
"max_volume": 25.0,
|
|
||||||
"transfer_rate": 10.0
|
|
||||||
},
|
|
||||||
"data": {
|
|
||||||
"status": "Idle",
|
|
||||||
"current_volume": 0.0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "reagent_bottle_1",
|
|
||||||
"name": "试剂瓶1-DMF",
|
|
||||||
"children": [],
|
|
||||||
"parent": "ComprehensiveProtocolStation",
|
|
||||||
"type": "container",
|
|
||||||
"class": null,
|
|
||||||
"position": {
|
|
||||||
"x": 200,
|
|
||||||
"y": 150,
|
|
||||||
"z": 0
|
|
||||||
},
|
|
||||||
"config": {
|
|
||||||
"volume": 1000.0,
|
|
||||||
"reagent": "DMF"
|
|
||||||
},
|
|
||||||
"data": {
|
|
||||||
"current_volume": 1000.0,
|
|
||||||
"reagent_name": "DMF"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "reagent_bottle_2",
|
|
||||||
"name": "试剂瓶2-乙酸乙酯",
|
|
||||||
"children": [],
|
|
||||||
"parent": "ComprehensiveProtocolStation",
|
|
||||||
"type": "container",
|
|
||||||
"class": null,
|
|
||||||
"position": {
|
|
||||||
"x": 250,
|
|
||||||
"y": 150,
|
|
||||||
"z": 0
|
|
||||||
},
|
|
||||||
"config": {
|
|
||||||
"volume": 1000.0,
|
|
||||||
"reagent": "ethyl_acetate"
|
|
||||||
},
|
|
||||||
"data": {
|
|
||||||
"current_volume": 1000.0,
|
|
||||||
"reagent_name": "ethyl_acetate"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "reagent_bottle_3",
|
|
||||||
"name": "试剂瓶3-己烷",
|
|
||||||
"children": [],
|
|
||||||
"parent": "ComprehensiveProtocolStation",
|
|
||||||
"type": "container",
|
|
||||||
"class": null,
|
|
||||||
"position": {
|
|
||||||
"x": 300,
|
|
||||||
"y": 150,
|
|
||||||
"z": 0
|
|
||||||
},
|
|
||||||
"config": {
|
|
||||||
"volume": 1000.0,
|
|
||||||
"reagent": "hexane"
|
|
||||||
},
|
|
||||||
"data": {
|
|
||||||
"current_volume": 1000.0,
|
|
||||||
"reagent_name": "hexane"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "reagent_bottle_4",
|
|
||||||
"name": "试剂瓶4-甲醇",
|
|
||||||
"children": [],
|
|
||||||
"parent": "ComprehensiveProtocolStation",
|
|
||||||
"type": "container",
|
|
||||||
"class": null,
|
|
||||||
"position": {
|
|
||||||
"x": 900,
|
|
||||||
"y": 150,
|
|
||||||
"z": 0
|
|
||||||
},
|
|
||||||
"config": {
|
|
||||||
"volume": 1000.0,
|
|
||||||
"reagent": "methanol"
|
|
||||||
},
|
|
||||||
"data": {
|
|
||||||
"current_volume": 1000.0,
|
|
||||||
"reagent_name": "methanol"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "reagent_bottle_5",
|
|
||||||
"name": "试剂瓶5-水",
|
|
||||||
"children": [],
|
|
||||||
"parent": "ComprehensiveProtocolStation",
|
|
||||||
"type": "container",
|
|
||||||
"class": null,
|
|
||||||
"position": {
|
|
||||||
"x": 950,
|
|
||||||
"y": 150,
|
|
||||||
"z": 0
|
|
||||||
},
|
|
||||||
"config": {
|
|
||||||
"volume": 1000.0,
|
|
||||||
"reagent": "water"
|
|
||||||
},
|
|
||||||
"data": {
|
|
||||||
"current_volume": 1000.0,
|
|
||||||
"reagent_name": "water"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "centrifuge_1",
|
|
||||||
"name": "离心机",
|
|
||||||
"children": [],
|
|
||||||
"parent": "ComprehensiveProtocolStation",
|
|
||||||
"type": "device",
|
|
||||||
"class": "virtual_centrifuge",
|
|
||||||
"position": {
|
|
||||||
"x": 200,
|
|
||||||
"y": 400,
|
|
||||||
"z": 0
|
|
||||||
},
|
|
||||||
"config": {
|
|
||||||
"max_speed": 15000.0,
|
|
||||||
"max_temp": 40.0,
|
|
||||||
"min_temp": 4.0
|
|
||||||
},
|
|
||||||
"data": {
|
|
||||||
"current_speed": 0.0,
|
|
||||||
"status": "Idle"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "rotavap_1",
|
|
||||||
"name": "旋转蒸发仪",
|
|
||||||
"children": [],
|
|
||||||
"parent": "ComprehensiveProtocolStation",
|
|
||||||
"type": "device",
|
|
||||||
"class": "virtual_rotavap",
|
|
||||||
"position": {
|
|
||||||
"x": 300,
|
|
||||||
"y": 400,
|
|
||||||
"z": 0
|
|
||||||
},
|
|
||||||
"config": {
|
|
||||||
"max_temp": 180.0,
|
|
||||||
"max_rotation_speed": 280.0
|
|
||||||
},
|
|
||||||
"data": {
|
|
||||||
"status": "Idle",
|
|
||||||
"current_temp": 25.0,
|
|
||||||
"rotation_speed": 0.0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "main_reactor",
|
|
||||||
"name": "主反应器",
|
|
||||||
"children": [],
|
|
||||||
"parent": "ComprehensiveProtocolStation",
|
|
||||||
"type": "container",
|
|
||||||
"class": null,
|
|
||||||
"position": {
|
|
||||||
"x": 400,
|
|
||||||
"y": 450,
|
|
||||||
"z": 0
|
|
||||||
},
|
|
||||||
"config": {
|
|
||||||
"volume": 500.0,
|
|
||||||
"max_temp": 200.0,
|
|
||||||
"min_temp": -20.0,
|
|
||||||
"has_stirrer": true,
|
|
||||||
"has_heater": true
|
|
||||||
},
|
|
||||||
"data": {
|
|
||||||
"current_volume": 0.0,
|
|
||||||
"current_temp": 25.0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "heater_1",
|
|
||||||
"name": "加热器",
|
|
||||||
"children": [],
|
|
||||||
"parent": "ComprehensiveProtocolStation",
|
|
||||||
"type": "device",
|
|
||||||
"class": "virtual_heatchill",
|
|
||||||
"position": {
|
|
||||||
"x": 450,
|
|
||||||
"y": 450,
|
|
||||||
"z": 0
|
|
||||||
},
|
|
||||||
"config": {
|
|
||||||
"max_temp": 200.0,
|
|
||||||
"min_temp": -20.0
|
|
||||||
},
|
|
||||||
"data": {
|
|
||||||
"status": "Idle",
|
|
||||||
"current_temp": 25.0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "stirrer_1",
|
|
||||||
"name": "搅拌器1",
|
|
||||||
"children": [],
|
|
||||||
"parent": "ComprehensiveProtocolStation",
|
|
||||||
"type": "device",
|
|
||||||
"class": "virtual_stirrer",
|
|
||||||
"position": {
|
|
||||||
"x": 350,
|
|
||||||
"y": 450,
|
|
||||||
"z": 0
|
|
||||||
},
|
|
||||||
"config": {
|
|
||||||
"max_speed": 2000.0
|
|
||||||
},
|
|
||||||
"data": {
|
|
||||||
"status": "Idle",
|
|
||||||
"current_speed": 0.0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "stirrer_2",
|
|
||||||
"name": "搅拌器2",
|
|
||||||
"children": [],
|
|
||||||
"parent": "ComprehensiveProtocolStation",
|
|
||||||
"type": "device",
|
|
||||||
"class": "virtual_stirrer",
|
|
||||||
"position": {
|
|
||||||
"x": 351,
|
|
||||||
"y": 451,
|
|
||||||
"z": 0
|
|
||||||
},
|
|
||||||
"config": {
|
|
||||||
"max_speed": 2000.0
|
|
||||||
},
|
|
||||||
"data": {
|
|
||||||
"status": "Idle",
|
|
||||||
"current_speed": 0.0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "waste_bottle_1",
|
|
||||||
"name": "废液瓶1",
|
|
||||||
"children": [],
|
|
||||||
"parent": "ComprehensiveProtocolStation",
|
|
||||||
"type": "container",
|
|
||||||
"class": null,
|
|
||||||
"position": {
|
|
||||||
"x": 500,
|
|
||||||
"y": 400,
|
|
||||||
"z": 0
|
|
||||||
},
|
|
||||||
"config": {
|
|
||||||
"volume": 2000.0
|
|
||||||
},
|
|
||||||
"data": {
|
|
||||||
"current_volume": 0.0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "waste_bottle_2",
|
|
||||||
"name": "废液瓶2",
|
|
||||||
"children": [],
|
|
||||||
"parent": "ComprehensiveProtocolStation",
|
|
||||||
"type": "container",
|
|
||||||
"class": null,
|
|
||||||
"position": {
|
|
||||||
"x": 1100,
|
|
||||||
"y": 500,
|
|
||||||
"z": 0
|
|
||||||
},
|
|
||||||
"config": {
|
|
||||||
"volume": 2000.0
|
|
||||||
},
|
|
||||||
"data": {
|
|
||||||
"current_volume": 0.0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "solenoid_valve_1",
|
|
||||||
"name": "电磁阀1",
|
|
||||||
"children": [],
|
|
||||||
"parent": "ComprehensiveProtocolStation",
|
|
||||||
"type": "device",
|
|
||||||
"class": "virtual_solenoid_valve",
|
|
||||||
"position": {
|
|
||||||
"x": 700,
|
|
||||||
"y": 200,
|
|
||||||
"z": 0
|
|
||||||
},
|
|
||||||
"config": {
|
|
||||||
"voltage": 12.0,
|
|
||||||
"response_time": 0.1
|
|
||||||
},
|
|
||||||
"data": {
|
|
||||||
"valve_state": "Closed",
|
|
||||||
"is_open": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "solenoid_valve_2",
|
|
||||||
"name": "电磁阀2",
|
|
||||||
"children": [],
|
|
||||||
"parent": "ComprehensiveProtocolStation",
|
|
||||||
"type": "device",
|
|
||||||
"class": "virtual_solenoid_valve",
|
|
||||||
"position": {
|
|
||||||
"x": 700,
|
|
||||||
"y": 150,
|
|
||||||
"z": 0
|
|
||||||
},
|
|
||||||
"config": {
|
|
||||||
"voltage": 12.0,
|
|
||||||
"response_time": 0.1
|
|
||||||
},
|
|
||||||
"data": {
|
|
||||||
"valve_state": "Closed",
|
|
||||||
"is_open": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "vacuum_pump_1",
|
|
||||||
"name": "真空泵",
|
|
||||||
"children": [],
|
|
||||||
"parent": "ComprehensiveProtocolStation",
|
|
||||||
"type": "device",
|
|
||||||
"class": "virtual_vacuum_pump",
|
|
||||||
"position": {
|
|
||||||
"x": 650,
|
|
||||||
"y": 200,
|
|
||||||
"z": 0
|
|
||||||
},
|
|
||||||
"config": {
|
|
||||||
"max_vacuum": 0.1,
|
|
||||||
"pump_rate": 50.0
|
|
||||||
},
|
|
||||||
"data": {
|
|
||||||
"status": "Off",
|
|
||||||
"current_vacuum": 1.0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "gas_source_1",
|
|
||||||
"name": "气源",
|
|
||||||
"children": [],
|
|
||||||
"parent": "ComprehensiveProtocolStation",
|
|
||||||
"type": "device",
|
|
||||||
"class": "gas_source.mock",
|
|
||||||
"position": {
|
|
||||||
"x": 650,
|
|
||||||
"y": 150,
|
|
||||||
"z": 0
|
|
||||||
},
|
|
||||||
"config": {
|
|
||||||
"gas_type": "nitrogen",
|
|
||||||
"max_pressure": 5.0
|
|
||||||
},
|
|
||||||
"data": {
|
|
||||||
"status": "Off",
|
|
||||||
"current_pressure": 0.0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "filter_1",
|
|
||||||
"name": "过滤器",
|
|
||||||
"children": [],
|
|
||||||
"parent": "ComprehensiveProtocolStation",
|
|
||||||
"type": "device",
|
|
||||||
"class": "virtual_filter",
|
|
||||||
"position": {
|
|
||||||
"x": 900,
|
|
||||||
"y": 400,
|
|
||||||
"z": 0
|
|
||||||
},
|
|
||||||
"config": {
|
|
||||||
"filter_type": "membrane",
|
|
||||||
"max_pressure": 5.0
|
|
||||||
},
|
|
||||||
"data": {
|
|
||||||
"status": "Ready",
|
|
||||||
"pressure": 0.0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "column_1",
|
|
||||||
"name": "洗脱柱",
|
|
||||||
"children": [],
|
|
||||||
"parent": "ComprehensiveProtocolStation",
|
|
||||||
"type": "device",
|
|
||||||
"class": "virtual_column",
|
|
||||||
"position": {
|
|
||||||
"x": 950,
|
|
||||||
"y": 400,
|
|
||||||
"z": 0
|
|
||||||
},
|
|
||||||
"config": {
|
|
||||||
"column_type": "silica_gel",
|
|
||||||
"length": 30.0,
|
|
||||||
"diameter": 2.5
|
|
||||||
},
|
|
||||||
"data": {
|
|
||||||
"status": "Ready",
|
|
||||||
"loaded": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "separator_1",
|
|
||||||
"name": "分液器",
|
|
||||||
"children": [],
|
|
||||||
"parent": "ComprehensiveProtocolStation",
|
|
||||||
"type": "device",
|
|
||||||
"class": "virtual_separator",
|
|
||||||
"position": {
|
|
||||||
"x": 1000,
|
|
||||||
"y": 450,
|
|
||||||
"z": 0
|
|
||||||
},
|
|
||||||
"config": {
|
|
||||||
"volume": 250.0,
|
|
||||||
"has_phases": true
|
|
||||||
},
|
|
||||||
"data": {
|
|
||||||
"status": "Ready",
|
|
||||||
"phase_separation": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "collection_bottle_1",
|
|
||||||
"name": "接收瓶1",
|
|
||||||
"children": [],
|
|
||||||
"parent": "ComprehensiveProtocolStation",
|
|
||||||
"type": "container",
|
|
||||||
"class": null,
|
|
||||||
"position": {
|
|
||||||
"x": 900,
|
|
||||||
"y": 500,
|
|
||||||
"z": 0
|
|
||||||
},
|
|
||||||
"config": {
|
|
||||||
"volume": 250.0
|
|
||||||
},
|
|
||||||
"data": {
|
|
||||||
"current_volume": 0.0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "collection_bottle_2",
|
|
||||||
"name": "接收瓶2",
|
|
||||||
"children": [],
|
|
||||||
"parent": "ComprehensiveProtocolStation",
|
|
||||||
"type": "container",
|
|
||||||
"class": null,
|
|
||||||
"position": {
|
|
||||||
"x": 950,
|
|
||||||
"y": 500,
|
|
||||||
"z": 0
|
|
||||||
},
|
|
||||||
"config": {
|
|
||||||
"volume": 250.0
|
|
||||||
},
|
|
||||||
"data": {
|
|
||||||
"current_volume": 0.0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "collection_bottle_3",
|
|
||||||
"name": "接收瓶3",
|
|
||||||
"children": [],
|
|
||||||
"parent": "ComprehensiveProtocolStation",
|
|
||||||
"type": "container",
|
|
||||||
"class": null,
|
|
||||||
"position": {
|
|
||||||
"x": 1050,
|
|
||||||
"y": 500,
|
|
||||||
"z": 0
|
|
||||||
},
|
|
||||||
"config": {
|
|
||||||
"volume": 250.0
|
|
||||||
},
|
|
||||||
"data": {
|
|
||||||
"current_volume": 0.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"links": [
|
|
||||||
{
|
|
||||||
"id": "link_valve1_pump1",
|
|
||||||
"source": "multiway_valve_1",
|
|
||||||
"target": "transfer_pump_1",
|
|
||||||
"source_port": "port_0",
|
|
||||||
"target_port": "inlet",
|
|
||||||
"type": "fluid",
|
|
||||||
"port": {
|
|
||||||
"multiway_valve_1": "port_0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "link_valve1_reagent1",
|
|
||||||
"source": "multiway_valve_1",
|
|
||||||
"target": "reagent_bottle_1",
|
|
||||||
"source_port": "port_1",
|
|
||||||
"target_port": "outlet",
|
|
||||||
"type": "fluid",
|
|
||||||
"port": {
|
|
||||||
"multiway_valve_1": "port_1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "link_valve1_reagent2",
|
|
||||||
"source": "multiway_valve_1",
|
|
||||||
"target": "reagent_bottle_2",
|
|
||||||
"source_port": "port_2",
|
|
||||||
"target_port": "outlet",
|
|
||||||
"type": "fluid",
|
|
||||||
"port": {
|
|
||||||
"multiway_valve_1": "port_2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "link_valve1_reagent3",
|
|
||||||
"source": "multiway_valve_1",
|
|
||||||
"target": "reagent_bottle_3",
|
|
||||||
"source_port": "port_3",
|
|
||||||
"target_port": "outlet",
|
|
||||||
"type": "fluid",
|
|
||||||
"port": {
|
|
||||||
"multiway_valve_1": "port_3"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "link_valve1_centrifuge",
|
|
||||||
"source": "multiway_valve_1",
|
|
||||||
"target": "centrifuge_1",
|
|
||||||
"source_port": "port_4",
|
|
||||||
"target_port": "inlet",
|
|
||||||
"type": "fluid",
|
|
||||||
"port": {
|
|
||||||
"multiway_valve_1": "port_4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "link_valve1_rotavap",
|
|
||||||
"source": "multiway_valve_1",
|
|
||||||
"target": "rotavap_1",
|
|
||||||
"source_port": "port_5",
|
|
||||||
"target_port": "inlet",
|
|
||||||
"type": "fluid",
|
|
||||||
"port": {
|
|
||||||
"multiway_valve_1": "port_5"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "link_valve1_reactor",
|
|
||||||
"source": "multiway_valve_1",
|
|
||||||
"target": "main_reactor",
|
|
||||||
"source_port": "port_6",
|
|
||||||
"target_port": "inlet",
|
|
||||||
"type": "fluid",
|
|
||||||
"port": {
|
|
||||||
"multiway_valve_1": "port_6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "link_valve1_waste1",
|
|
||||||
"source": "multiway_valve_1",
|
|
||||||
"target": "waste_bottle_1",
|
|
||||||
"source_port": "port_7",
|
|
||||||
"target_port": "inlet",
|
|
||||||
"type": "fluid",
|
|
||||||
"port": {
|
|
||||||
"multiway_valve_1": "port_7"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "link_valve1_valve2",
|
|
||||||
"source": "multiway_valve_1",
|
|
||||||
"target": "multiway_valve_2",
|
|
||||||
"source_port": "port_8",
|
|
||||||
"target_port": "port_1",
|
|
||||||
"type": "fluid",
|
|
||||||
"port": {
|
|
||||||
"multiway_valve_1": "port_8",
|
|
||||||
"multiway_valve_2": "port_1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "link_valve2_pump2",
|
|
||||||
"source": "multiway_valve_2",
|
|
||||||
"target": "transfer_pump_2",
|
|
||||||
"source_port": "port_0",
|
|
||||||
"target_port": "inlet",
|
|
||||||
"type": "fluid",
|
|
||||||
"port": {
|
|
||||||
"multiway_valve_2": "port_0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "link_valve2_solenoid1",
|
|
||||||
"source": "multiway_valve_2",
|
|
||||||
"target": "solenoid_valve_1",
|
|
||||||
"source_port": "port_2",
|
|
||||||
"target_port": "inlet",
|
|
||||||
"type": "fluid",
|
|
||||||
"port": {
|
|
||||||
"multiway_valve_2": "port_2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "link_solenoid1_vacuum",
|
|
||||||
"source": "solenoid_valve_1",
|
|
||||||
"target": "vacuum_pump_1",
|
|
||||||
"source_port": "outlet",
|
|
||||||
"target_port": "inlet",
|
|
||||||
"type": "fluid"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "link_valve2_solenoid2",
|
|
||||||
"source": "multiway_valve_2",
|
|
||||||
"target": "solenoid_valve_2",
|
|
||||||
"source_port": "port_3",
|
|
||||||
"target_port": "inlet",
|
|
||||||
"type": "fluid",
|
|
||||||
"port": {
|
|
||||||
"multiway_valve_2": "port_3"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "link_solenoid2_gas",
|
|
||||||
"source": "solenoid_valve_2",
|
|
||||||
"target": "gas_source_1",
|
|
||||||
"source_port": "outlet",
|
|
||||||
"target_port": "outlet",
|
|
||||||
"type": "fluid"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "link_valve2_filter",
|
|
||||||
"source": "multiway_valve_2",
|
|
||||||
"target": "filter_1",
|
|
||||||
"source_port": "port_4",
|
|
||||||
"target_port": "inlet",
|
|
||||||
"type": "fluid",
|
|
||||||
"port": {
|
|
||||||
"multiway_valve_2": "port_4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "link_filter_collection1",
|
|
||||||
"source": "filter_1",
|
|
||||||
"target": "collection_bottle_1",
|
|
||||||
"source_port": "filtrate_outlet",
|
|
||||||
"target_port": "inlet",
|
|
||||||
"type": "fluid"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "link_valve2_column",
|
|
||||||
"source": "multiway_valve_2",
|
|
||||||
"target": "column_1",
|
|
||||||
"source_port": "port_5",
|
|
||||||
"target_port": "inlet",
|
|
||||||
"type": "fluid",
|
|
||||||
"port": {
|
|
||||||
"multiway_valve_2": "port_5"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "link_column_collection2",
|
|
||||||
"source": "column_1",
|
|
||||||
"target": "collection_bottle_2",
|
|
||||||
"source_port": "outlet",
|
|
||||||
"target_port": "inlet",
|
|
||||||
"type": "fluid"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "link_valve2_separator",
|
|
||||||
"source": "multiway_valve_2",
|
|
||||||
"target": "separator_1",
|
|
||||||
"source_port": "port_6",
|
|
||||||
"target_port": "inlet",
|
|
||||||
"type": "fluid",
|
|
||||||
"port": {
|
|
||||||
"multiway_valve_2": "port_6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "link_separator_collection3",
|
|
||||||
"source": "separator_1",
|
|
||||||
"target": "collection_bottle_3",
|
|
||||||
"source_port": "top_outlet",
|
|
||||||
"target_port": "inlet",
|
|
||||||
"type": "fluid"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "link_separator_stirrer_2",
|
|
||||||
"source": "separator_1",
|
|
||||||
"target": "stirrer_2",
|
|
||||||
"source_port": "top_outlet",
|
|
||||||
"target_port": "inlet",
|
|
||||||
"type": "fluid"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "link_separator_waste2",
|
|
||||||
"source": "separator_1",
|
|
||||||
"target": "waste_bottle_2",
|
|
||||||
"source_port": "bottom_outlet",
|
|
||||||
"target_port": "inlet",
|
|
||||||
"type": "fluid"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "link_valve2_reagent4",
|
|
||||||
"source": "multiway_valve_2",
|
|
||||||
"target": "reagent_bottle_4",
|
|
||||||
"source_port": "port_7",
|
|
||||||
"target_port": "outlet",
|
|
||||||
"type": "fluid",
|
|
||||||
"port": {
|
|
||||||
"multiway_valve_2": "port_7"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "link_valve2_reagent5",
|
|
||||||
"source": "multiway_valve_2",
|
|
||||||
"target": "reagent_bottle_5",
|
|
||||||
"source_port": "port_8",
|
|
||||||
"target_port": "outlet",
|
|
||||||
"type": "fluid",
|
|
||||||
"port": {
|
|
||||||
"multiway_valve_2": "port_8"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "mech_stirrer_reactor",
|
|
||||||
"source": "stirrer_1",
|
|
||||||
"target": "main_reactor",
|
|
||||||
"type": "fluid"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "thermal_heater_reactor",
|
|
||||||
"source": "heater_1",
|
|
||||||
"target": "main_reactor",
|
|
||||||
"type": "fluid"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -4,83 +4,58 @@
|
|||||||
"id": "AddTestStation",
|
"id": "AddTestStation",
|
||||||
"name": "添加试剂测试工作站",
|
"name": "添加试剂测试工作站",
|
||||||
"children": [
|
"children": [
|
||||||
"transfer_pump",
|
"pump_add",
|
||||||
"multiway_valve",
|
"flask_1",
|
||||||
"stirrer",
|
"flask_2",
|
||||||
"flask_reagent1",
|
"flask_3",
|
||||||
"flask_reagent2",
|
"flask_4",
|
||||||
"flask_reagent3",
|
|
||||||
"flask_reagent4",
|
|
||||||
"reactor",
|
"reactor",
|
||||||
"flask_waste",
|
"stirrer",
|
||||||
"flask_rinsing",
|
"flask_air"
|
||||||
"flask_buffer"
|
|
||||||
],
|
],
|
||||||
"parent": null,
|
"parent": null,
|
||||||
"type": "device",
|
"type": "device",
|
||||||
"class": "workstation",
|
"class": "workstation",
|
||||||
"position": {
|
"position": {
|
||||||
"x": 620,
|
"x": 620.6111111111111,
|
||||||
"y": 171,
|
"y": 171,
|
||||||
"z": 0
|
"z": 0
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"protocol_type": ["AddProtocol", "TransferProtocol", "StartStirProtocol", "StopStirProtocol"]
|
"protocol_type": ["AddProtocol", "PumpTransferProtocol", "CleanProtocol"]
|
||||||
},
|
},
|
||||||
"data": {}
|
"data": {}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "transfer_pump",
|
"id": "pump_add",
|
||||||
"name": "注射器泵",
|
"name": "pump_add",
|
||||||
"children": [],
|
"children": [],
|
||||||
"parent": "AddTestStation",
|
"parent": "AddTestStation",
|
||||||
"type": "device",
|
"type": "device",
|
||||||
"class": "virtual_transfer_pump",
|
"class": "virtual_pump",
|
||||||
"position": {
|
"position": {
|
||||||
"x": 520,
|
"x": 520.6111111111111,
|
||||||
"y": 300,
|
"y": 300,
|
||||||
"z": 0
|
"z": 0
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"port": "VIRTUAL",
|
"port": "VIRTUAL",
|
||||||
"max_volume": 50.0,
|
"max_volume": 25.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": "搅拌器",
|
"name": "stirrer",
|
||||||
"children": [],
|
"children": [],
|
||||||
"parent": "AddTestStation",
|
"parent": "AddTestStation",
|
||||||
"type": "device",
|
"type": "device",
|
||||||
"class": "virtual_stirrer",
|
"class": "virtual_stirrer",
|
||||||
"position": {
|
"position": {
|
||||||
"x": 720,
|
"x": 698.1111111111111,
|
||||||
"y": 450,
|
"y": 478,
|
||||||
"z": 0
|
"z": 0
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
@@ -93,115 +68,15 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "flask_reagent1",
|
"id": "flask_1",
|
||||||
"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": 400,
|
"y": 428,
|
||||||
"z": 0
|
|
||||||
},
|
|
||||||
"config": {
|
|
||||||
"max_volume": 1000.0
|
|
||||||
},
|
|
||||||
"data": {
|
|
||||||
"liquid": [
|
|
||||||
{
|
|
||||||
"name": "甲醇",
|
|
||||||
"volume": 800.0,
|
|
||||||
"concentration": "99.9%"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "flask_reagent2",
|
|
||||||
"name": "试剂瓶2 (乙醇)",
|
|
||||||
"children": [],
|
|
||||||
"parent": "AddTestStation",
|
|
||||||
"type": "container",
|
|
||||||
"class": null,
|
|
||||||
"position": {
|
|
||||||
"x": 180,
|
|
||||||
"y": 400,
|
|
||||||
"z": 0
|
|
||||||
},
|
|
||||||
"config": {
|
|
||||||
"max_volume": 1000.0
|
|
||||||
},
|
|
||||||
"data": {
|
|
||||||
"liquid": [
|
|
||||||
{
|
|
||||||
"name": "乙醇",
|
|
||||||
"volume": 750.0,
|
|
||||||
"concentration": "95%"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "flask_reagent3",
|
|
||||||
"name": "试剂瓶3 (丙酮)",
|
|
||||||
"children": [],
|
|
||||||
"parent": "AddTestStation",
|
|
||||||
"type": "container",
|
|
||||||
"class": null,
|
|
||||||
"position": {
|
|
||||||
"x": 260,
|
|
||||||
"y": 400,
|
|
||||||
"z": 0
|
|
||||||
},
|
|
||||||
"config": {
|
|
||||||
"max_volume": 1000.0
|
|
||||||
},
|
|
||||||
"data": {
|
|
||||||
"liquid": [
|
|
||||||
{
|
|
||||||
"name": "丙酮",
|
|
||||||
"volume": 900.0,
|
|
||||||
"concentration": "99.5%"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "flask_reagent4",
|
|
||||||
"name": "试剂瓶4 (二氯甲烷)",
|
|
||||||
"children": [],
|
|
||||||
"parent": "AddTestStation",
|
|
||||||
"type": "container",
|
|
||||||
"class": null,
|
|
||||||
"position": {
|
|
||||||
"x": 340,
|
|
||||||
"y": 400,
|
|
||||||
"z": 0
|
|
||||||
},
|
|
||||||
"config": {
|
|
||||||
"max_volume": 1000.0
|
|
||||||
},
|
|
||||||
"data": {
|
|
||||||
"liquid": [
|
|
||||||
{
|
|
||||||
"name": "二氯甲烷",
|
|
||||||
"volume": 850.0,
|
|
||||||
"concentration": "99.8%"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "reactor",
|
|
||||||
"name": "反应器",
|
|
||||||
"children": [],
|
|
||||||
"parent": "AddTestStation",
|
|
||||||
"type": "container",
|
|
||||||
"class": null,
|
|
||||||
"position": {
|
|
||||||
"x": 720,
|
|
||||||
"y": 400,
|
|
||||||
"z": 0
|
"z": 0
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
@@ -212,165 +87,164 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "flask_waste",
|
"id": "flask_2",
|
||||||
"name": "废液瓶",
|
"name": "通用试剂瓶2",
|
||||||
"children": [],
|
"children": [],
|
||||||
"parent": "AddTestStation",
|
"parent": "AddTestStation",
|
||||||
"type": "container",
|
"type": "container",
|
||||||
"class": null,
|
"class": null,
|
||||||
"position": {
|
"position": {
|
||||||
"x": 850,
|
"x": 250,
|
||||||
"y": 400,
|
"y": 428,
|
||||||
"z": 0
|
"z": 0
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"max_volume": 3000.0
|
"max_volume": 2000.0
|
||||||
},
|
},
|
||||||
"data": {
|
"data": {
|
||||||
"liquid": []
|
"liquid": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "flask_rinsing",
|
"id": "flask_3",
|
||||||
"name": "冲洗液瓶",
|
"name": "通用试剂瓶3",
|
||||||
"children": [],
|
"children": [],
|
||||||
"parent": "AddTestStation",
|
"parent": "AddTestStation",
|
||||||
"type": "container",
|
"type": "container",
|
||||||
"class": null,
|
"class": null,
|
||||||
"position": {
|
"position": {
|
||||||
"x": 950,
|
"x": 400,
|
||||||
|
"y": 428,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 2000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "flask_4",
|
||||||
|
"name": "通用试剂瓶4",
|
||||||
|
"children": [],
|
||||||
|
"parent": "AddTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 550,
|
||||||
|
"y": 428,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"max_volume": 2000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"liquid": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reactor",
|
||||||
|
"name": "reactor",
|
||||||
|
"children": [],
|
||||||
|
"parent": "AddTestStation",
|
||||||
|
"type": "container",
|
||||||
|
"class": null,
|
||||||
|
"position": {
|
||||||
|
"x": 698.1111111111111,
|
||||||
|
"y": 428,
|
||||||
|
"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,
|
"y": 300,
|
||||||
"z": 0
|
"z": 0
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"max_volume": 1000.0
|
"max_volume": 2000.0
|
||||||
},
|
},
|
||||||
"data": {
|
"data": {
|
||||||
"liquid": [
|
"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": "transfer_pump",
|
|
||||||
"target": "multiway_valve",
|
|
||||||
"type": "physical",
|
|
||||||
"port": {
|
|
||||||
"transfer_pump": "syringe-port",
|
|
||||||
"multiway_valve": "multiway-valve-inlet"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"source": "multiway_valve",
|
|
||||||
"target": "flask_reagent1",
|
|
||||||
"type": "physical",
|
|
||||||
"port": {
|
|
||||||
"multiway_valve": "multiway-valve-port-1",
|
|
||||||
"flask_reagent1": "top"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"source": "multiway_valve",
|
|
||||||
"target": "flask_reagent2",
|
|
||||||
"type": "physical",
|
|
||||||
"port": {
|
|
||||||
"multiway_valve": "multiway-valve-port-2",
|
|
||||||
"flask_reagent2": "top"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"source": "multiway_valve",
|
|
||||||
"target": "flask_reagent3",
|
|
||||||
"type": "physical",
|
|
||||||
"port": {
|
|
||||||
"multiway_valve": "multiway-valve-port-3",
|
|
||||||
"flask_reagent3": "top"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"source": "multiway_valve",
|
|
||||||
"target": "flask_reagent4",
|
|
||||||
"type": "physical",
|
|
||||||
"port": {
|
|
||||||
"multiway_valve": "multiway-valve-port-4",
|
|
||||||
"flask_reagent4": "top"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"source": "multiway_valve",
|
|
||||||
"target": "reactor",
|
|
||||||
"type": "physical",
|
|
||||||
"port": {
|
|
||||||
"multiway_valve": "multiway-valve-port-5",
|
|
||||||
"reactor": "top"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"source": "multiway_valve",
|
|
||||||
"target": "flask_waste",
|
|
||||||
"type": "physical",
|
|
||||||
"port": {
|
|
||||||
"multiway_valve": "multiway-valve-port-6",
|
|
||||||
"flask_waste": "top"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"source": "multiway_valve",
|
|
||||||
"target": "flask_rinsing",
|
|
||||||
"type": "physical",
|
|
||||||
"port": {
|
|
||||||
"multiway_valve": "multiway-valve-port-7",
|
|
||||||
"flask_rinsing": "top"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"source": "multiway_valve",
|
|
||||||
"target": "flask_buffer",
|
|
||||||
"type": "physical",
|
|
||||||
"port": {
|
|
||||||
"multiway_valve": "multiway-valve-port-8",
|
|
||||||
"flask_buffer": "top"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"source": "stirrer",
|
"source": "stirrer",
|
||||||
"target": "reactor",
|
"target": "reactor",
|
||||||
"type": "physical",
|
"type": "physical",
|
||||||
"port": {
|
"port": {
|
||||||
"stirrer": "stirrer-vessel",
|
"stirrer": "top",
|
||||||
"reactor": "bottom"
|
"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"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "pump_add",
|
||||||
|
"target": "flask_air",
|
||||||
|
"type": "physical",
|
||||||
|
"port": {
|
||||||
|
"pump_add": "inlet",
|
||||||
|
"flask_air": "top"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -15,107 +15,37 @@ def generate_add_protocol(
|
|||||||
purpose: str
|
purpose: str
|
||||||
) -> List[Dict[str, Any]]:
|
) -> List[Dict[str, Any]]:
|
||||||
"""
|
"""
|
||||||
生成添加试剂的协议序列
|
生成添加试剂的协议序列 - 严格按照 Add.action
|
||||||
|
|
||||||
流程:
|
|
||||||
1. 找到包含目标试剂的试剂瓶
|
|
||||||
2. 配置八通阀门到试剂瓶位置
|
|
||||||
3. 使用注射器泵吸取试剂
|
|
||||||
4. 配置八通阀门到反应器位置
|
|
||||||
5. 使用注射器泵推送试剂到反应器
|
|
||||||
6. 如果需要,启动搅拌
|
|
||||||
"""
|
"""
|
||||||
action_sequence = []
|
action_sequence = []
|
||||||
|
|
||||||
# 验证目标容器存在
|
|
||||||
if vessel not in G.nodes():
|
|
||||||
raise ValueError(f"目标容器 {vessel} 不存在")
|
|
||||||
|
|
||||||
# 如果指定了体积,执行液体转移
|
# 如果指定了体积,执行液体转移
|
||||||
if volume > 0:
|
if volume > 0:
|
||||||
# 1. 查找注射器泵 (transfer pump)
|
# 查找可用的试剂瓶
|
||||||
transfer_pump_nodes = [node for node in G.nodes()
|
|
||||||
if G.nodes[node].get('class') == 'virtual_transfer_pump']
|
|
||||||
|
|
||||||
if not transfer_pump_nodes:
|
|
||||||
raise ValueError("没有找到可用的注射器泵 (virtual_transfer_pump)")
|
|
||||||
|
|
||||||
transfer_pump_id = transfer_pump_nodes[0]
|
|
||||||
|
|
||||||
# 2. 查找八通阀门
|
|
||||||
multiway_valve_nodes = [node for node in G.nodes()
|
|
||||||
if G.nodes[node].get('class') == 'virtual_multiway_valve']
|
|
||||||
|
|
||||||
if not multiway_valve_nodes:
|
|
||||||
raise ValueError("没有找到可用的八通阀门 (virtual_multiway_valve)")
|
|
||||||
|
|
||||||
valve_id = multiway_valve_nodes[0]
|
|
||||||
|
|
||||||
# 3. 查找包含指定试剂的试剂瓶
|
|
||||||
reagent_vessel = None
|
|
||||||
available_flasks = [node for node in G.nodes()
|
available_flasks = [node for node in G.nodes()
|
||||||
if node.startswith('flask_')
|
if node.startswith('flask_')
|
||||||
and G.nodes[node].get('type') == 'container']
|
and G.nodes[node].get('type') == 'container']
|
||||||
|
|
||||||
# 简化:使用第一个可用的试剂瓶,实际应该根据试剂名称匹配
|
if not available_flasks:
|
||||||
if available_flasks:
|
|
||||||
reagent_vessel = available_flasks[0]
|
|
||||||
else:
|
|
||||||
raise ValueError("没有找到可用的试剂容器")
|
raise ValueError("没有找到可用的试剂容器")
|
||||||
|
|
||||||
# 4. 获取试剂瓶和反应器对应的阀门位置
|
reagent_vessel = available_flasks[0]
|
||||||
# 这需要根据实际连接图来确定,这里假设:
|
|
||||||
reagent_valve_position = 1 # 试剂瓶连接到阀门位置1
|
|
||||||
reactor_valve_position = 2 # 反应器连接到阀门位置2
|
|
||||||
|
|
||||||
# 5. 执行添加操作序列
|
# 查找泵设备
|
||||||
|
pump_nodes = [node for node in G.nodes()
|
||||||
|
if G.nodes[node].get('class') == 'virtual_pump']
|
||||||
|
|
||||||
# 5.1 设置阀门到试剂瓶位置
|
if pump_nodes:
|
||||||
|
pump_id = pump_nodes[0]
|
||||||
action_sequence.append({
|
action_sequence.append({
|
||||||
"device_id": valve_id,
|
"device_id": pump_id,
|
||||||
"action_name": "set_position",
|
|
||||||
"action_kwargs": {
|
|
||||||
"position": reagent_valve_position
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
# 5.2 使用注射器泵从试剂瓶吸取液体
|
|
||||||
action_sequence.append({
|
|
||||||
"device_id": transfer_pump_id,
|
|
||||||
"action_name": "transfer",
|
"action_name": "transfer",
|
||||||
"action_kwargs": {
|
"action_kwargs": {
|
||||||
"from_vessel": reagent_vessel,
|
"from_vessel": reagent_vessel,
|
||||||
"to_vessel": transfer_pump_id, # 吸入到注射器
|
|
||||||
"volume": volume,
|
|
||||||
"amount": amount,
|
|
||||||
"time": time / 2, # 吸取时间为总时间的一半
|
|
||||||
"viscous": viscous,
|
|
||||||
"rinsing_solvent": "",
|
|
||||||
"rinsing_volume": 0.0,
|
|
||||||
"rinsing_repeats": 0,
|
|
||||||
"solid": False
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
# 5.3 设置阀门到反应器位置
|
|
||||||
action_sequence.append({
|
|
||||||
"device_id": valve_id,
|
|
||||||
"action_name": "set_position",
|
|
||||||
"action_kwargs": {
|
|
||||||
"position": reactor_valve_position
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
# 5.4 使用注射器泵将液体推送到反应器
|
|
||||||
action_sequence.append({
|
|
||||||
"device_id": transfer_pump_id,
|
|
||||||
"action_name": "transfer",
|
|
||||||
"action_kwargs": {
|
|
||||||
"from_vessel": transfer_pump_id, # 从注射器推出
|
|
||||||
"to_vessel": vessel,
|
"to_vessel": vessel,
|
||||||
"volume": volume,
|
"volume": volume,
|
||||||
"amount": amount,
|
"amount": amount,
|
||||||
"time": time / 2, # 推送时间为总时间的一半
|
"time": time,
|
||||||
"viscous": viscous,
|
"viscous": viscous,
|
||||||
"rinsing_solvent": "",
|
"rinsing_solvent": "",
|
||||||
"rinsing_volume": 0.0,
|
"rinsing_volume": 0.0,
|
||||||
@@ -124,7 +54,7 @@ def generate_add_protocol(
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
# 6. 如果需要搅拌,启动搅拌器
|
# 如果需要搅拌,使用 StartStir 而不是 Stir
|
||||||
if stir:
|
if stir:
|
||||||
stirrer_nodes = [node for node in G.nodes()
|
stirrer_nodes = [node for node in G.nodes()
|
||||||
if G.nodes[node].get('class') == 'virtual_stirrer']
|
if G.nodes[node].get('class') == 'virtual_stirrer']
|
||||||
@@ -133,155 +63,11 @@ def generate_add_protocol(
|
|||||||
stirrer_id = stirrer_nodes[0]
|
stirrer_id = stirrer_nodes[0]
|
||||||
action_sequence.append({
|
action_sequence.append({
|
||||||
"device_id": stirrer_id,
|
"device_id": stirrer_id,
|
||||||
"action_name": "start_stir",
|
"action_name": "start_stir", # 使用 start_stir 而不是 stir
|
||||||
"action_kwargs": {
|
"action_kwargs": {
|
||||||
"vessel": vessel,
|
"vessel": vessel,
|
||||||
"stir_speed": stir_speed,
|
"stir_speed": stir_speed,
|
||||||
"purpose": f"添加 {reagent} 后搅拌混合"
|
"purpose": f"添加 {reagent} 后搅拌"
|
||||||
}
|
|
||||||
})
|
|
||||||
else:
|
|
||||||
print("警告:需要搅拌但未找到搅拌设备")
|
|
||||||
|
|
||||||
return action_sequence
|
|
||||||
|
|
||||||
|
|
||||||
def find_valve_position_for_vessel(G: nx.DiGraph, valve_id: str, vessel_id: str) -> int:
|
|
||||||
"""
|
|
||||||
根据连接图找到容器对应的阀门位置
|
|
||||||
|
|
||||||
Args:
|
|
||||||
G: 网络图
|
|
||||||
valve_id: 阀门设备ID
|
|
||||||
vessel_id: 容器ID
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
int: 阀门位置编号 (1-8)
|
|
||||||
"""
|
|
||||||
# 查找阀门到容器的连接
|
|
||||||
edges = G.edges(data=True)
|
|
||||||
|
|
||||||
for source, target, data in edges:
|
|
||||||
if source == valve_id and target == vessel_id:
|
|
||||||
# 从连接数据中提取端口信息
|
|
||||||
port_info = data.get('port', {})
|
|
||||||
valve_port = port_info.get(valve_id, '')
|
|
||||||
|
|
||||||
# 解析端口名称获取位置编号
|
|
||||||
if valve_port.startswith('multiway-valve-port-'):
|
|
||||||
position = valve_port.split('-')[-1]
|
|
||||||
return int(position)
|
|
||||||
|
|
||||||
# 默认返回位置1
|
|
||||||
return 1
|
|
||||||
|
|
||||||
|
|
||||||
def generate_add_with_autodiscovery(
|
|
||||||
G: nx.DiGraph,
|
|
||||||
vessel: str,
|
|
||||||
reagent: str,
|
|
||||||
volume: float,
|
|
||||||
**kwargs
|
|
||||||
) -> List[Dict[str, Any]]:
|
|
||||||
"""
|
|
||||||
智能添加协议生成器 - 自动发现设备连接关系
|
|
||||||
"""
|
|
||||||
action_sequence = []
|
|
||||||
|
|
||||||
# 查找必需的设备
|
|
||||||
devices = {
|
|
||||||
'transfer_pump': None,
|
|
||||||
'multiway_valve': None,
|
|
||||||
'stirrer': None
|
|
||||||
}
|
|
||||||
|
|
||||||
for node in G.nodes():
|
|
||||||
node_class = G.nodes[node].get('class')
|
|
||||||
if node_class == 'virtual_transfer_pump':
|
|
||||||
devices['transfer_pump'] = node
|
|
||||||
elif node_class == 'virtual_multiway_valve':
|
|
||||||
devices['multiway_valve'] = node
|
|
||||||
elif node_class == 'virtual_stirrer':
|
|
||||||
devices['stirrer'] = node
|
|
||||||
|
|
||||||
# 验证必需设备
|
|
||||||
if not devices['transfer_pump']:
|
|
||||||
raise ValueError("缺少注射器泵设备")
|
|
||||||
if not devices['multiway_valve']:
|
|
||||||
raise ValueError("缺少八通阀门设备")
|
|
||||||
|
|
||||||
# 查找试剂容器
|
|
||||||
reagent_vessels = [node for node in G.nodes()
|
|
||||||
if node.startswith('flask_')
|
|
||||||
and G.nodes[node].get('type') == 'container']
|
|
||||||
|
|
||||||
if not reagent_vessels:
|
|
||||||
raise ValueError("没有找到试剂容器")
|
|
||||||
|
|
||||||
# 执行添加流程
|
|
||||||
reagent_vessel = reagent_vessels[0]
|
|
||||||
reagent_pos = find_valve_position_for_vessel(G, devices['multiway_valve'], reagent_vessel)
|
|
||||||
reactor_pos = find_valve_position_for_vessel(G, devices['multiway_valve'], vessel)
|
|
||||||
|
|
||||||
# 生成操作序列
|
|
||||||
action_sequence.extend([
|
|
||||||
# 切换到试剂瓶
|
|
||||||
{
|
|
||||||
"device_id": devices['multiway_valve'],
|
|
||||||
"action_name": "set_position",
|
|
||||||
"action_kwargs": {"position": reagent_pos}
|
|
||||||
},
|
|
||||||
# 吸取试剂
|
|
||||||
{
|
|
||||||
"device_id": devices['transfer_pump'],
|
|
||||||
"action_name": "transfer",
|
|
||||||
"action_kwargs": {
|
|
||||||
"from_vessel": reagent_vessel,
|
|
||||||
"to_vessel": devices['transfer_pump'],
|
|
||||||
"volume": volume,
|
|
||||||
"amount": kwargs.get('amount', ''),
|
|
||||||
"time": kwargs.get('time', 10.0) / 2,
|
|
||||||
"viscous": kwargs.get('viscous', False),
|
|
||||||
"rinsing_solvent": "",
|
|
||||||
"rinsing_volume": 0.0,
|
|
||||||
"rinsing_repeats": 0,
|
|
||||||
"solid": False
|
|
||||||
}
|
|
||||||
},
|
|
||||||
# 切换到反应器
|
|
||||||
{
|
|
||||||
"device_id": devices['multiway_valve'],
|
|
||||||
"action_name": "set_position",
|
|
||||||
"action_kwargs": {"position": reactor_pos}
|
|
||||||
},
|
|
||||||
# 推送到反应器
|
|
||||||
{
|
|
||||||
"device_id": devices['transfer_pump'],
|
|
||||||
"action_name": "transfer",
|
|
||||||
"action_kwargs": {
|
|
||||||
"from_vessel": devices['transfer_pump'],
|
|
||||||
"to_vessel": vessel,
|
|
||||||
"volume": volume,
|
|
||||||
"amount": kwargs.get('amount', ''),
|
|
||||||
"time": kwargs.get('time', 10.0) / 2,
|
|
||||||
"viscous": kwargs.get('viscous', False),
|
|
||||||
"rinsing_solvent": "",
|
|
||||||
"rinsing_volume": 0.0,
|
|
||||||
"rinsing_repeats": 0,
|
|
||||||
"solid": False
|
|
||||||
}
|
|
||||||
}
|
|
||||||
])
|
|
||||||
|
|
||||||
# 如果需要搅拌
|
|
||||||
if kwargs.get('stir', False) and devices['stirrer']:
|
|
||||||
action_sequence.append({
|
|
||||||
"device_id": devices['stirrer'],
|
|
||||||
"action_name": "start_stir",
|
|
||||||
"action_kwargs": {
|
|
||||||
"vessel": vessel,
|
|
||||||
"stir_speed": kwargs.get('stir_speed', 300.0),
|
|
||||||
"purpose": f"添加 {reagent} 后混合"
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -1,221 +0,0 @@
|
|||||||
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})"
|
|
||||||
|
|
||||||
|
|
||||||
# 使用示例
|
|
||||||
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()}")
|
|
||||||
@@ -1,151 +0,0 @@
|
|||||||
import time
|
|
||||||
from typing import Union
|
|
||||||
|
|
||||||
|
|
||||||
class VirtualSolenoidValve:
|
|
||||||
"""
|
|
||||||
虚拟电磁阀门 - 简单的开关型阀门,只有开启和关闭两个状态
|
|
||||||
"""
|
|
||||||
def __init__(self, port: str = "VIRTUAL", voltage: float = 12.0, response_time: float = 0.1):
|
|
||||||
self.port = port
|
|
||||||
self.voltage = voltage
|
|
||||||
self.response_time = response_time
|
|
||||||
|
|
||||||
# 状态属性
|
|
||||||
self._status = "Idle"
|
|
||||||
self._valve_state = "Closed" # "Open" or "Closed"
|
|
||||||
self._is_open = False
|
|
||||||
|
|
||||||
@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"
|
|
||||||
|
|
||||||
def set_valve_position(self, position: Union[str, bool]):
|
|
||||||
"""
|
|
||||||
设置阀门位置
|
|
||||||
|
|
||||||
Args:
|
|
||||||
position: "OPEN"/"CLOSED" 或 True/False
|
|
||||||
"""
|
|
||||||
self._status = "Busy"
|
|
||||||
|
|
||||||
# 模拟阀门响应时间
|
|
||||||
time.sleep(self.response_time)
|
|
||||||
|
|
||||||
if isinstance(position, str):
|
|
||||||
target_open = position.upper() == "OPEN"
|
|
||||||
elif isinstance(position, bool):
|
|
||||||
target_open = position
|
|
||||||
else:
|
|
||||||
self._status = "Error"
|
|
||||||
return "Error: Invalid position"
|
|
||||||
|
|
||||||
self._is_open = target_open
|
|
||||||
self._valve_state = "Open" if target_open else "Closed"
|
|
||||||
self._status = "Idle"
|
|
||||||
|
|
||||||
return f"Valve {'opened' if target_open else 'closed'}"
|
|
||||||
|
|
||||||
def open(self):
|
|
||||||
"""打开电磁阀"""
|
|
||||||
self._status = "Busy"
|
|
||||||
time.sleep(self.response_time)
|
|
||||||
|
|
||||||
self._is_open = True
|
|
||||||
self._valve_state = "Open"
|
|
||||||
self._status = "Idle"
|
|
||||||
|
|
||||||
return "Valve opened"
|
|
||||||
|
|
||||||
def close(self):
|
|
||||||
"""关闭电磁阀"""
|
|
||||||
self._status = "Busy"
|
|
||||||
time.sleep(self.response_time)
|
|
||||||
|
|
||||||
self._is_open = False
|
|
||||||
self._valve_state = "Closed"
|
|
||||||
self._status = "Idle"
|
|
||||||
|
|
||||||
return "Valve closed"
|
|
||||||
|
|
||||||
def set_state(self, command: Union[bool, str]):
|
|
||||||
"""
|
|
||||||
设置阀门状态 - 兼容 SendCmd 类型
|
|
||||||
|
|
||||||
Args:
|
|
||||||
command: True/False 或 "open"/"close"
|
|
||||||
"""
|
|
||||||
if isinstance(command, bool):
|
|
||||||
return self.open() if command else self.close()
|
|
||||||
elif isinstance(command, str):
|
|
||||||
if command.lower() in ["open", "on", "true", "1"]:
|
|
||||||
return self.open()
|
|
||||||
elif command.lower() in ["close", "closed", "off", "false", "0"]:
|
|
||||||
return self.close()
|
|
||||||
else:
|
|
||||||
self._status = "Error"
|
|
||||||
return "Error: Invalid command"
|
|
||||||
else:
|
|
||||||
self._status = "Error"
|
|
||||||
return "Error: Invalid command type"
|
|
||||||
|
|
||||||
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 {
|
|
||||||
"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()
|
|
||||||
}
|
|
||||||
|
|
||||||
def reset(self):
|
|
||||||
"""重置阀门到关闭状态"""
|
|
||||||
return self.close()
|
|
||||||
|
|
||||||
def test_cycle(self, cycles: int = 3, delay: float = 1.0):
|
|
||||||
"""
|
|
||||||
测试阀门开关循环
|
|
||||||
|
|
||||||
Args:
|
|
||||||
cycles: 循环次数
|
|
||||||
delay: 每次开关间隔时间(秒)
|
|
||||||
"""
|
|
||||||
results = []
|
|
||||||
for i in range(cycles):
|
|
||||||
# 打开
|
|
||||||
result_open = self.open()
|
|
||||||
results.append(f"Cycle {i+1} - Open: {result_open}")
|
|
||||||
time.sleep(delay)
|
|
||||||
|
|
||||||
# 关闭
|
|
||||||
result_close = self.close()
|
|
||||||
results.append(f"Cycle {i+1} - Close: {result_close}")
|
|
||||||
time.sleep(delay)
|
|
||||||
|
|
||||||
return results
|
|
||||||
@@ -1,285 +1,149 @@
|
|||||||
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 VirtualTransferPump:
|
||||||
|
"""Virtual pump device specifically for Transfer protocol"""
|
||||||
|
|
||||||
class VirtualPumpMode(Enum):
|
def __init__(self, device_id: str = None, config: Dict[str, Any] = None, **kwargs):
|
||||||
Normal = 0
|
# 处理可能的不同调用方式
|
||||||
AccuratePos = 1
|
if device_id is None and 'id' in kwargs:
|
||||||
AccuratePosVel = 2
|
device_id = kwargs.pop('id')
|
||||||
|
if config is None and 'config' in kwargs:
|
||||||
|
config = kwargs.pop('config')
|
||||||
|
|
||||||
|
# 设置默认值
|
||||||
|
self.device_id = device_id or "unknown_transfer_pump"
|
||||||
|
self.config = config or {}
|
||||||
|
|
||||||
class VirtualPump:
|
self.logger = logging.getLogger(f"VirtualTransferPump.{self.device_id}")
|
||||||
"""虚拟泵类 - 模拟泵的基本功能,无需实际硬件"""
|
self.data = {}
|
||||||
|
|
||||||
def __init__(self, device_id: str = None, max_volume: float = 25.0, mode: VirtualPumpMode = VirtualPumpMode.Normal):
|
# 添加调试信息
|
||||||
self.device_id = device_id or "virtual_pump"
|
print(f"=== VirtualTransferPump {self.device_id} is being created! ===")
|
||||||
self.max_volume = max_volume
|
print(f"=== Config: {self.config} ===")
|
||||||
self.mode = mode
|
print(f"=== Kwargs: {kwargs} ===")
|
||||||
|
|
||||||
# 状态变量
|
# 从config或kwargs中获取配置参数
|
||||||
self._status = "Idle"
|
self.port = self.config.get('port') or kwargs.get('port', 'VIRTUAL')
|
||||||
self._position = 0.0 # 当前柱塞位置 (ml)
|
self._max_volume = self.config.get('max_volume') or kwargs.get('max_volume', 50.0)
|
||||||
self._max_velocity = 5.0 # 默认最大速度 (ml/s)
|
self._transfer_rate = self.config.get('transfer_rate') or kwargs.get('transfer_rate', 5.0)
|
||||||
self._current_volume = 0.0 # 当前注射器中的体积
|
self._current_volume = 0.0
|
||||||
|
self.is_running = False
|
||||||
self.logger = logging.getLogger(f"VirtualPump.{self.device_id}")
|
|
||||||
|
|
||||||
async def initialize(self) -> bool:
|
async def initialize(self) -> bool:
|
||||||
"""初始化虚拟泵"""
|
"""Initialize virtual transfer pump"""
|
||||||
self.logger.info(f"Initializing virtual pump {self.device_id}")
|
print(f"=== VirtualTransferPump {self.device_id} initialize() called! ===")
|
||||||
self._status = "Idle"
|
self.logger.info(f"Initializing virtual transfer pump {self.device_id}")
|
||||||
self._position = 0.0
|
self.data.update({
|
||||||
self._current_volume = 0.0
|
"status": "Idle",
|
||||||
|
"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 pump {self.device_id}")
|
self.logger.info(f"Cleaning up virtual transfer 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._status
|
return self.data.get("status", "Unknown")
|
||||||
|
|
||||||
@property
|
|
||||||
def position(self) -> float:
|
|
||||||
"""当前柱塞位置 (ml)"""
|
|
||||||
return self._position
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_volume(self) -> float:
|
def current_volume(self) -> float:
|
||||||
"""当前注射器中的体积 (ml)"""
|
return self.data.get("current_volume", 0.0)
|
||||||
return self._current_volume
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def max_velocity(self) -> float:
|
def max_volume(self) -> float:
|
||||||
return self._max_velocity
|
return self.data.get("max_volume", self._max_volume)
|
||||||
|
|
||||||
def set_max_velocity(self, velocity: float):
|
@property
|
||||||
"""设置最大速度 (ml/s)"""
|
def transfer_rate(self) -> float:
|
||||||
self._max_velocity = max(0.1, min(50.0, velocity)) # 限制在合理范围内
|
return self.data.get("transfer_rate", self._transfer_rate)
|
||||||
self.logger.info(f"Set max velocity to {self._max_velocity} ml/s")
|
|
||||||
|
|
||||||
def get_status(self) -> str:
|
@property
|
||||||
"""获取泵状态"""
|
def from_vessel(self) -> str:
|
||||||
return self._status
|
return self.data.get("from_vessel", "")
|
||||||
|
|
||||||
async def _simulate_operation(self, duration: float):
|
@property
|
||||||
"""模拟操作延时"""
|
def to_vessel(self) -> str:
|
||||||
self._status = "Busy"
|
return self.data.get("to_vessel", "")
|
||||||
await asyncio.sleep(duration)
|
|
||||||
self._status = "Idle"
|
|
||||||
|
|
||||||
def _calculate_duration(self, volume: float, velocity: float = None) -> float:
|
@property
|
||||||
"""计算操作持续时间"""
|
def progress(self) -> float:
|
||||||
if velocity is None:
|
return self.data.get("progress", 0.0)
|
||||||
velocity = self._max_velocity
|
|
||||||
return abs(volume) / velocity
|
|
||||||
|
|
||||||
# 基本泵操作
|
@property
|
||||||
async def set_position(self, position: float, velocity: float = None):
|
def transferred_volume(self) -> float:
|
||||||
"""
|
return self.data.get("transferred_volume", 0.0)
|
||||||
移动到绝对位置
|
|
||||||
|
|
||||||
Args:
|
@property
|
||||||
position (float): 目标位置 (ml)
|
def current_status(self) -> str:
|
||||||
velocity (float): 移动速度 (ml/s)
|
return self.data.get("current_status", "Ready")
|
||||||
"""
|
|
||||||
position = max(0, min(self.max_volume, position)) # 限制在有效范围内
|
|
||||||
|
|
||||||
volume_to_move = abs(position - self._position)
|
|
||||||
duration = self._calculate_duration(volume_to_move, velocity)
|
|
||||||
|
|
||||||
self.logger.info(f"Moving to position {position} ml (current: {self._position} ml)")
|
|
||||||
|
|
||||||
# 模拟移动过程
|
|
||||||
await self._simulate_operation(duration)
|
|
||||||
|
|
||||||
self._position = position
|
|
||||||
self._current_volume = position # 假设位置等于体积
|
|
||||||
|
|
||||||
self.logger.info(f"Reached position {self._position} ml")
|
|
||||||
|
|
||||||
async def pull_plunger(self, volume: float, velocity: float = None):
|
|
||||||
"""
|
|
||||||
拉取柱塞(吸液)
|
|
||||||
|
|
||||||
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):
|
|
||||||
"""
|
|
||||||
吸液操作
|
|
||||||
|
|
||||||
Args:
|
|
||||||
volume (float): 吸液体积 (ml)
|
|
||||||
velocity (float): 吸液速度 (ml/s)
|
|
||||||
"""
|
|
||||||
await self.pull_plunger(volume, velocity)
|
|
||||||
|
|
||||||
async def dispense(self, volume: float, velocity: float = None):
|
|
||||||
"""
|
|
||||||
排液操作
|
|
||||||
|
|
||||||
Args:
|
|
||||||
volume (float): 排液体积 (ml)
|
|
||||||
velocity (float): 排液速度 (ml/s)
|
|
||||||
"""
|
|
||||||
await self.push_plunger(volume, velocity)
|
|
||||||
|
|
||||||
async def transfer(self, volume: float, aspirate_velocity: float = None, dispense_velocity: float = None):
|
|
||||||
"""
|
|
||||||
转移操作(先吸后排)
|
|
||||||
|
|
||||||
Args:
|
|
||||||
volume (float): 转移体积 (ml)
|
|
||||||
aspirate_velocity (float): 吸液速度 (ml/s)
|
|
||||||
dispense_velocity (float): 排液速度 (ml/s)
|
|
||||||
"""
|
|
||||||
# 吸液
|
|
||||||
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"VirtualPump({self.device_id}: {self._current_volume:.2f}/{self.max_volume} ml, {self._status})"
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return self.__str__()
|
|
||||||
|
|
||||||
|
|
||||||
# 使用示例
|
|
||||||
async def demo():
|
|
||||||
"""虚拟泵使用示例"""
|
|
||||||
pump = VirtualPump("demo_pump", max_volume=50.0)
|
|
||||||
|
|
||||||
await pump.initialize()
|
|
||||||
|
|
||||||
print(f"Initial state: {pump}")
|
|
||||||
|
|
||||||
# 吸液测试
|
|
||||||
await pump.aspirate(10.0, velocity=2.0)
|
|
||||||
print(f"After aspirating 10ml: {pump}")
|
|
||||||
|
|
||||||
# 排液测试
|
|
||||||
await pump.dispense(5.0, velocity=3.0)
|
|
||||||
print(f"After dispensing 5ml: {pump}")
|
|
||||||
|
|
||||||
# 转移测试
|
|
||||||
await pump.transfer(3.0)
|
|
||||||
print(f"After transfer 3ml: {pump}")
|
|
||||||
|
|
||||||
# 清空测试
|
|
||||||
await pump.empty_syringe()
|
|
||||||
print(f"After emptying: {pump}")
|
|
||||||
|
|
||||||
print("\nPump info:", pump.get_pump_info())
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
asyncio.run(demo())
|
|
||||||
@@ -89,7 +89,7 @@ mock_filter:
|
|||||||
target_volume: Float64
|
target_volume: Float64
|
||||||
action_value_mappings:
|
action_value_mappings:
|
||||||
filter:
|
filter:
|
||||||
type: Filter
|
type: ProtocolFilter
|
||||||
goal:
|
goal:
|
||||||
vessel: vessel
|
vessel: vessel
|
||||||
filtrate_vessel: filtrate_vessel
|
filtrate_vessel: filtrate_vessel
|
||||||
@@ -737,7 +737,7 @@ mock_stirrer_new:
|
|||||||
max_stir_speed: Float64
|
max_stir_speed: Float64
|
||||||
action_value_mappings:
|
action_value_mappings:
|
||||||
start_stir:
|
start_stir:
|
||||||
type: StartStir
|
type: ProtocolStartStir
|
||||||
goal:
|
goal:
|
||||||
vessel: vessel
|
vessel: vessel
|
||||||
stir_speed: stir_speed
|
stir_speed: stir_speed
|
||||||
@@ -760,7 +760,7 @@ mock_stirrer_new:
|
|||||||
result:
|
result:
|
||||||
success: success
|
success: success
|
||||||
stop_stir:
|
stop_stir:
|
||||||
type: StopStir
|
type: ProtocolStopStir
|
||||||
goal:
|
goal:
|
||||||
vessel: vessel
|
vessel: vessel
|
||||||
feedback:
|
feedback:
|
||||||
|
|||||||
@@ -1,49 +1,3 @@
|
|||||||
# 虚拟设备清单及连接特性
|
|
||||||
|
|
||||||
# 1. virtual_pump - 虚拟泵
|
|
||||||
# 描述:具有多通道阀门特性的泵,根据valve_position可连接多个容器
|
|
||||||
# 连接特性:1个输入口 + 1个输出口(当前配置,实际应该有多个输出口)
|
|
||||||
# 数据类型:fluid(流体连接)
|
|
||||||
|
|
||||||
# 2. virtual_stirrer - 虚拟搅拌器
|
|
||||||
# 描述:机械连接设备,提供搅拌功能
|
|
||||||
# 连接特性:1个双向连接点(bidirectional)
|
|
||||||
# 数据类型:mechanical(机械连接)
|
|
||||||
|
|
||||||
# 3a. virtual_valve - 虚拟八通阀门
|
|
||||||
# 描述:8通阀门(实际配置为7通),可切换流向
|
|
||||||
# 连接特性:1个口连接注射泵 + 7个输出口
|
|
||||||
# 数据类型:fluid(流体连接)
|
|
||||||
|
|
||||||
# 3b. virtual_solenoid_valve (电磁阀门)
|
|
||||||
# 描述:简单的开关型电磁阀,只有开启和关闭两个状态
|
|
||||||
# 连接特性:1个输入口 + 1个输出口,控制通断
|
|
||||||
# 数据类型:fluid(流体连接)
|
|
||||||
|
|
||||||
# 4. virtual_centrifuge - 虚拟离心机
|
|
||||||
# 描述:单个样品处理设备,原地处理样品
|
|
||||||
# 连接特性:1个输入口 + 1个输出口
|
|
||||||
# 数据类型:resource(资源/样品连接)
|
|
||||||
|
|
||||||
# 5. virtual_filter - 虚拟过滤器
|
|
||||||
# 描述:分离设备,将样品分离为滤液和滤渣
|
|
||||||
# 连接特性:1个输入口 + 2个输出口(滤液和滤渣)
|
|
||||||
# 数据类型:resource(资源/样品连接)
|
|
||||||
|
|
||||||
# 6. virtual_heatchill - 虚拟加热/冷却器
|
|
||||||
# 描述:温控设备,容器直接放置在设备上进行温度控制
|
|
||||||
# 连接特性:1个双向连接点(bidirectional)
|
|
||||||
# 数据类型:mechanical(机械/物理接触连接)
|
|
||||||
|
|
||||||
# 7. virtual_transfer_pump - 虚拟转移泵(注射器式)
|
|
||||||
# 描述:注射器式转移泵,通过同一个口吸入和排出液体
|
|
||||||
# 连接特性:1个双向连接点(bidirectional)
|
|
||||||
# 数据类型:fluid(流体连接)
|
|
||||||
|
|
||||||
# 8. virtual_column - 虚拟色谱柱
|
|
||||||
# 描述:分离纯化设备,用于样品纯化
|
|
||||||
# 连接特性:1个输入口 + 1个输出口
|
|
||||||
# 数据类型:resource(资源/样品连接)
|
|
||||||
virtual_pump:
|
virtual_pump:
|
||||||
description: Virtual Pump for PumpTransferProtocol Testing
|
description: Virtual Pump for PumpTransferProtocol Testing
|
||||||
class:
|
class:
|
||||||
@@ -76,29 +30,11 @@ virtual_pump:
|
|||||||
set_valve_position:
|
set_valve_position:
|
||||||
type: FloatSingleInput
|
type: FloatSingleInput
|
||||||
goal:
|
goal:
|
||||||
float_in: valve_position
|
Int32: Int32
|
||||||
feedback:
|
feedback:
|
||||||
status: status
|
status: status
|
||||||
result:
|
result:
|
||||||
success: success
|
success: success
|
||||||
# 虚拟泵节点配置 - 具有多通道阀门特性,根据valve_position可连接多个容器
|
|
||||||
handles:
|
|
||||||
input:
|
|
||||||
- handler_key: pump-inlet
|
|
||||||
label: Pump Inlet
|
|
||||||
data_type: fluid
|
|
||||||
io_type: target
|
|
||||||
data_source: handle
|
|
||||||
data_key: fluid_in
|
|
||||||
description: "泵的进液口,连接源容器"
|
|
||||||
output:
|
|
||||||
- handler_key: pump-outlet-1
|
|
||||||
label: Outlet Port 1
|
|
||||||
data_type: fluid
|
|
||||||
io_type: source
|
|
||||||
data_source: executor
|
|
||||||
data_key: fluid_out_1
|
|
||||||
description: "阀门位置1的出液口"
|
|
||||||
schema:
|
schema:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
@@ -129,7 +65,7 @@ virtual_stirrer:
|
|||||||
result:
|
result:
|
||||||
success: success
|
success: success
|
||||||
start_stir:
|
start_stir:
|
||||||
type: StartStir
|
type: ProtocolStartStir
|
||||||
goal:
|
goal:
|
||||||
vessel: vessel
|
vessel: vessel
|
||||||
stir_speed: stir_speed
|
stir_speed: stir_speed
|
||||||
@@ -139,23 +75,13 @@ virtual_stirrer:
|
|||||||
result:
|
result:
|
||||||
success: success
|
success: success
|
||||||
stop_stir:
|
stop_stir:
|
||||||
type: StopStir
|
type: ProtocolStopStir
|
||||||
goal:
|
goal:
|
||||||
vessel: vessel
|
vessel: vessel
|
||||||
feedback:
|
feedback:
|
||||||
status: status
|
status: status
|
||||||
result:
|
result:
|
||||||
success: success
|
success: success
|
||||||
# 虚拟搅拌器节点配置 - 机械连接设备,单一双向连接点
|
|
||||||
handles:
|
|
||||||
bidirectional:
|
|
||||||
- handler_key: stirrer-vessel
|
|
||||||
label: Vessel Connection
|
|
||||||
data_type: mechanical
|
|
||||||
io_type: bidirectional
|
|
||||||
data_source: handle
|
|
||||||
data_key: vessel
|
|
||||||
description: "搅拌器的机械连接口,直接与反应容器连接提供搅拌功能"
|
|
||||||
schema:
|
schema:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
@@ -170,10 +96,10 @@ virtual_stirrer:
|
|||||||
default: 1000.0
|
default: 1000.0
|
||||||
additionalProperties: false
|
additionalProperties: false
|
||||||
|
|
||||||
virtual_multiway_valve:
|
virtual_valve:
|
||||||
description: Virtual 8-Way Valve for flow direction control
|
description: Virtual Valve for AddProtocol Testing
|
||||||
class:
|
class:
|
||||||
module: unilabos.devices.virtual.virtual_multiway_valve:VirtualMultiwayValve
|
module: unilabos.devices.virtual.virtual_valve:VirtualValve
|
||||||
type: python
|
type: python
|
||||||
status_types:
|
status_types:
|
||||||
status: String
|
status: String
|
||||||
@@ -189,73 +115,18 @@ virtual_multiway_valve:
|
|||||||
feedback: {}
|
feedback: {}
|
||||||
result:
|
result:
|
||||||
success: success
|
success: success
|
||||||
# 八通阀门节点配置 - 1个输入口,8个输出口,可切换流向
|
open:
|
||||||
handles:
|
type: EmptyIn
|
||||||
input:
|
goal: {}
|
||||||
- handler_key: multiway-valve-inlet
|
feedback: {}
|
||||||
label: Valve Inlet
|
result:
|
||||||
data_type: fluid
|
success: success
|
||||||
io_type: target
|
close:
|
||||||
data_source: handle
|
type: EmptyIn
|
||||||
data_key: fluid_in
|
goal: {}
|
||||||
description: "八通阀门进液口,接收来源流体"
|
feedback: {}
|
||||||
output:
|
result:
|
||||||
- handler_key: multiway-valve-port-1
|
success: success
|
||||||
label: Port 1
|
|
||||||
data_type: fluid
|
|
||||||
io_type: source
|
|
||||||
data_source: executor
|
|
||||||
data_key: fluid_port_1
|
|
||||||
description: "八通阀门端口1,position=1时流体从此口流出"
|
|
||||||
- handler_key: multiway-valve-port-2
|
|
||||||
label: Port 2
|
|
||||||
data_type: fluid
|
|
||||||
io_type: source
|
|
||||||
data_source: executor
|
|
||||||
data_key: fluid_port_2
|
|
||||||
description: "八通阀门端口2,position=2时流体从此口流出"
|
|
||||||
- handler_key: multiway-valve-port-3
|
|
||||||
label: Port 3
|
|
||||||
data_type: fluid
|
|
||||||
io_type: source
|
|
||||||
data_source: executor
|
|
||||||
data_key: fluid_port_3
|
|
||||||
description: "八通阀门端口3,position=3时流体从此口流出"
|
|
||||||
- handler_key: multiway-valve-port-4
|
|
||||||
label: Port 4
|
|
||||||
data_type: fluid
|
|
||||||
io_type: source
|
|
||||||
data_source: executor
|
|
||||||
data_key: fluid_port_4
|
|
||||||
description: "八通阀门端口4,position=4时流体从此口流出"
|
|
||||||
- handler_key: multiway-valve-port-5
|
|
||||||
label: Port 5
|
|
||||||
data_type: fluid
|
|
||||||
io_type: source
|
|
||||||
data_source: executor
|
|
||||||
data_key: fluid_port_5
|
|
||||||
description: "八通阀门端口5,position=5时流体从此口流出"
|
|
||||||
- handler_key: multiway-valve-port-6
|
|
||||||
label: Port 6
|
|
||||||
data_type: fluid
|
|
||||||
io_type: source
|
|
||||||
data_source: executor
|
|
||||||
data_key: fluid_port_6
|
|
||||||
description: "八通阀门端口6,position=6时流体从此口流出"
|
|
||||||
- handler_key: multiway-valve-port-7
|
|
||||||
label: Port 7
|
|
||||||
data_type: fluid
|
|
||||||
io_type: source
|
|
||||||
data_source: executor
|
|
||||||
data_key: fluid_port_7
|
|
||||||
description: "八通阀门端口7,position=7时流体从此口流出"
|
|
||||||
- handler_key: multiway-valve-port-8
|
|
||||||
label: Port 8
|
|
||||||
data_type: fluid
|
|
||||||
io_type: source
|
|
||||||
data_source: executor
|
|
||||||
data_key: fluid_port_8
|
|
||||||
description: "八通阀门端口8,position=8时流体从此口流出"
|
|
||||||
schema:
|
schema:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
@@ -264,62 +135,9 @@ virtual_multiway_valve:
|
|||||||
default: "VIRTUAL"
|
default: "VIRTUAL"
|
||||||
positions:
|
positions:
|
||||||
type: integer
|
type: integer
|
||||||
default: 8
|
default: 6
|
||||||
additionalProperties: false
|
|
||||||
virtual_solenoid_valve:
|
|
||||||
description: Virtual Solenoid Valve for simple on/off flow control
|
|
||||||
class:
|
|
||||||
module: unilabos.devices.virtual.virtual_solenoid_valve:VirtualSolenoidValve
|
|
||||||
type: python
|
|
||||||
status_types:
|
|
||||||
status: String
|
|
||||||
valve_state: String # "open" or "closed"
|
|
||||||
is_open: Bool
|
|
||||||
action_value_mappings:
|
|
||||||
open:
|
|
||||||
type: SendCmd
|
|
||||||
goal:
|
|
||||||
command: "open"
|
|
||||||
feedback: {}
|
|
||||||
result:
|
|
||||||
success: success
|
|
||||||
close:
|
|
||||||
type: SendCmd
|
|
||||||
goal:
|
|
||||||
command: "close"
|
|
||||||
feedback: {}
|
|
||||||
result:
|
|
||||||
success: success
|
|
||||||
set_state:
|
|
||||||
type: SendCmd
|
|
||||||
goal:
|
|
||||||
command: command
|
|
||||||
feedback: {}
|
|
||||||
result:
|
|
||||||
success: success
|
|
||||||
# 电磁阀门节点配置 - 双向流通的开关型阀门,流动方向由泵决定
|
|
||||||
handles:
|
|
||||||
bidirectional:
|
|
||||||
- handler_key: solenoid-valve-port
|
|
||||||
label: Valve Port
|
|
||||||
data_type: fluid
|
|
||||||
io_type: bidirectional
|
|
||||||
data_source: handle
|
|
||||||
data_key: fluid_port
|
|
||||||
description: "电磁阀的双向流体口,开启时允许流体双向通过,关闭时完全阻断"
|
|
||||||
schema:
|
|
||||||
type: object
|
|
||||||
properties:
|
|
||||||
port:
|
|
||||||
type: string
|
|
||||||
default: "VIRTUAL"
|
|
||||||
voltage:
|
|
||||||
type: number
|
|
||||||
default: 12.0
|
|
||||||
response_time:
|
|
||||||
type: number
|
|
||||||
default: 0.1
|
|
||||||
additionalProperties: false
|
additionalProperties: false
|
||||||
|
|
||||||
virtual_centrifuge:
|
virtual_centrifuge:
|
||||||
description: Virtual Centrifuge for CentrifugeProtocol Testing
|
description: Virtual Centrifuge for CentrifugeProtocol Testing
|
||||||
class:
|
class:
|
||||||
@@ -338,7 +156,7 @@ virtual_centrifuge:
|
|||||||
time_remaining: Float64
|
time_remaining: Float64
|
||||||
action_value_mappings:
|
action_value_mappings:
|
||||||
centrifuge:
|
centrifuge:
|
||||||
type: Centrifuge
|
type: ProtocolCentrifuge
|
||||||
goal:
|
goal:
|
||||||
vessel: vessel
|
vessel: vessel
|
||||||
speed: speed
|
speed: speed
|
||||||
@@ -352,24 +170,6 @@ virtual_centrifuge:
|
|||||||
result:
|
result:
|
||||||
success: success
|
success: success
|
||||||
message: message
|
message: message
|
||||||
# 虚拟离心机节点配置 - 单个样品处理设备,输入输出都是同一个样品容器
|
|
||||||
handles:
|
|
||||||
input:
|
|
||||||
- handler_key: centrifuge-sample
|
|
||||||
label: Sample Input
|
|
||||||
data_type: resource
|
|
||||||
io_type: target
|
|
||||||
data_source: handle
|
|
||||||
data_key: vessel
|
|
||||||
description: "需要离心的样品容器"
|
|
||||||
output:
|
|
||||||
- handler_key: centrifuge-sample-out
|
|
||||||
label: Centrifuged Sample
|
|
||||||
data_type: resource
|
|
||||||
io_type: source
|
|
||||||
data_source: executor
|
|
||||||
data_key: vessel
|
|
||||||
description: "经过离心处理的样品容器"
|
|
||||||
schema:
|
schema:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
@@ -405,7 +205,7 @@ virtual_filter:
|
|||||||
message: String
|
message: String
|
||||||
action_value_mappings:
|
action_value_mappings:
|
||||||
filter_sample:
|
filter_sample:
|
||||||
type: Filter
|
type: ProtocolFilter
|
||||||
goal:
|
goal:
|
||||||
vessel: vessel
|
vessel: vessel
|
||||||
filtrate_vessel: filtrate_vessel
|
filtrate_vessel: filtrate_vessel
|
||||||
@@ -422,31 +222,6 @@ virtual_filter:
|
|||||||
result:
|
result:
|
||||||
success: success
|
success: success
|
||||||
message: message
|
message: message
|
||||||
# 虚拟过滤器节点配置 - 分离设备,1个输入(原始样品),2个输出(滤液和滤渣)
|
|
||||||
handles:
|
|
||||||
input:
|
|
||||||
- handler_key: filter-sample-in
|
|
||||||
label: Sample Input
|
|
||||||
data_type: resource
|
|
||||||
io_type: target
|
|
||||||
data_source: handle
|
|
||||||
data_key: vessel
|
|
||||||
description: "需要过滤的原始样品容器"
|
|
||||||
output:
|
|
||||||
- handler_key: filter-filtrate-out
|
|
||||||
label: Filtrate Output
|
|
||||||
data_type: resource
|
|
||||||
io_type: source
|
|
||||||
data_source: executor
|
|
||||||
data_key: filtrate_vessel
|
|
||||||
description: "过滤后的滤液容器"
|
|
||||||
- handler_key: filter-residue-out
|
|
||||||
label: Filter Residue
|
|
||||||
data_type: resource
|
|
||||||
io_type: source
|
|
||||||
data_source: executor
|
|
||||||
data_key: residue_vessel
|
|
||||||
description: "过滤后的滤渣(固体残留物)"
|
|
||||||
schema:
|
schema:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
@@ -500,16 +275,6 @@ virtual_heatchill:
|
|||||||
status: status
|
status: status
|
||||||
result:
|
result:
|
||||||
success: success
|
success: success
|
||||||
# 虚拟加热/冷却器节点配置 - 温控设备,单一双向连接点用于放置容器
|
|
||||||
handles:
|
|
||||||
bidirectional:
|
|
||||||
- handler_key: heatchill-vessel
|
|
||||||
label: Vessel Connection
|
|
||||||
data_type: mechanical
|
|
||||||
io_type: bidirectional
|
|
||||||
data_source: handle
|
|
||||||
data_key: vessel
|
|
||||||
description: "加热/冷却器的物理连接口,容器直接放置在设备上进行温度控制"
|
|
||||||
schema:
|
schema:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
@@ -528,7 +293,7 @@ virtual_heatchill:
|
|||||||
additionalProperties: false
|
additionalProperties: false
|
||||||
|
|
||||||
virtual_transfer_pump:
|
virtual_transfer_pump:
|
||||||
description: Virtual Transfer Pump for TransferProtocol Testing (Syringe-style)
|
description: Virtual Transfer Pump for TransferProtocol Testing
|
||||||
class:
|
class:
|
||||||
module: unilabos.devices.virtual.virtual_transferpump:VirtualTransferPump
|
module: unilabos.devices.virtual.virtual_transferpump:VirtualTransferPump
|
||||||
type: python
|
type: python
|
||||||
@@ -544,7 +309,7 @@ virtual_transfer_pump:
|
|||||||
current_status: String
|
current_status: String
|
||||||
action_value_mappings:
|
action_value_mappings:
|
||||||
transfer:
|
transfer:
|
||||||
type: Transfer
|
type: ProtocolTransfer
|
||||||
goal:
|
goal:
|
||||||
from_vessel: from_vessel
|
from_vessel: from_vessel
|
||||||
to_vessel: to_vessel
|
to_vessel: to_vessel
|
||||||
@@ -563,16 +328,6 @@ virtual_transfer_pump:
|
|||||||
result:
|
result:
|
||||||
success: success
|
success: success
|
||||||
message: message
|
message: message
|
||||||
# 注射器式转移泵节点配置 - 只有一个双向连接口,可吸入和排出液体
|
|
||||||
handles:
|
|
||||||
bidirectional:
|
|
||||||
- handler_key: syringe-port
|
|
||||||
label: Syringe Port
|
|
||||||
data_type: fluid
|
|
||||||
io_type: bidirectional
|
|
||||||
data_source: handle
|
|
||||||
data_key: fluid_port
|
|
||||||
description: "注射器式转移泵的唯一连接口,通过阀门切换实现吸入和排出"
|
|
||||||
schema:
|
schema:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
@@ -604,7 +359,7 @@ virtual_column:
|
|||||||
current_status: String
|
current_status: String
|
||||||
action_value_mappings:
|
action_value_mappings:
|
||||||
run_column:
|
run_column:
|
||||||
type: RunColumn
|
type: ProtocolRunColumn
|
||||||
goal:
|
goal:
|
||||||
from_vessel: from_vessel
|
from_vessel: from_vessel
|
||||||
to_vessel: to_vessel
|
to_vessel: to_vessel
|
||||||
@@ -615,24 +370,6 @@ virtual_column:
|
|||||||
result:
|
result:
|
||||||
success: success
|
success: success
|
||||||
message: message
|
message: message
|
||||||
# 虚拟色谱柱节点配置 - 分离纯化设备,1个样品输入口,1个纯化产物输出口
|
|
||||||
handles:
|
|
||||||
input:
|
|
||||||
- handler_key: column-sample-inlet
|
|
||||||
label: Sample Input
|
|
||||||
data_type: resource
|
|
||||||
io_type: target
|
|
||||||
data_source: handle
|
|
||||||
data_key: from_vessel
|
|
||||||
description: "需要纯化的样品输入口"
|
|
||||||
output:
|
|
||||||
- handler_key: column-product-outlet
|
|
||||||
label: Purified Product
|
|
||||||
data_type: resource
|
|
||||||
io_type: source
|
|
||||||
data_source: executor
|
|
||||||
data_key: to_vessel
|
|
||||||
description: "经过色谱柱纯化的产物输出口"
|
|
||||||
schema:
|
schema:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
|||||||
@@ -29,23 +29,23 @@ set(action_files
|
|||||||
"action/HeatChillStart.action"
|
"action/HeatChillStart.action"
|
||||||
"action/HeatChillStop.action"
|
"action/HeatChillStop.action"
|
||||||
|
|
||||||
"action/CleanVessel.action"
|
"action/ProtocolCleanVessel.action"
|
||||||
"action/Dissolve.action"
|
"action/ProtocolDissolve.action"
|
||||||
"action/FilterThrough.action"
|
"action/ProtocolFilterThrough.action"
|
||||||
"action/RunColumn.action"
|
"action/ProtocolRunColumn.action"
|
||||||
"action/Wait.action"
|
"action/ProtocolWait.action"
|
||||||
"action/WashSolid.action"
|
"action/ProtocolWashSolid.action"
|
||||||
"action/Filter.action"
|
"action/ProtocolFilter.action"
|
||||||
"action/Add.action"
|
|
||||||
"action/Centrifuge.action"
|
"action/ProtocolCentrifuge.action"
|
||||||
"action/Crystallize.action"
|
"action/ProtocolCrystallize.action"
|
||||||
"action/Dry.action"
|
"action/ProtocolDry.action"
|
||||||
"action/Purge.action"
|
"action/ProtocolPurge.action"
|
||||||
"action/StartPurge.action"
|
"action/ProtocolStartPurge.action"
|
||||||
"action/StartStir.action"
|
"action/ProtocolStartStir.action"
|
||||||
"action/StopPurge.action"
|
"action/ProtocolStopPurge.action"
|
||||||
"action/StopStir.action"
|
"action/ProtocolStopStir.action"
|
||||||
"action/Transfer.action"
|
"action/ProtocolTransfer.action"
|
||||||
|
|
||||||
"action/LiquidHandlerProtocolCreation.action"
|
"action/LiquidHandlerProtocolCreation.action"
|
||||||
"action/LiquidHandlerAspirate.action"
|
"action/LiquidHandlerAspirate.action"
|
||||||
|
|||||||
Reference in New Issue
Block a user