mirror of
https://github.com/dptech-corp/Uni-Lab-OS.git
synced 2025-12-17 21:11:12 +00:00
Compare commits
1 Commits
85c6f4e688
...
v0.10.7
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9aeffebde1 |
114
.github/workflows/conda-pack-build.yml
vendored
114
.github/workflows/conda-pack-build.yml
vendored
@@ -42,7 +42,7 @@ jobs:
|
|||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
# Windows uses cmd for better conda/mamba compatibility, Unix uses bash
|
# Windows uses cmd for better conda/mamba compatibility, Unix uses bash
|
||||||
shell: ${{ matrix.platform == 'win-64' && 'cmd' || 'bash' }}
|
shell: ${{ matrix.platform == 'win-64' && 'cmd /C CALL {0}' || 'bash -el {0}' }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Check if platform should be built
|
- name: Check if platform should be built
|
||||||
@@ -73,6 +73,7 @@ jobs:
|
|||||||
channels: conda-forge,robostack-staging,uni-lab,defaults
|
channels: conda-forge,robostack-staging,uni-lab,defaults
|
||||||
channel-priority: flexible
|
channel-priority: flexible
|
||||||
activate-environment: unilab
|
activate-environment: unilab
|
||||||
|
auto-activate-base: true
|
||||||
auto-update-conda: false
|
auto-update-conda: false
|
||||||
show-channel-urls: true
|
show-channel-urls: true
|
||||||
|
|
||||||
@@ -81,7 +82,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
echo Installing unilabos and dependencies to unilab environment...
|
echo Installing unilabos and dependencies to unilab environment...
|
||||||
echo Using mamba for faster and more reliable dependency resolution...
|
echo Using mamba for faster and more reliable dependency resolution...
|
||||||
mamba install -n unilab uni-lab::unilabos conda-pack -c uni-lab -c robostack-staging -c conda-forge -y
|
mamba install uni-lab::unilabos conda-pack -c uni-lab -c robostack-staging -c conda-forge -y
|
||||||
|
|
||||||
- name: Install conda-pack, unilabos and dependencies (Unix)
|
- name: Install conda-pack, unilabos and dependencies (Unix)
|
||||||
if: steps.should_build.outputs.should_build == 'true' && matrix.platform != 'win-64'
|
if: steps.should_build.outputs.should_build == 'true' && matrix.platform != 'win-64'
|
||||||
@@ -89,15 +90,15 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
echo "Installing unilabos and dependencies to unilab environment..."
|
echo "Installing unilabos and dependencies to unilab environment..."
|
||||||
echo "Using mamba for faster and more reliable dependency resolution..."
|
echo "Using mamba for faster and more reliable dependency resolution..."
|
||||||
mamba install -n unilab uni-lab::unilabos conda-pack -c uni-lab -c robostack-staging -c conda-forge -y
|
mamba install uni-lab::unilabos conda-pack -c uni-lab -c robostack-staging -c conda-forge -y
|
||||||
|
|
||||||
- name: Get latest ros-humble-unilabos-msgs version (Windows)
|
- name: Get latest ros-humble-unilabos-msgs version (Windows)
|
||||||
if: steps.should_build.outputs.should_build == 'true' && matrix.platform == 'win-64'
|
if: steps.should_build.outputs.should_build == 'true' && matrix.platform == 'win-64'
|
||||||
id: msgs_version_win
|
id: msgs_version_win
|
||||||
run: |
|
run: |
|
||||||
echo Checking installed ros-humble-unilabos-msgs version...
|
echo Checking installed ros-humble-unilabos-msgs version...
|
||||||
conda list -n unilab ros-humble-unilabos-msgs
|
conda list ros-humble-unilabos-msgs
|
||||||
for /f "tokens=2" %%i in ('conda list -n unilab ros-humble-unilabos-msgs --json ^| python -c "import sys, json; pkgs=json.load(sys.stdin); print(pkgs[0]['version'] if pkgs else 'not-found')"') do set VERSION=%%i
|
for /f "tokens=2" %%i in ('conda list ros-humble-unilabos-msgs --json ^| python -c "import sys, json; pkgs=json.load(sys.stdin); print(pkgs[0]['version'] if pkgs else 'not-found')"') do set VERSION=%%i
|
||||||
echo installed_version=%VERSION% >> %GITHUB_OUTPUT%
|
echo installed_version=%VERSION% >> %GITHUB_OUTPUT%
|
||||||
echo Installed ros-humble-unilabos-msgs version: %VERSION%
|
echo Installed ros-humble-unilabos-msgs version: %VERSION%
|
||||||
|
|
||||||
@@ -107,7 +108,7 @@ jobs:
|
|||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
echo "Checking installed ros-humble-unilabos-msgs version..."
|
echo "Checking installed ros-humble-unilabos-msgs version..."
|
||||||
VERSION=$(conda list -n unilab ros-humble-unilabos-msgs --json | python -c "import sys, json; pkgs=json.load(sys.stdin); print(pkgs[0]['version'] if pkgs else 'not-found')")
|
VERSION=$(conda list ros-humble-unilabos-msgs --json | python -c "import sys, json; pkgs=json.load(sys.stdin); print(pkgs[0]['version'] if pkgs else 'not-found')")
|
||||||
echo "installed_version=$VERSION" >> $GITHUB_OUTPUT
|
echo "installed_version=$VERSION" >> $GITHUB_OUTPUT
|
||||||
echo "Installed ros-humble-unilabos-msgs version: $VERSION"
|
echo "Installed ros-humble-unilabos-msgs version: $VERSION"
|
||||||
|
|
||||||
@@ -118,7 +119,7 @@ jobs:
|
|||||||
mamba search ros-humble-unilabos-msgs -c uni-lab -c robostack-staging -c conda-forge || echo Search completed
|
mamba search ros-humble-unilabos-msgs -c uni-lab -c robostack-staging -c conda-forge || echo Search completed
|
||||||
echo.
|
echo.
|
||||||
echo Updating ros-humble-unilabos-msgs to latest version...
|
echo Updating ros-humble-unilabos-msgs to latest version...
|
||||||
mamba update -n unilab ros-humble-unilabos-msgs -c uni-lab -c robostack-staging -c conda-forge -y || echo Already at latest version
|
mamba update ros-humble-unilabos-msgs -c uni-lab -c robostack-staging -c conda-forge -y || echo Already at latest version
|
||||||
|
|
||||||
- name: Check for newer ros-humble-unilabos-msgs (Unix)
|
- name: Check for newer ros-humble-unilabos-msgs (Unix)
|
||||||
if: steps.should_build.outputs.should_build == 'true' && matrix.platform != 'win-64'
|
if: steps.should_build.outputs.should_build == 'true' && matrix.platform != 'win-64'
|
||||||
@@ -128,65 +129,65 @@ jobs:
|
|||||||
mamba search ros-humble-unilabos-msgs -c uni-lab -c robostack-staging -c conda-forge || echo "Search completed"
|
mamba search ros-humble-unilabos-msgs -c uni-lab -c robostack-staging -c conda-forge || echo "Search completed"
|
||||||
echo ""
|
echo ""
|
||||||
echo "Updating ros-humble-unilabos-msgs to latest version..."
|
echo "Updating ros-humble-unilabos-msgs to latest version..."
|
||||||
mamba update -n unilab ros-humble-unilabos-msgs -c uni-lab -c robostack-staging -c conda-forge -y || echo "Already at latest version"
|
mamba update ros-humble-unilabos-msgs -c uni-lab -c robostack-staging -c conda-forge -y || echo "Already at latest version"
|
||||||
|
|
||||||
- name: Install latest unilabos from source (Windows)
|
- name: Install latest unilabos from source (Windows)
|
||||||
if: steps.should_build.outputs.should_build == 'true' && matrix.platform == 'win-64'
|
if: steps.should_build.outputs.should_build == 'true' && matrix.platform == 'win-64'
|
||||||
run: |
|
run: |
|
||||||
echo Uninstalling existing unilabos...
|
echo Uninstalling existing unilabos...
|
||||||
mamba run -n unilab pip uninstall unilabos -y || echo unilabos not installed via pip
|
pip uninstall unilabos -y || echo unilabos not installed via pip
|
||||||
echo Installing unilabos from source (branch: ${{ github.event.inputs.branch }})...
|
echo Installing unilabos from source (branch: ${{ github.event.inputs.branch }})...
|
||||||
mamba run -n unilab pip install .
|
pip install .
|
||||||
echo Verifying installation...
|
echo Verifying installation...
|
||||||
mamba run -n unilab pip show unilabos
|
pip show unilabos
|
||||||
|
|
||||||
- name: Install latest unilabos from source (Unix)
|
- name: Install latest unilabos from source (Unix)
|
||||||
if: steps.should_build.outputs.should_build == 'true' && matrix.platform != 'win-64'
|
if: steps.should_build.outputs.should_build == 'true' && matrix.platform != 'win-64'
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
echo "Uninstalling existing unilabos..."
|
echo "Uninstalling existing unilabos..."
|
||||||
mamba run -n unilab pip uninstall unilabos -y || echo "unilabos not installed via pip"
|
pip uninstall unilabos -y || echo "unilabos not installed via pip"
|
||||||
echo "Installing unilabos from source (branch: ${{ github.event.inputs.branch }})..."
|
echo "Installing unilabos from source (branch: ${{ github.event.inputs.branch }})..."
|
||||||
mamba run -n unilab pip install .
|
pip install .
|
||||||
echo "Verifying installation..."
|
echo "Verifying installation..."
|
||||||
mamba run -n unilab pip show unilabos
|
pip show unilabos
|
||||||
|
|
||||||
- name: Display environment info (Windows)
|
- name: Display environment info (Windows)
|
||||||
if: steps.should_build.outputs.should_build == 'true' && matrix.platform == 'win-64'
|
if: steps.should_build.outputs.should_build == 'true' && matrix.platform == 'win-64'
|
||||||
run: |
|
run: |
|
||||||
echo === Environment Information ===
|
echo === Environment Information ===
|
||||||
mamba env list
|
conda env list
|
||||||
echo.
|
echo.
|
||||||
echo === Installed Packages ===
|
echo === Installed Packages ===
|
||||||
mamba list -n unilab | findstr /C:"unilabos" /C:"ros-humble-unilabos-msgs" || mamba list -n unilab
|
conda list | findstr /C:"unilabos" /C:"ros-humble-unilabos-msgs" || conda list
|
||||||
echo.
|
echo.
|
||||||
echo === Python Packages ===
|
echo === Python Packages ===
|
||||||
mamba run -n unilab pip list | findstr unilabos || mamba run -n unilab pip list
|
pip list | findstr unilabos || pip list
|
||||||
|
|
||||||
- name: Display environment info (Unix)
|
- name: Display environment info (Unix)
|
||||||
if: steps.should_build.outputs.should_build == 'true' && matrix.platform != 'win-64'
|
if: steps.should_build.outputs.should_build == 'true' && matrix.platform != 'win-64'
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
echo "=== Environment Information ==="
|
echo "=== Environment Information ==="
|
||||||
mamba env list
|
conda env list
|
||||||
echo ""
|
echo ""
|
||||||
echo "=== Installed Packages ==="
|
echo "=== Installed Packages ==="
|
||||||
mamba list -n unilab | grep -E "(unilabos|ros-humble-unilabos-msgs)" || mamba list -n unilab
|
conda list | grep -E "(unilabos|ros-humble-unilabos-msgs)" || conda list
|
||||||
echo ""
|
echo ""
|
||||||
echo "=== Python Packages ==="
|
echo "=== Python Packages ==="
|
||||||
mamba run -n unilab pip list | grep unilabos || mamba run -n unilab pip list
|
pip list | grep unilabos || pip list
|
||||||
|
|
||||||
- name: Verify environment integrity (Windows)
|
- name: Verify environment integrity (Windows)
|
||||||
if: steps.should_build.outputs.should_build == 'true' && matrix.platform == 'win-64'
|
if: steps.should_build.outputs.should_build == 'true' && matrix.platform == 'win-64'
|
||||||
run: |
|
run: |
|
||||||
echo Verifying Python version...
|
echo Verifying Python version...
|
||||||
mamba run -n unilab python -c "import sys; print(f'Python version: {sys.version}')"
|
python -c "import sys; print(f'Python version: {sys.version}')"
|
||||||
echo Verifying unilabos import...
|
echo Verifying unilabos import...
|
||||||
mamba run -n unilab python -c "import unilabos; print(f'UniLabOS version: {unilabos.__version__}')" || echo Warning: Could not import unilabos
|
python -c "import unilabos; print(f'UniLabOS version: {unilabos.__version__}')" || echo Warning: Could not import unilabos
|
||||||
echo Checking critical packages...
|
echo Checking critical packages...
|
||||||
mamba run -n unilab python -c "import rclpy; print('ROS2 rclpy: OK')"
|
python -c "import rclpy; print('ROS2 rclpy: OK')"
|
||||||
echo Running comprehensive verification script...
|
echo Running comprehensive verification script...
|
||||||
mamba run -n unilab python scripts\verify_installation.py --auto-install || echo Warning: Verification script reported issues
|
python scripts\verify_installation.py || echo Warning: Verification script reported issues
|
||||||
echo Environment verification complete!
|
echo Environment verification complete!
|
||||||
|
|
||||||
- name: Verify environment integrity (Unix)
|
- name: Verify environment integrity (Unix)
|
||||||
@@ -194,20 +195,20 @@ jobs:
|
|||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
echo "Verifying Python version..."
|
echo "Verifying Python version..."
|
||||||
mamba run -n unilab python -c "import sys; print(f'Python version: {sys.version}')"
|
python -c "import sys; print(f'Python version: {sys.version}')"
|
||||||
echo "Verifying unilabos import..."
|
echo "Verifying unilabos import..."
|
||||||
mamba run -n unilab python -c "import unilabos; print(f'UniLabOS version: {unilabos.__version__}')" || echo "Warning: Could not import unilabos"
|
python -c "import unilabos; print(f'UniLabOS version: {unilabos.__version__}')" || echo "Warning: Could not import unilabos"
|
||||||
echo "Checking critical packages..."
|
echo "Checking critical packages..."
|
||||||
mamba run -n unilab python -c "import rclpy; print('ROS2 rclpy: OK')"
|
python -c "import rclpy; print('ROS2 rclpy: OK')"
|
||||||
echo "Running comprehensive verification script..."
|
echo "Running comprehensive verification script..."
|
||||||
mamba run -n unilab python scripts/verify_installation.py --auto-install || echo "Warning: Verification script reported issues"
|
python scripts/verify_installation.py || echo "Warning: Verification script reported issues"
|
||||||
echo "Environment verification complete!"
|
echo "Environment verification complete!"
|
||||||
|
|
||||||
- name: Pack conda environment (Windows)
|
- name: Pack conda environment (Windows)
|
||||||
if: steps.should_build.outputs.should_build == 'true' && matrix.platform == 'win-64'
|
if: steps.should_build.outputs.should_build == 'true' && matrix.platform == 'win-64'
|
||||||
run: |
|
run: |
|
||||||
echo Packing unilab environment with conda-pack...
|
echo Packing unilab environment with conda-pack...
|
||||||
mamba activate unilab && conda pack -n unilab -o unilab-env-${{ matrix.platform }}.tar.gz --ignore-missing-files
|
conda pack -n unilab -o unilab-env-${{ matrix.platform }}.tar.gz --ignore-missing-files
|
||||||
echo Pack file created:
|
echo Pack file created:
|
||||||
dir unilab-env-${{ matrix.platform }}.tar.gz
|
dir unilab-env-${{ matrix.platform }}.tar.gz
|
||||||
|
|
||||||
@@ -216,7 +217,6 @@ jobs:
|
|||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
echo "Packing unilab environment with conda-pack..."
|
echo "Packing unilab environment with conda-pack..."
|
||||||
mamba install conda-pack -c conda-forge -y
|
|
||||||
conda pack -n unilab -o unilab-env-${{ matrix.platform }}.tar.gz --ignore-missing-files
|
conda pack -n unilab -o unilab-env-${{ matrix.platform }}.tar.gz --ignore-missing-files
|
||||||
echo "Pack file created:"
|
echo "Pack file created:"
|
||||||
ls -lh unilab-env-${{ matrix.platform }}.tar.gz
|
ls -lh unilab-env-${{ matrix.platform }}.tar.gz
|
||||||
@@ -242,10 +242,6 @@ jobs:
|
|||||||
echo Adding: verify_installation.py
|
echo Adding: verify_installation.py
|
||||||
copy scripts\verify_installation.py dist-package\
|
copy scripts\verify_installation.py dist-package\
|
||||||
|
|
||||||
rem Copy source code repository (including .git)
|
|
||||||
echo Adding: Uni-Lab-OS source repository
|
|
||||||
robocopy . dist-package\Uni-Lab-OS /E /XD dist-package /NFL /NDL /NJH /NJS /NC /NS || if %ERRORLEVEL% LSS 8 exit /b 0
|
|
||||||
|
|
||||||
rem Create README using Python script
|
rem Create README using Python script
|
||||||
echo Creating: README.txt
|
echo Creating: README.txt
|
||||||
python scripts\create_readme.py ${{ matrix.platform }} ${{ github.event.inputs.branch }} dist-package\README.txt
|
python scripts\create_readme.py ${{ matrix.platform }} ${{ github.event.inputs.branch }} dist-package\README.txt
|
||||||
@@ -278,10 +274,6 @@ jobs:
|
|||||||
echo "Adding: verify_installation.py"
|
echo "Adding: verify_installation.py"
|
||||||
cp scripts/verify_installation.py dist-package/
|
cp scripts/verify_installation.py dist-package/
|
||||||
|
|
||||||
# Copy source code repository (including .git)
|
|
||||||
echo "Adding: Uni-Lab-OS source repository"
|
|
||||||
rsync -a --exclude='dist-package' . dist-package/Uni-Lab-OS
|
|
||||||
|
|
||||||
# Create README using Python script
|
# Create README using Python script
|
||||||
echo "Creating: README.txt"
|
echo "Creating: README.txt"
|
||||||
python scripts/create_readme.py ${{ matrix.platform }} ${{ github.event.inputs.branch }} dist-package/README.txt
|
python scripts/create_readme.py ${{ matrix.platform }} ${{ github.event.inputs.branch }} dist-package/README.txt
|
||||||
@@ -291,6 +283,46 @@ jobs:
|
|||||||
ls -lh dist-package/
|
ls -lh dist-package/
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
|
- name: Finalize Windows distribution package
|
||||||
|
if: steps.should_build.outputs.should_build == 'true' && matrix.platform == 'win-64'
|
||||||
|
run: |
|
||||||
|
echo ==========================================
|
||||||
|
echo Windows distribution package ready
|
||||||
|
echo.
|
||||||
|
echo Package will be uploaded as artifact
|
||||||
|
echo GitHub Actions will automatically create ZIP
|
||||||
|
echo.
|
||||||
|
echo Contents:
|
||||||
|
dir /b dist-package
|
||||||
|
echo.
|
||||||
|
echo Users will download a ZIP containing:
|
||||||
|
echo - install_unilab.bat
|
||||||
|
echo - unilab-env-${{ matrix.platform }}.tar.gz
|
||||||
|
echo - verify_installation.py
|
||||||
|
echo - README.txt
|
||||||
|
echo ==========================================
|
||||||
|
|
||||||
|
- name: Create Unix/Linux TAR.GZ archive
|
||||||
|
if: steps.should_build.outputs.should_build == 'true' && matrix.platform != 'win-64'
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
echo "=========================================="
|
||||||
|
echo "Creating Unix/Linux TAR.GZ archive..."
|
||||||
|
echo "Archive: unilab-pack-${{ matrix.platform }}.tar.gz"
|
||||||
|
echo "Contents: install_unilab.sh + unilab-env-${{ matrix.platform }}.tar.gz + extras"
|
||||||
|
tar -czf unilab-pack-${{ matrix.platform }}.tar.gz -C dist-package .
|
||||||
|
echo "=========================================="
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Final package created:"
|
||||||
|
ls -lh unilab-pack-*
|
||||||
|
echo ""
|
||||||
|
echo "Users can now:"
|
||||||
|
echo " 1. Download unilab-pack-${{ matrix.platform }}.tar.gz"
|
||||||
|
echo " 2. Extract it: tar -xzf unilab-pack-${{ matrix.platform }}.tar.gz"
|
||||||
|
echo " 3. Run: bash install_unilab.sh"
|
||||||
|
echo ""
|
||||||
|
|
||||||
- name: Upload distribution package
|
- name: Upload distribution package
|
||||||
if: steps.should_build.outputs.should_build == 'true'
|
if: steps.should_build.outputs.should_build == 'true'
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
@@ -333,8 +365,12 @@ jobs:
|
|||||||
echo "Distribution package contents:"
|
echo "Distribution package contents:"
|
||||||
ls -lh dist-package/
|
ls -lh dist-package/
|
||||||
echo ""
|
echo ""
|
||||||
|
echo "Package size (tar.gz):"
|
||||||
|
ls -lh unilab-pack-*.tar.gz
|
||||||
|
echo ""
|
||||||
echo "Artifact name: unilab-pack-${{ matrix.platform }}-${{ github.event.inputs.branch }}"
|
echo "Artifact name: unilab-pack-${{ matrix.platform }}-${{ github.event.inputs.branch }}"
|
||||||
echo ""
|
echo ""
|
||||||
echo "After download:"
|
echo "After download:"
|
||||||
echo " install_unilab.sh"
|
echo " - Windows/macOS: Extract ZIP, then: tar -xzf unilab-pack-${{ matrix.platform }}.tar.gz"
|
||||||
|
echo " - Linux: Extract ZIP (or download tar.gz directly), run install_unilab.sh"
|
||||||
echo "=========================================="
|
echo "=========================================="
|
||||||
|
|||||||
43
.github/workflows/deploy-docs.yml
vendored
43
.github/workflows/deploy-docs.yml
vendored
@@ -39,39 +39,24 @@ jobs:
|
|||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
ref: ${{ github.event.inputs.branch || github.ref }}
|
ref: ${{ github.event.inputs.branch || github.ref }}
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Setup Miniforge (with mamba)
|
- name: Setup Python environment
|
||||||
uses: conda-incubator/setup-miniconda@v3
|
uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
miniforge-version: latest
|
python-version: '3.10'
|
||||||
use-mamba: true
|
|
||||||
python-version: '3.11.11'
|
|
||||||
channels: conda-forge,robostack-staging,uni-lab,defaults
|
|
||||||
channel-priority: flexible
|
|
||||||
activate-environment: unilab
|
|
||||||
auto-update-conda: false
|
|
||||||
show-channel-urls: true
|
|
||||||
|
|
||||||
- name: Install unilabos and dependencies
|
- name: Install system dependencies
|
||||||
run: |
|
run: |
|
||||||
echo "Installing unilabos and dependencies to unilab environment..."
|
sudo apt-get update
|
||||||
echo "Using mamba for faster and more reliable dependency resolution..."
|
sudo apt-get install -y pandoc
|
||||||
mamba install -n unilab uni-lab::unilabos -c uni-lab -c robostack-staging -c conda-forge -y
|
|
||||||
|
|
||||||
- name: Install latest unilabos from source
|
- name: Install Python dependencies
|
||||||
run: |
|
run: |
|
||||||
echo "Uninstalling existing unilabos..."
|
python -m pip install --upgrade pip
|
||||||
mamba run -n unilab pip uninstall unilabos -y || echo "unilabos not installed via pip"
|
# Install package in development mode to get version info
|
||||||
echo "Installing unilabos from source..."
|
pip install -e .
|
||||||
mamba run -n unilab pip install .
|
# Install documentation dependencies
|
||||||
echo "Verifying installation..."
|
pip install -r docs/requirements.txt
|
||||||
mamba run -n unilab pip show unilabos
|
|
||||||
|
|
||||||
- name: Install documentation dependencies
|
|
||||||
run: |
|
|
||||||
echo "Installing documentation build dependencies..."
|
|
||||||
mamba run -n unilab pip install -r docs/requirements.txt
|
|
||||||
|
|
||||||
- name: Setup Pages
|
- name: Setup Pages
|
||||||
id: pages
|
id: pages
|
||||||
@@ -83,8 +68,8 @@ jobs:
|
|||||||
cd docs
|
cd docs
|
||||||
# Clean previous builds
|
# Clean previous builds
|
||||||
rm -rf _build
|
rm -rf _build
|
||||||
# Build HTML documentation in conda environment
|
# Build HTML documentation
|
||||||
mamba run -n unilab python -m sphinx -b html . _build/html -v
|
python -m sphinx -b html . _build/html -v
|
||||||
|
|
||||||
- name: Check build results
|
- name: Check build results
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ Join the [Intelligent Organic Chemistry Synthesis Competition](https://bohrium.d
|
|||||||
|
|
||||||
Detailed documentation can be found at:
|
Detailed documentation can be found at:
|
||||||
|
|
||||||
- [Online Documentation](https://xuwznln.github.io/Uni-Lab-OS-Doc/)
|
- [Online Documentation](https://dptech-corp.github.io/Uni-Lab-OS/)
|
||||||
|
|
||||||
## Quick Start
|
## Quick Start
|
||||||
|
|
||||||
@@ -55,7 +55,7 @@ pip install .
|
|||||||
|
|
||||||
3. Start Uni-Lab System:
|
3. Start Uni-Lab System:
|
||||||
|
|
||||||
Please refer to [Documentation - Boot Examples](https://xuwznln.github.io/Uni-Lab-OS-Doc/boot_examples/index.html)
|
Please refer to [Documentation - Boot Examples](https://dptech-corp.github.io/Uni-Lab-OS/boot_examples/index.html)
|
||||||
|
|
||||||
## Message Format
|
## Message Format
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ Uni-Lab-OS 是一个用于实验室自动化的综合平台,旨在连接和控
|
|||||||
|
|
||||||
详细文档可在以下位置找到:
|
详细文档可在以下位置找到:
|
||||||
|
|
||||||
- [在线文档](https://xuwznln.github.io/Uni-Lab-OS-Doc/)
|
- [在线文档](https://dptech-corp.github.io/Uni-Lab-OS/)
|
||||||
|
|
||||||
## 快速开始
|
## 快速开始
|
||||||
|
|
||||||
@@ -57,7 +57,7 @@ pip install .
|
|||||||
|
|
||||||
3. 启动 Uni-Lab 系统:
|
3. 启动 Uni-Lab 系统:
|
||||||
|
|
||||||
请见[文档-启动样例](https://xuwznln.github.io/Uni-Lab-OS-Doc/boot_examples/index.html)
|
请见[文档-启动样例](https://dptech-corp.github.io/Uni-Lab-OS/boot_examples/index.html)
|
||||||
|
|
||||||
## 消息格式
|
## 消息格式
|
||||||
|
|
||||||
|
|||||||
14473
bioyond_yihua_YB.json
14473
bioyond_yihua_YB.json
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -91,7 +91,7 @@
|
|||||||
使用以下命令启动模拟反应器:
|
使用以下命令启动模拟反应器:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
unilab -g test/experiments/mock_reactor.json
|
unilab -g test/experiments/mock_reactor.json --app_bridges ""
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. 执行抽真空和充气操作
|
### 2. 执行抽真空和充气操作
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ extensions = [
|
|||||||
"myst_parser",
|
"myst_parser",
|
||||||
"sphinx.ext.autodoc",
|
"sphinx.ext.autodoc",
|
||||||
"sphinx.ext.napoleon", # 如果您使用 Google 或 NumPy 风格的 docstrings
|
"sphinx.ext.napoleon", # 如果您使用 Google 或 NumPy 风格的 docstrings
|
||||||
"sphinx_rtd_theme",
|
"sphinx_rtd_theme"
|
||||||
]
|
]
|
||||||
|
|
||||||
source_suffix = {
|
source_suffix = {
|
||||||
|
|||||||
@@ -1,68 +0,0 @@
|
|||||||
{
|
|
||||||
"nodes": [
|
|
||||||
{
|
|
||||||
"id": "bioyond_cell_workstation",
|
|
||||||
"name": "配液分液工站",
|
|
||||||
"parent": null,
|
|
||||||
"children": [
|
|
||||||
"YB_Bioyond_Deck"
|
|
||||||
],
|
|
||||||
"type": "device",
|
|
||||||
"class": "bioyond_cell",
|
|
||||||
"config": {
|
|
||||||
"deck": {
|
|
||||||
"data": {
|
|
||||||
"_resource_child_name": "YB_Bioyond_Deck",
|
|
||||||
"_resource_type": "unilabos.resources.bioyond.decks:BIOYOND_YB_Deck"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"protocol_type": []
|
|
||||||
},
|
|
||||||
"data": {}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "YB_Bioyond_Deck",
|
|
||||||
"name": "YB_Bioyond_Deck",
|
|
||||||
"children": [],
|
|
||||||
"parent": "bioyond_cell_workstation",
|
|
||||||
"type": "deck",
|
|
||||||
"class": "BIOYOND_YB_Deck",
|
|
||||||
"position": {
|
|
||||||
"x": 0,
|
|
||||||
"y": 0,
|
|
||||||
"z": 0
|
|
||||||
},
|
|
||||||
"config": {
|
|
||||||
"type": "BIOYOND_YB_Deck",
|
|
||||||
"setup": true,
|
|
||||||
"rotation": {
|
|
||||||
"x": 0,
|
|
||||||
"y": 0,
|
|
||||||
"z": 0,
|
|
||||||
"type": "Rotation"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"data": {}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "BatteryStation",
|
|
||||||
"name": "扣电工作站",
|
|
||||||
"children": [
|
|
||||||
"coin_cell_deck"
|
|
||||||
],
|
|
||||||
"parent": null,
|
|
||||||
"type": "device",
|
|
||||||
"class": "coincellassemblyworkstation_device",
|
|
||||||
"position": {
|
|
||||||
"x": 600,
|
|
||||||
"y": 400,
|
|
||||||
"z": 0
|
|
||||||
},
|
|
||||||
"config": {
|
|
||||||
"debug_mode": false,
|
|
||||||
"protocol_type": []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"links": []
|
|
||||||
}
|
|
||||||
@@ -172,7 +172,7 @@ Examples:
|
|||||||
with open(output_path, "w", encoding="utf-8") as f:
|
with open(output_path, "w", encoding="utf-8") as f:
|
||||||
f.write(readme_content)
|
f.write(readme_content)
|
||||||
|
|
||||||
print(f" README.txt created: {output_path}")
|
print(f"✓ README.txt created: {output_path}")
|
||||||
print(f" Platform: {args.platform}")
|
print(f" Platform: {args.platform}")
|
||||||
print(f" Branch: {args.branch}")
|
print(f" Branch: {args.branch}")
|
||||||
|
|
||||||
|
|||||||
@@ -8,10 +8,7 @@ This script verifies that UniLabOS and its dependencies are correctly installed.
|
|||||||
Run this script after installing the conda-pack environment to ensure everything works.
|
Run this script after installing the conda-pack environment to ensure everything works.
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
python verify_installation.py [--auto-install]
|
python verify_installation.py
|
||||||
|
|
||||||
Options:
|
|
||||||
--auto-install Automatically install missing packages
|
|
||||||
|
|
||||||
Or in the conda environment:
|
Or in the conda environment:
|
||||||
conda activate unilab
|
conda activate unilab
|
||||||
@@ -20,15 +17,14 @@ Usage:
|
|||||||
|
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
import argparse
|
|
||||||
|
|
||||||
# IMPORTANT: Set UTF-8 encoding BEFORE any other imports
|
# IMPORTANT: Set UTF-8 encoding BEFORE any other imports
|
||||||
# This ensures all subsequent imports (including unilabos) can output UTF-8 characters
|
# This ensures all subsequent imports (including unilabos) can output UTF-8 characters
|
||||||
if sys.platform == "win32":
|
if sys.platform == "win32":
|
||||||
# Method 1: Reconfigure stdout/stderr to use UTF-8 with error handling
|
# Method 1: Reconfigure stdout/stderr to use UTF-8 with error handling
|
||||||
try:
|
try:
|
||||||
sys.stdout.reconfigure(encoding="utf-8", errors="replace") # type: ignore
|
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
|
||||||
sys.stderr.reconfigure(encoding="utf-8", errors="replace") # type: ignore
|
sys.stderr.reconfigure(encoding="utf-8", errors="replace")
|
||||||
except (AttributeError, OSError):
|
except (AttributeError, OSError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -53,7 +49,7 @@ CHECK_MARK = "[OK]"
|
|||||||
CROSS_MARK = "[FAIL]"
|
CROSS_MARK = "[FAIL]"
|
||||||
|
|
||||||
|
|
||||||
def check_package(package_name: str, display_name: str | None = None) -> bool:
|
def check_package(package_name: str, display_name: str = None) -> bool:
|
||||||
"""
|
"""
|
||||||
Check if a package can be imported.
|
Check if a package can be imported.
|
||||||
|
|
||||||
@@ -91,25 +87,9 @@ def check_python_version() -> bool:
|
|||||||
|
|
||||||
def main():
|
def main():
|
||||||
"""Run all verification checks."""
|
"""Run all verification checks."""
|
||||||
# Parse command line arguments
|
|
||||||
parser = argparse.ArgumentParser(
|
|
||||||
description="Verify UniLabOS installation",
|
|
||||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--auto-install",
|
|
||||||
action="store_true",
|
|
||||||
help="Automatically install missing packages",
|
|
||||||
)
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
print("=" * 60)
|
print("=" * 60)
|
||||||
print("UniLabOS Installation Verification")
|
print("UniLabOS Installation Verification")
|
||||||
print("=" * 60)
|
print("=" * 60)
|
||||||
if args.auto_install:
|
|
||||||
print("Mode: Auto-install missing packages")
|
|
||||||
else:
|
|
||||||
print("Mode: Verification only")
|
|
||||||
print()
|
print()
|
||||||
|
|
||||||
all_passed = True
|
all_passed = True
|
||||||
@@ -133,16 +113,14 @@ def main():
|
|||||||
|
|
||||||
print(f" {CHECK_MARK} UniLabOS installed")
|
print(f" {CHECK_MARK} UniLabOS installed")
|
||||||
|
|
||||||
# Check environment with optional auto-install
|
# Check environment without auto-install (verification only)
|
||||||
# Set show_details=False to suppress detailed Chinese output that may cause encoding issues
|
# Set show_details=False to suppress detailed Chinese output that may cause encoding issues
|
||||||
env_check_passed = check_environment(auto_install=args.auto_install, show_details=False)
|
env_check_passed = check_environment(auto_install=False, show_details=False)
|
||||||
|
|
||||||
if env_check_passed:
|
if env_check_passed:
|
||||||
print(f" {CHECK_MARK} All required packages available")
|
print(f" {CHECK_MARK} All required packages available")
|
||||||
else:
|
else:
|
||||||
print(f" {CROSS_MARK} Some optional packages are missing")
|
print(f" {CROSS_MARK} Some optional packages are missing")
|
||||||
if not args.auto_install:
|
|
||||||
print(" Hint: Run with --auto-install to automatically install missing packages")
|
|
||||||
except ImportError:
|
except ImportError:
|
||||||
print(f" {CROSS_MARK} UniLabOS not installed")
|
print(f" {CROSS_MARK} UniLabOS not installed")
|
||||||
all_passed = False
|
all_passed = False
|
||||||
|
|||||||
@@ -1,695 +0,0 @@
|
|||||||
import json
|
|
||||||
import logging
|
|
||||||
import traceback
|
|
||||||
import uuid
|
|
||||||
import xml.etree.ElementTree as ET
|
|
||||||
from typing import Any, Dict, List
|
|
||||||
|
|
||||||
import networkx as nx
|
|
||||||
import matplotlib.pyplot as plt
|
|
||||||
import requests
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class SimpleGraph:
|
|
||||||
"""简单的有向图实现,用于构建工作流图"""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.nodes = {}
|
|
||||||
self.edges = []
|
|
||||||
|
|
||||||
def add_node(self, node_id, **attrs):
|
|
||||||
"""添加节点"""
|
|
||||||
self.nodes[node_id] = attrs
|
|
||||||
|
|
||||||
def add_edge(self, source, target, **attrs):
|
|
||||||
"""添加边"""
|
|
||||||
edge = {"source": source, "target": target, **attrs}
|
|
||||||
self.edges.append(edge)
|
|
||||||
|
|
||||||
def to_dict(self):
|
|
||||||
"""转换为工作流图格式"""
|
|
||||||
nodes_list = []
|
|
||||||
for node_id, attrs in self.nodes.items():
|
|
||||||
node_attrs = attrs.copy()
|
|
||||||
params = node_attrs.pop("parameters", {}) or {}
|
|
||||||
node_attrs.update(params)
|
|
||||||
nodes_list.append({"id": node_id, **node_attrs})
|
|
||||||
|
|
||||||
return {
|
|
||||||
"directed": True,
|
|
||||||
"multigraph": False,
|
|
||||||
"graph": {},
|
|
||||||
"nodes": nodes_list,
|
|
||||||
"links": self.edges,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def extract_json_from_markdown(text: str) -> str:
|
|
||||||
"""从markdown代码块中提取JSON"""
|
|
||||||
text = text.strip()
|
|
||||||
if text.startswith("```json\n"):
|
|
||||||
text = text[8:]
|
|
||||||
if text.startswith("```\n"):
|
|
||||||
text = text[4:]
|
|
||||||
if text.endswith("\n```"):
|
|
||||||
text = text[:-4]
|
|
||||||
return text
|
|
||||||
|
|
||||||
|
|
||||||
def convert_to_type(val: str) -> Any:
|
|
||||||
"""将字符串值转换为适当的数据类型"""
|
|
||||||
if val == "True":
|
|
||||||
return True
|
|
||||||
if val == "False":
|
|
||||||
return False
|
|
||||||
if val == "?":
|
|
||||||
return None
|
|
||||||
if val.endswith(" g"):
|
|
||||||
return float(val.split(" ")[0])
|
|
||||||
if val.endswith("mg"):
|
|
||||||
return float(val.split("mg")[0])
|
|
||||||
elif val.endswith("mmol"):
|
|
||||||
return float(val.split("mmol")[0]) / 1000
|
|
||||||
elif val.endswith("mol"):
|
|
||||||
return float(val.split("mol")[0])
|
|
||||||
elif val.endswith("ml"):
|
|
||||||
return float(val.split("ml")[0])
|
|
||||||
elif val.endswith("RPM"):
|
|
||||||
return float(val.split("RPM")[0])
|
|
||||||
elif val.endswith(" °C"):
|
|
||||||
return float(val.split(" ")[0])
|
|
||||||
elif val.endswith(" %"):
|
|
||||||
return float(val.split(" ")[0])
|
|
||||||
return val
|
|
||||||
|
|
||||||
|
|
||||||
def refactor_data(data: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
|
||||||
"""统一的数据重构函数,根据操作类型自动选择模板"""
|
|
||||||
refactored_data = []
|
|
||||||
|
|
||||||
# 定义操作映射,包含生物实验和有机化学的所有操作
|
|
||||||
OPERATION_MAPPING = {
|
|
||||||
# 生物实验操作
|
|
||||||
"transfer_liquid": "SynBioFactory-liquid_handler.prcxi-transfer_liquid",
|
|
||||||
"transfer": "SynBioFactory-liquid_handler.biomek-transfer",
|
|
||||||
"incubation": "SynBioFactory-liquid_handler.biomek-incubation",
|
|
||||||
"move_labware": "SynBioFactory-liquid_handler.biomek-move_labware",
|
|
||||||
"oscillation": "SynBioFactory-liquid_handler.biomek-oscillation",
|
|
||||||
# 有机化学操作
|
|
||||||
"HeatChillToTemp": "SynBioFactory-workstation-HeatChillProtocol",
|
|
||||||
"StopHeatChill": "SynBioFactory-workstation-HeatChillStopProtocol",
|
|
||||||
"StartHeatChill": "SynBioFactory-workstation-HeatChillStartProtocol",
|
|
||||||
"HeatChill": "SynBioFactory-workstation-HeatChillProtocol",
|
|
||||||
"Dissolve": "SynBioFactory-workstation-DissolveProtocol",
|
|
||||||
"Transfer": "SynBioFactory-workstation-TransferProtocol",
|
|
||||||
"Evaporate": "SynBioFactory-workstation-EvaporateProtocol",
|
|
||||||
"Recrystallize": "SynBioFactory-workstation-RecrystallizeProtocol",
|
|
||||||
"Filter": "SynBioFactory-workstation-FilterProtocol",
|
|
||||||
"Dry": "SynBioFactory-workstation-DryProtocol",
|
|
||||||
"Add": "SynBioFactory-workstation-AddProtocol",
|
|
||||||
}
|
|
||||||
|
|
||||||
UNSUPPORTED_OPERATIONS = ["Purge", "Wait", "Stir", "ResetHandling"]
|
|
||||||
|
|
||||||
for step in data:
|
|
||||||
operation = step.get("action")
|
|
||||||
if not operation or operation in UNSUPPORTED_OPERATIONS:
|
|
||||||
continue
|
|
||||||
|
|
||||||
# 处理重复操作
|
|
||||||
if operation == "Repeat":
|
|
||||||
times = step.get("times", step.get("parameters", {}).get("times", 1))
|
|
||||||
sub_steps = step.get("steps", step.get("parameters", {}).get("steps", []))
|
|
||||||
for i in range(int(times)):
|
|
||||||
sub_data = refactor_data(sub_steps)
|
|
||||||
refactored_data.extend(sub_data)
|
|
||||||
continue
|
|
||||||
|
|
||||||
# 获取模板名称
|
|
||||||
template = OPERATION_MAPPING.get(operation)
|
|
||||||
if not template:
|
|
||||||
# 自动推断模板类型
|
|
||||||
if operation.lower() in ["transfer", "incubation", "move_labware", "oscillation"]:
|
|
||||||
template = f"SynBioFactory-liquid_handler.biomek-{operation}"
|
|
||||||
else:
|
|
||||||
template = f"SynBioFactory-workstation-{operation}Protocol"
|
|
||||||
|
|
||||||
# 创建步骤数据
|
|
||||||
step_data = {
|
|
||||||
"template": template,
|
|
||||||
"description": step.get("description", step.get("purpose", f"{operation} operation")),
|
|
||||||
"lab_node_type": "Device",
|
|
||||||
"parameters": step.get("parameters", step.get("action_args", {})),
|
|
||||||
}
|
|
||||||
refactored_data.append(step_data)
|
|
||||||
|
|
||||||
return refactored_data
|
|
||||||
|
|
||||||
|
|
||||||
def build_protocol_graph(
|
|
||||||
labware_info: List[Dict[str, Any]], protocol_steps: List[Dict[str, Any]], workstation_name: str
|
|
||||||
) -> SimpleGraph:
|
|
||||||
"""统一的协议图构建函数,根据设备类型自动选择构建逻辑"""
|
|
||||||
G = SimpleGraph()
|
|
||||||
resource_last_writer = {}
|
|
||||||
LAB_NAME = "SynBioFactory"
|
|
||||||
|
|
||||||
protocol_steps = refactor_data(protocol_steps)
|
|
||||||
|
|
||||||
# 检查协议步骤中的模板来判断协议类型
|
|
||||||
has_biomek_template = any(
|
|
||||||
("biomek" in step.get("template", "")) or ("prcxi" in step.get("template", ""))
|
|
||||||
for step in protocol_steps
|
|
||||||
)
|
|
||||||
|
|
||||||
if has_biomek_template:
|
|
||||||
# 生物实验协议图构建
|
|
||||||
for labware_id, labware in labware_info.items():
|
|
||||||
node_id = str(uuid.uuid4())
|
|
||||||
|
|
||||||
labware_attrs = labware.copy()
|
|
||||||
labware_id = labware_attrs.pop("id", labware_attrs.get("name", f"labware_{uuid.uuid4()}"))
|
|
||||||
labware_attrs["description"] = labware_id
|
|
||||||
labware_attrs["lab_node_type"] = (
|
|
||||||
"Reagent" if "Plate" in str(labware_id) else "Labware" if "Rack" in str(labware_id) else "Sample"
|
|
||||||
)
|
|
||||||
labware_attrs["device_id"] = workstation_name
|
|
||||||
|
|
||||||
G.add_node(node_id, template=f"{LAB_NAME}-host_node-create_resource", **labware_attrs)
|
|
||||||
resource_last_writer[labware_id] = f"{node_id}:labware"
|
|
||||||
|
|
||||||
# 处理协议步骤
|
|
||||||
prev_node = None
|
|
||||||
for i, step in enumerate(protocol_steps):
|
|
||||||
node_id = str(uuid.uuid4())
|
|
||||||
G.add_node(node_id, **step)
|
|
||||||
|
|
||||||
# 添加控制流边
|
|
||||||
if prev_node is not None:
|
|
||||||
G.add_edge(prev_node, node_id, source_port="ready", target_port="ready")
|
|
||||||
prev_node = node_id
|
|
||||||
|
|
||||||
# 处理物料流
|
|
||||||
params = step.get("parameters", {})
|
|
||||||
if "sources" in params and params["sources"] in resource_last_writer:
|
|
||||||
source_node, source_port = resource_last_writer[params["sources"]].split(":")
|
|
||||||
G.add_edge(source_node, node_id, source_port=source_port, target_port="labware")
|
|
||||||
|
|
||||||
if "targets" in params:
|
|
||||||
resource_last_writer[params["targets"]] = f"{node_id}:labware"
|
|
||||||
|
|
||||||
# 添加协议结束节点
|
|
||||||
end_id = str(uuid.uuid4())
|
|
||||||
G.add_node(end_id, template=f"{LAB_NAME}-liquid_handler.biomek-run_protocol")
|
|
||||||
if prev_node is not None:
|
|
||||||
G.add_edge(prev_node, end_id, source_port="ready", target_port="ready")
|
|
||||||
|
|
||||||
else:
|
|
||||||
# 有机化学协议图构建
|
|
||||||
WORKSTATION_ID = workstation_name
|
|
||||||
|
|
||||||
# 为所有labware创建资源节点
|
|
||||||
for item_id, item in labware_info.items():
|
|
||||||
# item_id = item.get("id") or item.get("name", f"item_{uuid.uuid4()}")
|
|
||||||
node_id = str(uuid.uuid4())
|
|
||||||
|
|
||||||
# 判断节点类型
|
|
||||||
if item.get("type") == "hardware" or "reactor" in str(item_id).lower():
|
|
||||||
if "reactor" not in str(item_id).lower():
|
|
||||||
continue
|
|
||||||
lab_node_type = "Sample"
|
|
||||||
description = f"Prepare Reactor: {item_id}"
|
|
||||||
liquid_type = []
|
|
||||||
liquid_volume = []
|
|
||||||
else:
|
|
||||||
lab_node_type = "Reagent"
|
|
||||||
description = f"Add Reagent to Flask: {item_id}"
|
|
||||||
liquid_type = [item_id]
|
|
||||||
liquid_volume = [1e5]
|
|
||||||
|
|
||||||
G.add_node(
|
|
||||||
node_id,
|
|
||||||
template=f"{LAB_NAME}-host_node-create_resource",
|
|
||||||
description=description,
|
|
||||||
lab_node_type=lab_node_type,
|
|
||||||
res_id=item_id,
|
|
||||||
device_id=WORKSTATION_ID,
|
|
||||||
class_name="container",
|
|
||||||
parent=WORKSTATION_ID,
|
|
||||||
bind_locations={"x": 0.0, "y": 0.0, "z": 0.0},
|
|
||||||
liquid_input_slot=[-1],
|
|
||||||
liquid_type=liquid_type,
|
|
||||||
liquid_volume=liquid_volume,
|
|
||||||
slot_on_deck="",
|
|
||||||
role=item.get("role", ""),
|
|
||||||
)
|
|
||||||
resource_last_writer[item_id] = f"{node_id}:labware"
|
|
||||||
|
|
||||||
last_control_node_id = None
|
|
||||||
|
|
||||||
# 处理协议步骤
|
|
||||||
for step in protocol_steps:
|
|
||||||
node_id = str(uuid.uuid4())
|
|
||||||
G.add_node(node_id, **step)
|
|
||||||
|
|
||||||
# 控制流
|
|
||||||
if last_control_node_id is not None:
|
|
||||||
G.add_edge(last_control_node_id, node_id, source_port="ready", target_port="ready")
|
|
||||||
last_control_node_id = node_id
|
|
||||||
|
|
||||||
# 物料流
|
|
||||||
params = step.get("parameters", {})
|
|
||||||
input_resources = {
|
|
||||||
"Vessel": params.get("vessel"),
|
|
||||||
"ToVessel": params.get("to_vessel"),
|
|
||||||
"FromVessel": params.get("from_vessel"),
|
|
||||||
"reagent": params.get("reagent"),
|
|
||||||
"solvent": params.get("solvent"),
|
|
||||||
"compound": params.get("compound"),
|
|
||||||
"sources": params.get("sources"),
|
|
||||||
"targets": params.get("targets"),
|
|
||||||
}
|
|
||||||
|
|
||||||
for target_port, resource_name in input_resources.items():
|
|
||||||
if resource_name and resource_name in resource_last_writer:
|
|
||||||
source_node, source_port = resource_last_writer[resource_name].split(":")
|
|
||||||
G.add_edge(source_node, node_id, source_port=source_port, target_port=target_port)
|
|
||||||
|
|
||||||
output_resources = {
|
|
||||||
"VesselOut": params.get("vessel"),
|
|
||||||
"FromVesselOut": params.get("from_vessel"),
|
|
||||||
"ToVesselOut": params.get("to_vessel"),
|
|
||||||
"FiltrateOut": params.get("filtrate_vessel"),
|
|
||||||
"reagent": params.get("reagent"),
|
|
||||||
"solvent": params.get("solvent"),
|
|
||||||
"compound": params.get("compound"),
|
|
||||||
"sources_out": params.get("sources"),
|
|
||||||
"targets_out": params.get("targets"),
|
|
||||||
}
|
|
||||||
|
|
||||||
for source_port, resource_name in output_resources.items():
|
|
||||||
if resource_name:
|
|
||||||
resource_last_writer[resource_name] = f"{node_id}:{source_port}"
|
|
||||||
|
|
||||||
return G
|
|
||||||
|
|
||||||
|
|
||||||
def draw_protocol_graph(protocol_graph: SimpleGraph, output_path: str):
|
|
||||||
"""
|
|
||||||
(辅助功能) 使用 networkx 和 matplotlib 绘制协议工作流图,用于可视化。
|
|
||||||
"""
|
|
||||||
if not protocol_graph:
|
|
||||||
print("Cannot draw graph: Graph object is empty.")
|
|
||||||
return
|
|
||||||
|
|
||||||
G = nx.DiGraph()
|
|
||||||
|
|
||||||
for node_id, attrs in protocol_graph.nodes.items():
|
|
||||||
label = attrs.get("description", attrs.get("template", node_id[:8]))
|
|
||||||
G.add_node(node_id, label=label, **attrs)
|
|
||||||
|
|
||||||
for edge in protocol_graph.edges:
|
|
||||||
G.add_edge(edge["source"], edge["target"])
|
|
||||||
|
|
||||||
plt.figure(figsize=(20, 15))
|
|
||||||
try:
|
|
||||||
pos = nx.nx_agraph.graphviz_layout(G, prog="dot")
|
|
||||||
except Exception:
|
|
||||||
pos = nx.shell_layout(G) # Fallback layout
|
|
||||||
|
|
||||||
node_labels = {node: data["label"] for node, data in G.nodes(data=True)}
|
|
||||||
nx.draw(
|
|
||||||
G,
|
|
||||||
pos,
|
|
||||||
with_labels=False,
|
|
||||||
node_size=2500,
|
|
||||||
node_color="skyblue",
|
|
||||||
node_shape="o",
|
|
||||||
edge_color="gray",
|
|
||||||
width=1.5,
|
|
||||||
arrowsize=15,
|
|
||||||
)
|
|
||||||
nx.draw_networkx_labels(G, pos, labels=node_labels, font_size=8, font_weight="bold")
|
|
||||||
|
|
||||||
plt.title("Chemical Protocol Workflow Graph", size=15)
|
|
||||||
plt.savefig(output_path, dpi=300, bbox_inches="tight")
|
|
||||||
plt.close()
|
|
||||||
print(f" - Visualization saved to '{output_path}'")
|
|
||||||
|
|
||||||
|
|
||||||
from networkx.drawing.nx_agraph import to_agraph
|
|
||||||
import re
|
|
||||||
|
|
||||||
COMPASS = {"n","e","s","w","ne","nw","se","sw","c"}
|
|
||||||
|
|
||||||
def _is_compass(port: str) -> bool:
|
|
||||||
return isinstance(port, str) and port.lower() in COMPASS
|
|
||||||
|
|
||||||
def draw_protocol_graph_with_ports(protocol_graph, output_path: str, rankdir: str = "LR"):
|
|
||||||
"""
|
|
||||||
使用 Graphviz 端口语法绘制协议工作流图。
|
|
||||||
- 若边上的 source_port/target_port 是 compass(n/e/s/w/...),直接用 compass。
|
|
||||||
- 否则自动为节点创建 record 形状并定义命名端口 <portname>。
|
|
||||||
最终由 PyGraphviz 渲染并输出到 output_path(后缀决定格式,如 .png/.svg/.pdf)。
|
|
||||||
"""
|
|
||||||
if not protocol_graph:
|
|
||||||
print("Cannot draw graph: Graph object is empty.")
|
|
||||||
return
|
|
||||||
|
|
||||||
# 1) 先用 networkx 搭建有向图,保留端口属性
|
|
||||||
G = nx.DiGraph()
|
|
||||||
for node_id, attrs in protocol_graph.nodes.items():
|
|
||||||
label = attrs.get("description", attrs.get("template", node_id[:8]))
|
|
||||||
# 保留一个干净的“中心标签”,用于放在 record 的中间槽
|
|
||||||
G.add_node(node_id, _core_label=str(label), **{k:v for k,v in attrs.items() if k not in ("label",)})
|
|
||||||
|
|
||||||
edges_data = []
|
|
||||||
in_ports_by_node = {} # 收集命名输入端口
|
|
||||||
out_ports_by_node = {} # 收集命名输出端口
|
|
||||||
|
|
||||||
for edge in protocol_graph.edges:
|
|
||||||
u = edge["source"]
|
|
||||||
v = edge["target"]
|
|
||||||
sp = edge.get("source_port")
|
|
||||||
tp = edge.get("target_port")
|
|
||||||
|
|
||||||
# 记录到图里(保留原始端口信息)
|
|
||||||
G.add_edge(u, v, source_port=sp, target_port=tp)
|
|
||||||
edges_data.append((u, v, sp, tp))
|
|
||||||
|
|
||||||
# 如果不是 compass,就按“命名端口”先归类,等会儿给节点造 record
|
|
||||||
if sp and not _is_compass(sp):
|
|
||||||
out_ports_by_node.setdefault(u, set()).add(str(sp))
|
|
||||||
if tp and not _is_compass(tp):
|
|
||||||
in_ports_by_node.setdefault(v, set()).add(str(tp))
|
|
||||||
|
|
||||||
# 2) 转为 AGraph,使用 Graphviz 渲染
|
|
||||||
A = to_agraph(G)
|
|
||||||
A.graph_attr.update(rankdir=rankdir, splines="true", concentrate="false", fontsize="10")
|
|
||||||
A.node_attr.update(shape="box", style="rounded,filled", fillcolor="lightyellow", color="#999999", fontname="Helvetica")
|
|
||||||
A.edge_attr.update(arrowsize="0.8", color="#666666")
|
|
||||||
|
|
||||||
# 3) 为需要命名端口的节点设置 record 形状与 label
|
|
||||||
# 左列 = 输入端口;中间 = 核心标签;右列 = 输出端口
|
|
||||||
for n in A.nodes():
|
|
||||||
node = A.get_node(n)
|
|
||||||
core = G.nodes[n].get("_core_label", n)
|
|
||||||
|
|
||||||
in_ports = sorted(in_ports_by_node.get(n, []))
|
|
||||||
out_ports = sorted(out_ports_by_node.get(n, []))
|
|
||||||
|
|
||||||
# 如果该节点涉及命名端口,则用 record;否则保留原 box
|
|
||||||
if in_ports or out_ports:
|
|
||||||
def port_fields(ports):
|
|
||||||
if not ports:
|
|
||||||
return " " # 必须留一个空槽占位
|
|
||||||
# 每个端口一个小格子,<p> name
|
|
||||||
return "|".join(f"<{re.sub(r'[^A-Za-z0-9_:.|-]', '_', p)}> {p}" for p in ports)
|
|
||||||
|
|
||||||
left = port_fields(in_ports)
|
|
||||||
right = port_fields(out_ports)
|
|
||||||
|
|
||||||
# 三栏:左(入) | 中(节点名) | 右(出)
|
|
||||||
record_label = f"{{ {left} | {core} | {right} }}"
|
|
||||||
node.attr.update(shape="record", label=record_label)
|
|
||||||
else:
|
|
||||||
# 没有命名端口:普通盒子,显示核心标签
|
|
||||||
node.attr.update(label=str(core))
|
|
||||||
|
|
||||||
# 4) 给边设置 headport / tailport
|
|
||||||
# - 若端口为 compass:直接用 compass(e.g., headport="e")
|
|
||||||
# - 若端口为命名端口:使用在 record 中定义的 <port> 名(同名即可)
|
|
||||||
for (u, v, sp, tp) in edges_data:
|
|
||||||
e = A.get_edge(u, v)
|
|
||||||
|
|
||||||
# Graphviz 属性:tail 是源,head 是目标
|
|
||||||
if sp:
|
|
||||||
if _is_compass(sp):
|
|
||||||
e.attr["tailport"] = sp.lower()
|
|
||||||
else:
|
|
||||||
# 与 record label 中 <port> 名一致;特殊字符已在 label 中做了清洗
|
|
||||||
e.attr["tailport"] = re.sub(r'[^A-Za-z0-9_:.|-]', '_', str(sp))
|
|
||||||
|
|
||||||
if tp:
|
|
||||||
if _is_compass(tp):
|
|
||||||
e.attr["headport"] = tp.lower()
|
|
||||||
else:
|
|
||||||
e.attr["headport"] = re.sub(r'[^A-Za-z0-9_:.|-]', '_', str(tp))
|
|
||||||
|
|
||||||
# 可选:若想让边更贴边缘,可设置 constraint/spline 等
|
|
||||||
# e.attr["arrowhead"] = "vee"
|
|
||||||
|
|
||||||
# 5) 输出
|
|
||||||
A.draw(output_path, prog="dot")
|
|
||||||
print(f" - Port-aware workflow rendered to '{output_path}'")
|
|
||||||
|
|
||||||
|
|
||||||
def flatten_xdl_procedure(procedure_elem: ET.Element) -> List[ET.Element]:
|
|
||||||
"""展平嵌套的XDL程序结构"""
|
|
||||||
flattened_operations = []
|
|
||||||
TEMP_UNSUPPORTED_PROTOCOL = ["Purge", "Wait", "Stir", "ResetHandling"]
|
|
||||||
|
|
||||||
def extract_operations(element: ET.Element):
|
|
||||||
if element.tag not in ["Prep", "Reaction", "Workup", "Purification", "Procedure"]:
|
|
||||||
if element.tag not in TEMP_UNSUPPORTED_PROTOCOL:
|
|
||||||
flattened_operations.append(element)
|
|
||||||
|
|
||||||
for child in element:
|
|
||||||
extract_operations(child)
|
|
||||||
|
|
||||||
for child in procedure_elem:
|
|
||||||
extract_operations(child)
|
|
||||||
|
|
||||||
return flattened_operations
|
|
||||||
|
|
||||||
|
|
||||||
def parse_xdl_content(xdl_content: str) -> tuple:
|
|
||||||
"""解析XDL内容"""
|
|
||||||
try:
|
|
||||||
xdl_content_cleaned = "".join(c for c in xdl_content if c.isprintable())
|
|
||||||
root = ET.fromstring(xdl_content_cleaned)
|
|
||||||
|
|
||||||
synthesis_elem = root.find("Synthesis")
|
|
||||||
if synthesis_elem is None:
|
|
||||||
return None, None, None
|
|
||||||
|
|
||||||
# 解析硬件组件
|
|
||||||
hardware_elem = synthesis_elem.find("Hardware")
|
|
||||||
hardware = []
|
|
||||||
if hardware_elem is not None:
|
|
||||||
hardware = [{"id": c.get("id"), "type": c.get("type")} for c in hardware_elem.findall("Component")]
|
|
||||||
|
|
||||||
# 解析试剂
|
|
||||||
reagents_elem = synthesis_elem.find("Reagents")
|
|
||||||
reagents = []
|
|
||||||
if reagents_elem is not None:
|
|
||||||
reagents = [{"name": r.get("name"), "role": r.get("role", "")} for r in reagents_elem.findall("Reagent")]
|
|
||||||
|
|
||||||
# 解析程序
|
|
||||||
procedure_elem = synthesis_elem.find("Procedure")
|
|
||||||
if procedure_elem is None:
|
|
||||||
return None, None, None
|
|
||||||
|
|
||||||
flattened_operations = flatten_xdl_procedure(procedure_elem)
|
|
||||||
return hardware, reagents, flattened_operations
|
|
||||||
|
|
||||||
except ET.ParseError as e:
|
|
||||||
raise ValueError(f"Invalid XDL format: {e}")
|
|
||||||
|
|
||||||
|
|
||||||
def convert_xdl_to_dict(xdl_content: str) -> Dict[str, Any]:
|
|
||||||
"""
|
|
||||||
将XDL XML格式转换为标准的字典格式
|
|
||||||
|
|
||||||
Args:
|
|
||||||
xdl_content: XDL XML内容
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
转换结果,包含步骤和器材信息
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
hardware, reagents, flattened_operations = parse_xdl_content(xdl_content)
|
|
||||||
if hardware is None:
|
|
||||||
return {"error": "Failed to parse XDL content", "success": False}
|
|
||||||
|
|
||||||
# 将XDL元素转换为字典格式
|
|
||||||
steps_data = []
|
|
||||||
for elem in flattened_operations:
|
|
||||||
# 转换参数类型
|
|
||||||
parameters = {}
|
|
||||||
for key, val in elem.attrib.items():
|
|
||||||
converted_val = convert_to_type(val)
|
|
||||||
if converted_val is not None:
|
|
||||||
parameters[key] = converted_val
|
|
||||||
|
|
||||||
step_dict = {
|
|
||||||
"operation": elem.tag,
|
|
||||||
"parameters": parameters,
|
|
||||||
"description": elem.get("purpose", f"Operation: {elem.tag}"),
|
|
||||||
}
|
|
||||||
steps_data.append(step_dict)
|
|
||||||
|
|
||||||
# 合并硬件和试剂为统一的labware_info格式
|
|
||||||
labware_data = []
|
|
||||||
labware_data.extend({"id": hw["id"], "type": "hardware", **hw} for hw in hardware)
|
|
||||||
labware_data.extend({"name": reagent["name"], "type": "reagent", **reagent} for reagent in reagents)
|
|
||||||
|
|
||||||
return {
|
|
||||||
"success": True,
|
|
||||||
"steps": steps_data,
|
|
||||||
"labware": labware_data,
|
|
||||||
"message": f"Successfully converted XDL to dict format. Found {len(steps_data)} steps and {len(labware_data)} labware items.",
|
|
||||||
}
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
error_msg = f"XDL conversion failed: {str(e)}"
|
|
||||||
logger.error(error_msg)
|
|
||||||
return {"error": error_msg, "success": False}
|
|
||||||
|
|
||||||
|
|
||||||
def create_workflow(
|
|
||||||
steps_info: str,
|
|
||||||
labware_info: str,
|
|
||||||
workflow_name: str = "Generated Workflow",
|
|
||||||
workstation_name: str = "workstation",
|
|
||||||
workflow_description: str = "Auto-generated workflow from protocol",
|
|
||||||
) -> Dict[str, Any]:
|
|
||||||
"""
|
|
||||||
创建工作流,输入数据已经是统一的字典格式
|
|
||||||
|
|
||||||
Args:
|
|
||||||
steps_info: 步骤信息 (JSON字符串,已经是list of dict格式)
|
|
||||||
labware_info: 实验器材和试剂信息 (JSON字符串,已经是list of dict格式)
|
|
||||||
workflow_name: 工作流名称
|
|
||||||
workflow_description: 工作流描述
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
创建结果,包含工作流UUID和详细信息
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
# 直接解析JSON数据
|
|
||||||
steps_info_clean = extract_json_from_markdown(steps_info)
|
|
||||||
labware_info_clean = extract_json_from_markdown(labware_info)
|
|
||||||
|
|
||||||
steps_data = json.loads(steps_info_clean)
|
|
||||||
labware_data = json.loads(labware_info_clean)
|
|
||||||
|
|
||||||
# 统一处理所有数据
|
|
||||||
protocol_graph = build_protocol_graph(labware_data, steps_data, workstation_name=workstation_name)
|
|
||||||
|
|
||||||
# 检测协议类型(用于标签)
|
|
||||||
protocol_type = "bio" if any("biomek" in step.get("template", "") for step in refactored_steps) else "organic"
|
|
||||||
|
|
||||||
# 转换为工作流格式
|
|
||||||
data = protocol_graph.to_dict()
|
|
||||||
|
|
||||||
# 转换节点格式
|
|
||||||
for i, node in enumerate(data["nodes"]):
|
|
||||||
description = node.get("description", "")
|
|
||||||
onode = {
|
|
||||||
"template": node.pop("template"),
|
|
||||||
"id": node["id"],
|
|
||||||
"lab_node_type": node.get("lab_node_type", "Device"),
|
|
||||||
"name": description or f"Node {i + 1}",
|
|
||||||
"params": {"default": node},
|
|
||||||
"handles": {},
|
|
||||||
}
|
|
||||||
|
|
||||||
# 处理边连接
|
|
||||||
for edge in data["links"]:
|
|
||||||
if edge["source"] == node["id"]:
|
|
||||||
source_port = edge.get("source_port", "output")
|
|
||||||
if source_port not in onode["handles"]:
|
|
||||||
onode["handles"][source_port] = {"type": "source"}
|
|
||||||
|
|
||||||
if edge["target"] == node["id"]:
|
|
||||||
target_port = edge.get("target_port", "input")
|
|
||||||
if target_port not in onode["handles"]:
|
|
||||||
onode["handles"][target_port] = {"type": "target"}
|
|
||||||
|
|
||||||
data["nodes"][i] = onode
|
|
||||||
|
|
||||||
# 发送到API创建工作流
|
|
||||||
api_secret = configs.Lab.Key
|
|
||||||
if not api_secret:
|
|
||||||
return {"error": "API SecretKey is not configured", "success": False}
|
|
||||||
|
|
||||||
# Step 1: 创建工作流
|
|
||||||
workflow_url = f"{configs.Lab.Api}/api/v1/workflow/"
|
|
||||||
headers = {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
}
|
|
||||||
params = {"secret_key": api_secret}
|
|
||||||
|
|
||||||
graph_data = {"name": workflow_name, **data}
|
|
||||||
|
|
||||||
logger.info(f"Creating workflow: {workflow_name}")
|
|
||||||
response = requests.post(
|
|
||||||
workflow_url, params=params, json=graph_data, headers=headers, timeout=configs.Lab.Timeout
|
|
||||||
)
|
|
||||||
response.raise_for_status()
|
|
||||||
|
|
||||||
workflow_info = response.json()
|
|
||||||
|
|
||||||
if workflow_info.get("code") != 0:
|
|
||||||
error_msg = f"API returned an error: {workflow_info.get('msg', 'Unknown Error')}"
|
|
||||||
logger.error(error_msg)
|
|
||||||
return {"error": error_msg, "success": False}
|
|
||||||
|
|
||||||
workflow_uuid = workflow_info.get("data", {}).get("uuid")
|
|
||||||
if not workflow_uuid:
|
|
||||||
return {"error": "Failed to get workflow UUID from response", "success": False}
|
|
||||||
|
|
||||||
# Step 2: 添加到模板库(可选)
|
|
||||||
try:
|
|
||||||
library_url = f"{configs.Lab.Api}/api/flociety/vs/workflows/library/"
|
|
||||||
lib_payload = {
|
|
||||||
"workflow_uuid": workflow_uuid,
|
|
||||||
"title": workflow_name,
|
|
||||||
"description": workflow_description,
|
|
||||||
"labels": [protocol_type.title(), "Auto-generated"],
|
|
||||||
}
|
|
||||||
|
|
||||||
library_response = requests.post(
|
|
||||||
library_url, params=params, json=lib_payload, headers=headers, timeout=configs.Lab.Timeout
|
|
||||||
)
|
|
||||||
library_response.raise_for_status()
|
|
||||||
|
|
||||||
library_info = library_response.json()
|
|
||||||
logger.info(f"Workflow added to library: {library_info}")
|
|
||||||
|
|
||||||
return {
|
|
||||||
"success": True,
|
|
||||||
"workflow_uuid": workflow_uuid,
|
|
||||||
"workflow_info": workflow_info.get("data"),
|
|
||||||
"library_info": library_info.get("data"),
|
|
||||||
"protocol_type": protocol_type,
|
|
||||||
"message": f"Workflow '{workflow_name}' created successfully",
|
|
||||||
}
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
# 即使添加到库失败,工作流创建仍然成功
|
|
||||||
logger.warning(f"Failed to add workflow to library: {str(e)}")
|
|
||||||
return {
|
|
||||||
"success": True,
|
|
||||||
"workflow_uuid": workflow_uuid,
|
|
||||||
"workflow_info": workflow_info.get("data"),
|
|
||||||
"protocol_type": protocol_type,
|
|
||||||
"message": f"Workflow '{workflow_name}' created successfully (library addition failed)",
|
|
||||||
}
|
|
||||||
|
|
||||||
except requests.exceptions.RequestException as e:
|
|
||||||
error_msg = f"Network error when calling API: {str(e)}"
|
|
||||||
logger.error(error_msg)
|
|
||||||
return {"error": error_msg, "success": False}
|
|
||||||
except json.JSONDecodeError as e:
|
|
||||||
error_msg = f"JSON parsing error: {str(e)}"
|
|
||||||
logger.error(error_msg)
|
|
||||||
return {"error": error_msg, "success": False}
|
|
||||||
except Exception as e:
|
|
||||||
error_msg = f"An unexpected error occurred: {str(e)}"
|
|
||||||
logger.error(error_msg)
|
|
||||||
logger.error(traceback.format_exc())
|
|
||||||
return {"error": error_msg, "success": False}
|
|
||||||
@@ -170,16 +170,15 @@
|
|||||||
"z": 0
|
"z": 0
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"max_volume": 1000.0,
|
"max_volume": 1000.0
|
||||||
"type": "RegularContainer",
|
|
||||||
"category": "container",
|
|
||||||
"size_x": 200,
|
|
||||||
"size_y": 150,
|
|
||||||
"size_z": 0
|
|
||||||
},
|
},
|
||||||
"data": {
|
"data": {
|
||||||
"liquids": [["DMF", 500.0]],
|
"liquids": [
|
||||||
"pending_liquids": [["DMF", 500.0]]
|
{
|
||||||
|
"liquid_type": "DMF",
|
||||||
|
"liquid_volume": 1000.0
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -195,16 +194,15 @@
|
|||||||
"z": 0
|
"z": 0
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"max_volume": 1000.0,
|
"max_volume": 1000.0
|
||||||
"type": "RegularContainer",
|
|
||||||
"category": "container",
|
|
||||||
"size_x": 200,
|
|
||||||
"size_y": 150,
|
|
||||||
"size_z": 0
|
|
||||||
},
|
},
|
||||||
"data": {
|
"data": {
|
||||||
"liquids": [["ethyl_acetate", 1000.0]],
|
"liquids": [
|
||||||
"pending_liquids": [["ethyl_acetate", 1000.0]]
|
{
|
||||||
|
"liquid_type": "ethyl_acetate",
|
||||||
|
"liquid_volume": 1000.0
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -220,16 +218,15 @@
|
|||||||
"z": 0
|
"z": 0
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"max_volume": 1000.0,
|
"max_volume": 1000.0
|
||||||
"type": "RegularContainer",
|
|
||||||
"category": "container",
|
|
||||||
"size_x": 300,
|
|
||||||
"size_y": 150,
|
|
||||||
"size_z": 0
|
|
||||||
},
|
},
|
||||||
"data": {
|
"data": {
|
||||||
"liquids": [["hexane", 1000.0]],
|
"liquids": [
|
||||||
"pending_liquids": [["hexane", 1000.0]]
|
{
|
||||||
|
"liquid_type": "hexane",
|
||||||
|
"liquid_volume": 1000.0
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -245,16 +242,15 @@
|
|||||||
"z": 0
|
"z": 0
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"max_volume": 1000.0,
|
"max_volume": 1000.0
|
||||||
"type": "RegularContainer",
|
|
||||||
"category": "container",
|
|
||||||
"size_x": 900,
|
|
||||||
"size_y": 150,
|
|
||||||
"size_z": 0
|
|
||||||
},
|
},
|
||||||
"data": {
|
"data": {
|
||||||
"liquids": [["methanol", 1000.0]],
|
"liquids": [
|
||||||
"pending_liquids": [["methanol", 1000.0]]
|
{
|
||||||
|
"liquid_type": "methanol",
|
||||||
|
"liquid_volume": 1000.0
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -270,16 +266,15 @@
|
|||||||
"z": 0
|
"z": 0
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"max_volume": 1000.0,
|
"max_volume": 1000.0
|
||||||
"type": "RegularContainer",
|
|
||||||
"category": "container",
|
|
||||||
"size_x": 950,
|
|
||||||
"size_y": 150,
|
|
||||||
"size_z": 0
|
|
||||||
},
|
},
|
||||||
"data": {
|
"data": {
|
||||||
"liquids": [["water", 1000.0]],
|
"liquids": [
|
||||||
"pending_liquids": [["water", 1000.0]]
|
{
|
||||||
|
"liquid_type": "water",
|
||||||
|
"liquid_volume": 1000.0
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -340,16 +335,14 @@
|
|||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"max_volume": 500.0,
|
"max_volume": 500.0,
|
||||||
"type": "RegularContainer",
|
|
||||||
"category": "container",
|
|
||||||
"max_temp": 200.0,
|
"max_temp": 200.0,
|
||||||
"min_temp": -20.0,
|
"min_temp": -20.0,
|
||||||
"has_stirrer": true,
|
"has_stirrer": true,
|
||||||
"has_heater": true
|
"has_heater": true
|
||||||
},
|
},
|
||||||
"data": {
|
"data": {
|
||||||
"liquids": [],
|
"liquids": [
|
||||||
"pending_liquids": []
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -426,16 +419,11 @@
|
|||||||
"z": 0
|
"z": 0
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"max_volume": 2000.0,
|
"max_volume": 2000.0
|
||||||
"type": "RegularContainer",
|
|
||||||
"category": "container",
|
|
||||||
"size_x": 500,
|
|
||||||
"size_y": 400,
|
|
||||||
"size_z": 0
|
|
||||||
},
|
},
|
||||||
"data": {
|
"data": {
|
||||||
"liquids": [],
|
"liquids": [
|
||||||
"pending_liquids": []
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -451,16 +439,11 @@
|
|||||||
"z": 0
|
"z": 0
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"max_volume": 2000.0,
|
"max_volume": 2000.0
|
||||||
"type": "RegularContainer",
|
|
||||||
"category": "container",
|
|
||||||
"size_x": 1100,
|
|
||||||
"size_y": 500,
|
|
||||||
"size_z": 0
|
|
||||||
},
|
},
|
||||||
"data": {
|
"data": {
|
||||||
"liquids": [],
|
"liquids": [
|
||||||
"pending_liquids": []
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -666,16 +649,11 @@
|
|||||||
"z": 0
|
"z": 0
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"max_volume": 250.0,
|
"max_volume": 250.0
|
||||||
"type": "RegularContainer",
|
|
||||||
"category": "container",
|
|
||||||
"size_x": 900,
|
|
||||||
"size_y": 500,
|
|
||||||
"size_z": 0
|
|
||||||
},
|
},
|
||||||
"data": {
|
"data": {
|
||||||
"liquids": [],
|
"liquids": [
|
||||||
"pending_liquids": []
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -691,16 +669,11 @@
|
|||||||
"z": 0
|
"z": 0
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"max_volume": 250.0,
|
"max_volume": 250.0
|
||||||
"type": "RegularContainer",
|
|
||||||
"category": "container",
|
|
||||||
"size_x": 950,
|
|
||||||
"size_y": 500,
|
|
||||||
"size_z": 0
|
|
||||||
},
|
},
|
||||||
"data": {
|
"data": {
|
||||||
"liquids": [],
|
"liquids": [
|
||||||
"pending_liquids": []
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -716,16 +689,11 @@
|
|||||||
"z": 0
|
"z": 0
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"max_volume": 250.0,
|
"max_volume": 250.0
|
||||||
"type": "RegularContainer",
|
|
||||||
"category": "container",
|
|
||||||
"size_x": 1050,
|
|
||||||
"size_y": 500,
|
|
||||||
"size_z": 0
|
|
||||||
},
|
},
|
||||||
"data": {
|
"data": {
|
||||||
"liquids": [],
|
"liquids": [
|
||||||
"pending_liquids": []
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -765,11 +733,6 @@
|
|||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"max_volume": 500.0,
|
"max_volume": 500.0,
|
||||||
"size_x": 550,
|
|
||||||
"size_y": 250,
|
|
||||||
"size_z": 0,
|
|
||||||
"type": "RegularContainer",
|
|
||||||
"category": "container",
|
|
||||||
"reagent": "sodium_chloride",
|
"reagent": "sodium_chloride",
|
||||||
"physical_state": "solid"
|
"physical_state": "solid"
|
||||||
},
|
},
|
||||||
@@ -793,11 +756,6 @@
|
|||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"volume": 500.0,
|
"volume": 500.0,
|
||||||
"size_x": 600,
|
|
||||||
"size_y": 250,
|
|
||||||
"size_z": 0,
|
|
||||||
"type": "RegularContainer",
|
|
||||||
"category": "container",
|
|
||||||
"reagent": "sodium_carbonate",
|
"reagent": "sodium_carbonate",
|
||||||
"physical_state": "solid"
|
"physical_state": "solid"
|
||||||
},
|
},
|
||||||
@@ -821,11 +779,6 @@
|
|||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"volume": 500.0,
|
"volume": 500.0,
|
||||||
"size_x": 650,
|
|
||||||
"size_y": 250,
|
|
||||||
"size_z": 0,
|
|
||||||
"type": "RegularContainer",
|
|
||||||
"category": "container",
|
|
||||||
"reagent": "magnesium_chloride",
|
"reagent": "magnesium_chloride",
|
||||||
"physical_state": "solid"
|
"physical_state": "solid"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
],
|
],
|
||||||
"parent": null,
|
"parent": null,
|
||||||
"type": "device",
|
"type": "device",
|
||||||
"class": "bioyond_dispensing_station",
|
"class": "dispensing_station.bioyond",
|
||||||
"config": {
|
"config": {
|
||||||
"config": {
|
"config": {
|
||||||
"api_key": "DE9BDDA0",
|
"api_key": "DE9BDDA0",
|
||||||
@@ -20,6 +20,13 @@
|
|||||||
"_resource_type": "unilabos.resources.bioyond.decks:BIOYOND_PolymerPreparationStation_Deck"
|
"_resource_type": "unilabos.resources.bioyond.decks:BIOYOND_PolymerPreparationStation_Deck"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"station_config": {
|
||||||
|
"station_type": "dispensing_station",
|
||||||
|
"enable_dispensing_station": true,
|
||||||
|
"enable_reaction_station": false,
|
||||||
|
"station_name": "DispensingStation_001",
|
||||||
|
"description": "Bioyond配液工作站"
|
||||||
|
},
|
||||||
"protocol_type": []
|
"protocol_type": []
|
||||||
},
|
},
|
||||||
"data": {}
|
"data": {}
|
||||||
@@ -50,4 +57,4 @@
|
|||||||
"data": {}
|
"data": {}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
"type": "device",
|
"type": "device",
|
||||||
"class": "reaction_station.bioyond",
|
"class": "reaction_station.bioyond",
|
||||||
"config": {
|
"config": {
|
||||||
"config": {
|
"bioyond_config": {
|
||||||
"api_key": "DE9BDDA0",
|
"api_key": "DE9BDDA0",
|
||||||
"api_host": "http://192.168.1.200:44402",
|
"api_host": "http://192.168.1.200:44402",
|
||||||
"workflow_mappings": {
|
"workflow_mappings": {
|
||||||
@@ -19,18 +19,14 @@
|
|||||||
"Solid_feeding_vials": "3a160877-87e7-7699-7bc6-ec72b05eb5e6",
|
"Solid_feeding_vials": "3a160877-87e7-7699-7bc6-ec72b05eb5e6",
|
||||||
"Liquid_feeding_vials(non-titration)": "3a167d99-6158-c6f0-15b5-eb030f7d8e47",
|
"Liquid_feeding_vials(non-titration)": "3a167d99-6158-c6f0-15b5-eb030f7d8e47",
|
||||||
"Liquid_feeding_solvents": "3a160824-0665-01ed-285a-51ef817a9046",
|
"Liquid_feeding_solvents": "3a160824-0665-01ed-285a-51ef817a9046",
|
||||||
"Liquid_feeding(titration)": "3a16082a-96ac-0449-446a-4ed39f3365b6",
|
"Liquid_feeding(titration)": "3a160824-0665-01ed-285a-51ef817a9046",
|
||||||
"liquid_feeding_beaker": "3a16087e-124f-8ddb-8ec1-c2dff09ca784",
|
"Liquid_feeding_beaker": "3a16087e-124f-8ddb-8ec1-c2dff09ca784",
|
||||||
"Drip_back": "3a162cf9-6aac-565a-ddd7-682ba1796a4a"
|
"Drip_back": "3a162cf9-6aac-565a-ddd7-682ba1796a4a"
|
||||||
},
|
},
|
||||||
"material_type_mappings": {
|
"material_type_mappings": {
|
||||||
"烧杯": ["YB_1FlaskCarrier", "3a14196b-24f2-ca49-9081-0cab8021bf1a"],
|
"烧杯": "BIOYOND_PolymerStation_1FlaskCarrier",
|
||||||
"试剂瓶": ["YB_1BottleCarrier", ""],
|
"试剂瓶": "BIOYOND_PolymerStation_1BottleCarrier",
|
||||||
"样品板": ["YB_6StockCarrier", "3a14196e-b7a0-a5da-1931-35f3000281e9"],
|
"样品板": "BIOYOND_PolymerStation_6VialCarrier"
|
||||||
"分装板": ["YB_6VialCarrier", "3a14196e-5dfe-6e21-0c79-fe2036d052c4"],
|
|
||||||
"样品瓶": ["YB_Solid_Stock", "3a14196a-cf7d-8aea-48d8-b9662c7dba94"],
|
|
||||||
"90%分装小瓶": ["YB_Solid_Vial", "3a14196c-cdcf-088d-dc7d-5cf38f0ad9ea"],
|
|
||||||
"10%分装小瓶": ["YB_Liquid_Vial", "3a14196c-76be-2279-4e22-7310d69aed68"]
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"deck": {
|
"deck": {
|
||||||
@@ -46,6 +42,7 @@
|
|||||||
{
|
{
|
||||||
"id": "Bioyond_Deck",
|
"id": "Bioyond_Deck",
|
||||||
"name": "Bioyond_Deck",
|
"name": "Bioyond_Deck",
|
||||||
|
"sample_id": null,
|
||||||
"children": [
|
"children": [
|
||||||
],
|
],
|
||||||
"parent": "reaction_station_bioyond",
|
"parent": "reaction_station_bioyond",
|
||||||
|
|||||||
@@ -24,9 +24,9 @@
|
|||||||
"Drip_back": "3a162cf9-6aac-565a-ddd7-682ba1796a4a"
|
"Drip_back": "3a162cf9-6aac-565a-ddd7-682ba1796a4a"
|
||||||
},
|
},
|
||||||
"material_type_mappings": {
|
"material_type_mappings": {
|
||||||
"烧杯": "YB_1FlaskCarrier",
|
"烧杯": "BIOYOND_PolymerStation_1FlaskCarrier",
|
||||||
"试剂瓶": "YB_1BottleCarrier",
|
"试剂瓶": "BIOYOND_PolymerStation_1BottleCarrier",
|
||||||
"样品板": "YB_6VialCarrier"
|
"样品板": "BIOYOND_PolymerStation_6VialCarrier"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"deck": {
|
"deck": {
|
||||||
|
|||||||
@@ -3,8 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
from typing import Dict, Any, List
|
from typing import Dict, Any, Optional, List
|
||||||
from unilabos.ros.nodes.base_device_node import BaseROS2DeviceNode
|
|
||||||
|
|
||||||
|
|
||||||
class SmartPumpController:
|
class SmartPumpController:
|
||||||
@@ -15,8 +14,6 @@ class SmartPumpController:
|
|||||||
适用于实验室自动化系统中的液体处理任务。
|
适用于实验室自动化系统中的液体处理任务。
|
||||||
"""
|
"""
|
||||||
|
|
||||||
_ros_node: BaseROS2DeviceNode
|
|
||||||
|
|
||||||
def __init__(self, device_id: str = "smart_pump_01", port: str = "/dev/ttyUSB0"):
|
def __init__(self, device_id: str = "smart_pump_01", port: str = "/dev/ttyUSB0"):
|
||||||
"""
|
"""
|
||||||
初始化智能泵控制器
|
初始化智能泵控制器
|
||||||
@@ -33,9 +30,6 @@ class SmartPumpController:
|
|||||||
self.calibration_factor = 1.0
|
self.calibration_factor = 1.0
|
||||||
self.pump_mode = "continuous" # continuous, volume, rate
|
self.pump_mode = "continuous" # continuous, volume, rate
|
||||||
|
|
||||||
def post_init(self, ros_node: BaseROS2DeviceNode):
|
|
||||||
self._ros_node = ros_node
|
|
||||||
|
|
||||||
def connect_device(self, timeout: int = 10) -> bool:
|
def connect_device(self, timeout: int = 10) -> bool:
|
||||||
"""
|
"""
|
||||||
连接到泵设备
|
连接到泵设备
|
||||||
@@ -96,7 +90,7 @@ class SmartPumpController:
|
|||||||
pump_time = (volume / flow_rate) * 60 # 转换为秒
|
pump_time = (volume / flow_rate) * 60 # 转换为秒
|
||||||
|
|
||||||
self.current_flow_rate = flow_rate
|
self.current_flow_rate = flow_rate
|
||||||
await self._ros_node.sleep(min(pump_time, 3.0)) # 模拟泵送过程
|
await asyncio.sleep(min(pump_time, 3.0)) # 模拟泵送过程
|
||||||
|
|
||||||
self.total_volume_pumped += volume
|
self.total_volume_pumped += volume
|
||||||
self.current_flow_rate = 0.0
|
self.current_flow_rate = 0.0
|
||||||
@@ -176,8 +170,6 @@ class AdvancedTemperatureController:
|
|||||||
适用于需要精确温度控制的化学反应和材料处理过程。
|
适用于需要精确温度控制的化学反应和材料处理过程。
|
||||||
"""
|
"""
|
||||||
|
|
||||||
_ros_node: BaseROS2DeviceNode
|
|
||||||
|
|
||||||
def __init__(self, controller_id: str = "temp_controller_01"):
|
def __init__(self, controller_id: str = "temp_controller_01"):
|
||||||
"""
|
"""
|
||||||
初始化温度控制器
|
初始化温度控制器
|
||||||
@@ -193,9 +185,6 @@ class AdvancedTemperatureController:
|
|||||||
self.pid_enabled = True
|
self.pid_enabled = True
|
||||||
self.temperature_history: List[Dict] = []
|
self.temperature_history: List[Dict] = []
|
||||||
|
|
||||||
def post_init(self, ros_node: BaseROS2DeviceNode):
|
|
||||||
self._ros_node = ros_node
|
|
||||||
|
|
||||||
def set_target_temperature(self, temperature: float, rate: float = 10.0) -> bool:
|
def set_target_temperature(self, temperature: float, rate: float = 10.0) -> bool:
|
||||||
"""
|
"""
|
||||||
设置目标温度
|
设置目标温度
|
||||||
@@ -249,7 +238,7 @@ class AdvancedTemperatureController:
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
await self._ros_node.sleep(step_time)
|
await asyncio.sleep(step_time)
|
||||||
|
|
||||||
# 保持历史记录不超过100条
|
# 保持历史记录不超过100条
|
||||||
if len(self.temperature_history) > 100:
|
if len(self.temperature_history) > 100:
|
||||||
@@ -341,8 +330,6 @@ class MultiChannelAnalyzer:
|
|||||||
常用于光谱分析、电化学测量等应用场景。
|
常用于光谱分析、电化学测量等应用场景。
|
||||||
"""
|
"""
|
||||||
|
|
||||||
_ros_node: BaseROS2DeviceNode
|
|
||||||
|
|
||||||
def __init__(self, analyzer_id: str = "analyzer_01", channels: int = 8):
|
def __init__(self, analyzer_id: str = "analyzer_01", channels: int = 8):
|
||||||
"""
|
"""
|
||||||
初始化多通道分析仪
|
初始化多通道分析仪
|
||||||
@@ -357,9 +344,6 @@ class MultiChannelAnalyzer:
|
|||||||
self.is_measuring = False
|
self.is_measuring = False
|
||||||
self.sample_rate = 1000 # Hz
|
self.sample_rate = 1000 # Hz
|
||||||
|
|
||||||
def post_init(self, ros_node: BaseROS2DeviceNode):
|
|
||||||
self._ros_node = ros_node
|
|
||||||
|
|
||||||
def configure_channel(self, channel: int, enabled: bool = True, unit: str = "V") -> bool:
|
def configure_channel(self, channel: int, enabled: bool = True, unit: str = "V") -> bool:
|
||||||
"""
|
"""
|
||||||
配置通道
|
配置通道
|
||||||
@@ -392,7 +376,7 @@ class MultiChannelAnalyzer:
|
|||||||
|
|
||||||
# 模拟数据采集
|
# 模拟数据采集
|
||||||
measurements = []
|
measurements = []
|
||||||
for _ in range(duration):
|
for second in range(duration):
|
||||||
timestamp = asyncio.get_event_loop().time()
|
timestamp = asyncio.get_event_loop().time()
|
||||||
frame_data = {}
|
frame_data = {}
|
||||||
|
|
||||||
@@ -407,7 +391,7 @@ class MultiChannelAnalyzer:
|
|||||||
|
|
||||||
measurements.append({"timestamp": timestamp, "data": frame_data})
|
measurements.append({"timestamp": timestamp, "data": frame_data})
|
||||||
|
|
||||||
await self._ros_node.sleep(1.0) # 每秒采集一次
|
await asyncio.sleep(1.0) # 每秒采集一次
|
||||||
|
|
||||||
self.is_measuring = False
|
self.is_measuring = False
|
||||||
|
|
||||||
@@ -481,8 +465,6 @@ class AutomatedDispenser:
|
|||||||
集成称重功能,确保分配精度和重现性。
|
集成称重功能,确保分配精度和重现性。
|
||||||
"""
|
"""
|
||||||
|
|
||||||
_ros_node: BaseROS2DeviceNode
|
|
||||||
|
|
||||||
def __init__(self, dispenser_id: str = "dispenser_01"):
|
def __init__(self, dispenser_id: str = "dispenser_01"):
|
||||||
"""
|
"""
|
||||||
初始化自动分配器
|
初始化自动分配器
|
||||||
@@ -497,9 +479,6 @@ class AutomatedDispenser:
|
|||||||
self.container_capacity = 1000.0 # mL
|
self.container_capacity = 1000.0 # mL
|
||||||
self.precision_mode = True
|
self.precision_mode = True
|
||||||
|
|
||||||
def post_init(self, ros_node: BaseROS2DeviceNode):
|
|
||||||
self._ros_node = ros_node
|
|
||||||
|
|
||||||
def move_to_position(self, x: float, y: float, z: float) -> bool:
|
def move_to_position(self, x: float, y: float, z: float) -> bool:
|
||||||
"""
|
"""
|
||||||
移动到指定位置
|
移动到指定位置
|
||||||
@@ -538,7 +517,7 @@ class AutomatedDispenser:
|
|||||||
if viscosity == "high":
|
if viscosity == "high":
|
||||||
dispense_time *= 2 # 高粘度液体需要更长时间
|
dispense_time *= 2 # 高粘度液体需要更长时间
|
||||||
|
|
||||||
await self._ros_node.sleep(min(dispense_time, 5.0)) # 最多等待5秒
|
await asyncio.sleep(min(dispense_time, 5.0)) # 最多等待5秒
|
||||||
|
|
||||||
self.dispensed_total += volume
|
self.dispensed_total += volume
|
||||||
|
|
||||||
|
|||||||
@@ -1,52 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"id": "3a1d377b-299d-d0f2-ced9-48257f60dfad",
|
|
||||||
"typeName": "加样头(大)",
|
|
||||||
"code": "0005-00145",
|
|
||||||
"barCode": "",
|
|
||||||
"name": "LiDFOB",
|
|
||||||
"quantity": 9999.0,
|
|
||||||
"lockQuantity": 0.0,
|
|
||||||
"unit": "个",
|
|
||||||
"status": 1,
|
|
||||||
"isUse": false,
|
|
||||||
"locations": [
|
|
||||||
{
|
|
||||||
"id": "3a19da56-1379-ff7c-1745-07e200b44ce2",
|
|
||||||
"whid": "3a19da56-1378-613b-29f2-871e1a287aa5",
|
|
||||||
"whName": "粉末加样头堆栈",
|
|
||||||
"code": "0005-0001",
|
|
||||||
"x": 1,
|
|
||||||
"y": 1,
|
|
||||||
"z": 1,
|
|
||||||
"quantity": 0
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"detail": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "3a1d377b-6a81-6a7e-147c-f89f6463656d",
|
|
||||||
"typeName": "液",
|
|
||||||
"code": "0006-00141",
|
|
||||||
"barCode": "",
|
|
||||||
"name": "EMC",
|
|
||||||
"quantity": 99999.0,
|
|
||||||
"lockQuantity": 0.0,
|
|
||||||
"unit": "g",
|
|
||||||
"status": 1,
|
|
||||||
"isUse": false,
|
|
||||||
"locations": [
|
|
||||||
{
|
|
||||||
"id": "3a1baa20-a7b1-c665-8b9c-d8099d07d2f6",
|
|
||||||
"whid": "3a1baa20-a7b0-5c19-8844-5de8924d4e78",
|
|
||||||
"whName": "4号手套箱内部堆栈",
|
|
||||||
"code": "0015-0001",
|
|
||||||
"x": 1,
|
|
||||||
"y": 1,
|
|
||||||
"z": 1,
|
|
||||||
"quantity": 0
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"detail": []
|
|
||||||
}
|
|
||||||
]
|
|
||||||
@@ -1,99 +0,0 @@
|
|||||||
{
|
|
||||||
"typeId": "3a190c8b-3284-af78-d29f-9a69463ad047",
|
|
||||||
"code": "",
|
|
||||||
"barCode": "",
|
|
||||||
"name": "test",
|
|
||||||
"unit": "",
|
|
||||||
"parameters": "{}",
|
|
||||||
"quantity": "",
|
|
||||||
"details": [
|
|
||||||
{
|
|
||||||
"typeId": "3a190c8c-fe8f-bf48-0dc3-97afc7f508eb",
|
|
||||||
"code": "",
|
|
||||||
"name": "配液瓶(小)11",
|
|
||||||
"quantity": "1",
|
|
||||||
"x": 1,
|
|
||||||
"y": 1,
|
|
||||||
"z": 1,
|
|
||||||
"unit": "",
|
|
||||||
"parameters": "{}"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"typeId": "3a190c8c-fe8f-bf48-0dc3-97afc7f508eb",
|
|
||||||
"code": "",
|
|
||||||
"name": "配液瓶(小)21",
|
|
||||||
"quantity": "1",
|
|
||||||
"x": 2,
|
|
||||||
"y": 1,
|
|
||||||
"z": 1,
|
|
||||||
"unit": "",
|
|
||||||
"parameters": "{}"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"typeId": "3a190c8c-fe8f-bf48-0dc3-97afc7f508eb",
|
|
||||||
"code": "",
|
|
||||||
"name": "配液瓶(小)12",
|
|
||||||
"quantity": "1",
|
|
||||||
"x": 1,
|
|
||||||
"y": 2,
|
|
||||||
"z": 1,
|
|
||||||
"unit": "",
|
|
||||||
"parameters": "{}"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"typeId": "3a190c8c-fe8f-bf48-0dc3-97afc7f508eb",
|
|
||||||
"code": "",
|
|
||||||
"name": "配液瓶(小)22",
|
|
||||||
"quantity": "1",
|
|
||||||
"x": 2,
|
|
||||||
"y": 2,
|
|
||||||
"z": 1,
|
|
||||||
"unit": "",
|
|
||||||
"parameters": "{}"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"typeId": "3a190c8c-fe8f-bf48-0dc3-97afc7f508eb",
|
|
||||||
"code": "",
|
|
||||||
"name": "配液瓶(小)13",
|
|
||||||
"quantity": "1",
|
|
||||||
"x": 1,
|
|
||||||
"y": 3,
|
|
||||||
"z": 1,
|
|
||||||
"unit": "",
|
|
||||||
"parameters": "{}"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"typeId": "3a190c8c-fe8f-bf48-0dc3-97afc7f508eb",
|
|
||||||
"code": "",
|
|
||||||
"name": "配液瓶(小)23",
|
|
||||||
"quantity": "1",
|
|
||||||
"x": 2,
|
|
||||||
"y": 3,
|
|
||||||
"z": 1,
|
|
||||||
"unit": "",
|
|
||||||
"parameters": "{}"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"typeId": "3a190c8c-fe8f-bf48-0dc3-97afc7f508eb",
|
|
||||||
"code": "",
|
|
||||||
"name": "配液瓶(小)14",
|
|
||||||
"quantity": "1",
|
|
||||||
"x": 1,
|
|
||||||
"y": 4,
|
|
||||||
"z": 1,
|
|
||||||
"unit": "",
|
|
||||||
"parameters": "{}"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"typeId": "3a190c8c-fe8f-bf48-0dc3-97afc7f508eb",
|
|
||||||
"code": "",
|
|
||||||
"name": "配液瓶(小)24",
|
|
||||||
"quantity": "1",
|
|
||||||
"x": 2,
|
|
||||||
"y": 4,
|
|
||||||
"z": 1,
|
|
||||||
"unit": "",
|
|
||||||
"parameters": "{}"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,148 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"id": "3a1d4c14-a9fb-d7dc-9e96-7a3ad6e50219",
|
|
||||||
"typeName": "配液瓶(小)板",
|
|
||||||
"code": "0001-00093",
|
|
||||||
"barCode": "",
|
|
||||||
"name": "test",
|
|
||||||
"quantity": 2.0,
|
|
||||||
"lockQuantity": 0.0,
|
|
||||||
"unit": "块",
|
|
||||||
"status": 1,
|
|
||||||
"isUse": false,
|
|
||||||
"locations": [
|
|
||||||
{
|
|
||||||
"id": "3a19deae-2c7a-36f5-5e41-02c5b66feaea",
|
|
||||||
"whid": "3a19deae-2c79-05a3-9c76-8e6760424841",
|
|
||||||
"whName": "手动堆栈",
|
|
||||||
"code": "1",
|
|
||||||
"x": 1,
|
|
||||||
"y": 1,
|
|
||||||
"z": 1,
|
|
||||||
"quantity": 0
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"detail": [
|
|
||||||
{
|
|
||||||
"id": "3a1d4c14-a9fc-1daa-71fa-146cb1ccb930",
|
|
||||||
"detailMaterialId": "3a1d4c14-a9fc-4f38-4c48-68486c391c42",
|
|
||||||
"code": "0001-00093 - 05",
|
|
||||||
"name": "配液瓶(小)",
|
|
||||||
"quantity": "1",
|
|
||||||
"lockQuantity": "0",
|
|
||||||
"unit": "个",
|
|
||||||
"x": 1,
|
|
||||||
"y": 3,
|
|
||||||
"z": 1,
|
|
||||||
"associateId": null,
|
|
||||||
"typeName": "配液瓶(小)",
|
|
||||||
"typeId": "3a190c8c-fe8f-bf48-0dc3-97afc7f508eb"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "3a1d4c14-a9fc-3659-ea61-cd587da9e131",
|
|
||||||
"detailMaterialId": "3a1d4c14-a9fc-018f-93e5-c49343d37758",
|
|
||||||
"code": "0001-00093 - 08",
|
|
||||||
"name": "配液瓶(小)",
|
|
||||||
"quantity": "1",
|
|
||||||
"lockQuantity": "0",
|
|
||||||
"unit": "个",
|
|
||||||
"x": 2,
|
|
||||||
"y": 4,
|
|
||||||
"z": 1,
|
|
||||||
"associateId": null,
|
|
||||||
"typeName": "配液瓶(小)",
|
|
||||||
"typeId": "3a190c8c-fe8f-bf48-0dc3-97afc7f508eb"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "3a1d4c14-a9fc-3f94-de83-979d2646e313",
|
|
||||||
"detailMaterialId": "3a1d4c14-a9fc-9987-c0ef-4b7cbad49e6b",
|
|
||||||
"code": "0001-00093 - 01",
|
|
||||||
"name": "配液瓶(小)",
|
|
||||||
"quantity": "1",
|
|
||||||
"lockQuantity": "0",
|
|
||||||
"unit": "个",
|
|
||||||
"x": 1,
|
|
||||||
"y": 1,
|
|
||||||
"z": 1,
|
|
||||||
"associateId": null,
|
|
||||||
"typeName": "配液瓶(小)",
|
|
||||||
"typeId": "3a190c8c-fe8f-bf48-0dc3-97afc7f508eb"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "3a1d4c14-a9fc-8c35-6b25-913b11dbaf4e",
|
|
||||||
"detailMaterialId": "3a1d4c14-a9fc-9a83-865b-0c26ea5e8cc4",
|
|
||||||
"code": "0001-00093 - 03",
|
|
||||||
"name": "配液瓶(小)",
|
|
||||||
"quantity": "1",
|
|
||||||
"lockQuantity": "0",
|
|
||||||
"unit": "个",
|
|
||||||
"x": 1,
|
|
||||||
"y": 2,
|
|
||||||
"z": 1,
|
|
||||||
"associateId": null,
|
|
||||||
"typeName": "配液瓶(小)",
|
|
||||||
"typeId": "3a190c8c-fe8f-bf48-0dc3-97afc7f508eb"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "3a1d4c14-a9fc-b41f-e968-64953bfddccd",
|
|
||||||
"detailMaterialId": "3a1d4c14-a9fc-daf7-9d64-e5ec8d3ae0e2",
|
|
||||||
"code": "0001-00093 - 07",
|
|
||||||
"name": "配液瓶(小)",
|
|
||||||
"quantity": "1",
|
|
||||||
"lockQuantity": "0",
|
|
||||||
"unit": "个",
|
|
||||||
"x": 1,
|
|
||||||
"y": 4,
|
|
||||||
"z": 1,
|
|
||||||
"associateId": null,
|
|
||||||
"typeName": "配液瓶(小)",
|
|
||||||
"typeId": "3a190c8c-fe8f-bf48-0dc3-97afc7f508eb"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "3a1d4c14-a9fc-c20f-c26e-b1bb2cdc3bca",
|
|
||||||
"detailMaterialId": "3a1d4c14-a9fc-673b-ac83-aaaf71287f1f",
|
|
||||||
"code": "0001-00093 - 06",
|
|
||||||
"name": "配液瓶(小)",
|
|
||||||
"quantity": "1",
|
|
||||||
"lockQuantity": "0",
|
|
||||||
"unit": "个",
|
|
||||||
"x": 2,
|
|
||||||
"y": 3,
|
|
||||||
"z": 1,
|
|
||||||
"associateId": null,
|
|
||||||
"typeName": "配液瓶(小)",
|
|
||||||
"typeId": "3a190c8c-fe8f-bf48-0dc3-97afc7f508eb"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "3a1d4c14-a9fc-cf21-059c-fde361d82b6f",
|
|
||||||
"detailMaterialId": "3a1d4c14-a9fc-25b1-e736-6b0d8dac0fae",
|
|
||||||
"code": "0001-00093 - 02",
|
|
||||||
"name": "配液瓶(小)",
|
|
||||||
"quantity": "1",
|
|
||||||
"lockQuantity": "0",
|
|
||||||
"unit": "个",
|
|
||||||
"x": 2,
|
|
||||||
"y": 1,
|
|
||||||
"z": 1,
|
|
||||||
"associateId": null,
|
|
||||||
"typeName": "配液瓶(小)",
|
|
||||||
"typeId": "3a190c8c-fe8f-bf48-0dc3-97afc7f508eb"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "3a1d4c14-a9fc-d732-2b93-9b2bd2bf581b",
|
|
||||||
"detailMaterialId": "3a1d4c14-a9fc-7f5d-b6b6-8bcb2e15f320",
|
|
||||||
"code": "0001-00093 - 04",
|
|
||||||
"name": "配液瓶(小)",
|
|
||||||
"quantity": "1",
|
|
||||||
"lockQuantity": "0",
|
|
||||||
"unit": "个",
|
|
||||||
"x": 2,
|
|
||||||
"y": 2,
|
|
||||||
"z": 1,
|
|
||||||
"associateId": null,
|
|
||||||
"typeName": "配液瓶(小)",
|
|
||||||
"typeId": "3a190c8c-fe8f-bf48-0dc3-97afc7f508eb"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from unilabos.resources.bioyond.bottle_carriers import BIOYOND_Electrolyte_6VialCarrier, BIOYOND_Electrolyte_1BottleCarrier
|
from unilabos.resources.bioyond.bottle_carriers import BIOYOND_Electrolyte_6VialCarrier, BIOYOND_Electrolyte_1BottleCarrier
|
||||||
from unilabos.resources.bioyond.bottles import YB_Solid_Vial, YB_Solution_Beaker, YB_Reagent_Bottle
|
from unilabos.resources.bioyond.bottles import BIOYOND_PolymerStation_Solid_Vial, BIOYOND_PolymerStation_Solution_Beaker, BIOYOND_PolymerStation_Reagent_Bottle
|
||||||
|
|
||||||
|
|
||||||
def test_bottle_carrier() -> "BottleCarrier":
|
def test_bottle_carrier() -> "BottleCarrier":
|
||||||
@@ -16,9 +16,9 @@ def test_bottle_carrier() -> "BottleCarrier":
|
|||||||
print(f"1烧杯载架: {beaker_carrier.name}, 位置数: {len(beaker_carrier.sites)}")
|
print(f"1烧杯载架: {beaker_carrier.name}, 位置数: {len(beaker_carrier.sites)}")
|
||||||
|
|
||||||
# 创建瓶子和烧杯
|
# 创建瓶子和烧杯
|
||||||
powder_bottle = YB_Solid_Vial("powder_bottle_01")
|
powder_bottle = BIOYOND_PolymerStation_Solid_Vial("powder_bottle_01")
|
||||||
solution_beaker = YB_Solution_Beaker("solution_beaker_01")
|
solution_beaker = BIOYOND_PolymerStation_Solution_Beaker("solution_beaker_01")
|
||||||
reagent_bottle = YB_Reagent_Bottle("reagent_bottle_01")
|
reagent_bottle = BIOYOND_PolymerStation_Reagent_Bottle("reagent_bottle_01")
|
||||||
|
|
||||||
print(f"\n创建的物料:")
|
print(f"\n创建的物料:")
|
||||||
print(f"粉末瓶: {powder_bottle.name} - {powder_bottle.diameter}mm x {powder_bottle.height}mm, {powder_bottle.max_volume}μL")
|
print(f"粉末瓶: {powder_bottle.name} - {powder_bottle.diameter}mm x {powder_bottle.height}mm, {powder_bottle.max_volume}μL")
|
||||||
|
|||||||
@@ -12,13 +12,23 @@ lab_registry.setup()
|
|||||||
|
|
||||||
|
|
||||||
type_mapping = {
|
type_mapping = {
|
||||||
"烧杯": ("YB_1FlaskCarrier", "3a14196b-24f2-ca49-9081-0cab8021bf1a"),
|
"烧杯": "BIOYOND_PolymerStation_1FlaskCarrier",
|
||||||
"试剂瓶": ("YB_1BottleCarrier", ""),
|
"试剂瓶": "BIOYOND_PolymerStation_1BottleCarrier",
|
||||||
"样品板": ("YB_6StockCarrier", "3a14196e-b7a0-a5da-1931-35f3000281e9"),
|
"样品板": "BIOYOND_PolymerStation_6StockCarrier",
|
||||||
"分装板": ("YB_6VialCarrier", "3a14196e-5dfe-6e21-0c79-fe2036d052c4"),
|
"分装板": "BIOYOND_PolymerStation_6VialCarrier",
|
||||||
"样品瓶": ("YB_Solid_Stock", "3a14196a-cf7d-8aea-48d8-b9662c7dba94"),
|
"样品瓶": "BIOYOND_PolymerStation_Solid_Stock",
|
||||||
"90%分装小瓶": ("YB_Solid_Vial", "3a14196c-cdcf-088d-dc7d-5cf38f0ad9ea"),
|
"90%分装小瓶": "BIOYOND_PolymerStation_Solid_Vial",
|
||||||
"10%分装小瓶": ("YB_Liquid_Vial", "3a14196c-76be-2279-4e22-7310d69aed68"),
|
"10%分装小瓶": "BIOYOND_PolymerStation_Liquid_Vial",
|
||||||
|
}
|
||||||
|
|
||||||
|
type_uuid_mapping = {
|
||||||
|
"烧杯": "",
|
||||||
|
"试剂瓶": "",
|
||||||
|
"样品板": "",
|
||||||
|
"分装板": "3a14196e-5dfe-6e21-0c79-fe2036d052c4",
|
||||||
|
"样品瓶": "3a14196a-cf7d-8aea-48d8-b9662c7dba94",
|
||||||
|
"90%分装小瓶": "3a14196c-cdcf-088d-dc7d-5cf38f0ad9ea",
|
||||||
|
"10%分装小瓶": "3a14196c-76be-2279-4e22-7310d69aed68",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,115 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""
|
|
||||||
测试修改后的 get_child_identifier 函数
|
|
||||||
"""
|
|
||||||
|
|
||||||
from unilabos.resources.itemized_carrier import ItemizedCarrier, Bottle
|
|
||||||
from pylabrobot.resources.coordinate import Coordinate
|
|
||||||
|
|
||||||
def test_get_child_identifier_with_indices():
|
|
||||||
"""测试返回x,y,z索引的 get_child_identifier 函数"""
|
|
||||||
|
|
||||||
# 创建一些测试瓶子
|
|
||||||
bottle1 = Bottle("bottle1", diameter=25.0, height=50.0, max_volume=15.0)
|
|
||||||
bottle1.location = Coordinate(10, 20, 5)
|
|
||||||
|
|
||||||
bottle2 = Bottle("bottle2", diameter=25.0, height=50.0, max_volume=15.0)
|
|
||||||
bottle2.location = Coordinate(50, 20, 5)
|
|
||||||
|
|
||||||
bottle3 = Bottle("bottle3", diameter=25.0, height=50.0, max_volume=15.0)
|
|
||||||
bottle3.location = Coordinate(90, 20, 5)
|
|
||||||
|
|
||||||
# 创建载架,指定维度
|
|
||||||
sites = {
|
|
||||||
"A1": bottle1,
|
|
||||||
"A2": bottle2,
|
|
||||||
"A3": bottle3,
|
|
||||||
"B1": None, # 空位
|
|
||||||
"B2": None,
|
|
||||||
"B3": None
|
|
||||||
}
|
|
||||||
|
|
||||||
carrier = ItemizedCarrier(
|
|
||||||
name="test_carrier",
|
|
||||||
size_x=150,
|
|
||||||
size_y=100,
|
|
||||||
size_z=30,
|
|
||||||
num_items_x=3, # 3列
|
|
||||||
num_items_y=2, # 2行
|
|
||||||
num_items_z=1, # 1层
|
|
||||||
sites=sites
|
|
||||||
)
|
|
||||||
|
|
||||||
print("测试载架维度:")
|
|
||||||
print(f"num_items_x: {carrier.num_items_x}")
|
|
||||||
print(f"num_items_y: {carrier.num_items_y}")
|
|
||||||
print(f"num_items_z: {carrier.num_items_z}")
|
|
||||||
print()
|
|
||||||
|
|
||||||
# 测试获取bottle1的标识符信息 (A1 = idx:0, x:0, y:0, z:0)
|
|
||||||
result1 = carrier.get_child_identifier(bottle1)
|
|
||||||
print("测试bottle1 (A1):")
|
|
||||||
print(f" identifier: {result1['identifier']}")
|
|
||||||
print(f" idx: {result1['idx']}")
|
|
||||||
print(f" x index: {result1['x']}")
|
|
||||||
print(f" y index: {result1['y']}")
|
|
||||||
print(f" z index: {result1['z']}")
|
|
||||||
|
|
||||||
# Assert 验证 bottle1 (A1) 的结果
|
|
||||||
assert result1['identifier'] == 'A1', f"Expected identifier 'A1', got '{result1['identifier']}'"
|
|
||||||
assert result1['idx'] == 0, f"Expected idx 0, got {result1['idx']}"
|
|
||||||
assert result1['x'] == 0, f"Expected x index 0, got {result1['x']}"
|
|
||||||
assert result1['y'] == 0, f"Expected y index 0, got {result1['y']}"
|
|
||||||
assert result1['z'] == 0, f"Expected z index 0, got {result1['z']}"
|
|
||||||
print(" ✓ bottle1 (A1) 测试通过")
|
|
||||||
print()
|
|
||||||
|
|
||||||
# 测试获取bottle2的标识符信息 (A2 = idx:1, x:1, y:0, z:0)
|
|
||||||
result2 = carrier.get_child_identifier(bottle2)
|
|
||||||
print("测试bottle2 (A2):")
|
|
||||||
print(f" identifier: {result2['identifier']}")
|
|
||||||
print(f" idx: {result2['idx']}")
|
|
||||||
print(f" x index: {result2['x']}")
|
|
||||||
print(f" y index: {result2['y']}")
|
|
||||||
print(f" z index: {result2['z']}")
|
|
||||||
|
|
||||||
# Assert 验证 bottle2 (A2) 的结果
|
|
||||||
assert result2['identifier'] == 'A2', f"Expected identifier 'A2', got '{result2['identifier']}'"
|
|
||||||
assert result2['idx'] == 1, f"Expected idx 1, got {result2['idx']}"
|
|
||||||
assert result2['x'] == 1, f"Expected x index 1, got {result2['x']}"
|
|
||||||
assert result2['y'] == 0, f"Expected y index 0, got {result2['y']}"
|
|
||||||
assert result2['z'] == 0, f"Expected z index 0, got {result2['z']}"
|
|
||||||
print(" ✓ bottle2 (A2) 测试通过")
|
|
||||||
print()
|
|
||||||
|
|
||||||
# 测试获取bottle3的标识符信息 (A3 = idx:2, x:2, y:0, z:0)
|
|
||||||
result3 = carrier.get_child_identifier(bottle3)
|
|
||||||
print("测试bottle3 (A3):")
|
|
||||||
print(f" identifier: {result3['identifier']}")
|
|
||||||
print(f" idx: {result3['idx']}")
|
|
||||||
print(f" x index: {result3['x']}")
|
|
||||||
print(f" y index: {result3['y']}")
|
|
||||||
print(f" z index: {result3['z']}")
|
|
||||||
|
|
||||||
# Assert 验证 bottle3 (A3) 的结果
|
|
||||||
assert result3['identifier'] == 'A3', f"Expected identifier 'A3', got '{result3['identifier']}'"
|
|
||||||
assert result3['idx'] == 2, f"Expected idx 2, got {result3['idx']}"
|
|
||||||
assert result3['x'] == 2, f"Expected x index 2, got {result3['x']}"
|
|
||||||
assert result3['y'] == 0, f"Expected y index 0, got {result3['y']}"
|
|
||||||
assert result3['z'] == 0, f"Expected z index 0, got {result3['z']}"
|
|
||||||
print(" ✓ bottle3 (A3) 测试通过")
|
|
||||||
print()
|
|
||||||
|
|
||||||
# 测试错误情况:查找不存在的资源
|
|
||||||
bottle_not_exists = Bottle("bottle_not_exists", diameter=25.0, height=50.0, max_volume=15.0)
|
|
||||||
try:
|
|
||||||
carrier.get_child_identifier(bottle_not_exists)
|
|
||||||
assert False, "应该抛出 ValueError 异常"
|
|
||||||
except ValueError as e:
|
|
||||||
print("✓ 正确抛出了 ValueError 异常:", str(e))
|
|
||||||
assert "is not assigned to this carrier" in str(e), "异常消息应该包含预期的文本"
|
|
||||||
|
|
||||||
print("\n🎉 所有测试都通过了!")
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
test_get_child_identifier_with_indices()
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
from ast import If
|
|
||||||
import pytest
|
|
||||||
import json
|
|
||||||
import os
|
|
||||||
|
|
||||||
from pylabrobot.resources import Resource as ResourcePLR
|
|
||||||
from unilabos.resources.graphio import resource_bioyond_to_plr
|
|
||||||
from unilabos.ros.nodes.resource_tracker import ResourceTreeSet
|
|
||||||
from unilabos.registry.registry import lab_registry
|
|
||||||
|
|
||||||
from unilabos.resources.bioyond.decks import BIOYOND_PolymerReactionStation_Deck
|
|
||||||
from unilabos.resources.bioyond.decks import YB_Deck
|
|
||||||
|
|
||||||
lab_registry.setup()
|
|
||||||
|
|
||||||
|
|
||||||
type_mapping = {
|
|
||||||
"加样头(大)": ("YB_jia_yang_tou_da", "3a190ca0-b2f6-9aeb-8067-547e72c11469"),
|
|
||||||
"液": ("YB_1BottleCarrier", "3a190ca1-2add-2b23-f8e1-bbd348b7f790"),
|
|
||||||
"配液瓶(小)板": ("YB_peiyepingxiaoban", "3a190c8b-3284-af78-d29f-9a69463ad047"),
|
|
||||||
"配液瓶(小)": ("YB_pei_ye_xiao_Bottler", "3a190c8c-fe8f-bf48-0dc3-97afc7f508eb"),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def bioyond_materials_reaction() -> list[dict]:
|
|
||||||
print("加载 BioYond 物料数据...")
|
|
||||||
print(os.getcwd())
|
|
||||||
with open("bioyond_materials_reaction.json", "r", encoding="utf-8") as f:
|
|
||||||
data = json.load(f)
|
|
||||||
print(f"加载了 {len(data)} 条物料数据")
|
|
||||||
return data
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def bioyond_materials_liquidhandling_1() -> list[dict]:
|
|
||||||
print("加载 BioYond 物料数据...")
|
|
||||||
print(os.getcwd())
|
|
||||||
with open("bioyond_materials_liquidhandling_1.json", "r", encoding="utf-8") as f:
|
|
||||||
data = json.load(f)
|
|
||||||
print(f"加载了 {len(data)} 条物料数据")
|
|
||||||
return data
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def bioyond_materials_liquidhandling_2() -> list[dict]:
|
|
||||||
print("加载 BioYond 物料数据...")
|
|
||||||
print(os.getcwd())
|
|
||||||
with open("bioyond_materials_liquidhandling_2.json", "r", encoding="utf-8") as f:
|
|
||||||
data = json.load(f)
|
|
||||||
print(f"加载了 {len(data)} 条物料数据")
|
|
||||||
return data
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("materials_fixture", [
|
|
||||||
"bioyond_materials_reaction",
|
|
||||||
"bioyond_materials_liquidhandling_1",
|
|
||||||
])
|
|
||||||
def test_resourcetreeset_from_plr() -> list[dict]:
|
|
||||||
# 直接加载 bioyond_materials_reaction.json 文件
|
|
||||||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
|
||||||
json_path = os.path.join(current_dir, "test.json")
|
|
||||||
with open(json_path, "r", encoding="utf-8") as f:
|
|
||||||
materials = json.load(f)
|
|
||||||
deck = YB_Deck("test_deck")
|
|
||||||
output = resource_bioyond_to_plr(materials, type_mapping=type_mapping, deck=deck)
|
|
||||||
print(output)
|
|
||||||
# print(deck.summary())
|
|
||||||
|
|
||||||
r = ResourceTreeSet.from_plr_resources([deck])
|
|
||||||
print(r.dump())
|
|
||||||
# json.dump(deck.serialize(), open("test.json", "w", encoding="utf-8"), indent=4)
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
test_resourcetreeset_from_plr()
|
|
||||||
@@ -1,186 +0,0 @@
|
|||||||
{
|
|
||||||
"workflow": [
|
|
||||||
{
|
|
||||||
"action": "transfer_liquid",
|
|
||||||
"action_args": {
|
|
||||||
"sources": "Liquid_1",
|
|
||||||
"targets": "Liquid_2",
|
|
||||||
"asp_vol": 66.0,
|
|
||||||
"dis_vol": 66.0,
|
|
||||||
"asp_flow_rate": 94.0,
|
|
||||||
"dis_flow_rate": 94.0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"action": "transfer_liquid",
|
|
||||||
"action_args": {
|
|
||||||
"sources": "Liquid_2",
|
|
||||||
"targets": "Liquid_3",
|
|
||||||
"asp_vol": 58.0,
|
|
||||||
"dis_vol": 96.0,
|
|
||||||
"asp_flow_rate": 94.0,
|
|
||||||
"dis_flow_rate": 94.0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"action": "transfer_liquid",
|
|
||||||
"action_args": {
|
|
||||||
"sources": "Liquid_4",
|
|
||||||
"targets": "Liquid_2",
|
|
||||||
"asp_vol": 85.0,
|
|
||||||
"dis_vol": 170.0,
|
|
||||||
"asp_flow_rate": 94.0,
|
|
||||||
"dis_flow_rate": 94.0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"action": "transfer_liquid",
|
|
||||||
"action_args": {
|
|
||||||
"sources": "Liquid_4",
|
|
||||||
"targets": "Liquid_2",
|
|
||||||
"asp_vol": 63.333333333333336,
|
|
||||||
"dis_vol": 170.0,
|
|
||||||
"asp_flow_rate": 94.0,
|
|
||||||
"dis_flow_rate": 94.0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"action": "transfer_liquid",
|
|
||||||
"action_args": {
|
|
||||||
"sources": "Liquid_2",
|
|
||||||
"targets": "Liquid_3",
|
|
||||||
"asp_vol": 72.0,
|
|
||||||
"dis_vol": 150.0,
|
|
||||||
"asp_flow_rate": 94.0,
|
|
||||||
"dis_flow_rate": 94.0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"action": "transfer_liquid",
|
|
||||||
"action_args": {
|
|
||||||
"sources": "Liquid_4",
|
|
||||||
"targets": "Liquid_2",
|
|
||||||
"asp_vol": 85.0,
|
|
||||||
"dis_vol": 170.0,
|
|
||||||
"asp_flow_rate": 94.0,
|
|
||||||
"dis_flow_rate": 94.0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"action": "transfer_liquid",
|
|
||||||
"action_args": {
|
|
||||||
"sources": "Liquid_4",
|
|
||||||
"targets": "Liquid_2",
|
|
||||||
"asp_vol": 63.333333333333336,
|
|
||||||
"dis_vol": 170.0,
|
|
||||||
"asp_flow_rate": 94.0,
|
|
||||||
"dis_flow_rate": 94.0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"action": "transfer_liquid",
|
|
||||||
"action_args": {
|
|
||||||
"sources": "Liquid_2",
|
|
||||||
"targets": "Liquid_3",
|
|
||||||
"asp_vol": 72.0,
|
|
||||||
"dis_vol": 150.0,
|
|
||||||
"asp_flow_rate": 94.0,
|
|
||||||
"dis_flow_rate": 94.0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"action": "transfer_liquid",
|
|
||||||
"action_args": {
|
|
||||||
"sources": "Liquid_2",
|
|
||||||
"targets": "Liquid_3",
|
|
||||||
"asp_vol": 20.0,
|
|
||||||
"dis_vol": 20.0,
|
|
||||||
"asp_flow_rate": 7.6,
|
|
||||||
"dis_flow_rate": 7.6
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"action": "transfer_liquid",
|
|
||||||
"action_args": {
|
|
||||||
"sources": "Liquid_5",
|
|
||||||
"targets": "Liquid_2",
|
|
||||||
"asp_vol": 6.0,
|
|
||||||
"dis_vol": 12.0,
|
|
||||||
"asp_flow_rate": 7.6,
|
|
||||||
"dis_flow_rate": 7.6
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"action": "transfer_liquid",
|
|
||||||
"action_args": {
|
|
||||||
"sources": "Liquid_5",
|
|
||||||
"targets": "Liquid_2",
|
|
||||||
"asp_vol": 10.666666666666666,
|
|
||||||
"dis_vol": 12.0,
|
|
||||||
"asp_flow_rate": 7.599999999999999,
|
|
||||||
"dis_flow_rate": 7.6
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"action": "transfer_liquid",
|
|
||||||
"action_args": {
|
|
||||||
"sources": "Liquid_2",
|
|
||||||
"targets": "Liquid_6",
|
|
||||||
"asp_vol": 12.0,
|
|
||||||
"dis_vol": 10.0,
|
|
||||||
"asp_flow_rate": 7.6,
|
|
||||||
"dis_flow_rate": 7.6
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"reagent": {
|
|
||||||
"Liquid_6": {
|
|
||||||
"slot": 1,
|
|
||||||
"well": [
|
|
||||||
"A2"
|
|
||||||
],
|
|
||||||
"labware": "elution plate"
|
|
||||||
},
|
|
||||||
"Liquid_1": {
|
|
||||||
"slot": 2,
|
|
||||||
"well": [
|
|
||||||
"A1",
|
|
||||||
"A2",
|
|
||||||
"A4"
|
|
||||||
],
|
|
||||||
"labware": "reagent reservoir"
|
|
||||||
},
|
|
||||||
"Liquid_4": {
|
|
||||||
"slot": 2,
|
|
||||||
"well": [
|
|
||||||
"A1",
|
|
||||||
"A2",
|
|
||||||
"A4"
|
|
||||||
],
|
|
||||||
"labware": "reagent reservoir"
|
|
||||||
},
|
|
||||||
"Liquid_5": {
|
|
||||||
"slot": 2,
|
|
||||||
"well": [
|
|
||||||
"A1",
|
|
||||||
"A2",
|
|
||||||
"A4"
|
|
||||||
],
|
|
||||||
"labware": "reagent reservoir"
|
|
||||||
},
|
|
||||||
"Liquid_2": {
|
|
||||||
"slot": 4,
|
|
||||||
"well": [
|
|
||||||
"A2"
|
|
||||||
],
|
|
||||||
"labware": "TAG1 plate on Magnetic Module GEN2"
|
|
||||||
},
|
|
||||||
"Liquid_3": {
|
|
||||||
"slot": 12,
|
|
||||||
"well": [
|
|
||||||
"A1"
|
|
||||||
],
|
|
||||||
"labware": "Opentrons Fixed Trash"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 148 KiB |
@@ -1,63 +0,0 @@
|
|||||||
{
|
|
||||||
"steps_info": [
|
|
||||||
{
|
|
||||||
"step_number": 1,
|
|
||||||
"action": "transfer_liquid",
|
|
||||||
"parameters": {
|
|
||||||
"source": "sample supernatant",
|
|
||||||
"target": "antibody-coated well",
|
|
||||||
"volume": 100
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"step_number": 2,
|
|
||||||
"action": "transfer_liquid",
|
|
||||||
"parameters": {
|
|
||||||
"source": "washing buffer",
|
|
||||||
"target": "antibody-coated well",
|
|
||||||
"volume": 200
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"step_number": 3,
|
|
||||||
"action": "transfer_liquid",
|
|
||||||
"parameters": {
|
|
||||||
"source": "washing buffer",
|
|
||||||
"target": "antibody-coated well",
|
|
||||||
"volume": 200
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"step_number": 4,
|
|
||||||
"action": "transfer_liquid",
|
|
||||||
"parameters": {
|
|
||||||
"source": "washing buffer",
|
|
||||||
"target": "antibody-coated well",
|
|
||||||
"volume": 200
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"step_number": 5,
|
|
||||||
"action": "transfer_liquid",
|
|
||||||
"parameters": {
|
|
||||||
"source": "TMB substrate",
|
|
||||||
"target": "antibody-coated well",
|
|
||||||
"volume": 100
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"labware_info": [
|
|
||||||
{"reagent_name": "sample supernatant", "material_name": "96深孔板", "positions": 1},
|
|
||||||
{"reagent_name": "washing buffer", "material_name": "储液槽", "positions": 2},
|
|
||||||
{"reagent_name": "TMB substrate", "material_name": "储液槽", "positions": 3},
|
|
||||||
{"reagent_name": "antibody-coated well", "material_name": "96 细胞培养皿", "positions": 4},
|
|
||||||
{"reagent_name": "", "material_name": "300μL Tip头", "positions": 5},
|
|
||||||
{"reagent_name": "", "material_name": "300μL Tip头", "positions": 6},
|
|
||||||
{"reagent_name": "", "material_name": "300μL Tip头", "positions": 7},
|
|
||||||
{"reagent_name": "", "material_name": "300μL Tip头", "positions": 8},
|
|
||||||
{"reagent_name": "", "material_name": "300μL Tip头", "positions": 9},
|
|
||||||
{"reagent_name": "", "material_name": "300μL Tip头", "positions": 10},
|
|
||||||
{"reagent_name": "", "material_name": "300μL Tip头", "positions": 11},
|
|
||||||
{"reagent_name": "", "material_name": "300μL Tip头", "positions": 13}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 140 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 117 KiB |
@@ -1,94 +0,0 @@
|
|||||||
import json
|
|
||||||
import sys
|
|
||||||
from datetime import datetime
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
ROOT_DIR = Path(__file__).resolve().parents[2]
|
|
||||||
if str(ROOT_DIR) not in sys.path:
|
|
||||||
sys.path.insert(0, str(ROOT_DIR))
|
|
||||||
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
from scripts.workflow import build_protocol_graph, draw_protocol_graph, draw_protocol_graph_with_ports
|
|
||||||
|
|
||||||
|
|
||||||
ROOT_DIR = Path(__file__).resolve().parents[2]
|
|
||||||
if str(ROOT_DIR) not in sys.path:
|
|
||||||
sys.path.insert(0, str(ROOT_DIR))
|
|
||||||
|
|
||||||
|
|
||||||
def _normalize_steps(data):
|
|
||||||
normalized = []
|
|
||||||
for step in data:
|
|
||||||
action = step.get("action") or step.get("operation")
|
|
||||||
if not action:
|
|
||||||
continue
|
|
||||||
raw_params = step.get("parameters") or step.get("action_args") or {}
|
|
||||||
params = dict(raw_params)
|
|
||||||
|
|
||||||
if "source" in raw_params and "sources" not in raw_params:
|
|
||||||
params["sources"] = raw_params["source"]
|
|
||||||
if "target" in raw_params and "targets" not in raw_params:
|
|
||||||
params["targets"] = raw_params["target"]
|
|
||||||
|
|
||||||
description = step.get("description") or step.get("purpose")
|
|
||||||
step_dict = {"action": action, "parameters": params}
|
|
||||||
if description:
|
|
||||||
step_dict["description"] = description
|
|
||||||
normalized.append(step_dict)
|
|
||||||
return normalized
|
|
||||||
|
|
||||||
|
|
||||||
def _normalize_labware(data):
|
|
||||||
labware = {}
|
|
||||||
for item in data:
|
|
||||||
reagent_name = item.get("reagent_name")
|
|
||||||
key = reagent_name or item.get("material_name") or item.get("name")
|
|
||||||
if not key:
|
|
||||||
continue
|
|
||||||
key = str(key)
|
|
||||||
idx = 1
|
|
||||||
original_key = key
|
|
||||||
while key in labware:
|
|
||||||
idx += 1
|
|
||||||
key = f"{original_key}_{idx}"
|
|
||||||
|
|
||||||
labware[key] = {
|
|
||||||
"slot": item.get("positions") or item.get("slot"),
|
|
||||||
"labware": item.get("material_name") or item.get("labware"),
|
|
||||||
"well": item.get("well", []),
|
|
||||||
"type": item.get("type", "reagent"),
|
|
||||||
"role": item.get("role", ""),
|
|
||||||
"name": key,
|
|
||||||
}
|
|
||||||
return labware
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("protocol_name", [
|
|
||||||
"example_bio",
|
|
||||||
# "bioyond_materials_liquidhandling_1",
|
|
||||||
"example_prcxi",
|
|
||||||
])
|
|
||||||
def test_build_protocol_graph(protocol_name):
|
|
||||||
data_path = Path(__file__).with_name(f"{protocol_name}.json")
|
|
||||||
with data_path.open("r", encoding="utf-8") as fp:
|
|
||||||
d = json.load(fp)
|
|
||||||
|
|
||||||
if "workflow" in d and "reagent" in d:
|
|
||||||
protocol_steps = d["workflow"]
|
|
||||||
labware_info = d["reagent"]
|
|
||||||
elif "steps_info" in d and "labware_info" in d:
|
|
||||||
protocol_steps = _normalize_steps(d["steps_info"])
|
|
||||||
labware_info = _normalize_labware(d["labware_info"])
|
|
||||||
else:
|
|
||||||
raise ValueError("Unsupported protocol format")
|
|
||||||
|
|
||||||
graph = build_protocol_graph(
|
|
||||||
labware_info=labware_info,
|
|
||||||
protocol_steps=protocol_steps,
|
|
||||||
workstation_name="PRCXi",
|
|
||||||
)
|
|
||||||
timestamp = datetime.now().strftime("%Y%m%d_%H%M")
|
|
||||||
output_path = data_path.with_name(f"{protocol_name}_graph_{timestamp}.png")
|
|
||||||
draw_protocol_graph_with_ports(graph, str(output_path))
|
|
||||||
print(graph)
|
|
||||||
@@ -13,7 +13,7 @@ def start_backend(
|
|||||||
graph=None,
|
graph=None,
|
||||||
controllers_config: dict = {},
|
controllers_config: dict = {},
|
||||||
bridges=[],
|
bridges=[],
|
||||||
is_slave: bool = False,
|
without_host: bool = False,
|
||||||
visual: str = "None",
|
visual: str = "None",
|
||||||
resources_mesh_config: dict = {},
|
resources_mesh_config: dict = {},
|
||||||
**kwargs,
|
**kwargs,
|
||||||
@@ -32,7 +32,7 @@ def start_backend(
|
|||||||
raise ValueError(f"Unsupported backend: {backend}")
|
raise ValueError(f"Unsupported backend: {backend}")
|
||||||
|
|
||||||
backend_thread = threading.Thread(
|
backend_thread = threading.Thread(
|
||||||
target=main if not is_slave else slave,
|
target=main if not without_host else slave,
|
||||||
args=(
|
args=(
|
||||||
devices_config,
|
devices_config,
|
||||||
resources_config,
|
resources_config,
|
||||||
|
|||||||
@@ -11,14 +11,18 @@ from typing import Dict, Any, List
|
|||||||
import networkx as nx
|
import networkx as nx
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
|
from unilabos.ros.nodes.resource_tracker import ResourceTreeSet, ResourceDict
|
||||||
|
|
||||||
# 首先添加项目根目录到路径
|
# 首先添加项目根目录到路径
|
||||||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
unilabos_dir = os.path.dirname(os.path.dirname(current_dir))
|
unilabos_dir = os.path.dirname(os.path.dirname(current_dir))
|
||||||
if unilabos_dir not in sys.path:
|
if unilabos_dir not in sys.path:
|
||||||
sys.path.append(unilabos_dir)
|
sys.path.append(unilabos_dir)
|
||||||
|
|
||||||
from unilabos.utils.banner_print import print_status, print_unilab_banner
|
|
||||||
from unilabos.config.config import load_config, BasicConfig, HTTPConfig
|
from unilabos.config.config import load_config, BasicConfig, HTTPConfig
|
||||||
|
from unilabos.utils.banner_print import print_status, print_unilab_banner
|
||||||
|
from unilabos.resources.graphio import modify_to_backend_format
|
||||||
|
|
||||||
|
|
||||||
def load_config_from_file(config_path):
|
def load_config_from_file(config_path):
|
||||||
if config_path is None:
|
if config_path is None:
|
||||||
@@ -180,7 +184,6 @@ def main():
|
|||||||
working_dir = os.path.abspath(os.getcwd())
|
working_dir = os.path.abspath(os.getcwd())
|
||||||
else:
|
else:
|
||||||
working_dir = os.path.abspath(os.path.join(os.getcwd(), "unilabos_data"))
|
working_dir = os.path.abspath(os.path.join(os.getcwd(), "unilabos_data"))
|
||||||
|
|
||||||
if args_dict.get("working_dir"):
|
if args_dict.get("working_dir"):
|
||||||
working_dir = args_dict.get("working_dir", "")
|
working_dir = args_dict.get("working_dir", "")
|
||||||
if config_path and not os.path.exists(config_path):
|
if config_path and not os.path.exists(config_path):
|
||||||
@@ -212,14 +215,6 @@ def main():
|
|||||||
# 加载配置文件
|
# 加载配置文件
|
||||||
print_status(f"当前工作目录为 {working_dir}", "info")
|
print_status(f"当前工作目录为 {working_dir}", "info")
|
||||||
load_config_from_file(config_path)
|
load_config_from_file(config_path)
|
||||||
|
|
||||||
# 根据配置重新设置日志级别
|
|
||||||
from unilabos.utils.log import configure_logger, logger
|
|
||||||
|
|
||||||
if hasattr(BasicConfig, "log_level"):
|
|
||||||
logger.info(f"Log level set to '{BasicConfig.log_level}' from config file.")
|
|
||||||
configure_logger(loglevel=BasicConfig.log_level)
|
|
||||||
|
|
||||||
if args_dict["addr"] == "test":
|
if args_dict["addr"] == "test":
|
||||||
print_status("使用测试环境地址", "info")
|
print_status("使用测试环境地址", "info")
|
||||||
HTTPConfig.remote_addr = "https://uni-lab.test.bohrium.com/api/v1"
|
HTTPConfig.remote_addr = "https://uni-lab.test.bohrium.com/api/v1"
|
||||||
@@ -273,8 +268,6 @@ def main():
|
|||||||
from unilabos.app.web import http_client
|
from unilabos.app.web import http_client
|
||||||
from unilabos.app.web import start_server
|
from unilabos.app.web import start_server
|
||||||
from unilabos.app.register import register_devices_and_resources
|
from unilabos.app.register import register_devices_and_resources
|
||||||
from unilabos.resources.graphio import modify_to_backend_format
|
|
||||||
from unilabos.ros.nodes.resource_tracker import ResourceTreeSet, ResourceDict
|
|
||||||
|
|
||||||
# 显示启动横幅
|
# 显示启动横幅
|
||||||
print_unilab_banner(args_dict)
|
print_unilab_banner(args_dict)
|
||||||
@@ -356,7 +349,7 @@ def main():
|
|||||||
|
|
||||||
if BasicConfig.upload_registry:
|
if BasicConfig.upload_registry:
|
||||||
# 设备注册到服务端 - 需要 ak 和 sk
|
# 设备注册到服务端 - 需要 ak 和 sk
|
||||||
if BasicConfig.ak and BasicConfig.sk:
|
if args_dict.get("ak") and args_dict.get("sk"):
|
||||||
print_status("开始注册设备到服务端...", "info")
|
print_status("开始注册设备到服务端...", "info")
|
||||||
try:
|
try:
|
||||||
register_devices_and_resources(lab_registry)
|
register_devices_and_resources(lab_registry)
|
||||||
@@ -375,23 +368,22 @@ def main():
|
|||||||
|
|
||||||
args_dict["bridges"] = []
|
args_dict["bridges"] = []
|
||||||
|
|
||||||
|
# 获取通信客户端(仅支持WebSocket)
|
||||||
|
comm_client = get_communication_client()
|
||||||
|
|
||||||
|
if "websocket" in args_dict["app_bridges"]:
|
||||||
|
args_dict["bridges"].append(comm_client)
|
||||||
if "fastapi" in args_dict["app_bridges"]:
|
if "fastapi" in args_dict["app_bridges"]:
|
||||||
args_dict["bridges"].append(http_client)
|
args_dict["bridges"].append(http_client)
|
||||||
# 获取通信客户端(仅支持WebSocket)
|
if "websocket" in args_dict["app_bridges"]:
|
||||||
if BasicConfig.is_host_mode:
|
|
||||||
comm_client = get_communication_client()
|
|
||||||
if "websocket" in args_dict["app_bridges"]:
|
|
||||||
args_dict["bridges"].append(comm_client)
|
|
||||||
def _exit(signum, frame):
|
|
||||||
comm_client.stop()
|
|
||||||
sys.exit(0)
|
|
||||||
|
|
||||||
signal.signal(signal.SIGINT, _exit)
|
def _exit(signum, frame):
|
||||||
signal.signal(signal.SIGTERM, _exit)
|
comm_client.stop()
|
||||||
comm_client.start()
|
sys.exit(0)
|
||||||
else:
|
|
||||||
print_status("SlaveMode跳过Websocket连接")
|
|
||||||
|
|
||||||
|
signal.signal(signal.SIGINT, _exit)
|
||||||
|
signal.signal(signal.SIGTERM, _exit)
|
||||||
|
comm_client.start()
|
||||||
args_dict["resources_mesh_config"] = {}
|
args_dict["resources_mesh_config"] = {}
|
||||||
args_dict["resources_edge_config"] = resource_edge_info
|
args_dict["resources_edge_config"] = resource_edge_info
|
||||||
# web visiualize 2D
|
# web visiualize 2D
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
import json
|
import json
|
||||||
import time
|
import time
|
||||||
from typing import Optional, Tuple, Dict, Any
|
|
||||||
|
|
||||||
from unilabos.utils.log import logger
|
from unilabos.utils.log import logger
|
||||||
from unilabos.utils.type_check import TypeEncoder
|
from unilabos.utils.type_check import TypeEncoder
|
||||||
|
|
||||||
|
|
||||||
def register_devices_and_resources(lab_registry, gather_only=False) -> Optional[Tuple[Dict[str, Any], Dict[str, Any]]]:
|
def register_devices_and_resources(lab_registry):
|
||||||
"""
|
"""
|
||||||
注册设备和资源到服务器(仅支持HTTP)
|
注册设备和资源到服务器(仅支持HTTP)
|
||||||
"""
|
"""
|
||||||
@@ -29,8 +28,6 @@ def register_devices_and_resources(lab_registry, gather_only=False) -> Optional[
|
|||||||
resources_to_register[resource_info["id"]] = resource_info
|
resources_to_register[resource_info["id"]] = resource_info
|
||||||
logger.debug(f"[UniLab Register] 收集资源: {resource_info['id']}")
|
logger.debug(f"[UniLab Register] 收集资源: {resource_info['id']}")
|
||||||
|
|
||||||
if gather_only:
|
|
||||||
return devices_to_register, resources_to_register
|
|
||||||
# 注册设备
|
# 注册设备
|
||||||
if devices_to_register:
|
if devices_to_register:
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -6,8 +6,6 @@ HTTP客户端模块
|
|||||||
|
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import time
|
|
||||||
from threading import Thread
|
|
||||||
from typing import List, Dict, Any, Optional
|
from typing import List, Dict, Any, Optional
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
@@ -75,8 +73,6 @@ class HTTPClient:
|
|||||||
Returns:
|
Returns:
|
||||||
Dict[str, str]: 旧UUID到新UUID的映射关系 {old_uuid: new_uuid}
|
Dict[str, str]: 旧UUID到新UUID的映射关系 {old_uuid: new_uuid}
|
||||||
"""
|
"""
|
||||||
with open(os.path.join(BasicConfig.working_dir, "req_resource_tree_add.json"), "w", encoding="utf-8") as f:
|
|
||||||
f.write(json.dumps({"nodes": [x for xs in resources.dump() for x in xs], "mount_uuid": mount_uuid}, indent=4))
|
|
||||||
# 从序列化数据中提取所有节点的UUID(保存旧UUID)
|
# 从序列化数据中提取所有节点的UUID(保存旧UUID)
|
||||||
old_uuids = {n.res_content.uuid: n for n in resources.all_nodes}
|
old_uuids = {n.res_content.uuid: n for n in resources.all_nodes}
|
||||||
if not self.initialized or first_add:
|
if not self.initialized or first_add:
|
||||||
@@ -86,18 +82,16 @@ class HTTPClient:
|
|||||||
f"{self.remote_addr}/edge/material",
|
f"{self.remote_addr}/edge/material",
|
||||||
json={"nodes": [x for xs in resources.dump() for x in xs], "mount_uuid": mount_uuid},
|
json={"nodes": [x for xs in resources.dump() for x in xs], "mount_uuid": mount_uuid},
|
||||||
headers={"Authorization": f"Lab {self.auth}"},
|
headers={"Authorization": f"Lab {self.auth}"},
|
||||||
timeout=60,
|
timeout=100,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
response = requests.put(
|
response = requests.put(
|
||||||
f"{self.remote_addr}/edge/material",
|
f"{self.remote_addr}/edge/material",
|
||||||
json={"nodes": [x for xs in resources.dump() for x in xs], "mount_uuid": mount_uuid},
|
json={"nodes": [x for xs in resources.dump() for x in xs], "mount_uuid": mount_uuid},
|
||||||
headers={"Authorization": f"Lab {self.auth}"},
|
headers={"Authorization": f"Lab {self.auth}"},
|
||||||
timeout=10,
|
timeout=100,
|
||||||
)
|
)
|
||||||
|
|
||||||
with open(os.path.join(BasicConfig.working_dir, "res_resource_tree_add.json"), "w", encoding="utf-8") as f:
|
|
||||||
f.write(f"{response.status_code}" + "\n" + response.text)
|
|
||||||
# 处理响应,构建UUID映射
|
# 处理响应,构建UUID映射
|
||||||
uuid_mapping = {}
|
uuid_mapping = {}
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
@@ -128,16 +122,12 @@ class HTTPClient:
|
|||||||
Returns:
|
Returns:
|
||||||
Dict[str, str]: 旧UUID到新UUID的映射关系 {old_uuid: new_uuid}
|
Dict[str, str]: 旧UUID到新UUID的映射关系 {old_uuid: new_uuid}
|
||||||
"""
|
"""
|
||||||
with open(os.path.join(BasicConfig.working_dir, "req_resource_tree_get.json"), "w", encoding="utf-8") as f:
|
|
||||||
f.write(json.dumps({"uuids": uuid_list, "with_children": with_children}, indent=4))
|
|
||||||
response = requests.post(
|
response = requests.post(
|
||||||
f"{self.remote_addr}/edge/material/query",
|
f"{self.remote_addr}/edge/material/query",
|
||||||
json={"uuids": uuid_list, "with_children": with_children},
|
json={"uuids": uuid_list, "with_children": with_children},
|
||||||
headers={"Authorization": f"Lab {self.auth}"},
|
headers={"Authorization": f"Lab {self.auth}"},
|
||||||
timeout=100,
|
timeout=100,
|
||||||
)
|
)
|
||||||
with open(os.path.join(BasicConfig.working_dir, "res_resource_tree_get.json"), "w", encoding="utf-8") as f:
|
|
||||||
f.write(f"{response.status_code}" + "\n" + response.text)
|
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
res = response.json()
|
res = response.json()
|
||||||
if "code" in res and res["code"] != 0:
|
if "code" in res and res["code"] != 0:
|
||||||
@@ -193,16 +183,12 @@ class HTTPClient:
|
|||||||
Returns:
|
Returns:
|
||||||
Dict: 返回的资源数据
|
Dict: 返回的资源数据
|
||||||
"""
|
"""
|
||||||
with open(os.path.join(BasicConfig.working_dir, "req_resource_get.json"), "w", encoding="utf-8") as f:
|
|
||||||
f.write(json.dumps({"id": id, "with_children": with_children}, indent=4))
|
|
||||||
response = requests.get(
|
response = requests.get(
|
||||||
f"{self.remote_addr}/lab/material",
|
f"{self.remote_addr}/lab/material",
|
||||||
params={"id": id, "with_children": with_children},
|
params={"id": id, "with_children": with_children},
|
||||||
headers={"Authorization": f"Lab {self.auth}"},
|
headers={"Authorization": f"Lab {self.auth}"},
|
||||||
timeout=20,
|
timeout=20,
|
||||||
)
|
)
|
||||||
with open(os.path.join(BasicConfig.working_dir, "res_resource_get.json"), "w", encoding="utf-8") as f:
|
|
||||||
f.write(f"{response.status_code}" + "\n" + response.text)
|
|
||||||
return response.json()
|
return response.json()
|
||||||
|
|
||||||
def resource_del(self, id: str) -> requests.Response:
|
def resource_del(self, id: str) -> requests.Response:
|
||||||
|
|||||||
@@ -421,7 +421,7 @@ class MessageProcessor:
|
|||||||
ssl_context = ssl_module.create_default_context()
|
ssl_context = ssl_module.create_default_context()
|
||||||
|
|
||||||
ws_logger = logging.getLogger("websockets.client")
|
ws_logger = logging.getLogger("websockets.client")
|
||||||
# 日志级别已在 unilabos.utils.log 中统一配置为 WARNING
|
ws_logger.setLevel(logging.INFO)
|
||||||
|
|
||||||
async with websockets.connect(
|
async with websockets.connect(
|
||||||
self.websocket_url,
|
self.websocket_url,
|
||||||
@@ -1197,7 +1197,7 @@ class WebSocketClient(BaseCommunicationClient):
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
self.message_processor.send_message(message)
|
self.message_processor.send_message(message)
|
||||||
logger.trace(f"[WebSocketClient] Device status published: {device_id}.{property_name}")
|
logger.debug(f"[WebSocketClient] Device status published: {device_id}.{property_name}")
|
||||||
|
|
||||||
def publish_job_status(
|
def publish_job_status(
|
||||||
self, feedback_data: dict, item: QueueItem, status: str, return_info: Optional[dict] = None
|
self, feedback_data: dict, item: QueueItem, status: str, return_info: Optional[dict] = None
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import base64
|
|||||||
import traceback
|
import traceback
|
||||||
import os
|
import os
|
||||||
import importlib.util
|
import importlib.util
|
||||||
from typing import Optional, Literal
|
from typing import Optional
|
||||||
from unilabos.utils import logger
|
from unilabos.utils import logger
|
||||||
|
|
||||||
|
|
||||||
@@ -18,7 +18,6 @@ class BasicConfig:
|
|||||||
vis_2d_enable = False
|
vis_2d_enable = False
|
||||||
enable_resource_load = True
|
enable_resource_load = True
|
||||||
communication_protocol = "websocket"
|
communication_protocol = "websocket"
|
||||||
log_level: Literal['TRACE', 'DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'] = "DEBUG" # 'TRACE', 'DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def auth_secret(cls):
|
def auth_secret(cls):
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ from serial import Serial
|
|||||||
from serial.serialutil import SerialException
|
from serial.serialutil import SerialException
|
||||||
|
|
||||||
from unilabos.messages import Point3D
|
from unilabos.messages import Point3D
|
||||||
from unilabos.ros.nodes.base_device_node import BaseROS2DeviceNode
|
|
||||||
|
|
||||||
|
|
||||||
class GrblCNCConnectionError(Exception):
|
class GrblCNCConnectionError(Exception):
|
||||||
@@ -33,7 +32,6 @@ class GrblCNCInfo:
|
|||||||
class GrblCNCAsync:
|
class GrblCNCAsync:
|
||||||
_status: str = "Offline"
|
_status: str = "Offline"
|
||||||
_position: Point3D = Point3D(x=0.0, y=0.0, z=0.0)
|
_position: Point3D = Point3D(x=0.0, y=0.0, z=0.0)
|
||||||
_ros_node: BaseROS2DeviceNode
|
|
||||||
|
|
||||||
def __init__(self, port: str, address: str = "1", limits: tuple[int, int, int, int, int, int] = (-150, 150, -200, 0, 0, 60)):
|
def __init__(self, port: str, address: str = "1", limits: tuple[int, int, int, int, int, int] = (-150, 150, -200, 0, 0, 60)):
|
||||||
self.port = port
|
self.port = port
|
||||||
@@ -60,9 +58,6 @@ class GrblCNCAsync:
|
|||||||
self._run_future: Optional[Future[Any]] = None
|
self._run_future: Optional[Future[Any]] = None
|
||||||
self._run_lock = Lock()
|
self._run_lock = Lock()
|
||||||
|
|
||||||
def post_init(self, ros_node: BaseROS2DeviceNode):
|
|
||||||
self._ros_node = ros_node
|
|
||||||
|
|
||||||
def _read_all(self):
|
def _read_all(self):
|
||||||
data = self._serial.read_until(b"\n")
|
data = self._serial.read_until(b"\n")
|
||||||
data_decoded = data.decode()
|
data_decoded = data.decode()
|
||||||
@@ -153,7 +148,7 @@ class GrblCNCAsync:
|
|||||||
try:
|
try:
|
||||||
await self._query(command)
|
await self._query(command)
|
||||||
while True:
|
while True:
|
||||||
await self._ros_node.sleep(0.2) # Wait for 0.5 seconds before polling again
|
await asyncio.sleep(0.2) # Wait for 0.5 seconds before polling again
|
||||||
|
|
||||||
status = await self.get_status()
|
status = await self.get_status()
|
||||||
if "Idle" in status:
|
if "Idle" in status:
|
||||||
@@ -219,7 +214,7 @@ class GrblCNCAsync:
|
|||||||
self._pose_number = i
|
self._pose_number = i
|
||||||
self.pose_number_remaining = len(points) - i
|
self.pose_number_remaining = len(points) - i
|
||||||
await self.set_position(point)
|
await self.set_position(point)
|
||||||
await self._ros_node.sleep(0.5)
|
await asyncio.sleep(0.5)
|
||||||
self._step_number = -1
|
self._step_number = -1
|
||||||
|
|
||||||
async def stop_operation(self):
|
async def stop_operation(self):
|
||||||
@@ -240,7 +235,7 @@ class GrblCNCAsync:
|
|||||||
async def open(self):
|
async def open(self):
|
||||||
if self._read_task:
|
if self._read_task:
|
||||||
raise GrblCNCConnectionError
|
raise GrblCNCConnectionError
|
||||||
self._read_task = self._ros_node.create_task(self._read_loop())
|
self._read_task = asyncio.create_task(self._read_loop())
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await self.get_status()
|
await self.get_status()
|
||||||
|
|||||||
@@ -2,8 +2,6 @@ import time
|
|||||||
import asyncio
|
import asyncio
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
from unilabos.ros.nodes.base_device_node import BaseROS2DeviceNode
|
|
||||||
|
|
||||||
|
|
||||||
class Point3D(BaseModel):
|
class Point3D(BaseModel):
|
||||||
x: float
|
x: float
|
||||||
@@ -16,14 +14,9 @@ def d(a: Point3D, b: Point3D) -> float:
|
|||||||
|
|
||||||
|
|
||||||
class MockCNCAsync:
|
class MockCNCAsync:
|
||||||
_ros_node: BaseROS2DeviceNode["MockCNCAsync"]
|
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._position: Point3D = Point3D(x=0.0, y=0.0, z=0.0)
|
self._position: Point3D = Point3D(x=0.0, y=0.0, z=0.0)
|
||||||
self._status = "Idle"
|
self._status = "Idle"
|
||||||
|
|
||||||
def post_create(self, ros_node):
|
|
||||||
self._ros_node = ros_node
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def position(self) -> Point3D:
|
def position(self) -> Point3D:
|
||||||
@@ -45,5 +38,5 @@ class MockCNCAsync:
|
|||||||
self._position.x = current_pos.x + (position.x - current_pos.x) / 20 * (i+1)
|
self._position.x = current_pos.x + (position.x - current_pos.x) / 20 * (i+1)
|
||||||
self._position.y = current_pos.y + (position.y - current_pos.y) / 20 * (i+1)
|
self._position.y = current_pos.y + (position.y - current_pos.y) / 20 * (i+1)
|
||||||
self._position.z = current_pos.z + (position.z - current_pos.z) / 20 * (i+1)
|
self._position.z = current_pos.z + (position.z - current_pos.z) / 20 * (i+1)
|
||||||
await self._ros_node.sleep(move_time / 20)
|
await asyncio.sleep(move_time / 20)
|
||||||
self._status = "Idle"
|
self._status = "Idle"
|
||||||
|
|||||||
@@ -1,296 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
import serial
|
|
||||||
import time
|
|
||||||
import csv
|
|
||||||
import threading
|
|
||||||
import os
|
|
||||||
from collections import deque
|
|
||||||
from typing import Dict, Any, Optional
|
|
||||||
from pylabrobot.resources import Deck
|
|
||||||
|
|
||||||
from unilabos.devices.workstation.workstation_base import WorkstationBase
|
|
||||||
|
|
||||||
|
|
||||||
class ElectrolysisWaterPlatform(WorkstationBase):
|
|
||||||
"""
|
|
||||||
电解水平台工作站
|
|
||||||
基于 WorkstationBase 的电解水实验平台,支持串口通信和数据采集
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
deck: Deck,
|
|
||||||
port: str = "COM10",
|
|
||||||
baudrate: int = 115200,
|
|
||||||
csv_path: Optional[str] = None,
|
|
||||||
timeout: float = 0.2,
|
|
||||||
**kwargs
|
|
||||||
):
|
|
||||||
super().__init__(deck, **kwargs)
|
|
||||||
|
|
||||||
# ========== 配置 ==========
|
|
||||||
self.port = port
|
|
||||||
self.baudrate = baudrate
|
|
||||||
# 如果没有指定路径,默认保存在代码文件所在目录
|
|
||||||
if csv_path is None:
|
|
||||||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
|
||||||
self.csv_path = os.path.join(current_dir, "stm32_data.csv")
|
|
||||||
else:
|
|
||||||
self.csv_path = csv_path
|
|
||||||
self.ser_timeout = timeout
|
|
||||||
self.chunk_read = 128
|
|
||||||
|
|
||||||
# 串口对象
|
|
||||||
self.ser: Optional[serial.Serial] = None
|
|
||||||
self.stop_flag = False
|
|
||||||
|
|
||||||
# 线程对象
|
|
||||||
self.rx_thread: Optional[threading.Thread] = None
|
|
||||||
self.tx_thread: Optional[threading.Thread] = None
|
|
||||||
|
|
||||||
# ==== 接收(下位机->上位机):固定 1+13+1 = 15 字节 ====
|
|
||||||
self.RX_HEAD = 0x3E
|
|
||||||
self.RX_TAIL = 0x3E
|
|
||||||
self.RX_FRAME_LEN = 1 + 13 + 1 # 15
|
|
||||||
|
|
||||||
# ==== 发送(上位机->下位机):固定 1+9+1 = 11 字节 ====
|
|
||||||
self.TX_HEAD = 0x3E
|
|
||||||
self.TX_TAIL = 0xE3 # 协议图中标注 E3 作为帧尾
|
|
||||||
self.TX_FRAME_LEN = 1 + 9 + 1 # 11
|
|
||||||
|
|
||||||
def open_serial(self, port: Optional[str] = None, baudrate: Optional[int] = None, timeout: Optional[float] = None) -> Optional[serial.Serial]:
|
|
||||||
"""打开串口"""
|
|
||||||
port = port or self.port
|
|
||||||
baudrate = baudrate or self.baudrate
|
|
||||||
timeout = timeout or self.ser_timeout
|
|
||||||
try:
|
|
||||||
ser = serial.Serial(port, baudrate, timeout=timeout)
|
|
||||||
print(f"[OK] 串口 {port} 已打开,波特率 {baudrate}")
|
|
||||||
ser.reset_input_buffer()
|
|
||||||
ser.reset_output_buffer()
|
|
||||||
self.ser = ser
|
|
||||||
return ser
|
|
||||||
except serial.SerialException as e:
|
|
||||||
print(f"[ERR] 无法打开串口 {port}: {e}")
|
|
||||||
return None
|
|
||||||
|
|
||||||
def close_serial(self):
|
|
||||||
"""关闭串口"""
|
|
||||||
if self.ser and self.ser.is_open:
|
|
||||||
self.ser.close()
|
|
||||||
print("[INFO] 串口已关闭")
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def u16_be(h: int, l: int) -> int:
|
|
||||||
"""将两个字节组合成16位无符号整数(大端序)"""
|
|
||||||
return ((h & 0xFF) << 8) | (l & 0xFF)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def split_u16_be(val: int) -> tuple:
|
|
||||||
"""返回 (高字节, 低字节),输入会夹到 0..65535"""
|
|
||||||
v = int(max(0, min(65535, int(val))))
|
|
||||||
return (v >> 8) & 0xFF, v & 0xFF
|
|
||||||
|
|
||||||
# ================== 接收:固定15字节 ==================
|
|
||||||
def parse_rx_payload(self, dat13: bytes) -> Optional[Dict[str, Any]]:
|
|
||||||
"""解析 13 字节数据区(下位机发送到上位机)"""
|
|
||||||
if len(dat13) != 13:
|
|
||||||
return None
|
|
||||||
current_mA = self.u16_be(dat13[0], dat13[1])
|
|
||||||
voltage_mV = self.u16_be(dat13[2], dat13[3])
|
|
||||||
temperature_raw = self.u16_be(dat13[4], dat13[5])
|
|
||||||
tds_ppm = self.u16_be(dat13[6], dat13[7])
|
|
||||||
gas_sccm = self.u16_be(dat13[8], dat13[9])
|
|
||||||
liquid_mL = self.u16_be(dat13[10], dat13[11])
|
|
||||||
ph_raw = dat13[12] & 0xFF
|
|
||||||
|
|
||||||
return {
|
|
||||||
"Current_mA": current_mA,
|
|
||||||
"Voltage_mV": voltage_mV,
|
|
||||||
"Temperature_C": round(temperature_raw / 100.0, 2),
|
|
||||||
"TDS_ppm": tds_ppm,
|
|
||||||
"GasFlow_sccm": gas_sccm,
|
|
||||||
"LiquidFlow_mL": liquid_mL,
|
|
||||||
"pH": round(ph_raw / 10.0, 2)
|
|
||||||
}
|
|
||||||
|
|
||||||
def try_parse_rx_frame(self, frame15: bytes) -> Optional[Dict[str, Any]]:
|
|
||||||
"""尝试解析接收帧"""
|
|
||||||
if len(frame15) != self.RX_FRAME_LEN:
|
|
||||||
return None
|
|
||||||
if frame15[0] != self.RX_HEAD or frame15[-1] != self.RX_TAIL:
|
|
||||||
return None
|
|
||||||
return self.parse_rx_payload(frame15[1:-1])
|
|
||||||
|
|
||||||
def rx_thread_fn(self):
|
|
||||||
"""接收线程函数"""
|
|
||||||
headers = ["Timestamp", "Current_mA", "Voltage_mV",
|
|
||||||
"Temperature_C", "TDS_ppm", "GasFlow_sccm", "LiquidFlow_mL", "pH"]
|
|
||||||
|
|
||||||
new_file = not os.path.exists(self.csv_path)
|
|
||||||
f = open(self.csv_path, mode='a', newline='', encoding='utf-8')
|
|
||||||
writer = csv.writer(f)
|
|
||||||
if new_file:
|
|
||||||
writer.writerow(headers)
|
|
||||||
f.flush()
|
|
||||||
|
|
||||||
buf = deque(maxlen=8192)
|
|
||||||
print(f"[RX] 开始接收(帧长 {self.RX_FRAME_LEN} 字节);写入:{self.csv_path}")
|
|
||||||
|
|
||||||
try:
|
|
||||||
while not self.stop_flag and self.ser and self.ser.is_open:
|
|
||||||
chunk = self.ser.read(self.chunk_read)
|
|
||||||
if chunk:
|
|
||||||
buf.extend(chunk)
|
|
||||||
while True:
|
|
||||||
# 找帧头
|
|
||||||
try:
|
|
||||||
start = next(i for i, b in enumerate(buf) if b == self.RX_HEAD)
|
|
||||||
except StopIteration:
|
|
||||||
buf.clear()
|
|
||||||
break
|
|
||||||
if start > 0:
|
|
||||||
for _ in range(start):
|
|
||||||
buf.popleft()
|
|
||||||
if len(buf) < self.RX_FRAME_LEN:
|
|
||||||
break
|
|
||||||
candidate = bytes([buf[i] for i in range(self.RX_FRAME_LEN)])
|
|
||||||
if candidate[-1] == self.RX_TAIL:
|
|
||||||
parsed = self.try_parse_rx_frame(candidate)
|
|
||||||
for _ in range(self.RX_FRAME_LEN):
|
|
||||||
buf.popleft()
|
|
||||||
if parsed:
|
|
||||||
ts = time.strftime("%Y-%m-%d %H:%M:%S")
|
|
||||||
row = [ts,
|
|
||||||
parsed["Current_mA"], parsed["Voltage_mV"],
|
|
||||||
parsed["Temperature_C"], parsed["TDS_ppm"],
|
|
||||||
parsed["GasFlow_sccm"], parsed["LiquidFlow_mL"],
|
|
||||||
parsed["pH"]]
|
|
||||||
writer.writerow(row)
|
|
||||||
f.flush()
|
|
||||||
# 若不想打印可注释下一行
|
|
||||||
# print(f"[{ts}] I={parsed['Current_mA']} mA, V={parsed['Voltage_mV']} mV, "
|
|
||||||
# f"T={parsed['Temperature_C']} °C, TDS={parsed['TDS_ppm']}, "
|
|
||||||
# f"Gas={parsed['GasFlow_sccm']} sccm, Liq={parsed['LiquidFlow_mL']} mL, pH={parsed['pH']}")
|
|
||||||
else:
|
|
||||||
# 头不变,尾不对,丢1字节继续对齐
|
|
||||||
buf.popleft()
|
|
||||||
else:
|
|
||||||
time.sleep(0.01)
|
|
||||||
finally:
|
|
||||||
f.close()
|
|
||||||
print("[RX] 接收线程退出,CSV 已关闭")
|
|
||||||
|
|
||||||
# ================== 发送:固定11字节 ==================
|
|
||||||
def build_tx_frame(self, mode: int, current_ma: int, voltage_mv: int, temp_c: float, ki: float, pump_percent: float) -> bytes:
|
|
||||||
"""
|
|
||||||
发送帧:HEAD + [mode, I_hi, I_lo, V_hi, V_lo, T_hi, T_lo, Ki_byte, Pump_byte] + TAIL
|
|
||||||
- mode: 0=恒压, 1=恒流
|
|
||||||
- current_ma: mA (0..65535)
|
|
||||||
- voltage_mv: mV (0..65535)
|
|
||||||
- temp_c: ℃,将 *100 后拆分为高/低字节
|
|
||||||
- ki: 0.0..20.0 -> byte = round(ki * 10) 夹到 0..200
|
|
||||||
- pump_percent: 0..100 -> byte = round(pump * 2) 夹到 0..200
|
|
||||||
"""
|
|
||||||
mode_b = 1 if int(mode) == 1 else 0
|
|
||||||
|
|
||||||
i_hi, i_lo = self.split_u16_be(current_ma)
|
|
||||||
v_hi, v_lo = self.split_u16_be(voltage_mv)
|
|
||||||
|
|
||||||
t100 = int(round(float(temp_c) * 100.0))
|
|
||||||
t_hi, t_lo = self.split_u16_be(t100)
|
|
||||||
|
|
||||||
ki_b = int(max(0, min(200, round(float(ki) * 10))))
|
|
||||||
pump_b = int(max(0, min(200, round(float(pump_percent) * 2))))
|
|
||||||
|
|
||||||
return bytes((
|
|
||||||
self.TX_HEAD,
|
|
||||||
mode_b,
|
|
||||||
i_hi, i_lo,
|
|
||||||
v_hi, v_lo,
|
|
||||||
t_hi, t_lo,
|
|
||||||
ki_b,
|
|
||||||
pump_b,
|
|
||||||
self.TX_TAIL
|
|
||||||
))
|
|
||||||
|
|
||||||
def tx_thread_fn(self):
|
|
||||||
"""
|
|
||||||
发送线程函数
|
|
||||||
用户输入 6 个用逗号分隔的数值:
|
|
||||||
mode,current_mA,voltage_mV,set_temp_C,Ki,pump_percent
|
|
||||||
例如: 0,1000,500,0,0,50
|
|
||||||
"""
|
|
||||||
print("\n输入 6 个值(用英文逗号分隔),顺序为:")
|
|
||||||
print("mode,current_mA,voltage_mV,set_temp_C,Ki,pump_percent")
|
|
||||||
print("示例恒压:0,500,1000,25,0,100 (stop 结束)\n")
|
|
||||||
print("示例恒流:1,1000,500,25,0,100 (stop 结束)\n")
|
|
||||||
print("示例恒流:1,2000,500,25,0,100 (stop 结束)\n")
|
|
||||||
# 1,2000,500,25,0,100
|
|
||||||
|
|
||||||
while not self.stop_flag and self.ser and self.ser.is_open:
|
|
||||||
try:
|
|
||||||
line = input(">>> ").strip()
|
|
||||||
except EOFError:
|
|
||||||
self.stop_flag = True
|
|
||||||
break
|
|
||||||
|
|
||||||
if not line:
|
|
||||||
continue
|
|
||||||
if line.lower() == "stop":
|
|
||||||
self.stop_flag = True
|
|
||||||
print("[SYS] 停止程序")
|
|
||||||
break
|
|
||||||
|
|
||||||
try:
|
|
||||||
parts = [p.strip() for p in line.split(",")]
|
|
||||||
if len(parts) != 6:
|
|
||||||
raise ValueError("需要 6 个逗号分隔的数值")
|
|
||||||
mode = int(parts[0])
|
|
||||||
i_ma = int(float(parts[1]))
|
|
||||||
v_mv = int(float(parts[2]))
|
|
||||||
t_c = float(parts[3])
|
|
||||||
ki = float(parts[4])
|
|
||||||
pump = float(parts[5])
|
|
||||||
|
|
||||||
frame = self.build_tx_frame(mode, i_ma, v_mv, t_c, ki, pump)
|
|
||||||
self.ser.write(frame)
|
|
||||||
print("[TX]", " ".join(f"{b:02X}" for b in frame))
|
|
||||||
except Exception as e:
|
|
||||||
print("[TX] 输入/打包失败:", e)
|
|
||||||
print("格式:mode,current_mA,voltage_mV,set_temp_C,Ki,pump_percent")
|
|
||||||
continue
|
|
||||||
|
|
||||||
def start(self):
|
|
||||||
"""启动电解水平台"""
|
|
||||||
self.ser = self.open_serial()
|
|
||||||
if self.ser:
|
|
||||||
try:
|
|
||||||
self.rx_thread = threading.Thread(target=self.rx_thread_fn, daemon=True)
|
|
||||||
self.tx_thread = threading.Thread(target=self.tx_thread_fn, daemon=True)
|
|
||||||
self.rx_thread.start()
|
|
||||||
self.tx_thread.start()
|
|
||||||
print("[INFO] 电解水平台已启动")
|
|
||||||
self.tx_thread.join() # 等待用户输入线程结束(输入 stop)
|
|
||||||
finally:
|
|
||||||
self.close_serial()
|
|
||||||
|
|
||||||
def stop(self):
|
|
||||||
"""停止电解水平台"""
|
|
||||||
self.stop_flag = True
|
|
||||||
if self.rx_thread and self.rx_thread.is_alive():
|
|
||||||
self.rx_thread.join(timeout=2.0)
|
|
||||||
if self.tx_thread and self.tx_thread.is_alive():
|
|
||||||
self.tx_thread.join(timeout=2.0)
|
|
||||||
self.close_serial()
|
|
||||||
print("[INFO] 电解水平台已停止")
|
|
||||||
|
|
||||||
|
|
||||||
# ================== 主入口 ==================
|
|
||||||
if __name__ == "__main__":
|
|
||||||
# 创建一个简单的 Deck 用于测试
|
|
||||||
from pylabrobot.resources import Deck
|
|
||||||
|
|
||||||
deck = Deck()
|
|
||||||
platform = ElectrolysisWaterPlatform(deck)
|
|
||||||
platform.start()
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,11 +1,11 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
import re
|
||||||
|
import traceback
|
||||||
|
from typing import List, Sequence, Optional, Literal, Union, Iterator, Dict, Any, Callable, Set, cast
|
||||||
|
from collections import Counter
|
||||||
import asyncio
|
import asyncio
|
||||||
import time
|
import time
|
||||||
import traceback
|
import pprint as pp
|
||||||
from collections import Counter
|
|
||||||
from typing import List, Sequence, Optional, Literal, Union, Iterator, Dict, Any, Callable, Set, cast
|
|
||||||
|
|
||||||
from pylabrobot.liquid_handling import LiquidHandler, LiquidHandlerBackend, LiquidHandlerChatterboxBackend, Strictness
|
from pylabrobot.liquid_handling import LiquidHandler, LiquidHandlerBackend, LiquidHandlerChatterboxBackend, Strictness
|
||||||
from pylabrobot.liquid_handling.liquid_handler import TipPresenceProbingMethod
|
from pylabrobot.liquid_handling.liquid_handler import TipPresenceProbingMethod
|
||||||
from pylabrobot.liquid_handling.standard import GripDirection
|
from pylabrobot.liquid_handling.standard import GripDirection
|
||||||
@@ -25,8 +25,6 @@ from pylabrobot.resources import (
|
|||||||
Tip,
|
Tip,
|
||||||
)
|
)
|
||||||
|
|
||||||
from unilabos.ros.nodes.base_device_node import BaseROS2DeviceNode
|
|
||||||
|
|
||||||
|
|
||||||
class LiquidHandlerMiddleware(LiquidHandler):
|
class LiquidHandlerMiddleware(LiquidHandler):
|
||||||
def __init__(self, backend: LiquidHandlerBackend, deck: Deck, simulator: bool = False, channel_num: int = 8):
|
def __init__(self, backend: LiquidHandlerBackend, deck: Deck, simulator: bool = False, channel_num: int = 8):
|
||||||
@@ -538,7 +536,6 @@ class LiquidHandlerMiddleware(LiquidHandler):
|
|||||||
class LiquidHandlerAbstract(LiquidHandlerMiddleware):
|
class LiquidHandlerAbstract(LiquidHandlerMiddleware):
|
||||||
"""Extended LiquidHandler with additional operations."""
|
"""Extended LiquidHandler with additional operations."""
|
||||||
support_touch_tip = True
|
support_touch_tip = True
|
||||||
_ros_node: BaseROS2DeviceNode
|
|
||||||
|
|
||||||
def __init__(self, backend: LiquidHandlerBackend, deck: Deck, simulator: bool=False, channel_num:int = 8):
|
def __init__(self, backend: LiquidHandlerBackend, deck: Deck, simulator: bool=False, channel_num:int = 8):
|
||||||
"""Initialize a LiquidHandler.
|
"""Initialize a LiquidHandler.
|
||||||
@@ -551,11 +548,8 @@ class LiquidHandlerAbstract(LiquidHandlerMiddleware):
|
|||||||
self.group_info = dict()
|
self.group_info = dict()
|
||||||
super().__init__(backend, deck, simulator, channel_num)
|
super().__init__(backend, deck, simulator, channel_num)
|
||||||
|
|
||||||
def post_init(self, ros_node: BaseROS2DeviceNode):
|
|
||||||
self._ros_node = ros_node
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def set_liquid(cls, wells: list[Well], liquid_names: list[str], volumes: list[float]):
|
def set_liquid(self, wells: list[Well], liquid_names: list[str], volumes: list[float]):
|
||||||
"""Set the liquid in a well."""
|
"""Set the liquid in a well."""
|
||||||
for well, liquid_name, volume in zip(wells, liquid_names, volumes):
|
for well, liquid_name, volume in zip(wells, liquid_names, volumes):
|
||||||
well.set_liquids([(liquid_name, volume)]) # type: ignore
|
well.set_liquids([(liquid_name, volume)]) # type: ignore
|
||||||
@@ -1087,7 +1081,7 @@ class LiquidHandlerAbstract(LiquidHandlerMiddleware):
|
|||||||
print(f"Waiting time: {msg}")
|
print(f"Waiting time: {msg}")
|
||||||
print(f"Current time: {time.strftime('%H:%M:%S')}")
|
print(f"Current time: {time.strftime('%H:%M:%S')}")
|
||||||
print(f"Time to finish: {time.strftime('%H:%M:%S', time.localtime(time.time() + seconds))}")
|
print(f"Time to finish: {time.strftime('%H:%M:%S', time.localtime(time.time() + seconds))}")
|
||||||
await self._ros_node.sleep(seconds)
|
await asyncio.sleep(seconds)
|
||||||
if msg:
|
if msg:
|
||||||
print(f"Done: {msg}")
|
print(f"Done: {msg}")
|
||||||
print(f"Current time: {time.strftime('%H:%M:%S')}")
|
print(f"Current time: {time.strftime('%H:%M:%S')}")
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ from pylabrobot.liquid_handling.standard import (
|
|||||||
from pylabrobot.resources import Tip, Deck, Plate, Well, TipRack, Resource, Container, Coordinate, TipSpot, Trash
|
from pylabrobot.resources import Tip, Deck, Plate, Well, TipRack, Resource, Container, Coordinate, TipSpot, Trash
|
||||||
|
|
||||||
from unilabos.devices.liquid_handling.liquid_handler_abstract import LiquidHandlerAbstract
|
from unilabos.devices.liquid_handling.liquid_handler_abstract import LiquidHandlerAbstract
|
||||||
from unilabos.ros.nodes.base_device_node import BaseROS2DeviceNode
|
|
||||||
|
|
||||||
|
|
||||||
class PRCXIError(RuntimeError):
|
class PRCXIError(RuntimeError):
|
||||||
@@ -163,10 +162,6 @@ class PRCXI9300Handler(LiquidHandlerAbstract):
|
|||||||
)
|
)
|
||||||
super().__init__(backend=self._unilabos_backend, deck=deck, simulator=simulator, channel_num=channel_num)
|
super().__init__(backend=self._unilabos_backend, deck=deck, simulator=simulator, channel_num=channel_num)
|
||||||
|
|
||||||
def post_init(self, ros_node: BaseROS2DeviceNode):
|
|
||||||
super().post_init(ros_node)
|
|
||||||
self._unilabos_backend.post_init(ros_node)
|
|
||||||
|
|
||||||
def set_liquid(self, wells: list[Well], liquid_names: list[str], volumes: list[float]):
|
def set_liquid(self, wells: list[Well], liquid_names: list[str], volumes: list[float]):
|
||||||
return super().set_liquid(wells, liquid_names, volumes)
|
return super().set_liquid(wells, liquid_names, volumes)
|
||||||
|
|
||||||
@@ -429,7 +424,6 @@ class PRCXI9300Backend(LiquidHandlerBackend):
|
|||||||
|
|
||||||
_num_channels = 8 # 默认通道数为 8
|
_num_channels = 8 # 默认通道数为 8
|
||||||
_is_reset_ok = False
|
_is_reset_ok = False
|
||||||
_ros_node: BaseROS2DeviceNode
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_reset_ok(self) -> bool:
|
def is_reset_ok(self) -> bool:
|
||||||
@@ -462,9 +456,6 @@ class PRCXI9300Backend(LiquidHandlerBackend):
|
|||||||
self._execute_setup = setup
|
self._execute_setup = setup
|
||||||
self.debug = debug
|
self.debug = debug
|
||||||
|
|
||||||
def post_init(self, ros_node: BaseROS2DeviceNode):
|
|
||||||
self._ros_node = ros_node
|
|
||||||
|
|
||||||
def create_protocol(self, protocol_name):
|
def create_protocol(self, protocol_name):
|
||||||
self.protocol_name = protocol_name
|
self.protocol_name = protocol_name
|
||||||
self.steps_todo_list = []
|
self.steps_todo_list = []
|
||||||
@@ -509,7 +500,7 @@ class PRCXI9300Backend(LiquidHandlerBackend):
|
|||||||
self.api_client.call("IAutomation", "Reset")
|
self.api_client.call("IAutomation", "Reset")
|
||||||
while not self.is_reset_ok:
|
while not self.is_reset_ok:
|
||||||
print("Waiting for PRCXI9300 to reset...")
|
print("Waiting for PRCXI9300 to reset...")
|
||||||
await self._ros_node.sleep(1)
|
await asyncio.sleep(1)
|
||||||
print("PRCXI9300 reset successfully.")
|
print("PRCXI9300 reset successfully.")
|
||||||
except ConnectionRefusedError as e:
|
except ConnectionRefusedError as e:
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
@@ -542,9 +533,7 @@ class PRCXI9300Backend(LiquidHandlerBackend):
|
|||||||
tipspot_index = tipspot.parent.children.index(tipspot)
|
tipspot_index = tipspot.parent.children.index(tipspot)
|
||||||
tip_columns.append(tipspot_index // 8)
|
tip_columns.append(tipspot_index // 8)
|
||||||
if len(set(tip_columns)) != 1:
|
if len(set(tip_columns)) != 1:
|
||||||
raise ValueError(
|
raise ValueError("All pickups must be from the same tip column. Found different columns: " + str(tip_columns))
|
||||||
"All pickups must be from the same tip column. Found different columns: " + str(tip_columns)
|
|
||||||
)
|
|
||||||
PlateNo = plate_indexes[0] + 1
|
PlateNo = plate_indexes[0] + 1
|
||||||
hole_col = tip_columns[0] + 1
|
hole_col = tip_columns[0] + 1
|
||||||
hole_row = 1
|
hole_row = 1
|
||||||
@@ -1120,15 +1109,12 @@ class PRCXI9300Api:
|
|||||||
"LiquidDispensingMethod": liquid_method,
|
"LiquidDispensingMethod": liquid_method,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class DefaultLayout:
|
class DefaultLayout:
|
||||||
|
|
||||||
def __init__(self, product_name: str = "PRCXI9300"):
|
def __init__(self, product_name: str = "PRCXI9300"):
|
||||||
self.labresource = {}
|
self.labresource = {}
|
||||||
if product_name not in ["PRCXI9300", "PRCXI9320"]:
|
if product_name not in ["PRCXI9300", "PRCXI9320"]:
|
||||||
raise ValueError(
|
raise ValueError(f"Unsupported product_name: {product_name}. Only 'PRCXI9300' and 'PRCXI9320' are supported.")
|
||||||
f"Unsupported product_name: {product_name}. Only 'PRCXI9300' and 'PRCXI9320' are supported."
|
|
||||||
)
|
|
||||||
|
|
||||||
if product_name == "PRCXI9300":
|
if product_name == "PRCXI9300":
|
||||||
self.rows = 2
|
self.rows = 2
|
||||||
@@ -1143,93 +1129,25 @@ class DefaultLayout:
|
|||||||
self.layout = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]
|
self.layout = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]
|
||||||
self.trash_slot = 16
|
self.trash_slot = 16
|
||||||
self.waste_liquid_slot = 12
|
self.waste_liquid_slot = 12
|
||||||
self.default_layout = {
|
self.default_layout = {"MatrixId":f"{time.time()}","MatrixName":f"{time.time()}","MatrixCount":16,"WorkTablets":
|
||||||
"MatrixId": f"{time.time()}",
|
[{"Number": 1, "Code": "T1", "Material": {"uuid": "57b1e4711e9e4a32b529f3132fc5931f", "materialEnum": 0}},
|
||||||
"MatrixName": f"{time.time()}",
|
{"Number": 2, "Code": "T2", "Material": {"uuid": "57b1e4711e9e4a32b529f3132fc5931f", "materialEnum": 0}},
|
||||||
"MatrixCount": 16,
|
{"Number": 3, "Code": "T3", "Material": {"uuid": "57b1e4711e9e4a32b529f3132fc5931f", "materialEnum": 0}},
|
||||||
"WorkTablets": [
|
{"Number": 4, "Code": "T4", "Material": {"uuid": "57b1e4711e9e4a32b529f3132fc5931f", "materialEnum": 0}},
|
||||||
{
|
{"Number": 5, "Code": "T5", "Material": {"uuid": "57b1e4711e9e4a32b529f3132fc5931f", "materialEnum": 0}},
|
||||||
"Number": 1,
|
{"Number": 6, "Code": "T6", "Material": {"uuid": "57b1e4711e9e4a32b529f3132fc5931f", "materialEnum": 0}},
|
||||||
"Code": "T1",
|
{"Number": 7, "Code": "T7", "Material": {"uuid": "57b1e4711e9e4a32b529f3132fc5931f", "materialEnum": 0}},
|
||||||
"Material": {"uuid": "57b1e4711e9e4a32b529f3132fc5931f", "materialEnum": 0},
|
{"Number": 8, "Code": "T8", "Material": {"uuid": "57b1e4711e9e4a32b529f3132fc5931f", "materialEnum": 0}},
|
||||||
},
|
{"Number": 9, "Code": "T9", "Material": {"uuid": "57b1e4711e9e4a32b529f3132fc5931f", "materialEnum": 0}},
|
||||||
{
|
{"Number": 10, "Code": "T10", "Material": {"uuid": "57b1e4711e9e4a32b529f3132fc5931f", "materialEnum": 0}},
|
||||||
"Number": 2,
|
{"Number": 11, "Code": "T11", "Material": {"uuid": "57b1e4711e9e4a32b529f3132fc5931f", "materialEnum": 0}},
|
||||||
"Code": "T2",
|
{"Number": 12, "Code": "T12", "Material": {"uuid": "730067cf07ae43849ddf4034299030e9", "materialEnum": 0}}, # 这个设置成废液槽,用储液槽表示
|
||||||
"Material": {"uuid": "57b1e4711e9e4a32b529f3132fc5931f", "materialEnum": 0},
|
{"Number": 13, "Code": "T13", "Material": {"uuid": "57b1e4711e9e4a32b529f3132fc5931f", "materialEnum": 0}},
|
||||||
},
|
{"Number": 14, "Code": "T14", "Material": {"uuid": "57b1e4711e9e4a32b529f3132fc5931f", "materialEnum": 0}},
|
||||||
{
|
{"Number": 15, "Code": "T15", "Material": {"uuid": "57b1e4711e9e4a32b529f3132fc5931f", "materialEnum": 0}},
|
||||||
"Number": 3,
|
{"Number": 16, "Code": "T16", "Material": {"uuid": "730067cf07ae43849ddf4034299030e9", "materialEnum": 0}} # 这个设置成垃圾桶,用储液槽表示
|
||||||
"Code": "T3",
|
]
|
||||||
"Material": {"uuid": "57b1e4711e9e4a32b529f3132fc5931f", "materialEnum": 0},
|
}
|
||||||
},
|
|
||||||
{
|
|
||||||
"Number": 4,
|
|
||||||
"Code": "T4",
|
|
||||||
"Material": {"uuid": "57b1e4711e9e4a32b529f3132fc5931f", "materialEnum": 0},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Number": 5,
|
|
||||||
"Code": "T5",
|
|
||||||
"Material": {"uuid": "57b1e4711e9e4a32b529f3132fc5931f", "materialEnum": 0},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Number": 6,
|
|
||||||
"Code": "T6",
|
|
||||||
"Material": {"uuid": "57b1e4711e9e4a32b529f3132fc5931f", "materialEnum": 0},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Number": 7,
|
|
||||||
"Code": "T7",
|
|
||||||
"Material": {"uuid": "57b1e4711e9e4a32b529f3132fc5931f", "materialEnum": 0},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Number": 8,
|
|
||||||
"Code": "T8",
|
|
||||||
"Material": {"uuid": "57b1e4711e9e4a32b529f3132fc5931f", "materialEnum": 0},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Number": 9,
|
|
||||||
"Code": "T9",
|
|
||||||
"Material": {"uuid": "57b1e4711e9e4a32b529f3132fc5931f", "materialEnum": 0},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Number": 10,
|
|
||||||
"Code": "T10",
|
|
||||||
"Material": {"uuid": "57b1e4711e9e4a32b529f3132fc5931f", "materialEnum": 0},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Number": 11,
|
|
||||||
"Code": "T11",
|
|
||||||
"Material": {"uuid": "57b1e4711e9e4a32b529f3132fc5931f", "materialEnum": 0},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Number": 12,
|
|
||||||
"Code": "T12",
|
|
||||||
"Material": {"uuid": "730067cf07ae43849ddf4034299030e9", "materialEnum": 0},
|
|
||||||
}, # 这个设置成废液槽,用储液槽表示
|
|
||||||
{
|
|
||||||
"Number": 13,
|
|
||||||
"Code": "T13",
|
|
||||||
"Material": {"uuid": "57b1e4711e9e4a32b529f3132fc5931f", "materialEnum": 0},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Number": 14,
|
|
||||||
"Code": "T14",
|
|
||||||
"Material": {"uuid": "57b1e4711e9e4a32b529f3132fc5931f", "materialEnum": 0},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Number": 15,
|
|
||||||
"Code": "T15",
|
|
||||||
"Material": {"uuid": "57b1e4711e9e4a32b529f3132fc5931f", "materialEnum": 0},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Number": 16,
|
|
||||||
"Code": "T16",
|
|
||||||
"Material": {"uuid": "730067cf07ae43849ddf4034299030e9", "materialEnum": 0},
|
|
||||||
}, # 这个设置成垃圾桶,用储液槽表示
|
|
||||||
],
|
|
||||||
}
|
|
||||||
|
|
||||||
def get_layout(self) -> Dict[str, Any]:
|
def get_layout(self) -> Dict[str, Any]:
|
||||||
return {
|
return {
|
||||||
@@ -1237,7 +1155,7 @@ class DefaultLayout:
|
|||||||
"columns": self.columns,
|
"columns": self.columns,
|
||||||
"layout": self.layout,
|
"layout": self.layout,
|
||||||
"trash_slot": self.trash_slot,
|
"trash_slot": self.trash_slot,
|
||||||
"waste_liquid_slot": self.waste_liquid_slot,
|
"waste_liquid_slot": self.waste_liquid_slot
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_trash_slot(self) -> int:
|
def get_trash_slot(self) -> int:
|
||||||
@@ -1260,19 +1178,17 @@ class DefaultLayout:
|
|||||||
reserved_positions = {12, 16}
|
reserved_positions = {12, 16}
|
||||||
available_positions = [i for i in range(1, 17) if i not in reserved_positions]
|
available_positions = [i for i in range(1, 17) if i not in reserved_positions]
|
||||||
|
|
||||||
# 计算总需求
|
# 计算总需求
|
||||||
total_needed = sum(count for _, _, count in needs)
|
total_needed = sum(count for _, _, count in needs)
|
||||||
if total_needed > len(available_positions):
|
if total_needed > len(available_positions):
|
||||||
raise ValueError(
|
raise ValueError(f"需要 {total_needed} 个位置,但只有 {len(available_positions)} 个可用位置(排除位置12和16)")
|
||||||
f"需要 {total_needed} 个位置,但只有 {len(available_positions)} 个可用位置(排除位置12和16)"
|
|
||||||
)
|
|
||||||
|
|
||||||
# 依次分配位置
|
# 依次分配位置
|
||||||
current_pos = 0
|
current_pos = 0
|
||||||
for reagent_name, material_name, count in needs:
|
for reagent_name, material_name, count in needs:
|
||||||
|
|
||||||
material_uuid = self.labresource[material_name]["uuid"]
|
material_uuid = self.labresource[material_name]['uuid']
|
||||||
material_enum = self.labresource[material_name]["materialEnum"]
|
material_enum = self.labresource[material_name]['materialEnum']
|
||||||
|
|
||||||
for _ in range(count):
|
for _ in range(count):
|
||||||
if current_pos >= len(available_positions):
|
if current_pos >= len(available_positions):
|
||||||
@@ -1280,18 +1196,17 @@ class DefaultLayout:
|
|||||||
|
|
||||||
position = available_positions[current_pos]
|
position = available_positions[current_pos]
|
||||||
# 找到对应的tablet并更新
|
# 找到对应的tablet并更新
|
||||||
for tablet in self.default_layout["WorkTablets"]:
|
for tablet in self.default_layout['WorkTablets']:
|
||||||
if tablet["Number"] == position:
|
if tablet['Number'] == position:
|
||||||
tablet["Material"]["uuid"] = material_uuid
|
tablet['Material']['uuid'] = material_uuid
|
||||||
tablet["Material"]["materialEnum"] = material_enum
|
tablet['Material']['materialEnum'] = material_enum
|
||||||
layout_list.append(
|
layout_list.append(dict(reagent_name=reagent_name, material_name=material_name, positions=position))
|
||||||
dict(reagent_name=reagent_name, material_name=material_name, positions=position)
|
|
||||||
)
|
|
||||||
break
|
break
|
||||||
current_pos += 1
|
current_pos += 1
|
||||||
return self.default_layout, layout_list
|
return self.default_layout, layout_list
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
# Example usage
|
# Example usage
|
||||||
# 1. 用导出的json,给每个T1 T2板子设定相应的物料,如果是孔板和枪头盒,要对应区分
|
# 1. 用导出的json,给每个T1 T2板子设定相应的物料,如果是孔板和枪头盒,要对应区分
|
||||||
@@ -1387,7 +1302,10 @@ if __name__ == "__main__":
|
|||||||
|
|
||||||
# # # plate2.set_well_liquids(plate_2_liquids)
|
# # # plate2.set_well_liquids(plate_2_liquids)
|
||||||
|
|
||||||
# handler = PRCXI9300Handler(deck=deck, host="10.181.214.132", port=9999,
|
|
||||||
|
|
||||||
|
|
||||||
|
# handler = PRCXI9300Handler(deck=deck, host="10.181.214.132", port=9999,
|
||||||
# timeout=10.0, setup=False, debug=False,
|
# timeout=10.0, setup=False, debug=False,
|
||||||
# simulator=True,
|
# simulator=True,
|
||||||
# matrix_id="71593",
|
# matrix_id="71593",
|
||||||
@@ -1473,7 +1391,10 @@ if __name__ == "__main__":
|
|||||||
# # input("Press Enter to continue...") # Wait for user input before proceeding
|
# # input("Press Enter to continue...") # Wait for user input before proceeding
|
||||||
# # print("PRCXI9300Handler initialized with deck and host settings.")
|
# # print("PRCXI9300Handler initialized with deck and host settings.")
|
||||||
|
|
||||||
### 9320 ###
|
|
||||||
|
|
||||||
|
### 9320 ###
|
||||||
|
|
||||||
|
|
||||||
deck = PRCXI9300Deck(name="PRCXI_Deck", size_x=100, size_y=100, size_z=100)
|
deck = PRCXI9300Deck(name="PRCXI_Deck", size_x=100, size_y=100, size_z=100)
|
||||||
|
|
||||||
@@ -1491,15 +1412,12 @@ if __name__ == "__main__":
|
|||||||
new_plate: PRCXI9300Container = PRCXI9300Container.deserialize(well_containers)
|
new_plate: PRCXI9300Container = PRCXI9300Container.deserialize(well_containers)
|
||||||
return new_plate
|
return new_plate
|
||||||
|
|
||||||
def get_tip_rack(name: str, child_prefix: str = "tip") -> PRCXI9300Container:
|
def get_tip_rack(name: str, child_prefix: str="tip") -> PRCXI9300Container:
|
||||||
tip_racks = opentrons_96_tiprack_10ul(name).serialize()
|
tip_racks = opentrons_96_tiprack_10ul(name).serialize()
|
||||||
tip_rack = PRCXI9300Container(
|
tip_rack = PRCXI9300Container(
|
||||||
name=name,
|
name=name, size_x=50, size_y=50, size_z=10, category="tip_rack", ordering=collections.OrderedDict({
|
||||||
size_x=50,
|
k: f"{child_prefix}_{k}" for k, v in tip_racks["ordering"].items()
|
||||||
size_y=50,
|
})
|
||||||
size_z=10,
|
|
||||||
category="tip_rack",
|
|
||||||
ordering=collections.OrderedDict({k: f"{child_prefix}_{k}" for k, v in tip_racks["ordering"].items()}),
|
|
||||||
)
|
)
|
||||||
tip_rack_serialized = tip_rack.serialize()
|
tip_rack_serialized = tip_rack.serialize()
|
||||||
tip_rack_serialized["parent_name"] = deck.name
|
tip_rack_serialized["parent_name"] = deck.name
|
||||||
@@ -1711,7 +1629,6 @@ if __name__ == "__main__":
|
|||||||
)
|
)
|
||||||
backend: PRCXI9300Backend = handler.backend
|
backend: PRCXI9300Backend = handler.backend
|
||||||
from pylabrobot.resources import set_volume_tracking
|
from pylabrobot.resources import set_volume_tracking
|
||||||
|
|
||||||
set_volume_tracking(enabled=True)
|
set_volume_tracking(enabled=True)
|
||||||
# res = backend.api_client.get_all_materials()
|
# res = backend.api_client.get_all_materials()
|
||||||
asyncio.run(handler.setup()) # Initialize the handler and setup the connection
|
asyncio.run(handler.setup()) # Initialize the handler and setup the connection
|
||||||
@@ -1723,10 +1640,10 @@ if __name__ == "__main__":
|
|||||||
|
|
||||||
for well in plate13.get_all_items():
|
for well in plate13.get_all_items():
|
||||||
# well_pos = well.name.split("_")[1] # 走一行
|
# well_pos = well.name.split("_")[1] # 走一行
|
||||||
# if well_pos.startswith("A"):
|
# if well_pos.startswith("A"):
|
||||||
if well.name.startswith("PlateT13"): # 走整个Plate
|
if well.name.startswith("PlateT13"): # 走整个Plate
|
||||||
asyncio.run(handler.dispense([well], [0.01], [0]))
|
asyncio.run(handler.dispense([well], [0.01], [0]))
|
||||||
|
|
||||||
# asyncio.run(handler.dispense([plate10.get_item("H12")], [1], [0]))
|
# asyncio.run(handler.dispense([plate10.get_item("H12")], [1], [0]))
|
||||||
# asyncio.run(handler.dispense([plate13.get_item("A1")], [1], [0]))
|
# asyncio.run(handler.dispense([plate13.get_item("A1")], [1], [0]))
|
||||||
# asyncio.run(handler.dispense([plate14.get_item("C5")], [1], [0]))
|
# asyncio.run(handler.dispense([plate14.get_item("C5")], [1], [0]))
|
||||||
@@ -1735,25 +1652,26 @@ if __name__ == "__main__":
|
|||||||
asyncio.run(handler.run_protocol())
|
asyncio.run(handler.run_protocol())
|
||||||
time.sleep(5)
|
time.sleep(5)
|
||||||
os._exit(0)
|
os._exit(0)
|
||||||
# 第一种情景:一个孔往多个孔加液
|
# 第一种情景:一个孔往多个孔加液
|
||||||
# plate_2_liquids = handler.set_group("water", [plate2.children[0]], [300])
|
# plate_2_liquids = handler.set_group("water", [plate2.children[0]], [300])
|
||||||
# plate5_liquids = handler.set_group("master_mix", plate5.children[:23], [100]*23)
|
# plate5_liquids = handler.set_group("master_mix", plate5.children[:23], [100]*23)
|
||||||
# 第二个情景:多个孔往多个孔加液(但是个数得对应)
|
# 第二个情景:多个孔往多个孔加液(但是个数得对应)
|
||||||
plate_2_liquids = handler.set_group("water", plate2.children[:23], [300] * 23)
|
plate_2_liquids = handler.set_group("water", plate2.children[:23], [300]*23)
|
||||||
plate5_liquids = handler.set_group("master_mix", plate5.children[:23], [100] * 23)
|
plate5_liquids = handler.set_group("master_mix", plate5.children[:23], [100]*23)
|
||||||
|
|
||||||
# plate11.set_well_liquids([("Water", 100) if (i % 8 == 0 and i // 8 < 6) else (None, 100) for i in range(96)]) # Set liquids for every 8 wells in plate8
|
# plate11.set_well_liquids([("Water", 100) if (i % 8 == 0 and i // 8 < 6) else (None, 100) for i in range(96)]) # Set liquids for every 8 wells in plate8
|
||||||
|
|
||||||
# plate11.set_well_liquids([("Water", 100) if (i % 8 == 0 and i // 8 < 6) else (None, 100) for i in range(96)]) # Set liquids for every 8 wells in plate8
|
# plate11.set_well_liquids([("Water", 100) if (i % 8 == 0 and i // 8 < 6) else (None, 100) for i in range(96)]) # Set liquids for every 8 wells in plate8
|
||||||
|
|
||||||
# A = tree_to_list([resource_plr_to_ulab(deck)])
|
# A = tree_to_list([resource_plr_to_ulab(deck)])
|
||||||
# # with open("deck.json", "w", encoding="utf-8") as f:
|
# # with open("deck.json", "w", encoding="utf-8") as f:
|
||||||
# # json.dump(A, f, indent=4, ensure_ascii=False)
|
# # json.dump(A, f, indent=4, ensure_ascii=False)
|
||||||
|
|
||||||
# print(plate11.get_well(0).tracker.get_used_volume())
|
# print(plate11.get_well(0).tracker.get_used_volume())
|
||||||
# Initialize the backend and setup the connection
|
# Initialize the backend and setup the connection
|
||||||
asyncio.run(handler.transfer_group("water", "master_mix", 10)) # Reset tip tracking
|
asyncio.run(handler.transfer_group("water", "master_mix", 10)) # Reset tip tracking
|
||||||
|
|
||||||
|
|
||||||
# asyncio.run(handler.pick_up_tips([plate8.children[8]],[0]))
|
# asyncio.run(handler.pick_up_tips([plate8.children[8]],[0]))
|
||||||
# print(plate8.children[8])
|
# print(plate8.children[8])
|
||||||
# asyncio.run(handler.run_protocol())
|
# asyncio.run(handler.run_protocol())
|
||||||
@@ -1767,118 +1685,121 @@ if __name__ == "__main__":
|
|||||||
# print(plate1.children[0])
|
# print(plate1.children[0])
|
||||||
# asyncio.run(handler.discard_tips([0]))
|
# asyncio.run(handler.discard_tips([0]))
|
||||||
|
|
||||||
# asyncio.run(handler.add_liquid(
|
# asyncio.run(handler.add_liquid(
|
||||||
# asp_vols=[10]*7,
|
# asp_vols=[10]*7,
|
||||||
# dis_vols=[10]*7,
|
# dis_vols=[10]*7,
|
||||||
# reagent_sources=plate11.children[:7],
|
# reagent_sources=plate11.children[:7],
|
||||||
# targets=plate1.children[2:9],
|
# targets=plate1.children[2:9],
|
||||||
# use_channels=[0],
|
# use_channels=[0],
|
||||||
# flow_rates=[None] * 7,
|
# flow_rates=[None] * 7,
|
||||||
# offsets=[Coordinate(0, 0, 0)] * 7,
|
# offsets=[Coordinate(0, 0, 0)] * 7,
|
||||||
# liquid_height=[None] * 7,
|
# liquid_height=[None] * 7,
|
||||||
# blow_out_air_volume=[None] * 2,
|
# blow_out_air_volume=[None] * 2,
|
||||||
# delays=None,
|
# delays=None,
|
||||||
# mix_time=3,
|
# mix_time=3,
|
||||||
# mix_vol=5,
|
# mix_vol=5,
|
||||||
# spread="custom",
|
# spread="custom",
|
||||||
# ))
|
# ))
|
||||||
|
|
||||||
# asyncio.run(handler.run_protocol()) # Run the protocol
|
# asyncio.run(handler.run_protocol()) # Run the protocol
|
||||||
|
|
||||||
# # # asyncio.run(handler.transfer_liquid(
|
|
||||||
# # # asp_vols=[10]*2,
|
|
||||||
# # # dis_vols=[10]*2,
|
|
||||||
# # # sources=plate11.children[:2],
|
|
||||||
# # # targets=plate11.children[-2:],
|
|
||||||
# # # use_channels=[0],
|
|
||||||
# # # offsets=[Coordinate(0, 0, 0)] * 4,
|
|
||||||
# # # liquid_height=[None] * 2,
|
|
||||||
# # # blow_out_air_volume=[None] * 2,
|
|
||||||
# # # delays=None,
|
|
||||||
# # # mix_times=3,
|
|
||||||
# # # mix_vol=5,
|
|
||||||
# # # spread="wide",
|
|
||||||
# # # tip_racks=[plate8]
|
|
||||||
# # # ))
|
|
||||||
|
|
||||||
# # # asyncio.run(handler.remove_liquid(
|
|
||||||
# # # vols=[10]*2,
|
|
||||||
# # # sources=plate11.children[:2],
|
|
||||||
# # # waste_liquid=plate11.children[43],
|
|
||||||
# # # use_channels=[0],
|
|
||||||
# # # offsets=[Coordinate(0, 0, 0)] * 4,
|
|
||||||
# # # liquid_height=[None] * 2,
|
|
||||||
# # # blow_out_air_volume=[None] * 2,
|
|
||||||
# # # delays=None,
|
|
||||||
# # # spread="wide"
|
|
||||||
# # # ))
|
|
||||||
# # asyncio.run(handler.run_protocol())
|
|
||||||
|
|
||||||
# # # asyncio.run(handler.discard_tips())
|
|
||||||
# # # asyncio.run(handler.mix(well_containers.children[:8
|
|
||||||
# # # ], mix_time=3, mix_vol=50, height_to_bottom=0.5, offsets=Coordinate(0, 0, 0), mix_rate=100))
|
|
||||||
# # #print(json.dumps(handler._unilabos_backend.steps_todo_list, indent=2)) # Print matrix info
|
|
||||||
|
|
||||||
# # # asyncio.run(handler.remove_liquid(
|
# # # asyncio.run(handler.transfer_liquid(
|
||||||
# # # vols=[100]*16,
|
# # # asp_vols=[10]*2,
|
||||||
# # # sources=well_containers.children[-16:],
|
# # # dis_vols=[10]*2,
|
||||||
# # # waste_liquid=well_containers.children[:16], # 这个有些奇怪,但是好像也只能这么写
|
# # # sources=plate11.children[:2],
|
||||||
# # # use_channels=[0, 1, 2, 3, 4, 5, 6, 7],
|
# # # targets=plate11.children[-2:],
|
||||||
# # # flow_rates=[None] * 32,
|
# # # use_channels=[0],
|
||||||
# # # offsets=[Coordinate(0, 0, 0)] * 32,
|
# # # offsets=[Coordinate(0, 0, 0)] * 4,
|
||||||
# # # liquid_height=[None] * 32,
|
# # # liquid_height=[None] * 2,
|
||||||
# # # blow_out_air_volume=[None] * 32,
|
# # # blow_out_air_volume=[None] * 2,
|
||||||
# # # spread="wide",
|
# # # delays=None,
|
||||||
# # # ))
|
# # # mix_times=3,
|
||||||
# # # asyncio.run(handler.transfer_liquid(
|
# # # mix_vol=5,
|
||||||
# # # asp_vols=[100]*16,
|
# # # spread="wide",
|
||||||
# # # dis_vols=[100]*16,
|
# # # tip_racks=[plate8]
|
||||||
# # # tip_racks=[tip_rack],
|
# # # ))
|
||||||
# # # sources=well_containers.children[-16:],
|
|
||||||
# # # targets=well_containers.children[:16],
|
# # # asyncio.run(handler.remove_liquid(
|
||||||
# # # use_channels=[0, 1, 2, 3, 4, 5, 6, 7],
|
# # # vols=[10]*2,
|
||||||
# # # offsets=[Coordinate(0, 0, 0)] * 32,
|
# # # sources=plate11.children[:2],
|
||||||
# # # asp_flow_rates=[None] * 16,
|
# # # waste_liquid=plate11.children[43],
|
||||||
# # # dis_flow_rates=[None] * 16,
|
# # # use_channels=[0],
|
||||||
# # # liquid_height=[None] * 32,
|
# # # offsets=[Coordinate(0, 0, 0)] * 4,
|
||||||
# # # blow_out_air_volume=[None] * 32,
|
# # # liquid_height=[None] * 2,
|
||||||
# # # mix_times=3,
|
# # # blow_out_air_volume=[None] * 2,
|
||||||
# # # mix_vol=50,
|
# # # delays=None,
|
||||||
# # # spread="wide",
|
# # # spread="wide"
|
||||||
# # # ))
|
# # # ))
|
||||||
# # print(json.dumps(handler._unilabos_backend.steps_todo_list, indent=2)) # Print matrix info
|
# # asyncio.run(handler.run_protocol())
|
||||||
# # # input("pick_up_tips add step")
|
|
||||||
# asyncio.run(handler.run_protocol()) # Run the protocol
|
# # # asyncio.run(handler.discard_tips())
|
||||||
# # # input("Running protocol...")
|
# # # asyncio.run(handler.mix(well_containers.children[:8
|
||||||
# # # input("Press Enter to continue...") # Wait for user input before proceeding
|
# # # ], mix_time=3, mix_vol=50, height_to_bottom=0.5, offsets=Coordinate(0, 0, 0), mix_rate=100))
|
||||||
# # # print("PRCXI9300Handler initialized with deck and host settings.")
|
# # #print(json.dumps(handler._unilabos_backend.steps_todo_list, indent=2)) # Print matrix info
|
||||||
|
|
||||||
|
|
||||||
|
# # # asyncio.run(handler.remove_liquid(
|
||||||
|
# # # vols=[100]*16,
|
||||||
|
# # # sources=well_containers.children[-16:],
|
||||||
|
# # # waste_liquid=well_containers.children[:16], # 这个有些奇怪,但是好像也只能这么写
|
||||||
|
# # # use_channels=[0, 1, 2, 3, 4, 5, 6, 7],
|
||||||
|
# # # flow_rates=[None] * 32,
|
||||||
|
# # # offsets=[Coordinate(0, 0, 0)] * 32,
|
||||||
|
# # # liquid_height=[None] * 32,
|
||||||
|
# # # blow_out_air_volume=[None] * 32,
|
||||||
|
# # # spread="wide",
|
||||||
|
# # # ))
|
||||||
|
# # # asyncio.run(handler.transfer_liquid(
|
||||||
|
# # # asp_vols=[100]*16,
|
||||||
|
# # # dis_vols=[100]*16,
|
||||||
|
# # # tip_racks=[tip_rack],
|
||||||
|
# # # sources=well_containers.children[-16:],
|
||||||
|
# # # targets=well_containers.children[:16],
|
||||||
|
# # # use_channels=[0, 1, 2, 3, 4, 5, 6, 7],
|
||||||
|
# # # offsets=[Coordinate(0, 0, 0)] * 32,
|
||||||
|
# # # asp_flow_rates=[None] * 16,
|
||||||
|
# # # dis_flow_rates=[None] * 16,
|
||||||
|
# # # liquid_height=[None] * 32,
|
||||||
|
# # # blow_out_air_volume=[None] * 32,
|
||||||
|
# # # mix_times=3,
|
||||||
|
# # # mix_vol=50,
|
||||||
|
# # # spread="wide",
|
||||||
|
# # # ))
|
||||||
|
# # print(json.dumps(handler._unilabos_backend.steps_todo_list, indent=2)) # Print matrix info
|
||||||
|
# # # input("pick_up_tips add step")
|
||||||
|
#asyncio.run(handler.run_protocol()) # Run the protocol
|
||||||
|
# # # input("Running protocol...")
|
||||||
|
# # # input("Press Enter to continue...") # Wait for user input before proceeding
|
||||||
|
# # # print("PRCXI9300Handler initialized with deck and host settings.")
|
||||||
|
|
||||||
|
|
||||||
|
# 一些推荐版位组合的测试样例:
|
||||||
|
|
||||||
|
# 一些推荐版位组合的测试样例:
|
||||||
|
|
||||||
# 一些推荐版位组合的测试样例:
|
|
||||||
|
|
||||||
# 一些推荐版位组合的测试样例:
|
|
||||||
|
|
||||||
with open("prcxi_material.json", "r") as f:
|
with open("prcxi_material.json", "r") as f:
|
||||||
material_info = json.load(f)
|
material_info = json.load(f)
|
||||||
|
|
||||||
layout = DefaultLayout("PRCXI9320")
|
layout = DefaultLayout("PRCXI9320")
|
||||||
layout.add_lab_resource(material_info)
|
layout.add_lab_resource(material_info)
|
||||||
MatrixLayout_1, dict_1 = layout.recommend_layout(
|
MatrixLayout_1, dict_1 = layout.recommend_layout([
|
||||||
[
|
("reagent_1", "96 细胞培养皿", 3),
|
||||||
("reagent_1", "96 细胞培养皿", 3),
|
("reagent_2", "12道储液槽", 1),
|
||||||
("reagent_2", "12道储液槽", 1),
|
("reagent_3", "200μL Tip头", 7),
|
||||||
("reagent_3", "200μL Tip头", 7),
|
("reagent_4", "10μL加长 Tip头", 1),
|
||||||
("reagent_4", "10μL加长 Tip头", 1),
|
])
|
||||||
]
|
|
||||||
)
|
|
||||||
print(dict_1)
|
print(dict_1)
|
||||||
MatrixLayout_2, dict_2 = layout.recommend_layout(
|
MatrixLayout_2, dict_2 = layout.recommend_layout([
|
||||||
[
|
("reagent_1", "96深孔板", 4),
|
||||||
("reagent_1", "96深孔板", 4),
|
("reagent_2", "12道储液槽", 1),
|
||||||
("reagent_2", "12道储液槽", 1),
|
("reagent_3", "200μL Tip头", 1),
|
||||||
("reagent_3", "200μL Tip头", 1),
|
("reagent_4", "10μL加长 Tip头", 1),
|
||||||
("reagent_4", "10μL加长 Tip头", 1),
|
])
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
# with open("prcxi_material.json", "r") as f:
|
# with open("prcxi_material.json", "r") as f:
|
||||||
# material_info = json.load(f)
|
# material_info = json.load(f)
|
||||||
|
|||||||
@@ -8,8 +8,6 @@ import serial.tools.list_ports
|
|||||||
from serial import Serial
|
from serial import Serial
|
||||||
from serial.serialutil import SerialException
|
from serial.serialutil import SerialException
|
||||||
|
|
||||||
from unilabos.ros.nodes.base_device_node import BaseROS2DeviceNode
|
|
||||||
|
|
||||||
|
|
||||||
class RunzeSyringePumpMode(Enum):
|
class RunzeSyringePumpMode(Enum):
|
||||||
Normal = 0
|
Normal = 0
|
||||||
@@ -79,8 +77,6 @@ class RunzeSyringePumpInfo:
|
|||||||
|
|
||||||
|
|
||||||
class RunzeSyringePumpAsync:
|
class RunzeSyringePumpAsync:
|
||||||
_ros_node: BaseROS2DeviceNode
|
|
||||||
|
|
||||||
def __init__(self, port: str, address: str = "1", volume: float = 25000, mode: RunzeSyringePumpMode = None):
|
def __init__(self, port: str, address: str = "1", volume: float = 25000, mode: RunzeSyringePumpMode = None):
|
||||||
self.port = port
|
self.port = port
|
||||||
self.address = address
|
self.address = address
|
||||||
@@ -106,9 +102,6 @@ class RunzeSyringePumpAsync:
|
|||||||
self._run_future: Optional[Future[Any]] = None
|
self._run_future: Optional[Future[Any]] = None
|
||||||
self._run_lock = Lock()
|
self._run_lock = Lock()
|
||||||
|
|
||||||
def post_init(self, ros_node: BaseROS2DeviceNode):
|
|
||||||
self._ros_node = ros_node
|
|
||||||
|
|
||||||
def _adjust_total_steps(self):
|
def _adjust_total_steps(self):
|
||||||
self.total_steps = 6000 if self.mode == RunzeSyringePumpMode.Normal else 48000
|
self.total_steps = 6000 if self.mode == RunzeSyringePumpMode.Normal else 48000
|
||||||
self.total_steps_vel = 48000 if self.mode == RunzeSyringePumpMode.AccuratePosVel else 6000
|
self.total_steps_vel = 48000 if self.mode == RunzeSyringePumpMode.AccuratePosVel else 6000
|
||||||
@@ -189,7 +182,7 @@ class RunzeSyringePumpAsync:
|
|||||||
try:
|
try:
|
||||||
await self._query(command)
|
await self._query(command)
|
||||||
while True:
|
while True:
|
||||||
await self._ros_node.sleep(0.5) # Wait for 0.5 seconds before polling again
|
await asyncio.sleep(0.5) # Wait for 0.5 seconds before polling again
|
||||||
|
|
||||||
status = await self.query_device_status()
|
status = await self.query_device_status()
|
||||||
if status == '`':
|
if status == '`':
|
||||||
@@ -371,7 +364,7 @@ class RunzeSyringePumpAsync:
|
|||||||
if self._read_task:
|
if self._read_task:
|
||||||
raise RunzeSyringePumpConnectionError
|
raise RunzeSyringePumpConnectionError
|
||||||
|
|
||||||
self._read_task = self._ros_node.create_task(self._read_loop())
|
self._read_task = asyncio.create_task(self._read_loop())
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await self.query_device_status()
|
await self.query_device_status()
|
||||||
|
|||||||
@@ -3,13 +3,9 @@ import logging
|
|||||||
import time as time_module
|
import time as time_module
|
||||||
from typing import Dict, Any, Optional
|
from typing import Dict, Any, Optional
|
||||||
|
|
||||||
from unilabos.ros.nodes.base_device_node import BaseROS2DeviceNode
|
|
||||||
|
|
||||||
|
|
||||||
class VirtualCentrifuge:
|
class VirtualCentrifuge:
|
||||||
"""Virtual centrifuge device - 简化版,只保留核心功能"""
|
"""Virtual centrifuge device - 简化版,只保留核心功能"""
|
||||||
|
|
||||||
_ros_node: BaseROS2DeviceNode
|
|
||||||
|
|
||||||
def __init__(self, device_id: Optional[str] = None, config: Optional[Dict[str, Any]] = None, **kwargs):
|
def __init__(self, device_id: Optional[str] = None, config: Optional[Dict[str, Any]] = None, **kwargs):
|
||||||
# 处理可能的不同调用方式
|
# 处理可能的不同调用方式
|
||||||
@@ -36,9 +32,6 @@ class VirtualCentrifuge:
|
|||||||
for key, value in kwargs.items():
|
for key, value in kwargs.items():
|
||||||
if key not in skip_keys and not hasattr(self, key):
|
if key not in skip_keys and not hasattr(self, key):
|
||||||
setattr(self, key, value)
|
setattr(self, key, value)
|
||||||
|
|
||||||
def post_init(self, ros_node: BaseROS2DeviceNode):
|
|
||||||
self._ros_node = ros_node
|
|
||||||
|
|
||||||
async def initialize(self) -> bool:
|
async def initialize(self) -> bool:
|
||||||
"""Initialize virtual centrifuge"""
|
"""Initialize virtual centrifuge"""
|
||||||
@@ -139,7 +132,7 @@ class VirtualCentrifuge:
|
|||||||
break
|
break
|
||||||
|
|
||||||
# 每秒更新一次
|
# 每秒更新一次
|
||||||
await self._ros_node.sleep(1.0)
|
await asyncio.sleep(1.0)
|
||||||
|
|
||||||
# 离心完成
|
# 离心完成
|
||||||
self.data.update({
|
self.data.update({
|
||||||
|
|||||||
@@ -2,13 +2,9 @@ import asyncio
|
|||||||
import logging
|
import logging
|
||||||
from typing import Dict, Any, Optional
|
from typing import Dict, Any, Optional
|
||||||
|
|
||||||
from unilabos.ros.nodes.base_device_node import BaseROS2DeviceNode
|
|
||||||
|
|
||||||
class VirtualColumn:
|
class VirtualColumn:
|
||||||
"""Virtual column device for RunColumn protocol 🏛️"""
|
"""Virtual column device for RunColumn protocol 🏛️"""
|
||||||
|
|
||||||
_ros_node: BaseROS2DeviceNode
|
|
||||||
|
|
||||||
def __init__(self, device_id: str = None, config: Dict[str, Any] = None, **kwargs):
|
def __init__(self, device_id: str = None, config: Dict[str, Any] = None, **kwargs):
|
||||||
# 处理可能的不同调用方式
|
# 处理可能的不同调用方式
|
||||||
if device_id is None and 'id' in kwargs:
|
if device_id is None and 'id' in kwargs:
|
||||||
@@ -32,9 +28,6 @@ class VirtualColumn:
|
|||||||
print(f"🏛️ === 虚拟色谱柱 {self.device_id} 已创建 === ✨")
|
print(f"🏛️ === 虚拟色谱柱 {self.device_id} 已创建 === ✨")
|
||||||
print(f"📏 柱参数: 流速={self._max_flow_rate}mL/min | 长度={self._column_length}cm | 直径={self._column_diameter}cm 🔬")
|
print(f"📏 柱参数: 流速={self._max_flow_rate}mL/min | 长度={self._column_length}cm | 直径={self._column_diameter}cm 🔬")
|
||||||
|
|
||||||
def post_init(self, ros_node: BaseROS2DeviceNode):
|
|
||||||
self._ros_node = ros_node
|
|
||||||
|
|
||||||
async def initialize(self) -> bool:
|
async def initialize(self) -> bool:
|
||||||
"""Initialize virtual column 🚀"""
|
"""Initialize virtual column 🚀"""
|
||||||
self.logger.info(f"🔧 初始化虚拟色谱柱 {self.device_id} ✨")
|
self.logger.info(f"🔧 初始化虚拟色谱柱 {self.device_id} ✨")
|
||||||
@@ -108,7 +101,7 @@ class VirtualColumn:
|
|||||||
step_time = separation_time / steps
|
step_time = separation_time / steps
|
||||||
|
|
||||||
for i in range(steps):
|
for i in range(steps):
|
||||||
await self._ros_node.sleep(step_time)
|
await asyncio.sleep(step_time)
|
||||||
|
|
||||||
progress = (i + 1) / steps * 100
|
progress = (i + 1) / steps * 100
|
||||||
volume_processed = (i + 1) * 5.0 # 假设每步处理5mL
|
volume_processed = (i + 1) * 5.0 # 假设每步处理5mL
|
||||||
|
|||||||
@@ -4,76 +4,70 @@ import time as time_module
|
|||||||
from typing import Dict, Any, Optional
|
from typing import Dict, Any, Optional
|
||||||
|
|
||||||
from unilabos.compile.utils.vessel_parser import get_vessel
|
from unilabos.compile.utils.vessel_parser import get_vessel
|
||||||
from unilabos.ros.nodes.base_device_node import BaseROS2DeviceNode
|
|
||||||
|
|
||||||
|
|
||||||
class VirtualFilter:
|
class VirtualFilter:
|
||||||
"""Virtual filter device - 完全按照 Filter.action 规范 🌊"""
|
"""Virtual filter device - 完全按照 Filter.action 规范 🌊"""
|
||||||
|
|
||||||
_ros_node: BaseROS2DeviceNode
|
|
||||||
|
|
||||||
def __init__(self, device_id: Optional[str] = None, config: Optional[Dict[str, Any]] = None, **kwargs):
|
def __init__(self, device_id: Optional[str] = None, config: Optional[Dict[str, Any]] = None, **kwargs):
|
||||||
if device_id is None and "id" in kwargs:
|
if device_id is None and 'id' in kwargs:
|
||||||
device_id = kwargs.pop("id")
|
device_id = kwargs.pop('id')
|
||||||
if config is None and "config" in kwargs:
|
if config is None and 'config' in kwargs:
|
||||||
config = kwargs.pop("config")
|
config = kwargs.pop('config')
|
||||||
|
|
||||||
self.device_id = device_id or "unknown_filter"
|
self.device_id = device_id or "unknown_filter"
|
||||||
self.config = config or {}
|
self.config = config or {}
|
||||||
self.logger = logging.getLogger(f"VirtualFilter.{self.device_id}")
|
self.logger = logging.getLogger(f"VirtualFilter.{self.device_id}")
|
||||||
self.data = {}
|
self.data = {}
|
||||||
|
|
||||||
# 从config或kwargs中获取配置参数
|
# 从config或kwargs中获取配置参数
|
||||||
self.port = self.config.get("port") or kwargs.get("port", "VIRTUAL")
|
self.port = self.config.get('port') or kwargs.get('port', 'VIRTUAL')
|
||||||
self._max_temp = self.config.get("max_temp") or kwargs.get("max_temp", 100.0)
|
self._max_temp = self.config.get('max_temp') or kwargs.get('max_temp', 100.0)
|
||||||
self._max_stir_speed = self.config.get("max_stir_speed") or kwargs.get("max_stir_speed", 1000.0)
|
self._max_stir_speed = self.config.get('max_stir_speed') or kwargs.get('max_stir_speed', 1000.0)
|
||||||
self._max_volume = self.config.get("max_volume") or kwargs.get("max_volume", 500.0)
|
self._max_volume = self.config.get('max_volume') or kwargs.get('max_volume', 500.0)
|
||||||
|
|
||||||
# 处理其他kwargs参数
|
# 处理其他kwargs参数
|
||||||
skip_keys = {"port", "max_temp", "max_stir_speed", "max_volume"}
|
skip_keys = {'port', 'max_temp', 'max_stir_speed', 'max_volume'}
|
||||||
for key, value in kwargs.items():
|
for key, value in kwargs.items():
|
||||||
if key not in skip_keys and not hasattr(self, key):
|
if key not in skip_keys and not hasattr(self, key):
|
||||||
setattr(self, key, value)
|
setattr(self, key, value)
|
||||||
|
|
||||||
def post_init(self, ros_node: BaseROS2DeviceNode):
|
|
||||||
self._ros_node = ros_node
|
|
||||||
|
|
||||||
async def initialize(self) -> bool:
|
async def initialize(self) -> bool:
|
||||||
"""Initialize virtual filter 🚀"""
|
"""Initialize virtual filter 🚀"""
|
||||||
self.logger.info(f"🔧 初始化虚拟过滤器 {self.device_id} ✨")
|
self.logger.info(f"🔧 初始化虚拟过滤器 {self.device_id} ✨")
|
||||||
|
|
||||||
# 按照 Filter.action 的 feedback 字段初始化
|
# 按照 Filter.action 的 feedback 字段初始化
|
||||||
self.data.update(
|
self.data.update({
|
||||||
{
|
"status": "Idle",
|
||||||
"status": "Idle",
|
"progress": 0.0, # Filter.action feedback
|
||||||
"progress": 0.0, # Filter.action feedback
|
"current_temp": 25.0, # Filter.action feedback
|
||||||
"current_temp": 25.0, # Filter.action feedback
|
"filtered_volume": 0.0, # Filter.action feedback
|
||||||
"filtered_volume": 0.0, # Filter.action feedback
|
"message": "Ready for filtration"
|
||||||
"message": "Ready for filtration",
|
})
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
self.logger.info(f"✅ 过滤器 {self.device_id} 初始化完成 🌊")
|
self.logger.info(f"✅ 过滤器 {self.device_id} 初始化完成 🌊")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
async def cleanup(self) -> bool:
|
async def cleanup(self) -> bool:
|
||||||
"""Cleanup virtual filter 🧹"""
|
"""Cleanup virtual filter 🧹"""
|
||||||
self.logger.info(f"🧹 清理虚拟过滤器 {self.device_id} 🔚")
|
self.logger.info(f"🧹 清理虚拟过滤器 {self.device_id} 🔚")
|
||||||
|
|
||||||
self.data.update({"status": "Offline"})
|
self.data.update({
|
||||||
|
"status": "Offline"
|
||||||
|
})
|
||||||
|
|
||||||
self.logger.info(f"✅ 过滤器 {self.device_id} 清理完成 💤")
|
self.logger.info(f"✅ 过滤器 {self.device_id} 清理完成 💤")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
async def filter(
|
async def filter(
|
||||||
self,
|
self,
|
||||||
vessel: dict,
|
vessel: dict,
|
||||||
filtrate_vessel: dict = {},
|
filtrate_vessel: dict = {},
|
||||||
stir: bool = False,
|
stir: bool = False,
|
||||||
stir_speed: float = 300.0,
|
stir_speed: float = 300.0,
|
||||||
temp: float = 25.0,
|
temp: float = 25.0,
|
||||||
continue_heatchill: bool = False,
|
continue_heatchill: bool = False,
|
||||||
volume: float = 0.0,
|
volume: float = 0.0
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""Execute filter action - 完全按照 Filter.action 参数 🌊"""
|
"""Execute filter action - 完全按照 Filter.action 参数 🌊"""
|
||||||
vessel_id, _ = get_vessel(vessel)
|
vessel_id, _ = get_vessel(vessel)
|
||||||
@@ -85,52 +79,59 @@ class VirtualFilter:
|
|||||||
temp = 25.0 # 0度自动设置为室温
|
temp = 25.0 # 0度自动设置为室温
|
||||||
self.logger.info(f"🌡️ 温度自动调整: {original_temp}°C → {temp}°C (室温) 🏠")
|
self.logger.info(f"🌡️ 温度自动调整: {original_temp}°C → {temp}°C (室温) 🏠")
|
||||||
elif temp < 4.0:
|
elif temp < 4.0:
|
||||||
temp = 4.0 # 小于4度自动设置为4度
|
temp = 4.0 # 小于4度自动设置为4度
|
||||||
self.logger.info(f"🌡️ 温度自动调整: {original_temp}°C → {temp}°C (最低温度) ❄️")
|
self.logger.info(f"🌡️ 温度自动调整: {original_temp}°C → {temp}°C (最低温度) ❄️")
|
||||||
|
|
||||||
self.logger.info(f"🌊 开始过滤操作: {vessel_id} → {filtrate_vessel_id} 🚰")
|
self.logger.info(f"🌊 开始过滤操作: {vessel_id} → {filtrate_vessel_id} 🚰")
|
||||||
self.logger.info(f" 🌪️ 搅拌: {stir} ({stir_speed} RPM)")
|
self.logger.info(f" 🌪️ 搅拌: {stir} ({stir_speed} RPM)")
|
||||||
self.logger.info(f" 🌡️ 温度: {temp}°C")
|
self.logger.info(f" 🌡️ 温度: {temp}°C")
|
||||||
self.logger.info(f" 💧 体积: {volume}mL")
|
self.logger.info(f" 💧 体积: {volume}mL")
|
||||||
self.logger.info(f" 🔥 保持加热: {continue_heatchill}")
|
self.logger.info(f" 🔥 保持加热: {continue_heatchill}")
|
||||||
|
|
||||||
# 验证参数
|
# 验证参数
|
||||||
if temp > self._max_temp or temp < 4.0:
|
if temp > self._max_temp or temp < 4.0:
|
||||||
error_msg = f"🌡️ 温度 {temp}°C 超出范围 (4-{self._max_temp}°C) ⚠️"
|
error_msg = f"🌡️ 温度 {temp}°C 超出范围 (4-{self._max_temp}°C) ⚠️"
|
||||||
self.logger.error(f"❌ {error_msg}")
|
self.logger.error(f"❌ {error_msg}")
|
||||||
self.data.update({"status": f"Error: 温度超出范围 ⚠️", "message": error_msg})
|
self.data.update({
|
||||||
|
"status": f"Error: 温度超出范围 ⚠️",
|
||||||
|
"message": error_msg
|
||||||
|
})
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if stir and stir_speed > self._max_stir_speed:
|
if stir and stir_speed > self._max_stir_speed:
|
||||||
error_msg = f"🌪️ 搅拌速度 {stir_speed} RPM 超出范围 (0-{self._max_stir_speed} RPM) ⚠️"
|
error_msg = f"🌪️ 搅拌速度 {stir_speed} RPM 超出范围 (0-{self._max_stir_speed} RPM) ⚠️"
|
||||||
self.logger.error(f"❌ {error_msg}")
|
self.logger.error(f"❌ {error_msg}")
|
||||||
self.data.update({"status": f"Error: 搅拌速度超出范围 ⚠️", "message": error_msg})
|
self.data.update({
|
||||||
|
"status": f"Error: 搅拌速度超出范围 ⚠️",
|
||||||
|
"message": error_msg
|
||||||
|
})
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if volume > self._max_volume:
|
if volume > self._max_volume:
|
||||||
error_msg = f"💧 过滤体积 {volume} mL 超出范围 (0-{self._max_volume} mL) ⚠️"
|
error_msg = f"💧 过滤体积 {volume} mL 超出范围 (0-{self._max_volume} mL) ⚠️"
|
||||||
self.logger.error(f"❌ {error_msg}")
|
self.logger.error(f"❌ {error_msg}")
|
||||||
self.data.update({"status": f"Error", "message": error_msg})
|
self.data.update({
|
||||||
|
"status": f"Error",
|
||||||
|
"message": error_msg
|
||||||
|
})
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# 开始过滤
|
# 开始过滤
|
||||||
filter_volume = volume if volume > 0 else 50.0
|
filter_volume = volume if volume > 0 else 50.0
|
||||||
self.logger.info(f"🚀 开始过滤 {filter_volume}mL 液体 💧")
|
self.logger.info(f"🚀 开始过滤 {filter_volume}mL 液体 💧")
|
||||||
|
|
||||||
self.data.update(
|
self.data.update({
|
||||||
{
|
"status": f"Running",
|
||||||
"status": f"Running",
|
"current_temp": temp,
|
||||||
"current_temp": temp,
|
"filtered_volume": 0.0,
|
||||||
"filtered_volume": 0.0,
|
"progress": 0.0,
|
||||||
"progress": 0.0,
|
"message": f"🚀 Starting filtration: {vessel_id} → {filtrate_vessel_id}"
|
||||||
"message": f"🚀 Starting filtration: {vessel_id} → {filtrate_vessel_id}",
|
})
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# 过滤过程 - 实时更新进度
|
# 过滤过程 - 实时更新进度
|
||||||
start_time = time_module.time()
|
start_time = time_module.time()
|
||||||
|
|
||||||
# 根据体积和搅拌估算过滤时间
|
# 根据体积和搅拌估算过滤时间
|
||||||
base_time = filter_volume / 5.0 # 5mL/s 基础速度
|
base_time = filter_volume / 5.0 # 5mL/s 基础速度
|
||||||
if stir:
|
if stir:
|
||||||
@@ -139,79 +140,78 @@ class VirtualFilter:
|
|||||||
if temp > 50.0:
|
if temp > 50.0:
|
||||||
base_time *= 0.7 # 高温加速过滤
|
base_time *= 0.7 # 高温加速过滤
|
||||||
self.logger.info(f"🔥 高温加速过滤,预计时间减少30% ⚡")
|
self.logger.info(f"🔥 高温加速过滤,预计时间减少30% ⚡")
|
||||||
|
|
||||||
filter_time = max(base_time, 10.0) # 最少10秒
|
filter_time = max(base_time, 10.0) # 最少10秒
|
||||||
self.logger.info(f"⏱️ 预计过滤时间: {filter_time:.1f}秒 ⌛")
|
self.logger.info(f"⏱️ 预计过滤时间: {filter_time:.1f}秒 ⌛")
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
current_time = time_module.time()
|
current_time = time_module.time()
|
||||||
elapsed = current_time - start_time
|
elapsed = current_time - start_time
|
||||||
remaining = max(0, filter_time - elapsed)
|
remaining = max(0, filter_time - elapsed)
|
||||||
progress = min(100.0, (elapsed / filter_time) * 100)
|
progress = min(100.0, (elapsed / filter_time) * 100)
|
||||||
current_filtered = (progress / 100.0) * filter_volume
|
current_filtered = (progress / 100.0) * filter_volume
|
||||||
|
|
||||||
# 更新状态 - 按照 Filter.action feedback 字段
|
# 更新状态 - 按照 Filter.action feedback 字段
|
||||||
status_msg = f"🌊 过滤中: {vessel}"
|
status_msg = f"🌊 过滤中: {vessel}"
|
||||||
if stir:
|
if stir:
|
||||||
status_msg += f" | 🌪️ 搅拌: {stir_speed} RPM"
|
status_msg += f" | 🌪️ 搅拌: {stir_speed} RPM"
|
||||||
status_msg += f" | 🌡️ {temp}°C | 📊 {progress:.1f}% | 💧 已过滤: {current_filtered:.1f}mL"
|
status_msg += f" | 🌡️ {temp}°C | 📊 {progress:.1f}% | 💧 已过滤: {current_filtered:.1f}mL"
|
||||||
|
|
||||||
self.data.update(
|
self.data.update({
|
||||||
{
|
"progress": progress, # Filter.action feedback
|
||||||
"progress": progress, # Filter.action feedback
|
"current_temp": temp, # Filter.action feedback
|
||||||
"current_temp": temp, # Filter.action feedback
|
"filtered_volume": current_filtered, # Filter.action feedback
|
||||||
"filtered_volume": current_filtered, # Filter.action feedback
|
"status": "Running",
|
||||||
"status": "Running",
|
"message": f"🌊 Filtering: {progress:.1f}% complete, {current_filtered:.1f}mL filtered"
|
||||||
"message": f"🌊 Filtering: {progress:.1f}% complete, {current_filtered:.1f}mL filtered",
|
})
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
# 进度日志(每25%打印一次)
|
# 进度日志(每25%打印一次)
|
||||||
if progress >= 25 and progress % 25 < 1:
|
if progress >= 25 and progress % 25 < 1:
|
||||||
self.logger.info(f"📊 过滤进度: {progress:.0f}% | 💧 {current_filtered:.1f}mL 完成 ✨")
|
self.logger.info(f"📊 过滤进度: {progress:.0f}% | 💧 {current_filtered:.1f}mL 完成 ✨")
|
||||||
|
|
||||||
if remaining <= 0:
|
if remaining <= 0:
|
||||||
break
|
break
|
||||||
|
|
||||||
await self._ros_node.sleep(1.0)
|
await asyncio.sleep(1.0)
|
||||||
|
|
||||||
# 过滤完成
|
# 过滤完成
|
||||||
final_temp = temp if continue_heatchill else 25.0
|
final_temp = temp if continue_heatchill else 25.0
|
||||||
final_status = f"✅ 过滤完成: {vessel} | 💧 {filter_volume}mL → {filtrate_vessel}"
|
final_status = f"✅ 过滤完成: {vessel} | 💧 {filter_volume}mL → {filtrate_vessel}"
|
||||||
if continue_heatchill:
|
if continue_heatchill:
|
||||||
final_status += " | 🔥 继续加热搅拌"
|
final_status += " | 🔥 继续加热搅拌"
|
||||||
self.logger.info(f"🔥 继续保持加热搅拌状态 🌪️")
|
self.logger.info(f"🔥 继续保持加热搅拌状态 🌪️")
|
||||||
|
|
||||||
self.data.update(
|
self.data.update({
|
||||||
{
|
"status": final_status,
|
||||||
"status": final_status,
|
"progress": 100.0, # Filter.action feedback
|
||||||
"progress": 100.0, # Filter.action feedback
|
"current_temp": final_temp, # Filter.action feedback
|
||||||
"current_temp": final_temp, # Filter.action feedback
|
"filtered_volume": filter_volume, # Filter.action feedback
|
||||||
"filtered_volume": filter_volume, # Filter.action feedback
|
"message": f"✅ Filtration completed: {filter_volume}mL filtered from {vessel_id}"
|
||||||
"message": f"✅ Filtration completed: {filter_volume}mL filtered from {vessel_id}",
|
})
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
self.logger.info(f"🎉 过滤完成! 💧 {filter_volume}mL 从 {vessel_id} 过滤到 {filtrate_vessel_id} ✨")
|
self.logger.info(f"🎉 过滤完成! 💧 {filter_volume}mL 从 {vessel_id} 过滤到 {filtrate_vessel_id} ✨")
|
||||||
self.logger.info(f"📊 最终状态: 温度 {final_temp}°C | 进度 100% | 体积 {filter_volume}mL 🏁")
|
self.logger.info(f"📊 最终状态: 温度 {final_temp}°C | 进度 100% | 体积 {filter_volume}mL 🏁")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
error_msg = f"过滤过程中发生错误: {str(e)} 💥"
|
error_msg = f"过滤过程中发生错误: {str(e)} 💥"
|
||||||
self.logger.error(f"❌ {error_msg}")
|
self.logger.error(f"❌ {error_msg}")
|
||||||
self.data.update({"status": f"Error", "message": f"❌ Filtration failed: {str(e)}"})
|
self.data.update({
|
||||||
|
"status": f"Error",
|
||||||
|
"message": f"❌ Filtration failed: {str(e)}"
|
||||||
|
})
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# === 核心状态属性 - 按照 Filter.action feedback 字段 ===
|
# === 核心状态属性 - 按照 Filter.action feedback 字段 ===
|
||||||
@property
|
@property
|
||||||
def status(self) -> str:
|
def status(self) -> str:
|
||||||
return self.data.get("status", "❓ Unknown")
|
return self.data.get("status", "❓ Unknown")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def progress(self) -> float:
|
def progress(self) -> float:
|
||||||
"""Filter.action feedback 字段 📊"""
|
"""Filter.action feedback 字段 📊"""
|
||||||
return self.data.get("progress", 0.0)
|
return self.data.get("progress", 0.0)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_temp(self) -> float:
|
def current_temp(self) -> float:
|
||||||
"""Filter.action feedback 字段 🌡️"""
|
"""Filter.action feedback 字段 🌡️"""
|
||||||
@@ -230,15 +230,15 @@ class VirtualFilter:
|
|||||||
@property
|
@property
|
||||||
def message(self) -> str:
|
def message(self) -> str:
|
||||||
return self.data.get("message", "")
|
return self.data.get("message", "")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def max_temp(self) -> float:
|
def max_temp(self) -> float:
|
||||||
return self._max_temp
|
return self._max_temp
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def max_stir_speed(self) -> float:
|
def max_stir_speed(self) -> float:
|
||||||
return self._max_stir_speed
|
return self._max_stir_speed
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def max_volume(self) -> float:
|
def max_volume(self) -> float:
|
||||||
return self._max_volume
|
return self._max_volume
|
||||||
@@ -3,13 +3,9 @@ import logging
|
|||||||
import time as time_module # 重命名time模块,避免与参数冲突
|
import time as time_module # 重命名time模块,避免与参数冲突
|
||||||
from typing import Dict, Any
|
from typing import Dict, Any
|
||||||
|
|
||||||
from unilabos.ros.nodes.base_device_node import BaseROS2DeviceNode
|
|
||||||
|
|
||||||
class VirtualHeatChill:
|
class VirtualHeatChill:
|
||||||
"""Virtual heat chill device for HeatChillProtocol testing 🌡️"""
|
"""Virtual heat chill device for HeatChillProtocol testing 🌡️"""
|
||||||
|
|
||||||
_ros_node: BaseROS2DeviceNode
|
|
||||||
|
|
||||||
def __init__(self, device_id: str = None, config: Dict[str, Any] = None, **kwargs):
|
def __init__(self, device_id: str = None, config: Dict[str, Any] = None, **kwargs):
|
||||||
# 处理可能的不同调用方式
|
# 处理可能的不同调用方式
|
||||||
if device_id is None and 'id' in kwargs:
|
if device_id is None and 'id' in kwargs:
|
||||||
@@ -39,9 +35,6 @@ class VirtualHeatChill:
|
|||||||
print(f"🌡️ === 虚拟温控设备 {self.device_id} 已创建 === ✨")
|
print(f"🌡️ === 虚拟温控设备 {self.device_id} 已创建 === ✨")
|
||||||
print(f"🔥 温度范围: {self._min_temp}°C ~ {self._max_temp}°C | 🌪️ 最大搅拌: {self._max_stir_speed} RPM")
|
print(f"🔥 温度范围: {self._min_temp}°C ~ {self._max_temp}°C | 🌪️ 最大搅拌: {self._max_stir_speed} RPM")
|
||||||
|
|
||||||
def post_init(self, ros_node: BaseROS2DeviceNode):
|
|
||||||
self._ros_node = ros_node
|
|
||||||
|
|
||||||
async def initialize(self) -> bool:
|
async def initialize(self) -> bool:
|
||||||
"""Initialize virtual heat chill 🚀"""
|
"""Initialize virtual heat chill 🚀"""
|
||||||
self.logger.info(f"🔧 初始化虚拟温控设备 {self.device_id} ✨")
|
self.logger.info(f"🔧 初始化虚拟温控设备 {self.device_id} ✨")
|
||||||
@@ -184,7 +177,7 @@ class VirtualHeatChill:
|
|||||||
break
|
break
|
||||||
|
|
||||||
# 等待1秒后再次检查
|
# 等待1秒后再次检查
|
||||||
await self._ros_node.sleep(1.0)
|
await asyncio.sleep(1.0)
|
||||||
|
|
||||||
# 操作完成
|
# 操作完成
|
||||||
final_stir_info = f" | 🌪️ 搅拌: {stir_speed} RPM" if stir else ""
|
final_stir_info = f" | 🌪️ 搅拌: {stir_speed} RPM" if stir else ""
|
||||||
|
|||||||
@@ -3,19 +3,13 @@ import logging
|
|||||||
import time as time_module
|
import time as time_module
|
||||||
from typing import Dict, Any, Optional
|
from typing import Dict, Any, Optional
|
||||||
|
|
||||||
from unilabos.ros.nodes.base_device_node import BaseROS2DeviceNode
|
|
||||||
|
|
||||||
|
|
||||||
def debug_print(message):
|
def debug_print(message):
|
||||||
"""调试输出 🔍"""
|
"""调试输出 🔍"""
|
||||||
print(f"🌪️ [ROTAVAP] {message}", flush=True)
|
print(f"🌪️ [ROTAVAP] {message}", flush=True)
|
||||||
|
|
||||||
|
|
||||||
class VirtualRotavap:
|
class VirtualRotavap:
|
||||||
"""Virtual rotary evaporator device - 简化版,只保留核心功能 🌪️"""
|
"""Virtual rotary evaporator device - 简化版,只保留核心功能 🌪️"""
|
||||||
|
|
||||||
_ros_node: BaseROS2DeviceNode
|
|
||||||
|
|
||||||
def __init__(self, device_id: Optional[str] = None, config: Optional[Dict[str, Any]] = None, **kwargs):
|
def __init__(self, device_id: Optional[str] = None, config: Optional[Dict[str, Any]] = None, **kwargs):
|
||||||
# 处理可能的不同调用方式
|
# 处理可能的不同调用方式
|
||||||
if device_id is None and "id" in kwargs:
|
if device_id is None and "id" in kwargs:
|
||||||
@@ -44,65 +38,56 @@ class VirtualRotavap:
|
|||||||
print(f"🌪️ === 虚拟旋转蒸发仪 {self.device_id} 已创建 === ✨")
|
print(f"🌪️ === 虚拟旋转蒸发仪 {self.device_id} 已创建 === ✨")
|
||||||
print(f"🔥 温度范围: 10°C ~ {self._max_temp}°C | 🌀 转速范围: 10 ~ {self._max_rotation_speed} RPM")
|
print(f"🔥 温度范围: 10°C ~ {self._max_temp}°C | 🌀 转速范围: 10 ~ {self._max_rotation_speed} RPM")
|
||||||
|
|
||||||
def post_init(self, ros_node: BaseROS2DeviceNode):
|
|
||||||
self._ros_node = ros_node
|
|
||||||
|
|
||||||
async def initialize(self) -> bool:
|
async def initialize(self) -> bool:
|
||||||
"""Initialize virtual rotary evaporator 🚀"""
|
"""Initialize virtual rotary evaporator 🚀"""
|
||||||
self.logger.info(f"🔧 初始化虚拟旋转蒸发仪 {self.device_id} ✨")
|
self.logger.info(f"🔧 初始化虚拟旋转蒸发仪 {self.device_id} ✨")
|
||||||
|
|
||||||
# 只保留核心状态
|
# 只保留核心状态
|
||||||
self.data.update(
|
self.data.update({
|
||||||
{
|
"status": "🏠 待机中",
|
||||||
"status": "🏠 待机中",
|
"rotavap_state": "Ready", # Ready, Evaporating, Completed, Error
|
||||||
"rotavap_state": "Ready", # Ready, Evaporating, Completed, Error
|
"current_temp": 25.0,
|
||||||
"current_temp": 25.0,
|
"target_temp": 25.0,
|
||||||
"target_temp": 25.0,
|
"rotation_speed": 0.0,
|
||||||
"rotation_speed": 0.0,
|
"vacuum_pressure": 1.0, # 大气压
|
||||||
"vacuum_pressure": 1.0, # 大气压
|
"evaporated_volume": 0.0,
|
||||||
"evaporated_volume": 0.0,
|
"progress": 0.0,
|
||||||
"progress": 0.0,
|
"remaining_time": 0.0,
|
||||||
"remaining_time": 0.0,
|
"message": "🌪️ Ready for evaporation"
|
||||||
"message": "🌪️ Ready for evaporation",
|
})
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
self.logger.info(f"✅ 旋转蒸发仪 {self.device_id} 初始化完成 🌪️")
|
self.logger.info(f"✅ 旋转蒸发仪 {self.device_id} 初始化完成 🌪️")
|
||||||
self.logger.info(
|
self.logger.info(f"📊 设备规格: 温度范围 10°C ~ {self._max_temp}°C | 转速范围 10 ~ {self._max_rotation_speed} RPM")
|
||||||
f"📊 设备规格: 温度范围 10°C ~ {self._max_temp}°C | 转速范围 10 ~ {self._max_rotation_speed} RPM"
|
|
||||||
)
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
async def cleanup(self) -> bool:
|
async def cleanup(self) -> bool:
|
||||||
"""Cleanup virtual rotary evaporator 🧹"""
|
"""Cleanup virtual rotary evaporator 🧹"""
|
||||||
self.logger.info(f"🧹 清理虚拟旋转蒸发仪 {self.device_id} 🔚")
|
self.logger.info(f"🧹 清理虚拟旋转蒸发仪 {self.device_id} 🔚")
|
||||||
|
|
||||||
self.data.update(
|
self.data.update({
|
||||||
{
|
"status": "💤 离线",
|
||||||
"status": "💤 离线",
|
"rotavap_state": "Offline",
|
||||||
"rotavap_state": "Offline",
|
"current_temp": 25.0,
|
||||||
"current_temp": 25.0,
|
"rotation_speed": 0.0,
|
||||||
"rotation_speed": 0.0,
|
"vacuum_pressure": 1.0,
|
||||||
"vacuum_pressure": 1.0,
|
"message": "💤 System offline"
|
||||||
"message": "💤 System offline",
|
})
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
self.logger.info(f"✅ 旋转蒸发仪 {self.device_id} 清理完成 💤")
|
self.logger.info(f"✅ 旋转蒸发仪 {self.device_id} 清理完成 💤")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
async def evaporate(
|
async def evaporate(
|
||||||
self,
|
self,
|
||||||
vessel: str,
|
vessel: str,
|
||||||
pressure: float = 0.1,
|
pressure: float = 0.1,
|
||||||
temp: float = 60.0,
|
temp: float = 60.0,
|
||||||
time: float = 180.0,
|
time: float = 180.0,
|
||||||
stir_speed: float = 100.0,
|
stir_speed: float = 100.0,
|
||||||
solvent: str = "",
|
solvent: str = "",
|
||||||
**kwargs,
|
**kwargs
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""Execute evaporate action - 简化版 🌪️"""
|
"""Execute evaporate action - 简化版 🌪️"""
|
||||||
|
|
||||||
# 🔧 新增:确保time参数是数值类型
|
# 🔧 新增:确保time参数是数值类型
|
||||||
if isinstance(time, str):
|
if isinstance(time, str):
|
||||||
try:
|
try:
|
||||||
@@ -113,31 +98,31 @@ class VirtualRotavap:
|
|||||||
elif not isinstance(time, (int, float)):
|
elif not isinstance(time, (int, float)):
|
||||||
self.logger.error(f"❌ 时间参数类型无效: {type(time)},使用默认值180.0秒")
|
self.logger.error(f"❌ 时间参数类型无效: {type(time)},使用默认值180.0秒")
|
||||||
time = 180.0
|
time = 180.0
|
||||||
|
|
||||||
# 确保time是float类型; 并加速
|
# 确保time是float类型; 并加速
|
||||||
time = float(time) / 10.0
|
time = float(time) / 10.0
|
||||||
|
|
||||||
# 🔧 简化处理:如果vessel就是设备自己,直接操作
|
# 🔧 简化处理:如果vessel就是设备自己,直接操作
|
||||||
if vessel == self.device_id:
|
if vessel == self.device_id:
|
||||||
debug_print(f"🎯 在设备 {self.device_id} 上直接执行蒸发操作")
|
debug_print(f"🎯 在设备 {self.device_id} 上直接执行蒸发操作")
|
||||||
actual_vessel = self.device_id
|
actual_vessel = self.device_id
|
||||||
else:
|
else:
|
||||||
actual_vessel = vessel
|
actual_vessel = vessel
|
||||||
|
|
||||||
# 参数预处理
|
# 参数预处理
|
||||||
if solvent:
|
if solvent:
|
||||||
self.logger.info(f"🧪 识别到溶剂: {solvent}")
|
self.logger.info(f"🧪 识别到溶剂: {solvent}")
|
||||||
# 根据溶剂调整参数
|
# 根据溶剂调整参数
|
||||||
solvent_lower = solvent.lower()
|
solvent_lower = solvent.lower()
|
||||||
if any(s in solvent_lower for s in ["water", "aqueous"]):
|
if any(s in solvent_lower for s in ['water', 'aqueous']):
|
||||||
temp = max(temp, 80.0)
|
temp = max(temp, 80.0)
|
||||||
pressure = max(pressure, 0.2)
|
pressure = max(pressure, 0.2)
|
||||||
self.logger.info(f"💧 水系溶剂:调整参数 → 温度 {temp}°C, 压力 {pressure} bar")
|
self.logger.info(f"💧 水系溶剂:调整参数 → 温度 {temp}°C, 压力 {pressure} bar")
|
||||||
elif any(s in solvent_lower for s in ["ethanol", "methanol", "acetone"]):
|
elif any(s in solvent_lower for s in ['ethanol', 'methanol', 'acetone']):
|
||||||
temp = min(temp, 50.0)
|
temp = min(temp, 50.0)
|
||||||
pressure = min(pressure, 0.05)
|
pressure = min(pressure, 0.05)
|
||||||
self.logger.info(f"⚡ 易挥发溶剂:调整参数 → 温度 {temp}°C, 压力 {pressure} bar")
|
self.logger.info(f"⚡ 易挥发溶剂:调整参数 → 温度 {temp}°C, 压力 {pressure} bar")
|
||||||
|
|
||||||
self.logger.info(f"🌪️ 开始蒸发操作: {actual_vessel}")
|
self.logger.info(f"🌪️ 开始蒸发操作: {actual_vessel}")
|
||||||
self.logger.info(f" 🥽 容器: {actual_vessel}")
|
self.logger.info(f" 🥽 容器: {actual_vessel}")
|
||||||
self.logger.info(f" 🌡️ 温度: {temp}°C")
|
self.logger.info(f" 🌡️ 温度: {temp}°C")
|
||||||
@@ -146,140 +131,126 @@ class VirtualRotavap:
|
|||||||
self.logger.info(f" 🌀 转速: {stir_speed} RPM")
|
self.logger.info(f" 🌀 转速: {stir_speed} RPM")
|
||||||
if solvent:
|
if solvent:
|
||||||
self.logger.info(f" 🧪 溶剂: {solvent}")
|
self.logger.info(f" 🧪 溶剂: {solvent}")
|
||||||
|
|
||||||
# 验证参数
|
# 验证参数
|
||||||
if temp > self._max_temp or temp < 10.0:
|
if temp > self._max_temp or temp < 10.0:
|
||||||
error_msg = f"🌡️ 温度 {temp}°C 超出范围 (10-{self._max_temp}°C) ⚠️"
|
error_msg = f"🌡️ 温度 {temp}°C 超出范围 (10-{self._max_temp}°C) ⚠️"
|
||||||
self.logger.error(f"❌ {error_msg}")
|
self.logger.error(f"❌ {error_msg}")
|
||||||
self.data.update(
|
self.data.update({
|
||||||
{
|
"status": f"❌ 错误: 温度超出范围",
|
||||||
"status": f"❌ 错误: 温度超出范围",
|
"rotavap_state": "Error",
|
||||||
"rotavap_state": "Error",
|
"current_temp": 25.0,
|
||||||
"current_temp": 25.0,
|
"progress": 0.0,
|
||||||
"progress": 0.0,
|
"evaporated_volume": 0.0,
|
||||||
"evaporated_volume": 0.0,
|
"message": error_msg
|
||||||
"message": error_msg,
|
})
|
||||||
}
|
|
||||||
)
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if stir_speed > self._max_rotation_speed or stir_speed < 10.0:
|
if stir_speed > self._max_rotation_speed or stir_speed < 10.0:
|
||||||
error_msg = f"🌀 旋转速度 {stir_speed} RPM 超出范围 (10-{self._max_rotation_speed} RPM) ⚠️"
|
error_msg = f"🌀 旋转速度 {stir_speed} RPM 超出范围 (10-{self._max_rotation_speed} RPM) ⚠️"
|
||||||
self.logger.error(f"❌ {error_msg}")
|
self.logger.error(f"❌ {error_msg}")
|
||||||
self.data.update(
|
self.data.update({
|
||||||
{
|
"status": f"❌ 错误: 转速超出范围",
|
||||||
"status": f"❌ 错误: 转速超出范围",
|
"rotavap_state": "Error",
|
||||||
"rotavap_state": "Error",
|
"current_temp": 25.0,
|
||||||
"current_temp": 25.0,
|
"progress": 0.0,
|
||||||
"progress": 0.0,
|
"evaporated_volume": 0.0,
|
||||||
"evaporated_volume": 0.0,
|
"message": error_msg
|
||||||
"message": error_msg,
|
})
|
||||||
}
|
|
||||||
)
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if pressure < 0.01 or pressure > 1.0:
|
if pressure < 0.01 or pressure > 1.0:
|
||||||
error_msg = f"💨 真空度 {pressure} bar 超出范围 (0.01-1.0 bar) ⚠️"
|
error_msg = f"💨 真空度 {pressure} bar 超出范围 (0.01-1.0 bar) ⚠️"
|
||||||
self.logger.error(f"❌ {error_msg}")
|
self.logger.error(f"❌ {error_msg}")
|
||||||
self.data.update(
|
self.data.update({
|
||||||
{
|
"status": f"❌ 错误: 压力超出范围",
|
||||||
"status": f"❌ 错误: 压力超出范围",
|
"rotavap_state": "Error",
|
||||||
"rotavap_state": "Error",
|
"current_temp": 25.0,
|
||||||
"current_temp": 25.0,
|
"progress": 0.0,
|
||||||
"progress": 0.0,
|
"evaporated_volume": 0.0,
|
||||||
"evaporated_volume": 0.0,
|
"message": error_msg
|
||||||
"message": error_msg,
|
})
|
||||||
}
|
|
||||||
)
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# 开始蒸发 - 🔧 现在time已经确保是float类型
|
# 开始蒸发 - 🔧 现在time已经确保是float类型
|
||||||
self.logger.info(f"🚀 启动蒸发程序! 预计用时 {time/60:.1f}分钟 ⏱️")
|
self.logger.info(f"🚀 启动蒸发程序! 预计用时 {time/60:.1f}分钟 ⏱️")
|
||||||
|
|
||||||
self.data.update(
|
self.data.update({
|
||||||
{
|
"status": f"🌪️ 蒸发中: {actual_vessel}",
|
||||||
"status": f"🌪️ 蒸发中: {actual_vessel}",
|
"rotavap_state": "Evaporating",
|
||||||
"rotavap_state": "Evaporating",
|
"current_temp": temp,
|
||||||
"current_temp": temp,
|
"target_temp": temp,
|
||||||
"target_temp": temp,
|
"rotation_speed": stir_speed,
|
||||||
"rotation_speed": stir_speed,
|
"vacuum_pressure": pressure,
|
||||||
"vacuum_pressure": pressure,
|
"remaining_time": time,
|
||||||
"remaining_time": time,
|
"progress": 0.0,
|
||||||
"progress": 0.0,
|
"evaporated_volume": 0.0,
|
||||||
"evaporated_volume": 0.0,
|
"message": f"🌪️ Evaporating {actual_vessel} at {temp}°C, {pressure} bar, {stir_speed} RPM"
|
||||||
"message": f"🌪️ Evaporating {actual_vessel} at {temp}°C, {pressure} bar, {stir_speed} RPM",
|
})
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# 蒸发过程 - 实时更新进度
|
# 蒸发过程 - 实时更新进度
|
||||||
start_time = time_module.time()
|
start_time = time_module.time()
|
||||||
total_time = time
|
total_time = time
|
||||||
last_logged_progress = 0
|
last_logged_progress = 0
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
current_time = time_module.time()
|
current_time = time_module.time()
|
||||||
elapsed = current_time - start_time
|
elapsed = current_time - start_time
|
||||||
remaining = max(0, total_time - elapsed)
|
remaining = max(0, total_time - elapsed)
|
||||||
progress = min(100.0, (elapsed / total_time) * 100)
|
progress = min(100.0, (elapsed / total_time) * 100)
|
||||||
|
|
||||||
# 模拟蒸发体积 - 根据溶剂类型调整
|
# 模拟蒸发体积 - 根据溶剂类型调整
|
||||||
if solvent and any(s in solvent.lower() for s in ["water", "aqueous"]):
|
if solvent and any(s in solvent.lower() for s in ['water', 'aqueous']):
|
||||||
evaporated_vol = progress * 0.6 # 水系溶剂蒸发慢
|
evaporated_vol = progress * 0.6 # 水系溶剂蒸发慢
|
||||||
elif solvent and any(s in solvent.lower() for s in ["ethanol", "methanol", "acetone"]):
|
elif solvent and any(s in solvent.lower() for s in ['ethanol', 'methanol', 'acetone']):
|
||||||
evaporated_vol = progress * 1.0 # 易挥发溶剂蒸发快
|
evaporated_vol = progress * 1.0 # 易挥发溶剂蒸发快
|
||||||
else:
|
else:
|
||||||
evaporated_vol = progress * 0.8 # 默认蒸发量
|
evaporated_vol = progress * 0.8 # 默认蒸发量
|
||||||
|
|
||||||
# 🔧 更新状态 - 确保包含所有必需字段
|
# 🔧 更新状态 - 确保包含所有必需字段
|
||||||
status_msg = f"🌪️ 蒸发中: {actual_vessel} | 🌡️ {temp}°C | 💨 {pressure} bar | 🌀 {stir_speed} RPM | 📊 {progress:.1f}% | ⏰ 剩余: {remaining:.0f}s"
|
status_msg = f"🌪️ 蒸发中: {actual_vessel} | 🌡️ {temp}°C | 💨 {pressure} bar | 🌀 {stir_speed} RPM | 📊 {progress:.1f}% | ⏰ 剩余: {remaining:.0f}s"
|
||||||
|
|
||||||
self.data.update(
|
self.data.update({
|
||||||
{
|
"remaining_time": remaining,
|
||||||
"remaining_time": remaining,
|
"progress": progress,
|
||||||
"progress": progress,
|
"evaporated_volume": evaporated_vol,
|
||||||
"evaporated_volume": evaporated_vol,
|
"current_temp": temp,
|
||||||
"current_temp": temp,
|
"status": status_msg,
|
||||||
"status": status_msg,
|
"message": f"🌪️ Evaporating: {progress:.1f}% complete, 💧 {evaporated_vol:.1f}mL evaporated, ⏰ {remaining:.0f}s remaining"
|
||||||
"message": f"🌪️ Evaporating: {progress:.1f}% complete, 💧 {evaporated_vol:.1f}mL evaporated, ⏰ {remaining:.0f}s remaining",
|
})
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
# 进度日志(每25%打印一次)
|
# 进度日志(每25%打印一次)
|
||||||
if progress >= 25 and int(progress) % 25 == 0 and int(progress) != last_logged_progress:
|
if progress >= 25 and int(progress) % 25 == 0 and int(progress) != last_logged_progress:
|
||||||
self.logger.info(
|
self.logger.info(f"📊 蒸发进度: {progress:.0f}% | 💧 已蒸发: {evaporated_vol:.1f}mL | ⏰ 剩余: {remaining:.0f}s ✨")
|
||||||
f"📊 蒸发进度: {progress:.0f}% | 💧 已蒸发: {evaporated_vol:.1f}mL | ⏰ 剩余: {remaining:.0f}s ✨"
|
|
||||||
)
|
|
||||||
last_logged_progress = int(progress)
|
last_logged_progress = int(progress)
|
||||||
|
|
||||||
# 时间到了,退出循环
|
# 时间到了,退出循环
|
||||||
if remaining <= 0:
|
if remaining <= 0:
|
||||||
break
|
break
|
||||||
|
|
||||||
# 每秒更新一次
|
# 每秒更新一次
|
||||||
await self._ros_node.sleep(1.0)
|
await asyncio.sleep(1.0)
|
||||||
|
|
||||||
# 蒸发完成
|
# 蒸发完成
|
||||||
if solvent and any(s in solvent.lower() for s in ["water", "aqueous"]):
|
if solvent and any(s in solvent.lower() for s in ['water', 'aqueous']):
|
||||||
final_evaporated = 60.0 # 水系溶剂
|
final_evaporated = 60.0 # 水系溶剂
|
||||||
elif solvent and any(s in solvent.lower() for s in ["ethanol", "methanol", "acetone"]):
|
elif solvent and any(s in solvent.lower() for s in ['ethanol', 'methanol', 'acetone']):
|
||||||
final_evaporated = 100.0 # 易挥发溶剂
|
final_evaporated = 100.0 # 易挥发溶剂
|
||||||
else:
|
else:
|
||||||
final_evaporated = 80.0 # 默认
|
final_evaporated = 80.0 # 默认
|
||||||
|
|
||||||
self.data.update(
|
self.data.update({
|
||||||
{
|
"status": f"✅ 蒸发完成: {actual_vessel} | 💧 蒸发量: {final_evaporated:.1f}mL",
|
||||||
"status": f"✅ 蒸发完成: {actual_vessel} | 💧 蒸发量: {final_evaporated:.1f}mL",
|
"rotavap_state": "Completed",
|
||||||
"rotavap_state": "Completed",
|
"evaporated_volume": final_evaporated,
|
||||||
"evaporated_volume": final_evaporated,
|
"progress": 100.0,
|
||||||
"progress": 100.0,
|
"current_temp": temp,
|
||||||
"current_temp": temp,
|
"remaining_time": 0.0,
|
||||||
"remaining_time": 0.0,
|
"rotation_speed": 0.0,
|
||||||
"rotation_speed": 0.0,
|
"vacuum_pressure": 1.0,
|
||||||
"vacuum_pressure": 1.0,
|
"message": f"✅ Evaporation completed: {final_evaporated}mL evaporated from {actual_vessel}"
|
||||||
"message": f"✅ Evaporation completed: {final_evaporated}mL evaporated from {actual_vessel}",
|
})
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
self.logger.info(f"🎉 蒸发操作完成! ✨")
|
self.logger.info(f"🎉 蒸发操作完成! ✨")
|
||||||
self.logger.info(f"📊 蒸发结果:")
|
self.logger.info(f"📊 蒸发结果:")
|
||||||
@@ -291,26 +262,24 @@ class VirtualRotavap:
|
|||||||
self.logger.info(f" ⏱️ 总用时: {total_time:.0f}s")
|
self.logger.info(f" ⏱️ 总用时: {total_time:.0f}s")
|
||||||
if solvent:
|
if solvent:
|
||||||
self.logger.info(f" 🧪 处理溶剂: {solvent} 🏁")
|
self.logger.info(f" 🧪 处理溶剂: {solvent} 🏁")
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# 出错处理
|
# 出错处理
|
||||||
error_msg = f"蒸发过程中发生错误: {str(e)} 💥"
|
error_msg = f"蒸发过程中发生错误: {str(e)} 💥"
|
||||||
self.logger.error(f"❌ {error_msg}")
|
self.logger.error(f"❌ {error_msg}")
|
||||||
|
|
||||||
self.data.update(
|
self.data.update({
|
||||||
{
|
"status": f"❌ 蒸发错误: {str(e)}",
|
||||||
"status": f"❌ 蒸发错误: {str(e)}",
|
"rotavap_state": "Error",
|
||||||
"rotavap_state": "Error",
|
"current_temp": 25.0,
|
||||||
"current_temp": 25.0,
|
"progress": 0.0,
|
||||||
"progress": 0.0,
|
"evaporated_volume": 0.0,
|
||||||
"evaporated_volume": 0.0,
|
"rotation_speed": 0.0,
|
||||||
"rotation_speed": 0.0,
|
"vacuum_pressure": 1.0,
|
||||||
"vacuum_pressure": 1.0,
|
"message": f"❌ Evaporation failed: {str(e)}"
|
||||||
"message": f"❌ Evaporation failed: {str(e)}",
|
})
|
||||||
}
|
|
||||||
)
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# === 核心状态属性 ===
|
# === 核心状态属性 ===
|
||||||
|
|||||||
@@ -2,13 +2,9 @@ import asyncio
|
|||||||
import logging
|
import logging
|
||||||
from typing import Dict, Any, Optional
|
from typing import Dict, Any, Optional
|
||||||
|
|
||||||
from unilabos.ros.nodes.base_device_node import BaseROS2DeviceNode
|
|
||||||
|
|
||||||
|
|
||||||
class VirtualSeparator:
|
class VirtualSeparator:
|
||||||
"""Virtual separator device for SeparateProtocol testing"""
|
"""Virtual separator device for SeparateProtocol testing"""
|
||||||
|
|
||||||
_ros_node: BaseROS2DeviceNode
|
|
||||||
|
|
||||||
def __init__(self, device_id: Optional[str] = None, config: Optional[Dict[str, Any]] = None, **kwargs):
|
def __init__(self, device_id: Optional[str] = None, config: Optional[Dict[str, Any]] = None, **kwargs):
|
||||||
# 处理可能的不同调用方式
|
# 处理可能的不同调用方式
|
||||||
@@ -39,9 +35,6 @@ class VirtualSeparator:
|
|||||||
for key, value in kwargs.items():
|
for key, value in kwargs.items():
|
||||||
if key not in skip_keys and not hasattr(self, key):
|
if key not in skip_keys and not hasattr(self, key):
|
||||||
setattr(self, key, value)
|
setattr(self, key, value)
|
||||||
|
|
||||||
def post_init(self, ros_node: BaseROS2DeviceNode):
|
|
||||||
self._ros_node = ros_node
|
|
||||||
|
|
||||||
async def initialize(self) -> bool:
|
async def initialize(self) -> bool:
|
||||||
"""Initialize virtual separator"""
|
"""Initialize virtual separator"""
|
||||||
@@ -126,14 +119,14 @@ class VirtualSeparator:
|
|||||||
for repeat in range(repeats):
|
for repeat in range(repeats):
|
||||||
# 搅拌阶段
|
# 搅拌阶段
|
||||||
for progress in range(0, 51, 10):
|
for progress in range(0, 51, 10):
|
||||||
await self._ros_node.sleep(simulation_time / (repeats * 10))
|
await asyncio.sleep(simulation_time / (repeats * 10))
|
||||||
overall_progress = ((repeat * 100) + (progress * 0.5)) / repeats
|
overall_progress = ((repeat * 100) + (progress * 0.5)) / repeats
|
||||||
self.data["progress"] = overall_progress
|
self.data["progress"] = overall_progress
|
||||||
self.data["message"] = f"第{repeat+1}次分离 - 搅拌中 ({progress}%)"
|
self.data["message"] = f"第{repeat+1}次分离 - 搅拌中 ({progress}%)"
|
||||||
|
|
||||||
# 静置分相阶段
|
# 静置分相阶段
|
||||||
for progress in range(50, 101, 10):
|
for progress in range(50, 101, 10):
|
||||||
await self._ros_node.sleep(simulation_time / (repeats * 10))
|
await asyncio.sleep(simulation_time / (repeats * 10))
|
||||||
overall_progress = ((repeat * 100) + (progress * 0.5)) / repeats
|
overall_progress = ((repeat * 100) + (progress * 0.5)) / repeats
|
||||||
self.data["progress"] = overall_progress
|
self.data["progress"] = overall_progress
|
||||||
self.data["message"] = f"第{repeat+1}次分离 - 静置分相中 ({progress}%)"
|
self.data["message"] = f"第{repeat+1}次分离 - 静置分相中 ({progress}%)"
|
||||||
|
|||||||
@@ -2,16 +2,11 @@ import time
|
|||||||
import asyncio
|
import asyncio
|
||||||
from typing import Union
|
from typing import Union
|
||||||
|
|
||||||
from unilabos.ros.nodes.base_device_node import BaseROS2DeviceNode
|
|
||||||
|
|
||||||
|
|
||||||
class VirtualSolenoidValve:
|
class VirtualSolenoidValve:
|
||||||
"""
|
"""
|
||||||
虚拟电磁阀门 - 简单的开关型阀门,只有开启和关闭两个状态
|
虚拟电磁阀门 - 简单的开关型阀门,只有开启和关闭两个状态
|
||||||
"""
|
"""
|
||||||
|
|
||||||
_ros_node: BaseROS2DeviceNode
|
|
||||||
|
|
||||||
def __init__(self, device_id: str = None, config: dict = None, **kwargs):
|
def __init__(self, device_id: str = None, config: dict = None, **kwargs):
|
||||||
# 从配置中获取参数,提供默认值
|
# 从配置中获取参数,提供默认值
|
||||||
if config is None:
|
if config is None:
|
||||||
@@ -26,9 +21,6 @@ class VirtualSolenoidValve:
|
|||||||
self._status = "Idle"
|
self._status = "Idle"
|
||||||
self._valve_state = "Closed" # "Open" or "Closed"
|
self._valve_state = "Closed" # "Open" or "Closed"
|
||||||
self._is_open = False
|
self._is_open = False
|
||||||
|
|
||||||
def post_init(self, ros_node: BaseROS2DeviceNode):
|
|
||||||
self._ros_node = ros_node
|
|
||||||
|
|
||||||
async def initialize(self) -> bool:
|
async def initialize(self) -> bool:
|
||||||
"""初始化设备"""
|
"""初始化设备"""
|
||||||
@@ -71,7 +63,7 @@ class VirtualSolenoidValve:
|
|||||||
self._status = "Busy"
|
self._status = "Busy"
|
||||||
|
|
||||||
# 模拟阀门响应时间
|
# 模拟阀门响应时间
|
||||||
await self._ros_node.sleep(self.response_time)
|
await asyncio.sleep(self.response_time)
|
||||||
|
|
||||||
# 处理不同的命令格式
|
# 处理不同的命令格式
|
||||||
if isinstance(command, str):
|
if isinstance(command, str):
|
||||||
|
|||||||
@@ -3,8 +3,6 @@ import logging
|
|||||||
import re
|
import re
|
||||||
from typing import Dict, Any, Optional
|
from typing import Dict, Any, Optional
|
||||||
|
|
||||||
from unilabos.ros.nodes.base_device_node import BaseROS2DeviceNode
|
|
||||||
|
|
||||||
class VirtualSolidDispenser:
|
class VirtualSolidDispenser:
|
||||||
"""
|
"""
|
||||||
虚拟固体粉末加样器 - 用于处理 Add Protocol 中的固体试剂添加 ⚗️
|
虚拟固体粉末加样器 - 用于处理 Add Protocol 中的固体试剂添加 ⚗️
|
||||||
@@ -15,8 +13,6 @@ class VirtualSolidDispenser:
|
|||||||
- 简单反馈:成功/失败 + 消息 📊
|
- 简单反馈:成功/失败 + 消息 📊
|
||||||
"""
|
"""
|
||||||
|
|
||||||
_ros_node: BaseROS2DeviceNode
|
|
||||||
|
|
||||||
def __init__(self, device_id: str = None, config: Dict[str, Any] = None, **kwargs):
|
def __init__(self, device_id: str = None, config: Dict[str, Any] = None, **kwargs):
|
||||||
self.device_id = device_id or "virtual_solid_dispenser"
|
self.device_id = device_id or "virtual_solid_dispenser"
|
||||||
self.config = config or {}
|
self.config = config or {}
|
||||||
@@ -36,9 +32,6 @@ class VirtualSolidDispenser:
|
|||||||
print(f"⚗️ === 虚拟固体分配器 {self.device_id} 创建成功! === ✨")
|
print(f"⚗️ === 虚拟固体分配器 {self.device_id} 创建成功! === ✨")
|
||||||
print(f"📊 设备规格: 最大容量 {self.max_capacity}g | 精度 {self.precision}g 🎯")
|
print(f"📊 设备规格: 最大容量 {self.max_capacity}g | 精度 {self.precision}g 🎯")
|
||||||
|
|
||||||
def post_init(self, ros_node: BaseROS2DeviceNode):
|
|
||||||
self._ros_node = ros_node
|
|
||||||
|
|
||||||
async def initialize(self) -> bool:
|
async def initialize(self) -> bool:
|
||||||
"""初始化固体加样器 🚀"""
|
"""初始化固体加样器 🚀"""
|
||||||
self.logger.info(f"🔧 初始化固体分配器 {self.device_id} ✨")
|
self.logger.info(f"🔧 初始化固体分配器 {self.device_id} ✨")
|
||||||
@@ -270,7 +263,7 @@ class VirtualSolidDispenser:
|
|||||||
|
|
||||||
for i in range(steps):
|
for i in range(steps):
|
||||||
progress = (i + 1) / steps * 100
|
progress = (i + 1) / steps * 100
|
||||||
await self._ros_node.sleep(step_time)
|
await asyncio.sleep(step_time)
|
||||||
if i % 2 == 0: # 每隔一步显示进度
|
if i % 2 == 0: # 每隔一步显示进度
|
||||||
self.logger.debug(f"📊 加样进度: {progress:.0f}% | {amount_emoji} 正在分配 {reagent}...")
|
self.logger.debug(f"📊 加样进度: {progress:.0f}% | {amount_emoji} 正在分配 {reagent}...")
|
||||||
|
|
||||||
|
|||||||
@@ -3,13 +3,9 @@ import logging
|
|||||||
import time as time_module
|
import time as time_module
|
||||||
from typing import Dict, Any
|
from typing import Dict, Any
|
||||||
|
|
||||||
from unilabos.ros.nodes.base_device_node import BaseROS2DeviceNode
|
|
||||||
|
|
||||||
class VirtualStirrer:
|
class VirtualStirrer:
|
||||||
"""Virtual stirrer device for StirProtocol testing - 功能完整版 🌪️"""
|
"""Virtual stirrer device for StirProtocol testing - 功能完整版 🌪️"""
|
||||||
|
|
||||||
_ros_node: BaseROS2DeviceNode
|
|
||||||
|
|
||||||
def __init__(self, device_id: str = None, config: Dict[str, Any] = None, **kwargs):
|
def __init__(self, device_id: str = None, config: Dict[str, Any] = None, **kwargs):
|
||||||
# 处理可能的不同调用方式
|
# 处理可能的不同调用方式
|
||||||
if device_id is None and 'id' in kwargs:
|
if device_id is None and 'id' in kwargs:
|
||||||
@@ -38,9 +34,6 @@ class VirtualStirrer:
|
|||||||
print(f"🌪️ === 虚拟搅拌器 {self.device_id} 已创建 === ✨")
|
print(f"🌪️ === 虚拟搅拌器 {self.device_id} 已创建 === ✨")
|
||||||
print(f"🔧 速度范围: {self._min_speed} ~ {self._max_speed} RPM | 📱 端口: {self.port}")
|
print(f"🔧 速度范围: {self._min_speed} ~ {self._max_speed} RPM | 📱 端口: {self.port}")
|
||||||
|
|
||||||
def post_init(self, ros_node: BaseROS2DeviceNode):
|
|
||||||
self._ros_node = ros_node
|
|
||||||
|
|
||||||
async def initialize(self) -> bool:
|
async def initialize(self) -> bool:
|
||||||
"""Initialize virtual stirrer 🚀"""
|
"""Initialize virtual stirrer 🚀"""
|
||||||
self.logger.info(f"🔧 初始化虚拟搅拌器 {self.device_id} ✨")
|
self.logger.info(f"🔧 初始化虚拟搅拌器 {self.device_id} ✨")
|
||||||
@@ -141,7 +134,7 @@ class VirtualStirrer:
|
|||||||
if remaining <= 0:
|
if remaining <= 0:
|
||||||
break
|
break
|
||||||
|
|
||||||
await self._ros_node.sleep(1.0)
|
await asyncio.sleep(1.0)
|
||||||
|
|
||||||
self.logger.info(f"✅ 搅拌阶段完成! 🌪️ {stir_speed} RPM × {stir_time}s")
|
self.logger.info(f"✅ 搅拌阶段完成! 🌪️ {stir_speed} RPM × {stir_time}s")
|
||||||
|
|
||||||
@@ -183,7 +176,7 @@ class VirtualStirrer:
|
|||||||
if remaining <= 0:
|
if remaining <= 0:
|
||||||
break
|
break
|
||||||
|
|
||||||
await self._ros_node.sleep(1.0)
|
await asyncio.sleep(1.0)
|
||||||
|
|
||||||
self.logger.info(f"✅ 沉降阶段完成! 🛑 静置 {settling_time}s")
|
self.logger.info(f"✅ 沉降阶段完成! 🛑 静置 {settling_time}s")
|
||||||
|
|
||||||
|
|||||||
@@ -4,8 +4,6 @@ from enum import Enum
|
|||||||
from typing import Union, Optional
|
from typing import Union, Optional
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from unilabos.ros.nodes.base_device_node import BaseROS2DeviceNode
|
|
||||||
|
|
||||||
|
|
||||||
class VirtualPumpMode(Enum):
|
class VirtualPumpMode(Enum):
|
||||||
Normal = 0
|
Normal = 0
|
||||||
@@ -16,8 +14,6 @@ class VirtualPumpMode(Enum):
|
|||||||
class VirtualTransferPump:
|
class VirtualTransferPump:
|
||||||
"""虚拟转移泵类 - 模拟泵的基本功能,无需实际硬件 🚰"""
|
"""虚拟转移泵类 - 模拟泵的基本功能,无需实际硬件 🚰"""
|
||||||
|
|
||||||
_ros_node: BaseROS2DeviceNode
|
|
||||||
|
|
||||||
def __init__(self, device_id: str = None, config: dict = None, **kwargs):
|
def __init__(self, device_id: str = None, config: dict = None, **kwargs):
|
||||||
"""
|
"""
|
||||||
初始化虚拟转移泵
|
初始化虚拟转移泵
|
||||||
@@ -57,9 +53,6 @@ class VirtualTransferPump:
|
|||||||
print(f"💨 快速模式: {'启用' if self._fast_mode else '禁用'} | 移动时间: {self._fast_move_time}s | 喷射时间: {self._fast_dispense_time}s")
|
print(f"💨 快速模式: {'启用' if self._fast_mode else '禁用'} | 移动时间: {self._fast_move_time}s | 喷射时间: {self._fast_dispense_time}s")
|
||||||
print(f"📊 最大容量: {self.max_volume}mL | 端口: {self.port}")
|
print(f"📊 最大容量: {self.max_volume}mL | 端口: {self.port}")
|
||||||
|
|
||||||
def post_init(self, ros_node: BaseROS2DeviceNode):
|
|
||||||
self._ros_node = ros_node
|
|
||||||
|
|
||||||
async def initialize(self) -> bool:
|
async def initialize(self) -> bool:
|
||||||
"""初始化虚拟泵 🚀"""
|
"""初始化虚拟泵 🚀"""
|
||||||
self.logger.info(f"🔧 初始化虚拟转移泵 {self.device_id} ✨")
|
self.logger.info(f"🔧 初始化虚拟转移泵 {self.device_id} ✨")
|
||||||
@@ -111,7 +104,7 @@ class VirtualTransferPump:
|
|||||||
async def _simulate_operation(self, duration: float):
|
async def _simulate_operation(self, duration: float):
|
||||||
"""模拟操作延时 ⏱️"""
|
"""模拟操作延时 ⏱️"""
|
||||||
self._status = "Busy"
|
self._status = "Busy"
|
||||||
await self._ros_node.sleep(duration)
|
await asyncio.sleep(duration)
|
||||||
self._status = "Idle"
|
self._status = "Idle"
|
||||||
|
|
||||||
def _calculate_duration(self, volume: float, velocity: float = None) -> float:
|
def _calculate_duration(self, volume: float, velocity: float = None) -> float:
|
||||||
@@ -230,7 +223,7 @@ class VirtualTransferPump:
|
|||||||
|
|
||||||
# 等待一小步时间
|
# 等待一小步时间
|
||||||
if i < steps and step_duration > 0:
|
if i < steps and step_duration > 0:
|
||||||
await self._ros_node.sleep(step_duration)
|
await asyncio.sleep(step_duration)
|
||||||
else:
|
else:
|
||||||
# 移动距离很小,直接完成
|
# 移动距离很小,直接完成
|
||||||
self._position = target_position
|
self._position = target_position
|
||||||
@@ -348,7 +341,7 @@ class VirtualTransferPump:
|
|||||||
|
|
||||||
# 短暂停顿
|
# 短暂停顿
|
||||||
self.logger.debug("⏸️ 短暂停顿...")
|
self.logger.debug("⏸️ 短暂停顿...")
|
||||||
await self._ros_node.sleep(0.1)
|
await asyncio.sleep(0.1)
|
||||||
|
|
||||||
# 排液
|
# 排液
|
||||||
await self.dispense(volume, dispense_velocity)
|
await self.dispense(volume, dispense_velocity)
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ def _initialize_material_system(self, deck_config: Dict[str, Any], children_conf
|
|||||||
**定义在**: `workstation_base.py`
|
**定义在**: `workstation_base.py`
|
||||||
|
|
||||||
**设计目的**:
|
**设计目的**:
|
||||||
- 提供外部物料系统(如Bioyond、LIMS等)集成的标准接口
|
- 提供外部物料系统(如Bioyong、LIMS等)集成的标准接口
|
||||||
- 双向同步:从外部系统同步到本地deck,以及将本地变更同步到外部系统
|
- 双向同步:从外部系统同步到本地deck,以及将本地变更同步到外部系统
|
||||||
- 处理外部系统的变更通知
|
- 处理外部系统的变更通知
|
||||||
|
|
||||||
@@ -59,7 +59,7 @@ async def handle_external_change(self, change_info: Dict[str, Any]) -> bool:
|
|||||||
**扩展功能**:
|
**扩展功能**:
|
||||||
- HTTP报送接收服务集成
|
- HTTP报送接收服务集成
|
||||||
- 具体工作流实现(液体转移、板洗等)
|
- 具体工作流实现(液体转移、板洗等)
|
||||||
- Bioyond物料系统同步器示例
|
- Bioyong物料系统同步器示例
|
||||||
- 外部报送处理方法
|
- 外部报送处理方法
|
||||||
|
|
||||||
## 技术栈
|
## 技术栈
|
||||||
@@ -142,11 +142,11 @@ success = workstation.execute_workflow("liquid_transfer", {
|
|||||||
### 3. 外部系统集成
|
### 3. 外部系统集成
|
||||||
|
|
||||||
```python
|
```python
|
||||||
class BioyondResourceSynchronizer(ResourceSynchronizer):
|
class BioyongResourceSynchronizer(ResourceSynchronizer):
|
||||||
"""Bioyond系统同步器"""
|
"""Bioyong系统同步器"""
|
||||||
|
|
||||||
async def sync_from_external(self) -> bool:
|
async def sync_from_external(self) -> bool:
|
||||||
# 从Bioyond API获取物料
|
# 从Bioyong API获取物料
|
||||||
external_materials = await self._fetch_bioyong_materials()
|
external_materials = await self._fetch_bioyong_materials()
|
||||||
|
|
||||||
# 转换并添加到本地deck
|
# 转换并添加到本地deck
|
||||||
|
|||||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@@ -1,7 +0,0 @@
|
|||||||
material_name
|
|
||||||
LiPF6
|
|
||||||
LiDFOB
|
|
||||||
DTD
|
|
||||||
LiFSI
|
|
||||||
LiPO2F2
|
|
||||||
|
|
||||||
|
File diff suppressed because it is too large
Load Diff
@@ -2,330 +2,128 @@
|
|||||||
"""
|
"""
|
||||||
配置文件 - 包含所有配置信息和映射关系
|
配置文件 - 包含所有配置信息和映射关系
|
||||||
"""
|
"""
|
||||||
import os
|
|
||||||
|
|
||||||
# ==================== API 基础配置 ====================
|
# API配置
|
||||||
# BioyondCellWorkstation 默认配置(包含所有必需参数)
|
|
||||||
API_CONFIG = {
|
API_CONFIG = {
|
||||||
# API 连接配置
|
"api_key": "",
|
||||||
# "api_host": os.getenv("BIOYOND_API_HOST", "http://172.16.1.143:44389"),#实机
|
"api_host": ""
|
||||||
"api_host": os.getenv("BIOYOND_API_HOST", "http://172.16.7.149:44388"),# 仿真机
|
}
|
||||||
"api_key": os.getenv("BIOYOND_API_KEY", "8A819E5C"),
|
|
||||||
"timeout": int(os.getenv("BIOYOND_TIMEOUT", "30")),
|
# 站点类型配置
|
||||||
|
STATION_TYPES = {
|
||||||
# 报送配置
|
"REACTION": "reaction_station", # 仅反应站
|
||||||
"report_token": os.getenv("BIOYOND_REPORT_TOKEN", "CHANGE_ME_TOKEN"),
|
"DISPENSING": "dispensing_station", # 仅配液站
|
||||||
|
"HYBRID": "hybrid_station" # 混合模式
|
||||||
# HTTP 服务配置
|
}
|
||||||
"HTTP_host": os.getenv("BIOYOND_HTTP_HOST", "172.16.2.140"), # HTTP服务监听地址,监听计算机飞连ip地址
|
|
||||||
"HTTP_port": int(os.getenv("BIOYOND_HTTP_PORT", "8080")),
|
# 默认站点配置
|
||||||
"debug_mode": False,# 调试模式
|
DEFAULT_STATION_CONFIG = {
|
||||||
|
"station_type": STATION_TYPES["REACTION"], # 默认反应站模式
|
||||||
|
"enable_reaction_station": True, # 是否启用反应站功能
|
||||||
|
"enable_dispensing_station": False, # 是否启用配液站功能
|
||||||
|
"station_name": "BioyondReactionStation", # 站点名称
|
||||||
|
"description": "Bioyond反应工作站" # 站点描述
|
||||||
|
}
|
||||||
|
|
||||||
|
# 工作流映射配置
|
||||||
|
WORKFLOW_MAPPINGS = {
|
||||||
|
"reactor_taken_out": "",
|
||||||
|
"reactor_taken_in": "",
|
||||||
|
"Solid_feeding_vials": "",
|
||||||
|
"Liquid_feeding_vials(non-titration)": "",
|
||||||
|
"Liquid_feeding_solvents": "",
|
||||||
|
"Liquid_feeding(titration)": "",
|
||||||
|
"liquid_feeding_beaker": "",
|
||||||
|
"Drip_back": "",
|
||||||
|
}
|
||||||
|
|
||||||
|
# 工作流名称到DisplaySectionName的映射
|
||||||
|
WORKFLOW_TO_SECTION_MAP = {
|
||||||
|
'reactor_taken_in': '反应器放入',
|
||||||
|
'liquid_feeding_beaker': '液体投料-烧杯',
|
||||||
|
'Liquid_feeding_vials(non-titration)': '液体投料-小瓶(非滴定)',
|
||||||
|
'Liquid_feeding_solvents': '液体投料-溶剂',
|
||||||
|
'Solid_feeding_vials': '固体投料-小瓶',
|
||||||
|
'Liquid_feeding(titration)': '液体投料-滴定',
|
||||||
|
'reactor_taken_out': '反应器取出'
|
||||||
}
|
}
|
||||||
|
|
||||||
# 库位映射配置
|
# 库位映射配置
|
||||||
WAREHOUSE_MAPPING = {
|
LOCATION_MAPPING = {
|
||||||
"粉末加样头堆栈": {
|
'A01': '',
|
||||||
"uuid": "",
|
'A02': '',
|
||||||
"site_uuids": {
|
'A03': '',
|
||||||
"A01": "3a19da56-1379-ff7c-1745-07e200b44ce2",
|
'A04': '',
|
||||||
"B01": "3a19da56-1379-2424-d751-fe6e94cef938",
|
'A05': '',
|
||||||
"C01": "3a19da56-1379-271c-03e3-6bdb590e395e",
|
'A06': '',
|
||||||
"D01": "3a19da56-1379-277f-2b1b-0d11f7cf92c6",
|
'A07': '',
|
||||||
"E01": "3a19da56-1379-2f1c-a15b-e01db90eb39a",
|
'A08': '',
|
||||||
"F01": "3a19da56-1379-3fa1-846b-088158ac0b3d",
|
'B01': '',
|
||||||
"G01": "3a19da56-1379-5aeb-d0cd-d3b4609d66e1",
|
'B02': '',
|
||||||
"H01": "3a19da56-1379-6077-8258-bdc036870b78",
|
'B03': '',
|
||||||
"I01": "3a19da56-1379-863b-a120-f606baf04617",
|
'B04': '',
|
||||||
"J01": "3a19da56-1379-8a74-74e5-35a9b41d4fd5",
|
'B05': '',
|
||||||
"K01": "3a19da56-1379-b270-b7af-f18773918abe",
|
'B06': '',
|
||||||
"L01": "3a19da56-1379-ba54-6d78-fd770a671ffc",
|
'B07': '',
|
||||||
"M01": "3a19da56-1379-c22d-c96f-0ceb5eb54a04",
|
'B08': '',
|
||||||
"N01": "3a19da56-1379-d64e-c6c5-c72ea4829888",
|
'C01': '',
|
||||||
"O01": "3a19da56-1379-d887-1a3c-6f9cce90f90e",
|
'C02': '',
|
||||||
"P01": "3a19da56-1379-e77d-0e65-7463b238a3b9",
|
'C03': '',
|
||||||
"Q01": "3a19da56-1379-edf6-1472-802ddb628774",
|
'C04': '',
|
||||||
"R01": "3a19da56-1379-f281-0273-e0ef78f0fd97",
|
'C05': '',
|
||||||
"S01": "3a19da56-1379-f924-7f68-df1fa51489f4",
|
'C06': '',
|
||||||
"T01": "3a19da56-1379-ff7c-1745-07e200b44ce2"
|
'C07': '',
|
||||||
}
|
'C08': '',
|
||||||
},
|
'D01': '',
|
||||||
"配液站内试剂仓库": {
|
'D02': '',
|
||||||
"uuid": "",
|
'D03': '',
|
||||||
"site_uuids": {
|
'D04': '',
|
||||||
"A01": "3a19da43-57b5-294f-d663-154a1cc32270",
|
'D05': '',
|
||||||
"B01": "3a19da43-57b5-7394-5f49-54efe2c9bef2",
|
'D06': '',
|
||||||
"C01": "3a19da43-57b5-5e75-552f-8dbd0ad1075f",
|
'D07': '',
|
||||||
"A02": "3a19da43-57b5-8441-db94-b4d3875a4b6c",
|
'D08': '',
|
||||||
"B02": "3a19da43-57b5-3e41-c181-5119dddaf50c",
|
|
||||||
"C02": "3a19da43-57b5-269b-282d-fba61fe8ce96",
|
|
||||||
"A03": "3a19da43-57b5-7c1e-d02e-c40e8c33f8a1",
|
|
||||||
"B03": "3a19da43-57b5-659f-621f-1dcf3f640363",
|
|
||||||
"C03": "3a19da43-57b5-855a-6e71-f398e376dee1",
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"试剂替换仓库": {
|
|
||||||
"uuid": "",
|
|
||||||
"site_uuids": {
|
|
||||||
"A01": "3a19da51-8f4e-30f3-ea08-4f8498e9b097",
|
|
||||||
"B01": "3a19da51-8f4e-1da7-beb0-80a4a01e67a8",
|
|
||||||
"C01": "3a19da51-8f4e-337d-2675-bfac46880b06",
|
|
||||||
"D01": "3a19da51-8f4e-e514-b92c-9c44dc5e489d",
|
|
||||||
"E01": "3a19da51-8f4e-22d1-dd5b-9774ddc80402",
|
|
||||||
"F01": "3a19da51-8f4e-273a-4871-dff41c29bfd9",
|
|
||||||
"G01": "3a19da51-8f4e-b32f-454f-74bc1a665653",
|
|
||||||
"H01": "3a19da51-8f4e-8c93-68c9-0b4382320f59",
|
|
||||||
"I01": "3a19da51-8f4e-360c-0149-291b47c6089b",
|
|
||||||
"J01": "3a19da51-8f4e-4152-9bca-8d64df8c1af0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"自动堆栈-左": {
|
|
||||||
"uuid": "",
|
|
||||||
"site_uuids": {
|
|
||||||
"A01": "3a19debc-84b5-4c1c-d3a1-26830cf273ff",
|
|
||||||
"A02": "3a19debc-84b5-033b-b31f-6b87f7c2bf52",
|
|
||||||
"B01": "3a19debc-84b5-3924-172f-719ab01b125c",
|
|
||||||
"B02": "3a19debc-84b5-aad8-70c6-b8c6bb2d8750"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"自动堆栈-右": {
|
|
||||||
"uuid": "",
|
|
||||||
"site_uuids": {
|
|
||||||
"A01": "3a19debe-5200-7df2-1dd9-7d202f158864",
|
|
||||||
"A02": "3a19debe-5200-573b-6120-8b51f50e1e50",
|
|
||||||
"B01": "3a19debe-5200-7cd8-7666-851b0a97e309",
|
|
||||||
"B02": "3a19debe-5200-e6d3-96a3-baa6e3d5e484"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"手动堆栈": {
|
|
||||||
"uuid": "",
|
|
||||||
"site_uuids": {
|
|
||||||
"A01": "3a19deae-2c7a-36f5-5e41-02c5b66feaea",
|
|
||||||
"A02": "3a19deae-2c7a-dc6d-c41e-ef285d946cfe",
|
|
||||||
"A03": "3a19deae-2c7a-5876-c454-6b7e224ca927",
|
|
||||||
"B01": "3a19deae-2c7a-2426-6d71-e9de3cb158b1",
|
|
||||||
"B02": "3a19deae-2c7a-79b0-5e44-efaafd1e4cf3",
|
|
||||||
"B03": "3a19deae-2c7a-b9eb-f4e3-e308e0cf839a",
|
|
||||||
"C01": "3a19deae-2c7a-32bc-768e-556647e292f3",
|
|
||||||
"C02": "3a19deae-2c7a-e97a-8484-f5a4599447c4",
|
|
||||||
"C03": "3a19deae-2c7a-3056-6504-10dc73fbc276",
|
|
||||||
"D01": "3a19deae-2c7a-ffad-875e-8c4cda61d440",
|
|
||||||
"D02": "3a19deae-2c7a-61be-601c-b6fb5610499a",
|
|
||||||
"D03": "3a19deae-2c7a-c0f7-05a7-e3fe2491e560",
|
|
||||||
"E01": "3a19deae-2c7a-a6f4-edd1-b436a7576363",
|
|
||||||
"E02": "3a19deae-2c7a-4367-96dd-1ca2186f4910",
|
|
||||||
"E03": "3a19deae-2c7a-b163-2219-23df15200311",
|
|
||||||
"F01": "3a19deae-2c7a-d594-fd6a-0d20de3c7c4a",
|
|
||||||
"F02": "3a19deae-2c7a-a194-ea63-8b342b8d8679",
|
|
||||||
"F03": "3a19deae-2c7a-f7c4-12bd-425799425698",
|
|
||||||
"G01": "3a19deae-2c7a-0b56-72f1-8ab86e53b955",
|
|
||||||
"G02": "3a19deae-2c7a-204e-95ed-1f1950f28343",
|
|
||||||
"G03": "3a19deae-2c7a-392b-62f1-4907c66343f8",
|
|
||||||
"H01": "3a19deae-2c7a-5602-e876-d27aca4e3201",
|
|
||||||
"H02": "3a19deae-2c7a-f15c-70e0-25b58a8c9702",
|
|
||||||
"H03": "3a19deae-2c7a-780b-8965-2e1345f7e834",
|
|
||||||
"I01": "3a19deae-2c7a-8849-e172-07de14ede928",
|
|
||||||
"I02": "3a19deae-2c7a-4772-a37f-ff99270bafc0",
|
|
||||||
"I03": "3a19deae-2c7a-cce7-6e4a-25ea4a2068c4",
|
|
||||||
"J01": "3a19deae-2c7a-1848-de92-b5d5ed054cc6",
|
|
||||||
"J02": "3a19deae-2c7a-1d45-b4f8-6f866530e205",
|
|
||||||
"J03": "3a19deae-2c7a-f237-89d9-8fe19025dee9"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"4号手套箱内部堆栈": {
|
|
||||||
"uuid": "",
|
|
||||||
"site_uuids": {
|
|
||||||
"A01": "3a1baa20-a7b1-c665-8b9c-d8099d07d2f6",
|
|
||||||
"A02": "3a1baa20-a7b1-93a7-c988-f9c8ad6c58c9",
|
|
||||||
"A03": "3a1baa20-a7b1-00ee-f751-da9b20b6c464",
|
|
||||||
"A04": "3a1baa20-a7b1-4712-c37b-0b5b658ef7b9",
|
|
||||||
"B01": "3a1baa20-a7b1-9847-fc9c-96d604cd1a8e",
|
|
||||||
"B02": "3a1baa20-a7b1-4ae9-e604-0601db06249c",
|
|
||||||
"B03": "3a1baa20-a7b1-8329-ea75-81ca559d9ce1",
|
|
||||||
"B04": "3a1baa20-a7b1-89c5-d96f-36e98a8f7268",
|
|
||||||
"C01": "3a1baa20-a7b1-32ec-39e6-8044733839d6",
|
|
||||||
"C02": "3a1baa20-a7b1-b573-e426-4c86040348b2",
|
|
||||||
"C03": "3a1baa20-a7b1-cca7-781e-0522b729bf5d",
|
|
||||||
"C04": "3a1baa20-a7b1-7c98-5fd9-5855355ae4b3"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"大分液瓶堆栈": {
|
|
||||||
"uuid": "",
|
|
||||||
"site_uuids": {
|
|
||||||
"A01": "3a19da3d-4f3d-bcac-2932-7542041e10e0",
|
|
||||||
"A02": "3a19da3d-4f3d-4d75-38ac-fb58ad0687c3",
|
|
||||||
"A03": "3a19da3d-4f3d-b25e-f2b1-85342a5b7eae",
|
|
||||||
"B01": "3a19da3d-4f3d-fd3e-058a-2733a0925767",
|
|
||||||
"B02": "3a19da3d-4f3d-37bd-a944-c391ad56857f",
|
|
||||||
"B03": "3a19da3d-4f3d-e353-7862-c6d1d4bc667f",
|
|
||||||
"C01": "3a19da3d-4f3d-9519-5da7-76179c958e70",
|
|
||||||
"C02": "3a19da3d-4f3d-b586-d7ed-9ec244f6f937",
|
|
||||||
"C03": "3a19da3d-4f3d-5061-249b-35dfef732811"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"小分液瓶堆栈": {
|
|
||||||
"uuid": "",
|
|
||||||
"site_uuids": {
|
|
||||||
"C03": "3a19da40-55bf-8943-d20d-a8b3ea0d16c0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"站内Tip头盒堆栈": {
|
|
||||||
"uuid": "",
|
|
||||||
"site_uuids": {
|
|
||||||
"A01": "3a19deab-d5cc-be1e-5c37-4e9e5a664388",
|
|
||||||
"A02": "3a19deab-d5cc-b394-8141-27cb3853e8ea",
|
|
||||||
"B01": "3a19deab-d5cc-4dca-596e-ca7414d5f501",
|
|
||||||
"B02": "3a19deab-d5cc-9bc0-442b-12d9d59aa62a",
|
|
||||||
"C01": "3a19deab-d5cc-2eaf-b6a4-f0d54e4f1246",
|
|
||||||
"C02": "3a19deab-d5cc-d9f4-25df-b8018c372bc7"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"配液站内配液大板仓库(无需提前上料)": {
|
|
||||||
"uuid": "",
|
|
||||||
"site_uuids": {
|
|
||||||
"A01": "3a1a21dc-06af-3915-9cb9-80a9dc42f386"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"配液站内配液小板仓库(无需以前入料)": {
|
|
||||||
"uuid": "",
|
|
||||||
"site_uuids": {
|
|
||||||
"A01": "3a1a21de-8e8b-7938-2d06-858b36c10e31"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"移液站内大瓶板仓库(无需提前如料)": {
|
|
||||||
"uuid": "",
|
|
||||||
"site_uuids": {
|
|
||||||
"A01": "3a1a224c-c727-fa62-1f2b-0037a84b9fca"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"移液站内小瓶板仓库(无需提前入料)": {
|
|
||||||
"uuid": "",
|
|
||||||
"site_uuids": {
|
|
||||||
"A01": "3a1a224d-ed49-710c-a9c3-3fc61d479cbb"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"适配器位仓库": {
|
|
||||||
"uuid": "",
|
|
||||||
"site_uuids": {
|
|
||||||
"A01": "3a1abd46-18fe-1f56-6ced-a1f7fe08e36c"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"1号2号手套箱交接堆栈": {
|
|
||||||
"uuid": "",
|
|
||||||
"site_uuids": {
|
|
||||||
"A01": "3a1baa49-7f77-35aa-60b1-e55a45d065fa"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"2号手套箱内部堆栈": {
|
|
||||||
"uuid": "",
|
|
||||||
"site_uuids": {
|
|
||||||
"A01": "3a1baa4b-393e-9f86-3921-7a18b0a8e371",
|
|
||||||
"A02": "3a1baa4b-393e-9425-928b-ee0f6f679d44",
|
|
||||||
"A03": "3a1baa4b-393e-0baf-632b-59dfdc931a3a",
|
|
||||||
"B01": "3a1baa4b-393e-f8aa-c8a9-956f3132f05c",
|
|
||||||
"B02": "3a1baa4b-393e-ef05-42f6-53f4c6e89d70",
|
|
||||||
"B03": "3a1baa4b-393e-c07b-a924-a9f0dfda9711",
|
|
||||||
"C01": "3a1baa4b-393e-4c2b-821a-16a7fe025c48",
|
|
||||||
"C02": "3a1baa4b-393e-2eaf-61a1-9063c832d5a2",
|
|
||||||
"C03": "3a1baa4b-393e-034e-8e28-8626d934a85f"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# 物料类型配置
|
# 物料类型配置
|
||||||
|
MATERIAL_TYPE_IDS = {
|
||||||
|
"样品板": "",
|
||||||
|
"样品": "",
|
||||||
|
"烧杯": ""
|
||||||
|
}
|
||||||
|
|
||||||
MATERIAL_TYPE_MAPPINGS = {
|
MATERIAL_TYPE_MAPPINGS = {
|
||||||
"100ml液体": ("YB_100ml_yeti", "d37166b3-ecaa-481e-bd84-3032b795ba07"),
|
"烧杯": "BIOYOND_PolymerStation_1FlaskCarrier",
|
||||||
"液": ("YB_ye", "3a190ca1-2add-2b23-f8e1-bbd348b7f790"),
|
"试剂瓶": "BIOYOND_PolymerStation_1BottleCarrier",
|
||||||
"高粘液": ("YB_gaonianye", "abe8df30-563d-43d2-85e0-cabec59ddc16"),
|
"样品板": "BIOYOND_PolymerStation_6VialCarrier",
|
||||||
"加样头(大)": ("YB_jia_yang_tou_da", "3a190ca0-b2f6-9aeb-8067-547e72c11469"),
|
|
||||||
# "加样头(大)板": ("YB_jia_yang_tou_da", "a8e714ae-2a4e-4eb9-9614-e4c140ec3f16"),
|
|
||||||
"5ml分液瓶板": ("YB_5ml_fenyepingban", "3a192fa4-007d-ec7b-456e-2a8be7a13f23"),
|
|
||||||
"5ml分液瓶": ("YB_5ml_fenyeping", "3a192c2a-ebb7-58a1-480d-8b3863bf74f4"),
|
|
||||||
"20ml分液瓶板": ("YB_20ml_fenyepingban", "3a192fa4-47db-3449-162a-eaf8aba57e27"),
|
|
||||||
"20ml分液瓶": ("YB_20ml_fenyeping", "3a192c2b-19e8-f0a3-035e-041ca8ca1035"),
|
|
||||||
"配液瓶(小)板": ("YB_peiyepingxiaoban", "3a190c8b-3284-af78-d29f-9a69463ad047"),
|
|
||||||
"配液瓶(小)": ("YB_pei_ye_xiao_Bottle", "3a190c8c-fe8f-bf48-0dc3-97afc7f508eb"),
|
|
||||||
"配液瓶(大)板": ("YB_peiyepingdaban", "53e50377-32dc-4781-b3c0-5ce45bc7dc27"),
|
|
||||||
"配液瓶(大)": ("YB_pei_ye_da_Bottle", "19c52ad1-51c5-494f-8854-576f4ca9c6ca"),
|
|
||||||
"适配器块": ("YB_shi_pei_qi_kuai", "efc3bb32-d504-4890-91c0-b64ed3ac80cf"),
|
|
||||||
"枪头盒": ("YB_qiang_tou_he", "3a192c2e-20f3-a44a-0334-c8301839d0b3"),
|
|
||||||
"枪头": ("YB_qiang_tou", "b6196971-1050-46da-9927-333e8dea062d"),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SOLID_LIQUID_MAPPINGS = {
|
# 步骤参数配置(各工作流的步骤UUID)
|
||||||
# 固体
|
WORKFLOW_STEP_IDS = {
|
||||||
"LiDFOB": {
|
"reactor_taken_in": {
|
||||||
"typeId": "3a190ca0-b2f6-9aeb-8067-547e72c11469",
|
"config": ""
|
||||||
"code": "",
|
|
||||||
"barCode": "",
|
|
||||||
"name": "LiDFOB",
|
|
||||||
"unit": "g",
|
|
||||||
"parameters": "",
|
|
||||||
"quantity": "2",
|
|
||||||
"warningQuantity": "1",
|
|
||||||
"details": []
|
|
||||||
},
|
},
|
||||||
# "LiPF6": {
|
"liquid_feeding_beaker": {
|
||||||
# "typeId": "3a190ca0-b2f6-9aeb-8067-547e72c11469",
|
"liquid": "",
|
||||||
# "code": "",
|
"observe": ""
|
||||||
# "barCode": "",
|
},
|
||||||
# "name": "LiPF6",
|
"liquid_feeding_vials_non_titration": {
|
||||||
# "unit": "g",
|
"liquid": "",
|
||||||
# "parameters": "",
|
"observe": ""
|
||||||
# "quantity": 2,
|
},
|
||||||
# "warningQuantity": 1,
|
"liquid_feeding_solvents": {
|
||||||
# "details": []
|
"liquid": "",
|
||||||
# },
|
"observe": ""
|
||||||
# "LiFSI": {
|
},
|
||||||
# "typeId": "3a190ca0-b2f6-9aeb-8067-547e72c11469",
|
"solid_feeding_vials": {
|
||||||
# "code": "",
|
"feeding": "",
|
||||||
# "barCode": "",
|
"observe": ""
|
||||||
# "name": "LiFSI",
|
},
|
||||||
# "unit": "g",
|
"liquid_feeding_titration": {
|
||||||
# "parameters": "",
|
"liquid": "",
|
||||||
# "quantity": 2,
|
"observe": ""
|
||||||
# "warningQuantity": 1,
|
},
|
||||||
# "details": []
|
"drip_back": {
|
||||||
# },
|
"liquid": "",
|
||||||
# "DTC": {
|
"observe": ""
|
||||||
# "typeId": "3a190ca0-b2f6-9aeb-8067-547e72c11469",
|
}
|
||||||
# "code": "",
|
|
||||||
# "barCode": "",
|
|
||||||
# "name": "DTC",
|
|
||||||
# "unit": "g",
|
|
||||||
# "parameters": "",
|
|
||||||
# "quantity": 2,
|
|
||||||
# "warningQuantity": 1,
|
|
||||||
# "details": []
|
|
||||||
# },
|
|
||||||
# "LiPO2F2": {
|
|
||||||
# "typeId": "3a190ca0-b2f6-9aeb-8067-547e72c11469",
|
|
||||||
# "code": "",
|
|
||||||
# "barCode": "",
|
|
||||||
# "name": "LiPO2F2",
|
|
||||||
# "unit": "g",
|
|
||||||
# "parameters": "",
|
|
||||||
# "quantity": 2,
|
|
||||||
# "warningQuantity": 1,
|
|
||||||
# "details": []
|
|
||||||
# },
|
|
||||||
# 液体
|
|
||||||
# "SA": ("BIOYOND_PolymerStation_Solid_Stock", "3a190ca0-b2f6-9aeb-8067-547e72c11469"),
|
|
||||||
# "EC": ("BIOYOND_PolymerStation_Solid_Stock", "3a190ca0-b2f6-9aeb-8067-547e72c11469"),
|
|
||||||
# "VC": ("BIOYOND_PolymerStation_Solid_Stock", "3a190ca0-b2f6-9aeb-8067-547e72c11469"),
|
|
||||||
# "AND": ("BIOYOND_PolymerStation_Solid_Stock", "3a190ca0-b2f6-9aeb-8067-547e72c11469"),
|
|
||||||
# "HTCN": ("BIOYOND_PolymerStation_Solid_Stock", "3a190ca0-b2f6-9aeb-8067-547e72c11469"),
|
|
||||||
# "DENE": ("BIOYOND_PolymerStation_Solid_Stock", "3a190ca0-b2f6-9aeb-8067-547e72c11469"),
|
|
||||||
# "TMSP": ("BIOYOND_PolymerStation_Solid_Stock", "3a190ca0-b2f6-9aeb-8067-547e72c11469"),
|
|
||||||
# "TMSB": ("BIOYOND_PolymerStation_Solid_Stock", "3a190ca0-b2f6-9aeb-8067-547e72c11469"),
|
|
||||||
# "EP": ("BIOYOND_PolymerStation_Solid_Stock", "3a190ca0-b2f6-9aeb-8067-547e72c11469"),
|
|
||||||
# "DEC": ("BIOYOND_PolymerStation_Solid_Stock", "3a190ca0-b2f6-9aeb-8067-547e72c11469"),
|
|
||||||
# "EMC": ("BIOYOND_PolymerStation_Solid_Stock", "3a190ca0-b2f6-9aeb-8067-547e72c11469"),
|
|
||||||
# "SN": ("BIOYOND_PolymerStation_Solid_Stock", "3a190ca0-b2f6-9aeb-8067-547e72c11469"),
|
|
||||||
# "DMC": ("BIOYOND_PolymerStation_Solid_Stock", "3a190ca0-b2f6-9aeb-8067-547e72c11469"),
|
|
||||||
# "FEC": ("BIOYOND_PolymerStation_Solid_Stock", "3a190ca0-b2f6-9aeb-8067-547e72c11469"),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
WORKFLOW_MAPPINGS = {}
|
|
||||||
|
|
||||||
LOCATION_MAPPING = {}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,205 +1,203 @@
|
|||||||
|
# experiment_workflow.py
|
||||||
"""
|
"""
|
||||||
实验流程主程序
|
实验流程主程序
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import json
|
import json
|
||||||
from unilabos.devices.workstation.bioyond_studio.reaction_station import BioyondReactionStation
|
from bioyond_rpc import BioyondV1RPC
|
||||||
from unilabos.devices.workstation.bioyond_studio.config import API_CONFIG, WORKFLOW_MAPPINGS, DECK_CONFIG, MATERIAL_TYPE_MAPPINGS
|
from config import API_CONFIG, WORKFLOW_MAPPINGS
|
||||||
|
|
||||||
|
|
||||||
def run_experiment():
|
def run_experiment():
|
||||||
"""运行实验流程"""
|
"""运行实验流程"""
|
||||||
|
|
||||||
# 初始化Bioyond客户端
|
# 初始化Bioyond客户端
|
||||||
config = {
|
config = {
|
||||||
**API_CONFIG,
|
**API_CONFIG,
|
||||||
"workflow_mappings": WORKFLOW_MAPPINGS,
|
"workflow_mappings": WORKFLOW_MAPPINGS
|
||||||
"material_type_mappings": MATERIAL_TYPE_MAPPINGS
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# 创建BioyondReactionStation实例,传入deck配置
|
Bioyond = BioyondV1RPC(config)
|
||||||
Bioyond = BioyondReactionStation(
|
|
||||||
config=config,
|
|
||||||
deck=DECK_CONFIG
|
|
||||||
)
|
|
||||||
|
|
||||||
print("\n============= 多工作流参数测试(简化接口+材料缓存)=============")
|
print("\n============= 多工作流参数测试(简化接口+材料缓存)=============")
|
||||||
|
|
||||||
# 显示可用的材料名称(前20个)
|
# 显示可用的材料名称(前20个)
|
||||||
available_materials = Bioyond.hardware_interface.get_available_materials()
|
available_materials = Bioyond.get_available_materials()
|
||||||
print(f"可用材料名称(前20个): {available_materials[:20]}")
|
print(f"可用材料名称(前20个): {available_materials[:20]}")
|
||||||
print(f"总共有 {len(available_materials)} 个材料可用\n")
|
print(f"总共有 {len(available_materials)} 个材料可用\n")
|
||||||
|
|
||||||
# 1. 反应器放入
|
# 1. 反应器放入
|
||||||
print("1. 添加反应器放入工作流,带参数...")
|
print("1. 添加反应器放入工作流,带参数...")
|
||||||
Bioyond.reactor_taken_in(
|
Bioyond.reactor_taken_in(
|
||||||
assign_material_name="BTDA-DD",
|
assign_material_name="BTDA-DD",
|
||||||
cutoff="10000",
|
cutoff="10000",
|
||||||
temperature="-10"
|
temperature="-10"
|
||||||
)
|
)
|
||||||
|
|
||||||
# 2. 液体投料-烧杯 (第一个)
|
# 2. 液体投料-烧杯 (第一个)
|
||||||
print("2. 添加液体投料-烧杯,带参数...")
|
print("2. 添加液体投料-烧杯,带参数...")
|
||||||
Bioyond.liquid_feeding_beaker(
|
Bioyond.liquid_feeding_beaker(
|
||||||
volume="34768.7",
|
volume="34768.7",
|
||||||
assign_material_name="ODA",
|
assign_material_name="ODA",
|
||||||
time="0",
|
time="0",
|
||||||
torque_variation="1",
|
torque_variation="1",
|
||||||
titration_type="1",
|
titrationType="1",
|
||||||
temperature=-10
|
temperature=-10
|
||||||
)
|
)
|
||||||
|
|
||||||
# 3. 液体投料-烧杯 (第二个)
|
# 3. 液体投料-烧杯 (第二个)
|
||||||
print("3. 添加液体投料-烧杯,带参数...")
|
print("3. 添加液体投料-烧杯,带参数...")
|
||||||
Bioyond.liquid_feeding_beaker(
|
Bioyond.liquid_feeding_beaker(
|
||||||
volume="34080.9",
|
volume="34080.9",
|
||||||
assign_material_name="MPDA",
|
assign_material_name="MPDA",
|
||||||
time="5",
|
time="5",
|
||||||
torque_variation="2",
|
torque_variation="2",
|
||||||
titration_type="1",
|
titrationType="1",
|
||||||
temperature=0
|
temperature=0
|
||||||
)
|
)
|
||||||
|
|
||||||
# 4. 液体投料-小瓶非滴定
|
# 4. 液体投料-小瓶非滴定
|
||||||
print("4. 添加液体投料-小瓶非滴定,带参数...")
|
print("4. 添加液体投料-小瓶非滴定,带参数...")
|
||||||
Bioyond.liquid_feeding_vials_non_titration(
|
Bioyond.liquid_feeding_vials_non_titration(
|
||||||
volume_formula="639.5",
|
volumeFormula="639.5",
|
||||||
assign_material_name="SIDA",
|
assign_material_name="SIDA",
|
||||||
titration_type="1",
|
titration_type="1",
|
||||||
time="0",
|
time="0",
|
||||||
torque_variation="1",
|
torque_variation="1",
|
||||||
temperature=-10
|
temperature=-10
|
||||||
)
|
)
|
||||||
|
|
||||||
# 5. 液体投料溶剂
|
# 5. 液体投料溶剂
|
||||||
print("5. 添加液体投料溶剂,带参数...")
|
print("5. 添加液体投料溶剂,带参数...")
|
||||||
Bioyond.liquid_feeding_solvents(
|
Bioyond.liquid_feeding_solvents(
|
||||||
assign_material_name="NMP",
|
assign_material_name="NMP",
|
||||||
volume="19000",
|
volume="19000",
|
||||||
titration_type="1",
|
titration_type="1",
|
||||||
time="5",
|
time="5",
|
||||||
torque_variation="2",
|
torque_variation="2",
|
||||||
temperature=-10
|
temperature=-10
|
||||||
)
|
)
|
||||||
|
|
||||||
# 6-8. 固体进料小瓶 (三个)
|
# 6-8. 固体进料小瓶 (三个)
|
||||||
print("6. 添加固体进料小瓶,带参数...")
|
print("6. 添加固体进料小瓶,带参数...")
|
||||||
Bioyond.solid_feeding_vials(
|
Bioyond.solid_feeding_vials(
|
||||||
material_id="3",
|
material_id="3",
|
||||||
time="180",
|
time="180",
|
||||||
torque_variation="2",
|
torque_variation="2",
|
||||||
assign_material_name="BTDA1",
|
assign_material_name="BTDA-1",
|
||||||
temperature=-10.00
|
temperature=-10.00
|
||||||
)
|
)
|
||||||
|
|
||||||
print("7. 添加固体进料小瓶,带参数...")
|
print("7. 添加固体进料小瓶,带参数...")
|
||||||
Bioyond.solid_feeding_vials(
|
Bioyond.solid_feeding_vials(
|
||||||
material_id="3",
|
material_id="3",
|
||||||
time="180",
|
time="180",
|
||||||
torque_variation="2",
|
torque_variation="2",
|
||||||
assign_material_name="BTDA2",
|
assign_material_name="BTDA-2",
|
||||||
temperature=25.00
|
temperature=25.00
|
||||||
)
|
)
|
||||||
|
|
||||||
print("8. 添加固体进料小瓶,带参数...")
|
print("8. 添加固体进料小瓶,带参数...")
|
||||||
Bioyond.solid_feeding_vials(
|
Bioyond.solid_feeding_vials(
|
||||||
material_id="3",
|
material_id="3",
|
||||||
time="480",
|
time="480",
|
||||||
torque_variation="2",
|
torque_variation="2",
|
||||||
assign_material_name="BTDA3",
|
assign_material_name="BTDA-3",
|
||||||
temperature=25.00
|
temperature=25.00
|
||||||
)
|
)
|
||||||
|
|
||||||
# 液体投料滴定(第一个)
|
# 液体投料滴定(第一个)
|
||||||
print("9. 添加液体投料滴定,带参数...") # ODPA
|
print("9. 添加液体投料滴定,带参数...") # ODPA
|
||||||
Bioyond.liquid_feeding_titration(
|
Bioyond.liquid_feeding_titration(
|
||||||
volume_formula="{{6-0-5}}+{{7-0-5}}+{{8-0-5}}",
|
volume_formula="1000",
|
||||||
assign_material_name="BTDA-DD",
|
assign_material_name="BTDA-DD",
|
||||||
titration_type="1",
|
titration_type="1",
|
||||||
time="360",
|
time="360",
|
||||||
torque_variation="2",
|
torque_variation="2",
|
||||||
temperature="25.00"
|
temperature="25.00"
|
||||||
)
|
)
|
||||||
|
|
||||||
# 液体投料滴定(第二个)
|
# 液体投料滴定(第二个)
|
||||||
print("10. 添加液体投料滴定,带参数...") # ODPA
|
print("10. 添加液体投料滴定,带参数...") # ODPA
|
||||||
Bioyond.liquid_feeding_titration(
|
Bioyond.liquid_feeding_titration(
|
||||||
volume_formula="500",
|
volume_formula="500",
|
||||||
assign_material_name="BTDA-DD",
|
assign_material_name="BTDA-DD",
|
||||||
titration_type="1",
|
titration_type="1",
|
||||||
time="360",
|
time="360",
|
||||||
torque_variation="2",
|
torque_variation="2",
|
||||||
temperature="25.00"
|
temperature="25.00"
|
||||||
)
|
)
|
||||||
|
|
||||||
# 液体投料滴定(第三个)
|
# 液体投料滴定(第三个)
|
||||||
print("11. 添加液体投料滴定,带参数...") # ODPA
|
print("11. 添加液体投料滴定,带参数...") # ODPA
|
||||||
Bioyond.liquid_feeding_titration(
|
Bioyond.liquid_feeding_titration(
|
||||||
volume_formula="500",
|
volume_formula="500",
|
||||||
assign_material_name="BTDA-DD",
|
assign_material_name="BTDA-DD",
|
||||||
titration_type="1",
|
titration_type="1",
|
||||||
time="360",
|
time="360",
|
||||||
torque_variation="2",
|
torque_variation="2",
|
||||||
temperature="25.00"
|
temperature="25.00"
|
||||||
)
|
)
|
||||||
|
|
||||||
print("12. 添加液体投料滴定,带参数...") # ODPA
|
print("12. 添加液体投料滴定,带参数...") # ODPA
|
||||||
Bioyond.liquid_feeding_titration(
|
Bioyond.liquid_feeding_titration(
|
||||||
volume_formula="500",
|
volume_formula="500",
|
||||||
assign_material_name="BTDA-DD",
|
assign_material_name="BTDA-DD",
|
||||||
titration_type="1",
|
titration_type="1",
|
||||||
time="360",
|
time="360",
|
||||||
torque_variation="2",
|
torque_variation="2",
|
||||||
temperature="25.00"
|
temperature="25.00"
|
||||||
)
|
)
|
||||||
|
|
||||||
print("13. 添加液体投料滴定,带参数...") # ODPA
|
print("13. 添加液体投料滴定,带参数...") # ODPA
|
||||||
Bioyond.liquid_feeding_titration(
|
Bioyond.liquid_feeding_titration(
|
||||||
volume_formula="500",
|
volume_formula="500",
|
||||||
assign_material_name="BTDA-DD",
|
assign_material_name="BTDA-DD",
|
||||||
titration_type="1",
|
titration_type="1",
|
||||||
time="360",
|
time="360",
|
||||||
torque_variation="2",
|
torque_variation="2",
|
||||||
|
temperature="25.00"
|
||||||
|
)
|
||||||
|
|
||||||
|
print("14. 添加液体投料滴定,带参数...") # ODPA
|
||||||
|
Bioyond.liquid_feeding_titration(
|
||||||
|
volume_formula="500",
|
||||||
|
assign_material_name="BTDA-DD",
|
||||||
|
titration_type="1",
|
||||||
|
time="360",
|
||||||
|
torque_variation="2",
|
||||||
temperature="25.00"
|
temperature="25.00"
|
||||||
)
|
)
|
||||||
|
|
||||||
print("14. 添加液体投料滴定,带参数...") # ODPA
|
|
||||||
Bioyond.liquid_feeding_titration(
|
|
||||||
volume_formula="500",
|
|
||||||
assign_material_name="BTDA-DD",
|
|
||||||
titration_type="1",
|
|
||||||
time="360",
|
|
||||||
torque_variation="2",
|
|
||||||
temperature="25.00"
|
|
||||||
)
|
|
||||||
|
|
||||||
print("15. 添加液体投料溶剂,带参数...")
|
print("15. 添加液体投料溶剂,带参数...")
|
||||||
Bioyond.liquid_feeding_solvents(
|
Bioyond.liquid_feeding_solvents(
|
||||||
assign_material_name="PGME",
|
assign_material_name="PGME",
|
||||||
volume="16894.6",
|
volume="16894.6",
|
||||||
titration_type="1",
|
titration_type="1",
|
||||||
time="360",
|
time="360",
|
||||||
torque_variation="2",
|
torque_variation="2",
|
||||||
temperature=25.00
|
temperature=25.00
|
||||||
)
|
)
|
||||||
|
|
||||||
# 16. 反应器取出
|
# 16. 反应器取出
|
||||||
print("16. 添加反应器取出工作流...")
|
print("16. 添加反应器取出工作流...")
|
||||||
Bioyond.reactor_taken_out()
|
Bioyond.reactor_taken_out()
|
||||||
|
|
||||||
# 显示当前工作流序列
|
# 显示当前工作流序列
|
||||||
sequence = Bioyond.get_workflow_sequence()
|
sequence = Bioyond.get_workflow_sequence()
|
||||||
print("\n当前工作流执行顺序:")
|
print("\n当前工作流执行顺序:")
|
||||||
print(sequence)
|
print(sequence)
|
||||||
|
|
||||||
# 执行process_and_execute_workflow,合并工作流并创建任务
|
# 执行process_and_execute_workflow,合并工作流并创建任务
|
||||||
print("\n4. 执行process_and_execute_workflow...")
|
print("\n4. 执行process_and_execute_workflow...")
|
||||||
|
|
||||||
result = Bioyond.process_and_execute_workflow(
|
result = Bioyond.process_and_execute_workflow(
|
||||||
workflow_name="test3",
|
workflow_name="test3_86",
|
||||||
task_name="实验3"
|
task_name="实验3_86"
|
||||||
)
|
)
|
||||||
|
|
||||||
# 显示执行结果
|
# 显示执行结果
|
||||||
print("\n5. 执行结果:")
|
print("\n5. 执行结果:")
|
||||||
if isinstance(result, str):
|
if isinstance(result, str):
|
||||||
@@ -207,9 +205,9 @@ def run_experiment():
|
|||||||
result_dict = json.loads(result)
|
result_dict = json.loads(result)
|
||||||
if result_dict.get("success"):
|
if result_dict.get("success"):
|
||||||
print("任务创建成功!")
|
print("任务创建成功!")
|
||||||
# print(f"- 工作流: {result_dict.get('workflow', {}).get('name')}")
|
print(f"- 工作流: {result_dict.get('workflow', {}).get('name')}")
|
||||||
# print(f"- 工作流ID: {result_dict.get('workflow', {}).get('id')}")
|
print(f"- 工作流ID: {result_dict.get('workflow', {}).get('id')}")
|
||||||
# print(f"- 任务结果: {result_dict.get('task')}")
|
print(f"- 任务结果: {result_dict.get('task')}")
|
||||||
else:
|
else:
|
||||||
print(f"任务创建失败: {result_dict.get('error')}")
|
print(f"任务创建失败: {result_dict.get('error')}")
|
||||||
except:
|
except:
|
||||||
@@ -222,179 +220,179 @@ def run_experiment():
|
|||||||
print(f"- 任务结果: {result.get('task')}")
|
print(f"- 任务结果: {result.get('task')}")
|
||||||
else:
|
else:
|
||||||
print(f"任务创建失败: {result.get('error')}")
|
print(f"任务创建失败: {result.get('error')}")
|
||||||
|
|
||||||
# 可选:启动调度器
|
# 可选:启动调度器
|
||||||
# Bioyond.scheduler_start()
|
# Bioyond.scheduler_start()
|
||||||
|
|
||||||
return Bioyond
|
return Bioyond
|
||||||
|
|
||||||
|
|
||||||
# def prepare_materials(bioyond):
|
def prepare_materials(bioyond):
|
||||||
# """准备实验材料(可选)"""
|
"""准备实验材料(可选)"""
|
||||||
|
|
||||||
# # 样品板材料数据定义
|
# 样品板材料数据定义
|
||||||
# material_data_yp_1 = {
|
material_data_yp_1 = {
|
||||||
# "typeId": "3a142339-80de-8f25-6093-1b1b1b6c322e",
|
"typeId": "3a142339-80de-8f25-6093-1b1b1b6c322e",
|
||||||
# "name": "样品板-1",
|
"name": "样品板-1",
|
||||||
# "unit": "个",
|
"unit": "个",
|
||||||
# "quantity": 1,
|
"quantity": 1,
|
||||||
# "details": [
|
"details": [
|
||||||
# {
|
{
|
||||||
# "typeId": "3a14233a-84a3-088d-6676-7cb4acd57c64",
|
"typeId": "3a14233a-84a3-088d-6676-7cb4acd57c64",
|
||||||
# "name": "BPDA-DD-1",
|
"name": "BPDA-DD-1",
|
||||||
# "quantity": 1,
|
"quantity": 1,
|
||||||
# "x": 1,
|
"x": 1,
|
||||||
# "y": 1,
|
"y": 1,
|
||||||
# "Parameters": "{\"molecular\": 1}"
|
"Parameters": "{\"molecular\": 1}"
|
||||||
# },
|
},
|
||||||
# {
|
{
|
||||||
# "typeId": "3a14233a-84a3-088d-6676-7cb4acd57c64",
|
"typeId": "3a14233a-84a3-088d-6676-7cb4acd57c64",
|
||||||
# "name": "PEPA",
|
"name": "PEPA",
|
||||||
# "quantity": 1,
|
"quantity": 1,
|
||||||
# "x": 1,
|
"x": 1,
|
||||||
# "y": 2,
|
"y": 2,
|
||||||
# "Parameters": "{\"molecular\": 1}"
|
"Parameters": "{\"molecular\": 1}"
|
||||||
# },
|
},
|
||||||
# {
|
{
|
||||||
# "typeId": "3a14233a-84a3-088d-6676-7cb4acd57c64",
|
"typeId": "3a14233a-84a3-088d-6676-7cb4acd57c64",
|
||||||
# "name": "BPDA-DD-2",
|
"name": "BPDA-DD-2",
|
||||||
# "quantity": 1,
|
"quantity": 1,
|
||||||
# "x": 1,
|
"x": 1,
|
||||||
# "y": 3,
|
"y": 3,
|
||||||
# "Parameters": "{\"molecular\": 1}"
|
"Parameters": "{\"molecular\": 1}"
|
||||||
# },
|
},
|
||||||
# {
|
{
|
||||||
# "typeId": "3a14233a-84a3-088d-6676-7cb4acd57c64",
|
"typeId": "3a14233a-84a3-088d-6676-7cb4acd57c64",
|
||||||
# "name": "BPDA-1",
|
"name": "BPDA-1",
|
||||||
# "quantity": 1,
|
"quantity": 1,
|
||||||
# "x": 2,
|
"x": 2,
|
||||||
# "y": 1,
|
"y": 1,
|
||||||
# "Parameters": "{\"molecular\": 1}"
|
"Parameters": "{\"molecular\": 1}"
|
||||||
# },
|
},
|
||||||
# {
|
{
|
||||||
# "typeId": "3a14233a-84a3-088d-6676-7cb4acd57c64",
|
"typeId": "3a14233a-84a3-088d-6676-7cb4acd57c64",
|
||||||
# "name": "PMDA",
|
"name": "PMDA",
|
||||||
# "quantity": 1,
|
"quantity": 1,
|
||||||
# "x": 2,
|
"x": 2,
|
||||||
# "y": 2,
|
"y": 2,
|
||||||
# "Parameters": "{\"molecular\": 1}"
|
"Parameters": "{\"molecular\": 1}"
|
||||||
# },
|
},
|
||||||
# {
|
{
|
||||||
# "typeId": "3a14233a-84a3-088d-6676-7cb4acd57c64",
|
"typeId": "3a14233a-84a3-088d-6676-7cb4acd57c64",
|
||||||
# "name": "BPDA-2",
|
"name": "BPDA-2",
|
||||||
# "quantity": 1,
|
"quantity": 1,
|
||||||
# "x": 2,
|
"x": 2,
|
||||||
# "y": 3,
|
"y": 3,
|
||||||
# "Parameters": "{\"molecular\": 1}"
|
"Parameters": "{\"molecular\": 1}"
|
||||||
# }
|
}
|
||||||
# ],
|
],
|
||||||
# "Parameters": "{}"
|
"Parameters": "{}"
|
||||||
# }
|
}
|
||||||
|
|
||||||
# material_data_yp_2 = {
|
material_data_yp_2 = {
|
||||||
# "typeId": "3a142339-80de-8f25-6093-1b1b1b6c322e",
|
"typeId": "3a142339-80de-8f25-6093-1b1b1b6c322e",
|
||||||
# "name": "样品板-2",
|
"name": "样品板-2",
|
||||||
# "unit": "个",
|
"unit": "个",
|
||||||
# "quantity": 1,
|
"quantity": 1,
|
||||||
# "details": [
|
"details": [
|
||||||
# {
|
{
|
||||||
# "typeId": "3a14233a-84a3-088d-6676-7cb4acd57c64",
|
"typeId": "3a14233a-84a3-088d-6676-7cb4acd57c64",
|
||||||
# "name": "BPDA-DD",
|
"name": "BPDA-DD",
|
||||||
# "quantity": 1,
|
"quantity": 1,
|
||||||
# "x": 1,
|
"x": 1,
|
||||||
# "y": 1,
|
"y": 1,
|
||||||
# "Parameters": "{\"molecular\": 1}"
|
"Parameters": "{\"molecular\": 1}"
|
||||||
# },
|
},
|
||||||
# {
|
{
|
||||||
# "typeId": "3a14233a-84a3-088d-6676-7cb4acd57c64",
|
"typeId": "3a14233a-84a3-088d-6676-7cb4acd57c64",
|
||||||
# "name": "SIDA",
|
"name": "SIDA",
|
||||||
# "quantity": 1,
|
"quantity": 1,
|
||||||
# "x": 1,
|
"x": 1,
|
||||||
# "y": 2,
|
"y": 2,
|
||||||
# "Parameters": "{\"molecular\": 1}"
|
"Parameters": "{\"molecular\": 1}"
|
||||||
# },
|
},
|
||||||
# {
|
{
|
||||||
# "typeId": "3a14233a-84a3-088d-6676-7cb4acd57c64",
|
"typeId": "3a14233a-84a3-088d-6676-7cb4acd57c64",
|
||||||
# "name": "BTDA-1",
|
"name": "BTDA-1",
|
||||||
# "quantity": 1,
|
"quantity": 1,
|
||||||
# "x": 2,
|
"x": 2,
|
||||||
# "y": 1,
|
"y": 1,
|
||||||
# "Parameters": "{\"molecular\": 1}"
|
"Parameters": "{\"molecular\": 1}"
|
||||||
# },
|
},
|
||||||
# {
|
{
|
||||||
# "typeId": "3a14233a-84a3-088d-6676-7cb4acd57c64",
|
"typeId": "3a14233a-84a3-088d-6676-7cb4acd57c64",
|
||||||
# "name": "BTDA-2",
|
"name": "BTDA-2",
|
||||||
# "quantity": 1,
|
"quantity": 1,
|
||||||
# "x": 2,
|
"x": 2,
|
||||||
# "y": 2,
|
"y": 2,
|
||||||
# "Parameters": "{\"molecular\": 1}"
|
"Parameters": "{\"molecular\": 1}"
|
||||||
# },
|
},
|
||||||
# {
|
{
|
||||||
# "typeId": "3a14233a-84a3-088d-6676-7cb4acd57c64",
|
"typeId": "3a14233a-84a3-088d-6676-7cb4acd57c64",
|
||||||
# "name": "BTDA-3",
|
"name": "BTDA-3",
|
||||||
# "quantity": 1,
|
"quantity": 1,
|
||||||
# "x": 2,
|
"x": 2,
|
||||||
# "y": 3,
|
"y": 3,
|
||||||
# "Parameters": "{\"molecular\": 1}"
|
"Parameters": "{\"molecular\": 1}"
|
||||||
# }
|
}
|
||||||
# ],
|
],
|
||||||
# "Parameters": "{}"
|
"Parameters": "{}"
|
||||||
# }
|
}
|
||||||
|
|
||||||
# # 烧杯材料数据定义
|
# 烧杯材料数据定义
|
||||||
# beaker_materials = [
|
beaker_materials = [
|
||||||
# {
|
{
|
||||||
# "typeId": "3a14233b-f0a9-ba84-eaa9-0d4718b361b6",
|
"typeId": "3a14233b-f0a9-ba84-eaa9-0d4718b361b6",
|
||||||
# "name": "PDA-1",
|
"name": "PDA-1",
|
||||||
# "unit": "微升",
|
"unit": "微升",
|
||||||
# "quantity": 1,
|
"quantity": 1,
|
||||||
# "parameters": "{\"DeviceMaterialType\":\"NMP\"}"
|
"parameters": "{\"DeviceMaterialType\":\"NMP\"}"
|
||||||
# },
|
},
|
||||||
# {
|
{
|
||||||
# "typeId": "3a14233b-f0a9-ba84-eaa9-0d4718b361b6",
|
"typeId": "3a14233b-f0a9-ba84-eaa9-0d4718b361b6",
|
||||||
# "name": "TFDB",
|
"name": "TFDB",
|
||||||
# "unit": "微升",
|
"unit": "微升",
|
||||||
# "quantity": 1,
|
"quantity": 1,
|
||||||
# "parameters": "{\"DeviceMaterialType\":\"NMP\"}"
|
"parameters": "{\"DeviceMaterialType\":\"NMP\"}"
|
||||||
# },
|
},
|
||||||
# {
|
{
|
||||||
# "typeId": "3a14233b-f0a9-ba84-eaa9-0d4718b361b6",
|
"typeId": "3a14233b-f0a9-ba84-eaa9-0d4718b361b6",
|
||||||
# "name": "ODA",
|
"name": "ODA",
|
||||||
# "unit": "微升",
|
"unit": "微升",
|
||||||
# "quantity": 1,
|
"quantity": 1,
|
||||||
# "parameters": "{\"DeviceMaterialType\":\"NMP\"}"
|
"parameters": "{\"DeviceMaterialType\":\"NMP\"}"
|
||||||
# },
|
},
|
||||||
# {
|
{
|
||||||
# "typeId": "3a14233b-f0a9-ba84-eaa9-0d4718b361b6",
|
"typeId": "3a14233b-f0a9-ba84-eaa9-0d4718b361b6",
|
||||||
# "name": "MPDA",
|
"name": "MPDA",
|
||||||
# "unit": "微升",
|
"unit": "微升",
|
||||||
# "quantity": 1,
|
"quantity": 1,
|
||||||
# "parameters": "{\"DeviceMaterialType\":\"NMP\"}"
|
"parameters": "{\"DeviceMaterialType\":\"NMP\"}"
|
||||||
# },
|
},
|
||||||
# {
|
{
|
||||||
# "typeId": "3a14233b-f0a9-ba84-eaa9-0d4718b361b6",
|
"typeId": "3a14233b-f0a9-ba84-eaa9-0d4718b361b6",
|
||||||
# "name": "PDA-2",
|
"name": "PDA-2",
|
||||||
# "unit": "微升",
|
"unit": "微升",
|
||||||
# "quantity": 1,
|
"quantity": 1,
|
||||||
# "parameters": "{\"DeviceMaterialType\":\"NMP\"}"
|
"parameters": "{\"DeviceMaterialType\":\"NMP\"}"
|
||||||
# }
|
}
|
||||||
# ]
|
]
|
||||||
|
|
||||||
# # 如果需要,可以在这里调用add_material方法添加材料
|
# 如果需要,可以在这里调用add_material方法添加材料
|
||||||
# # 例如:
|
# 例如:
|
||||||
# # result = bioyond.add_material(json.dumps(material_data_yp_1))
|
# result = bioyond.add_material(json.dumps(material_data_yp_1))
|
||||||
# # print(f"添加材料结果: {result}")
|
# print(f"添加材料结果: {result}")
|
||||||
|
|
||||||
# return {
|
return {
|
||||||
# "sample_plates": [material_data_yp_1, material_data_yp_2],
|
"sample_plates": [material_data_yp_1, material_data_yp_2],
|
||||||
# "beakers": beaker_materials
|
"beakers": beaker_materials
|
||||||
# }
|
}
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
# 运行主实验流程
|
# 运行主实验流程
|
||||||
bioyond_client = run_experiment()
|
bioyond_client = run_experiment()
|
||||||
|
|
||||||
# 可选:准备材料数据
|
# 可选:准备材料数据
|
||||||
# materials = prepare_materials(bioyond_client)
|
# materials = prepare_materials(bioyond_client)
|
||||||
# print(f"\n准备的材料数据: {materials}")
|
# print(f"\n准备的材料数据: {materials}")
|
||||||
|
|||||||
@@ -1,783 +0,0 @@
|
|||||||
import json
|
|
||||||
import requests
|
|
||||||
from typing import List, Dict, Any
|
|
||||||
from unilabos.devices.workstation.bioyond_studio.station import BioyondWorkstation
|
|
||||||
from unilabos.devices.workstation.bioyond_studio.config import (
|
|
||||||
WORKFLOW_STEP_IDS,
|
|
||||||
WORKFLOW_TO_SECTION_MAP,
|
|
||||||
ACTION_NAMES
|
|
||||||
)
|
|
||||||
from unilabos.devices.workstation.bioyond_studio.config import API_CONFIG
|
|
||||||
|
|
||||||
|
|
||||||
class BioyondReactionStation(BioyondWorkstation):
|
|
||||||
"""Bioyond反应站类
|
|
||||||
|
|
||||||
继承自BioyondWorkstation,提供反应站特定的业务方法
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, config: dict = None, deck=None, protocol_type=None, **kwargs):
|
|
||||||
"""初始化反应站
|
|
||||||
|
|
||||||
Args:
|
|
||||||
config: 配置字典,应包含workflow_mappings等配置
|
|
||||||
deck: Deck对象
|
|
||||||
protocol_type: 协议类型(由ROS系统传递,此处忽略)
|
|
||||||
**kwargs: 其他可能的参数
|
|
||||||
"""
|
|
||||||
if deck is None and config:
|
|
||||||
deck = config.get('deck')
|
|
||||||
|
|
||||||
print(f"BioyondReactionStation初始化 - config包含workflow_mappings: {'workflow_mappings' in (config or {})}")
|
|
||||||
if config and 'workflow_mappings' in config:
|
|
||||||
print(f"workflow_mappings内容: {config['workflow_mappings']}")
|
|
||||||
|
|
||||||
super().__init__(bioyond_config=config, deck=deck)
|
|
||||||
|
|
||||||
print(f"BioyondReactionStation初始化完成 - workflow_mappings: {self.workflow_mappings}")
|
|
||||||
print(f"workflow_mappings长度: {len(self.workflow_mappings)}")
|
|
||||||
|
|
||||||
# ==================== 工作流方法 ====================
|
|
||||||
|
|
||||||
def reactor_taken_out(self):
|
|
||||||
"""反应器取出"""
|
|
||||||
self.append_to_workflow_sequence('{"web_workflow_name": "reactor_taken_out"}')
|
|
||||||
reactor_taken_out_params = {"param_values": {}}
|
|
||||||
self.pending_task_params.append(reactor_taken_out_params)
|
|
||||||
print(f"成功添加反应器取出工作流")
|
|
||||||
print(f"当前队列长度: {len(self.pending_task_params)}")
|
|
||||||
return json.dumps({"suc": True})
|
|
||||||
|
|
||||||
def reactor_taken_in(
|
|
||||||
self,
|
|
||||||
assign_material_name: str,
|
|
||||||
cutoff: str = "900000",
|
|
||||||
temperature: float = -10.00
|
|
||||||
):
|
|
||||||
"""反应器放入
|
|
||||||
|
|
||||||
Args:
|
|
||||||
assign_material_name: 物料名称(不能为空)
|
|
||||||
cutoff: 粘度上限(需为有效数字字符串,默认 "900000")
|
|
||||||
temperature: 温度设定(°C,范围:-50.00 至 100.00)
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
str: JSON 字符串,格式为 {"suc": True}
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
ValueError: 若物料名称无效或 cutoff 格式错误
|
|
||||||
"""
|
|
||||||
if not assign_material_name:
|
|
||||||
raise ValueError("物料名称不能为空")
|
|
||||||
try:
|
|
||||||
float(cutoff)
|
|
||||||
except ValueError:
|
|
||||||
raise ValueError("cutoff 必须是有效的数字字符串")
|
|
||||||
|
|
||||||
self.append_to_workflow_sequence('{"web_workflow_name": "reactor_taken_in"}')
|
|
||||||
material_id = self.hardware_interface._get_material_id_by_name(assign_material_name)
|
|
||||||
if material_id is None:
|
|
||||||
raise ValueError(f"无法找到物料 {assign_material_name} 的 ID")
|
|
||||||
|
|
||||||
if isinstance(temperature, str):
|
|
||||||
temperature = float(temperature)
|
|
||||||
|
|
||||||
step_id = WORKFLOW_STEP_IDS["reactor_taken_in"]["config"]
|
|
||||||
reactor_taken_in_params = {
|
|
||||||
"param_values": {
|
|
||||||
step_id: {
|
|
||||||
ACTION_NAMES["reactor_taken_in"]["config"]: [
|
|
||||||
{"m": 0, "n": 3, "Key": "cutoff", "Value": cutoff},
|
|
||||||
{"m": 0, "n": 3, "Key": "assignMaterialName", "Value": material_id}
|
|
||||||
],
|
|
||||||
ACTION_NAMES["reactor_taken_in"]["stirring"]: [
|
|
||||||
{"m": 0, "n": 3, "Key": "temperature", "Value": f"{temperature:.2f}"}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.pending_task_params.append(reactor_taken_in_params)
|
|
||||||
print(f"成功添加反应器放入参数: material={assign_material_name}->ID:{material_id}, cutoff={cutoff}, temp={temperature:.2f}")
|
|
||||||
print(f"当前队列长度: {len(self.pending_task_params)}")
|
|
||||||
return json.dumps({"suc": True})
|
|
||||||
|
|
||||||
def solid_feeding_vials(
|
|
||||||
self,
|
|
||||||
material_id: str,
|
|
||||||
time: str = "0",
|
|
||||||
torque_variation: int = 1,
|
|
||||||
assign_material_name: str = None,
|
|
||||||
temperature: float = 25.00
|
|
||||||
):
|
|
||||||
"""固体进料小瓶
|
|
||||||
|
|
||||||
Args:
|
|
||||||
material_id: 粉末类型ID,1=盐(21分钟),2=面粉(27分钟),3=BTDA(38分钟)
|
|
||||||
time: 观察时间(分钟)
|
|
||||||
torque_variation: 是否观察(int类型, 1=否, 2=是)
|
|
||||||
assign_material_name: 物料名称(用于获取试剂瓶位ID)
|
|
||||||
temperature: 温度设定(°C)
|
|
||||||
"""
|
|
||||||
self.append_to_workflow_sequence('{"web_workflow_name": "Solid_feeding_vials"}')
|
|
||||||
material_id_m = self.hardware_interface._get_material_id_by_name(assign_material_name) if assign_material_name else None
|
|
||||||
|
|
||||||
if isinstance(temperature, str):
|
|
||||||
temperature = float(temperature)
|
|
||||||
|
|
||||||
feeding_step_id = WORKFLOW_STEP_IDS["solid_feeding_vials"]["feeding"]
|
|
||||||
observe_step_id = WORKFLOW_STEP_IDS["solid_feeding_vials"]["observe"]
|
|
||||||
|
|
||||||
solid_feeding_vials_params = {
|
|
||||||
"param_values": {
|
|
||||||
feeding_step_id: {
|
|
||||||
ACTION_NAMES["solid_feeding_vials"]["feeding"]: [
|
|
||||||
{"m": 0, "n": 3, "Key": "materialId", "Value": material_id},
|
|
||||||
{"m": 0, "n": 3, "Key": "assignMaterialName", "Value": material_id_m} if material_id_m else {}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
observe_step_id: {
|
|
||||||
ACTION_NAMES["solid_feeding_vials"]["observe"]: [
|
|
||||||
{"m": 1, "n": 0, "Key": "time", "Value": time},
|
|
||||||
{"m": 1, "n": 0, "Key": "torqueVariation", "Value": str(torque_variation)},
|
|
||||||
{"m": 1, "n": 0, "Key": "temperature", "Value": f"{temperature:.2f}"}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.pending_task_params.append(solid_feeding_vials_params)
|
|
||||||
print(f"成功添加固体进料小瓶参数: material_id={material_id}, time={time}min, torque={torque_variation}, temp={temperature:.2f}°C")
|
|
||||||
print(f"当前队列长度: {len(self.pending_task_params)}")
|
|
||||||
return json.dumps({"suc": True})
|
|
||||||
|
|
||||||
def liquid_feeding_vials_non_titration(
|
|
||||||
self,
|
|
||||||
volume_formula: str,
|
|
||||||
assign_material_name: str,
|
|
||||||
titration_type: str = "1",
|
|
||||||
time: str = "0",
|
|
||||||
torque_variation: int = 1,
|
|
||||||
temperature: float = 25.00
|
|
||||||
):
|
|
||||||
"""液体进料小瓶(非滴定)
|
|
||||||
|
|
||||||
Args:
|
|
||||||
volume_formula: 分液公式(μL)
|
|
||||||
assign_material_name: 物料名称
|
|
||||||
titration_type: 是否滴定(1=否, 2=是)
|
|
||||||
time: 观察时间(分钟)
|
|
||||||
torque_variation: 是否观察(int类型, 1=否, 2=是)
|
|
||||||
temperature: 温度(°C)
|
|
||||||
"""
|
|
||||||
self.append_to_workflow_sequence('{"web_workflow_name": "Liquid_feeding_vials(non-titration)"}')
|
|
||||||
material_id = self.hardware_interface._get_material_id_by_name(assign_material_name)
|
|
||||||
if material_id is None:
|
|
||||||
raise ValueError(f"无法找到物料 {assign_material_name} 的 ID")
|
|
||||||
|
|
||||||
if isinstance(temperature, str):
|
|
||||||
temperature = float(temperature)
|
|
||||||
|
|
||||||
liquid_step_id = WORKFLOW_STEP_IDS["liquid_feeding_vials_non_titration"]["liquid"]
|
|
||||||
observe_step_id = WORKFLOW_STEP_IDS["liquid_feeding_vials_non_titration"]["observe"]
|
|
||||||
|
|
||||||
params = {
|
|
||||||
"param_values": {
|
|
||||||
liquid_step_id: {
|
|
||||||
ACTION_NAMES["liquid_feeding_vials_non_titration"]["liquid"]: [
|
|
||||||
{"m": 0, "n": 3, "Key": "volumeFormula", "Value": volume_formula},
|
|
||||||
{"m": 0, "n": 3, "Key": "assignMaterialName", "Value": material_id},
|
|
||||||
{"m": 0, "n": 3, "Key": "titrationType", "Value": titration_type}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
observe_step_id: {
|
|
||||||
ACTION_NAMES["liquid_feeding_vials_non_titration"]["observe"]: [
|
|
||||||
{"m": 1, "n": 0, "Key": "time", "Value": time},
|
|
||||||
{"m": 1, "n": 0, "Key": "torqueVariation", "Value": str(torque_variation)},
|
|
||||||
{"m": 1, "n": 0, "Key": "temperature", "Value": f"{temperature:.2f}"}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.pending_task_params.append(params)
|
|
||||||
print(f"成功添加液体进料小瓶(非滴定)参数: volume={volume_formula}μL, material={assign_material_name}->ID:{material_id}")
|
|
||||||
print(f"当前队列长度: {len(self.pending_task_params)}")
|
|
||||||
return json.dumps({"suc": True})
|
|
||||||
|
|
||||||
def liquid_feeding_solvents(
|
|
||||||
self,
|
|
||||||
assign_material_name: str,
|
|
||||||
volume: str = None,
|
|
||||||
solvents = None,
|
|
||||||
titration_type: str = "1",
|
|
||||||
time: str = "360",
|
|
||||||
torque_variation: int = 2,
|
|
||||||
temperature: float = 25.00
|
|
||||||
):
|
|
||||||
"""液体进料-溶剂
|
|
||||||
|
|
||||||
Args:
|
|
||||||
assign_material_name: 物料名称
|
|
||||||
volume: 分液量(μL),直接指定体积(可选,如果提供solvents则自动计算)
|
|
||||||
solvents: 溶剂信息的字典或JSON字符串(可选),格式如下:
|
|
||||||
{
|
|
||||||
"additional_solvent": 33.55092503597727, # 溶剂体积(mL)
|
|
||||||
"total_liquid_volume": 48.00916988195499
|
|
||||||
}
|
|
||||||
如果提供solvents,则从中提取additional_solvent并转换为μL
|
|
||||||
titration_type: 是否滴定(1=否, 2=是)
|
|
||||||
time: 观察时间(分钟)
|
|
||||||
torque_variation: 是否观察(int类型, 1=否, 2=是)
|
|
||||||
temperature: 温度设定(°C)
|
|
||||||
"""
|
|
||||||
# 处理 volume 参数:优先使用直接传入的 volume,否则从 solvents 中提取
|
|
||||||
if volume is None and solvents is not None:
|
|
||||||
# 参数类型转换:如果是字符串则解析为字典
|
|
||||||
if isinstance(solvents, str):
|
|
||||||
try:
|
|
||||||
solvents = json.loads(solvents)
|
|
||||||
except json.JSONDecodeError as e:
|
|
||||||
raise ValueError(f"solvents参数JSON解析失败: {str(e)}")
|
|
||||||
|
|
||||||
# 参数验证
|
|
||||||
if not isinstance(solvents, dict):
|
|
||||||
raise ValueError("solvents 必须是字典类型或有效的JSON字符串")
|
|
||||||
|
|
||||||
# 提取 additional_solvent 值
|
|
||||||
additional_solvent = solvents.get("additional_solvent")
|
|
||||||
if additional_solvent is None:
|
|
||||||
raise ValueError("solvents 中没有找到 additional_solvent 字段")
|
|
||||||
|
|
||||||
# 转换为微升(μL) - 从毫升(mL)转换
|
|
||||||
volume = str(float(additional_solvent) * 1000)
|
|
||||||
elif volume is None:
|
|
||||||
raise ValueError("必须提供 volume 或 solvents 参数之一")
|
|
||||||
|
|
||||||
self.append_to_workflow_sequence('{"web_workflow_name": "Liquid_feeding_solvents"}')
|
|
||||||
material_id = self.hardware_interface._get_material_id_by_name(assign_material_name)
|
|
||||||
if material_id is None:
|
|
||||||
raise ValueError(f"无法找到物料 {assign_material_name} 的 ID")
|
|
||||||
|
|
||||||
if isinstance(temperature, str):
|
|
||||||
temperature = float(temperature)
|
|
||||||
|
|
||||||
liquid_step_id = WORKFLOW_STEP_IDS["liquid_feeding_solvents"]["liquid"]
|
|
||||||
observe_step_id = WORKFLOW_STEP_IDS["liquid_feeding_solvents"]["observe"]
|
|
||||||
|
|
||||||
params = {
|
|
||||||
"param_values": {
|
|
||||||
liquid_step_id: {
|
|
||||||
ACTION_NAMES["liquid_feeding_solvents"]["liquid"]: [
|
|
||||||
{"m": 0, "n": 1, "Key": "titrationType", "Value": titration_type},
|
|
||||||
{"m": 0, "n": 1, "Key": "volume", "Value": volume},
|
|
||||||
{"m": 0, "n": 1, "Key": "assignMaterialName", "Value": material_id}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
observe_step_id: {
|
|
||||||
ACTION_NAMES["liquid_feeding_solvents"]["observe"]: [
|
|
||||||
{"m": 1, "n": 0, "Key": "time", "Value": time},
|
|
||||||
{"m": 1, "n": 0, "Key": "torqueVariation", "Value": str(torque_variation)},
|
|
||||||
{"m": 1, "n": 0, "Key": "temperature", "Value": f"{temperature:.2f}"}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.pending_task_params.append(params)
|
|
||||||
print(f"成功添加液体进料溶剂参数: material={assign_material_name}->ID:{material_id}, volume={volume}μL")
|
|
||||||
print(f"当前队列长度: {len(self.pending_task_params)}")
|
|
||||||
return json.dumps({"suc": True})
|
|
||||||
|
|
||||||
def liquid_feeding_titration(
|
|
||||||
self,
|
|
||||||
volume_formula: str,
|
|
||||||
assign_material_name: str,
|
|
||||||
titration_type: str = "1",
|
|
||||||
time: str = "90",
|
|
||||||
torque_variation: int = 2,
|
|
||||||
temperature: float = 25.00
|
|
||||||
):
|
|
||||||
"""液体进料(滴定)
|
|
||||||
|
|
||||||
Args:
|
|
||||||
volume_formula: 分液公式(μL)
|
|
||||||
assign_material_name: 物料名称
|
|
||||||
titration_type: 是否滴定(1=否, 2=是)
|
|
||||||
time: 观察时间(分钟)
|
|
||||||
torque_variation: 是否观察(int类型, 1=否, 2=是)
|
|
||||||
temperature: 温度(°C)
|
|
||||||
"""
|
|
||||||
self.append_to_workflow_sequence('{"web_workflow_name": "Liquid_feeding(titration)"}')
|
|
||||||
material_id = self.hardware_interface._get_material_id_by_name(assign_material_name)
|
|
||||||
if material_id is None:
|
|
||||||
raise ValueError(f"无法找到物料 {assign_material_name} 的 ID")
|
|
||||||
|
|
||||||
if isinstance(temperature, str):
|
|
||||||
temperature = float(temperature)
|
|
||||||
|
|
||||||
liquid_step_id = WORKFLOW_STEP_IDS["liquid_feeding_titration"]["liquid"]
|
|
||||||
observe_step_id = WORKFLOW_STEP_IDS["liquid_feeding_titration"]["observe"]
|
|
||||||
|
|
||||||
params = {
|
|
||||||
"param_values": {
|
|
||||||
liquid_step_id: {
|
|
||||||
ACTION_NAMES["liquid_feeding_titration"]["liquid"]: [
|
|
||||||
{"m": 0, "n": 3, "Key": "volumeFormula", "Value": volume_formula},
|
|
||||||
{"m": 0, "n": 3, "Key": "titrationType", "Value": titration_type},
|
|
||||||
{"m": 0, "n": 3, "Key": "assignMaterialName", "Value": material_id}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
observe_step_id: {
|
|
||||||
ACTION_NAMES["liquid_feeding_titration"]["observe"]: [
|
|
||||||
{"m": 1, "n": 0, "Key": "time", "Value": time},
|
|
||||||
{"m": 1, "n": 0, "Key": "torqueVariation", "Value": str(torque_variation)},
|
|
||||||
{"m": 1, "n": 0, "Key": "temperature", "Value": f"{temperature:.2f}"}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.pending_task_params.append(params)
|
|
||||||
print(f"成功添加液体进料滴定参数: volume={volume_formula}μL, material={assign_material_name}->ID:{material_id}")
|
|
||||||
print(f"当前队列长度: {len(self.pending_task_params)}")
|
|
||||||
return json.dumps({"suc": True})
|
|
||||||
|
|
||||||
def liquid_feeding_beaker(
|
|
||||||
self,
|
|
||||||
volume: str = "35000",
|
|
||||||
assign_material_name: str = "BAPP",
|
|
||||||
time: str = "0",
|
|
||||||
torque_variation: int = 1,
|
|
||||||
titration_type: str = "1",
|
|
||||||
temperature: float = 25.00
|
|
||||||
):
|
|
||||||
"""液体进料烧杯
|
|
||||||
|
|
||||||
Args:
|
|
||||||
volume: 分液量(μL)
|
|
||||||
assign_material_name: 物料名称(试剂瓶位)
|
|
||||||
time: 观察时间(分钟)
|
|
||||||
torque_variation: 是否观察(int类型, 1=否, 2=是)
|
|
||||||
titration_type: 是否滴定(1=否, 2=是)
|
|
||||||
temperature: 温度设定(°C)
|
|
||||||
"""
|
|
||||||
self.append_to_workflow_sequence('{"web_workflow_name": "liquid_feeding_beaker"}')
|
|
||||||
material_id = self.hardware_interface._get_material_id_by_name(assign_material_name)
|
|
||||||
if material_id is None:
|
|
||||||
raise ValueError(f"无法找到物料 {assign_material_name} 的 ID")
|
|
||||||
|
|
||||||
if isinstance(temperature, str):
|
|
||||||
temperature = float(temperature)
|
|
||||||
|
|
||||||
liquid_step_id = WORKFLOW_STEP_IDS["liquid_feeding_beaker"]["liquid"]
|
|
||||||
observe_step_id = WORKFLOW_STEP_IDS["liquid_feeding_beaker"]["observe"]
|
|
||||||
|
|
||||||
params = {
|
|
||||||
"param_values": {
|
|
||||||
liquid_step_id: {
|
|
||||||
ACTION_NAMES["liquid_feeding_beaker"]["liquid"]: [
|
|
||||||
{"m": 0, "n": 2, "Key": "volume", "Value": volume},
|
|
||||||
{"m": 0, "n": 2, "Key": "assignMaterialName", "Value": material_id},
|
|
||||||
{"m": 0, "n": 2, "Key": "titrationType", "Value": titration_type}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
observe_step_id: {
|
|
||||||
ACTION_NAMES["liquid_feeding_beaker"]["observe"]: [
|
|
||||||
{"m": 1, "n": 0, "Key": "time", "Value": time},
|
|
||||||
{"m": 1, "n": 0, "Key": "torqueVariation", "Value": str(torque_variation)},
|
|
||||||
{"m": 1, "n": 0, "Key": "temperature", "Value": f"{temperature:.2f}"}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.pending_task_params.append(params)
|
|
||||||
print(f"成功添加液体进料烧杯参数: volume={volume}μL, material={assign_material_name}->ID:{material_id}")
|
|
||||||
print(f"当前队列长度: {len(self.pending_task_params)}")
|
|
||||||
return json.dumps({"suc": True})
|
|
||||||
|
|
||||||
def drip_back(
|
|
||||||
self,
|
|
||||||
assign_material_name: str,
|
|
||||||
volume: str,
|
|
||||||
titration_type: str = "1",
|
|
||||||
time: str = "90",
|
|
||||||
torque_variation: int = 2,
|
|
||||||
temperature: float = 25.00
|
|
||||||
):
|
|
||||||
"""滴回去
|
|
||||||
|
|
||||||
Args:
|
|
||||||
assign_material_name: 物料名称(液体种类)
|
|
||||||
volume: 分液量(μL)
|
|
||||||
titration_type: 是否滴定(1=否, 2=是)
|
|
||||||
time: 观察时间(分钟)
|
|
||||||
torque_variation: 是否观察(int类型, 1=否, 2=是)
|
|
||||||
temperature: 温度(°C)
|
|
||||||
"""
|
|
||||||
self.append_to_workflow_sequence('{"web_workflow_name": "drip_back"}')
|
|
||||||
material_id = self.hardware_interface._get_material_id_by_name(assign_material_name)
|
|
||||||
if material_id is None:
|
|
||||||
raise ValueError(f"无法找到物料 {assign_material_name} 的 ID")
|
|
||||||
|
|
||||||
if isinstance(temperature, str):
|
|
||||||
temperature = float(temperature)
|
|
||||||
|
|
||||||
liquid_step_id = WORKFLOW_STEP_IDS["drip_back"]["liquid"]
|
|
||||||
observe_step_id = WORKFLOW_STEP_IDS["drip_back"]["observe"]
|
|
||||||
|
|
||||||
params = {
|
|
||||||
"param_values": {
|
|
||||||
liquid_step_id: {
|
|
||||||
ACTION_NAMES["drip_back"]["liquid"]: [
|
|
||||||
{"m": 0, "n": 1, "Key": "titrationType", "Value": titration_type},
|
|
||||||
{"m": 0, "n": 1, "Key": "assignMaterialName", "Value": material_id},
|
|
||||||
{"m": 0, "n": 1, "Key": "volume", "Value": volume}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
observe_step_id: {
|
|
||||||
ACTION_NAMES["drip_back"]["observe"]: [
|
|
||||||
{"m": 1, "n": 0, "Key": "time", "Value": time},
|
|
||||||
{"m": 1, "n": 0, "Key": "torqueVariation", "Value": str(torque_variation)},
|
|
||||||
{"m": 1, "n": 0, "Key": "temperature", "Value": f"{temperature:.2f}"}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.pending_task_params.append(params)
|
|
||||||
print(f"成功添加滴回去参数: material={assign_material_name}->ID:{material_id}, volume={volume}μL")
|
|
||||||
print(f"当前队列长度: {len(self.pending_task_params)}")
|
|
||||||
return json.dumps({"suc": True})
|
|
||||||
|
|
||||||
# ==================== 工作流管理方法 ====================
|
|
||||||
|
|
||||||
def get_workflow_sequence(self) -> List[str]:
|
|
||||||
"""获取当前工作流执行顺序
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
工作流名称列表
|
|
||||||
"""
|
|
||||||
id_to_name = {workflow_id: name for name, workflow_id in self.workflow_mappings.items()}
|
|
||||||
workflow_names = []
|
|
||||||
for workflow_id in self.workflow_sequence:
|
|
||||||
workflow_name = id_to_name.get(workflow_id, workflow_id)
|
|
||||||
workflow_names.append(workflow_name)
|
|
||||||
print(f"工作流序列: {workflow_names}")
|
|
||||||
return workflow_names
|
|
||||||
|
|
||||||
def workflow_step_query(self, workflow_id: str) -> dict:
|
|
||||||
"""查询工作流步骤参数
|
|
||||||
|
|
||||||
Args:
|
|
||||||
workflow_id: 工作流ID
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
工作流步骤参数字典
|
|
||||||
"""
|
|
||||||
return self.hardware_interface.workflow_step_query(workflow_id)
|
|
||||||
|
|
||||||
def create_order(self, json_str: str) -> dict:
|
|
||||||
"""创建订单
|
|
||||||
|
|
||||||
Args:
|
|
||||||
json_str: 订单参数的JSON字符串
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
创建结果
|
|
||||||
"""
|
|
||||||
return self.hardware_interface.create_order(json_str)
|
|
||||||
|
|
||||||
# ==================== 工作流执行核心方法 ====================
|
|
||||||
|
|
||||||
def process_web_workflows(self, web_workflow_json: str) -> List[Dict[str, str]]:
|
|
||||||
"""处理网页工作流列表
|
|
||||||
|
|
||||||
Args:
|
|
||||||
web_workflow_json: JSON 格式的网页工作流列表
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
List[Dict[str, str]]: 包含工作流 ID 和名称的字典列表
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
web_workflow_data = json.loads(web_workflow_json)
|
|
||||||
web_workflow_list = web_workflow_data.get("web_workflow_list", [])
|
|
||||||
workflows_result = []
|
|
||||||
for name in web_workflow_list:
|
|
||||||
workflow_id = self.workflow_mappings.get(name, "")
|
|
||||||
if not workflow_id:
|
|
||||||
print(f"警告:未找到工作流名称 {name} 对应的 ID")
|
|
||||||
continue
|
|
||||||
workflows_result.append({"id": workflow_id, "name": name})
|
|
||||||
print(f"process_web_workflows 输出: {workflows_result}")
|
|
||||||
return workflows_result
|
|
||||||
except json.JSONDecodeError as e:
|
|
||||||
print(f"错误:无法解析 web_workflow_json: {e}")
|
|
||||||
return []
|
|
||||||
except Exception as e:
|
|
||||||
print(f"错误:处理工作流失败: {e}")
|
|
||||||
return []
|
|
||||||
|
|
||||||
def process_and_execute_workflow(self, workflow_name: str, task_name: str) -> dict:
|
|
||||||
"""
|
|
||||||
一站式处理工作流程:解析网页工作流列表,合并工作流(带参数),然后发布任务
|
|
||||||
|
|
||||||
Args:
|
|
||||||
workflow_name: 合并后的工作流名称
|
|
||||||
task_name: 任务名称
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
任务创建结果
|
|
||||||
"""
|
|
||||||
web_workflow_list = self.get_workflow_sequence()
|
|
||||||
print(f"\n{'='*60}")
|
|
||||||
print(f"📋 处理网页工作流列表: {web_workflow_list}")
|
|
||||||
print(f"{'='*60}")
|
|
||||||
|
|
||||||
web_workflow_json = json.dumps({"web_workflow_list": web_workflow_list})
|
|
||||||
workflows_result = self.process_web_workflows(web_workflow_json)
|
|
||||||
|
|
||||||
if not workflows_result:
|
|
||||||
return self._create_error_result("处理网页工作流列表失败", "process_web_workflows")
|
|
||||||
|
|
||||||
print(f"workflows_result 类型: {type(workflows_result)}")
|
|
||||||
print(f"workflows_result 内容: {workflows_result}")
|
|
||||||
|
|
||||||
workflows_with_params = self._build_workflows_with_parameters(workflows_result)
|
|
||||||
|
|
||||||
merge_data = {
|
|
||||||
"name": workflow_name,
|
|
||||||
"workflows": workflows_with_params
|
|
||||||
}
|
|
||||||
|
|
||||||
# print(f"\n🔄 合并工作流(带参数),名称: {workflow_name}")
|
|
||||||
merged_workflow = self.merge_workflow_with_parameters(json.dumps(merge_data))
|
|
||||||
|
|
||||||
if not merged_workflow:
|
|
||||||
return self._create_error_result("合并工作流失败", "merge_workflow_with_parameters")
|
|
||||||
|
|
||||||
workflow_id = merged_workflow.get("subWorkflows", [{}])[0].get("id", "")
|
|
||||||
# print(f"\n📤 使用工作流创建任务: {workflow_name} (ID: {workflow_id})")
|
|
||||||
|
|
||||||
order_params = [{
|
|
||||||
"orderCode": f"task_{self.hardware_interface.get_current_time_iso8601()}",
|
|
||||||
"orderName": task_name,
|
|
||||||
"workFlowId": workflow_id,
|
|
||||||
"borderNumber": 1,
|
|
||||||
"paramValues": {}
|
|
||||||
}]
|
|
||||||
|
|
||||||
result = self.create_order(json.dumps(order_params))
|
|
||||||
|
|
||||||
if not result:
|
|
||||||
return self._create_error_result("创建任务失败", "create_order")
|
|
||||||
|
|
||||||
# 清空工作流序列和参数,防止下次执行时累积重复
|
|
||||||
self.pending_task_params = []
|
|
||||||
self.clear_workflows() # 清空工作流序列,避免重复累积
|
|
||||||
|
|
||||||
# print(f"\n✅ 任务创建成功: {result}")
|
|
||||||
# print(f"\n✅ 任务创建成功")
|
|
||||||
print(f"{'='*60}\n")
|
|
||||||
return json.dumps({"success": True, "result": result})
|
|
||||||
|
|
||||||
def _build_workflows_with_parameters(self, workflows_result: list) -> list:
|
|
||||||
"""
|
|
||||||
构建带参数的工作流列表
|
|
||||||
|
|
||||||
Args:
|
|
||||||
workflows_result: 处理后的工作流列表(应为包含 id 和 name 的字典列表)
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
符合新接口格式的工作流参数结构
|
|
||||||
"""
|
|
||||||
workflows_with_params = []
|
|
||||||
total_params = 0
|
|
||||||
successful_params = 0
|
|
||||||
failed_params = []
|
|
||||||
|
|
||||||
for idx, workflow_info in enumerate(workflows_result):
|
|
||||||
if not isinstance(workflow_info, dict):
|
|
||||||
print(f"错误:workflows_result[{idx}] 不是字典,而是 {type(workflow_info)}: {workflow_info}")
|
|
||||||
continue
|
|
||||||
workflow_id = workflow_info.get("id")
|
|
||||||
if not workflow_id:
|
|
||||||
print(f"警告:workflows_result[{idx}] 缺少 'id' 键")
|
|
||||||
continue
|
|
||||||
workflow_name = workflow_info.get("name", "")
|
|
||||||
# print(f"\n🔧 处理工作流 [{idx}]: {workflow_name} (ID: {workflow_id})")
|
|
||||||
|
|
||||||
if idx >= len(self.pending_task_params):
|
|
||||||
# print(f" ⚠️ 无对应参数,跳过")
|
|
||||||
workflows_with_params.append({"id": workflow_id})
|
|
||||||
continue
|
|
||||||
|
|
||||||
param_data = self.pending_task_params[idx]
|
|
||||||
param_values = param_data.get("param_values", {})
|
|
||||||
if not param_values:
|
|
||||||
# print(f" ⚠️ 参数为空,跳过")
|
|
||||||
workflows_with_params.append({"id": workflow_id})
|
|
||||||
continue
|
|
||||||
|
|
||||||
step_parameters = {}
|
|
||||||
for step_id, actions_dict in param_values.items():
|
|
||||||
# print(f" 📍 步骤ID: {step_id}")
|
|
||||||
for action_name, param_list in actions_dict.items():
|
|
||||||
# print(f" 🔹 模块: {action_name}, 参数数量: {len(param_list)}")
|
|
||||||
if step_id not in step_parameters:
|
|
||||||
step_parameters[step_id] = {}
|
|
||||||
if action_name not in step_parameters[step_id]:
|
|
||||||
step_parameters[step_id][action_name] = []
|
|
||||||
for param_item in param_list:
|
|
||||||
param_key = param_item.get("Key", "")
|
|
||||||
param_value = param_item.get("Value", "")
|
|
||||||
total_params += 1
|
|
||||||
step_parameters[step_id][action_name].append({
|
|
||||||
"Key": param_key,
|
|
||||||
"DisplayValue": param_value,
|
|
||||||
"Value": param_value
|
|
||||||
})
|
|
||||||
successful_params += 1
|
|
||||||
# print(f" ✓ {param_key} = {param_value}")
|
|
||||||
|
|
||||||
workflows_with_params.append({
|
|
||||||
"id": workflow_id,
|
|
||||||
"stepParameters": step_parameters
|
|
||||||
})
|
|
||||||
|
|
||||||
self._print_mapping_stats(total_params, successful_params, failed_params)
|
|
||||||
return workflows_with_params
|
|
||||||
|
|
||||||
def _print_mapping_stats(self, total: int, success: int, failed: list):
|
|
||||||
"""打印参数映射统计"""
|
|
||||||
print(f"\n{'='*20} 参数映射统计 {'='*20}")
|
|
||||||
print(f"📊 总参数数量: {total}")
|
|
||||||
print(f"✅ 成功映射: {success}")
|
|
||||||
print(f"❌ 映射失败: {len(failed)}")
|
|
||||||
if not failed:
|
|
||||||
print("🎉 成功映射所有参数!")
|
|
||||||
else:
|
|
||||||
print(f"⚠️ 失败的参数: {', '.join(failed)}")
|
|
||||||
success_rate = (success/total*100) if total > 0 else 0
|
|
||||||
print(f"📈 映射成功率: {success_rate:.1f}%")
|
|
||||||
print("="*60)
|
|
||||||
|
|
||||||
def _create_error_result(self, error_msg: str, step: str) -> str:
|
|
||||||
"""创建统一的错误返回格式"""
|
|
||||||
print(f"❌ {error_msg}")
|
|
||||||
return json.dumps({
|
|
||||||
"success": False,
|
|
||||||
"error": f"process_and_execute_workflow: {error_msg}",
|
|
||||||
"method": "process_and_execute_workflow",
|
|
||||||
"step": step
|
|
||||||
})
|
|
||||||
|
|
||||||
def merge_workflow_with_parameters(self, json_str: str) -> dict:
|
|
||||||
"""
|
|
||||||
调用新接口:合并工作流并传递参数
|
|
||||||
|
|
||||||
Args:
|
|
||||||
json_str: JSON格式的字符串,包含:
|
|
||||||
- name: 工作流名称
|
|
||||||
- workflows: [{"id": "工作流ID", "stepParameters": {...}}]
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
合并后的工作流信息
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
data = json.loads(json_str)
|
|
||||||
|
|
||||||
# 在工作流名称后面添加时间戳,避免重复
|
|
||||||
if "name" in data and data["name"]:
|
|
||||||
timestamp = self.hardware_interface.get_current_time_iso8601().replace(":", "-").replace(".", "-")
|
|
||||||
original_name = data["name"]
|
|
||||||
data["name"] = f"{original_name}_{timestamp}"
|
|
||||||
print(f"🕒 工作流名称已添加时间戳: {original_name} -> {data['name']}")
|
|
||||||
|
|
||||||
request_data = {
|
|
||||||
"apiKey": API_CONFIG["api_key"],
|
|
||||||
"requestTime": self.hardware_interface.get_current_time_iso8601(),
|
|
||||||
"data": data
|
|
||||||
}
|
|
||||||
print(f"\n📤 发送合并请求:")
|
|
||||||
print(f" 工作流名称: {data.get('name')}")
|
|
||||||
print(f" 子工作流数量: {len(data.get('workflows', []))}")
|
|
||||||
|
|
||||||
# 打印完整的POST请求内容
|
|
||||||
print(f"\n🔍 POST请求详细内容:")
|
|
||||||
print(f" URL: {self.hardware_interface.host}/api/lims/workflow/merge-workflow-with-parameters")
|
|
||||||
print(f" Headers: {{'Content-Type': 'application/json'}}")
|
|
||||||
print(f" Request Data:")
|
|
||||||
print(f" {json.dumps(request_data, indent=4, ensure_ascii=False)}")
|
|
||||||
#
|
|
||||||
response = requests.post(
|
|
||||||
f"{self.hardware_interface.host}/api/lims/workflow/merge-workflow-with-parameters",
|
|
||||||
json=request_data,
|
|
||||||
headers={"Content-Type": "application/json"},
|
|
||||||
timeout=30
|
|
||||||
)
|
|
||||||
|
|
||||||
# # 打印响应详细内容
|
|
||||||
# print(f"\n📥 POST响应详细内容:")
|
|
||||||
# print(f" 状态码: {response.status_code}")
|
|
||||||
# print(f" 响应头: {dict(response.headers)}")
|
|
||||||
# print(f" 响应体: {response.text}")
|
|
||||||
# #
|
|
||||||
try:
|
|
||||||
result = response.json()
|
|
||||||
# #
|
|
||||||
# print(f"\n📋 解析后的响应JSON:")
|
|
||||||
# print(f" {json.dumps(result, indent=4, ensure_ascii=False)}")
|
|
||||||
# #
|
|
||||||
except json.JSONDecodeError:
|
|
||||||
print(f"❌ 服务器返回非 JSON 格式响应: {response.text}")
|
|
||||||
return None
|
|
||||||
|
|
||||||
if result.get("code") == 1:
|
|
||||||
print(f"✅ 工作流合并成功(带参数)")
|
|
||||||
return result.get("data", {})
|
|
||||||
else:
|
|
||||||
error_msg = result.get('message', '未知错误')
|
|
||||||
print(f"❌ 工作流合并失败: {error_msg}")
|
|
||||||
return None
|
|
||||||
|
|
||||||
except requests.exceptions.Timeout:
|
|
||||||
print(f"❌ 合并工作流请求超时")
|
|
||||||
return None
|
|
||||||
except requests.exceptions.RequestException as e:
|
|
||||||
print(f"❌ 合并工作流网络异常: {str(e)}")
|
|
||||||
return None
|
|
||||||
except json.JSONDecodeError as e:
|
|
||||||
print(f"❌ 合并工作流响应解析失败: {str(e)}")
|
|
||||||
return None
|
|
||||||
except Exception as e:
|
|
||||||
print(f"❌ 合并工作流异常: {str(e)}")
|
|
||||||
return None
|
|
||||||
|
|
||||||
def _validate_and_refresh_workflow_if_needed(self, workflow_name: str) -> bool:
|
|
||||||
"""验证工作流ID是否有效,如果无效则重新合并
|
|
||||||
|
|
||||||
Args:
|
|
||||||
workflow_name: 工作流名称
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
bool: 验证或刷新是否成功
|
|
||||||
"""
|
|
||||||
print(f"\n🔍 验证工作流ID有效性...")
|
|
||||||
if not self.workflow_sequence:
|
|
||||||
print(f" ⚠️ 工作流序列为空,需要重新合并")
|
|
||||||
return False
|
|
||||||
first_workflow_id = self.workflow_sequence[0]
|
|
||||||
try:
|
|
||||||
structure = self.workflow_step_query(first_workflow_id)
|
|
||||||
if structure:
|
|
||||||
print(f" ✅ 工作流ID有效")
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
print(f" ⚠️ 工作流ID已过期,需要重新合并")
|
|
||||||
return False
|
|
||||||
except Exception as e:
|
|
||||||
print(f" ❌ 工作流ID验证失败: {e}")
|
|
||||||
print(f" 💡 将重新合并工作流")
|
|
||||||
return False
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -24,13 +24,6 @@ class ElectrodeSheetState(TypedDict):
|
|||||||
thickness: float # 厚度 (mm)
|
thickness: float # 厚度 (mm)
|
||||||
mass: float # 质量 (g)
|
mass: float # 质量 (g)
|
||||||
material_type: str # 材料类型(正极、负极、隔膜、弹片、垫片、铝箔等)
|
material_type: str # 材料类型(正极、负极、隔膜、弹片、垫片、铝箔等)
|
||||||
height: float
|
|
||||||
electrolyte_name: str
|
|
||||||
data_electrolyte_code: str
|
|
||||||
open_circuit_voltage: float
|
|
||||||
assembly_pressure: float
|
|
||||||
electrolyte_volume: float
|
|
||||||
|
|
||||||
info: Optional[str] # 附加信息
|
info: Optional[str] # 附加信息
|
||||||
|
|
||||||
class ElectrodeSheet(Resource):
|
class ElectrodeSheet(Resource):
|
||||||
@@ -68,7 +61,6 @@ class ElectrodeSheet(Resource):
|
|||||||
info=None
|
info=None
|
||||||
)
|
)
|
||||||
|
|
||||||
# TODO: 这个还要不要?给self._unilabos_state赋值的?
|
|
||||||
def load_state(self, state: Dict[str, Any]) -> None:
|
def load_state(self, state: Dict[str, Any]) -> None:
|
||||||
"""格式不变"""
|
"""格式不变"""
|
||||||
super().load_state(state)
|
super().load_state(state)
|
||||||
@@ -154,10 +146,10 @@ class MaterialHole(Resource):
|
|||||||
):
|
):
|
||||||
"""放置极片"""
|
"""放置极片"""
|
||||||
# TODO: 这里要改,diameter找不到,加入._unilabos_state后应该没问题
|
# TODO: 这里要改,diameter找不到,加入._unilabos_state后应该没问题
|
||||||
#if resource._unilabos_state["diameter"] > self._unilabos_state["diameter"]:
|
if resource._unilabos_state["diameter"] > self._unilabos_state["diameter"]:
|
||||||
# raise ValueError(f"极片直径 {resource._unilabos_state['diameter']} 超过洞位直径 {self._unilabos_state['diameter']}")
|
raise ValueError(f"极片直径 {resource._unilabos_state['diameter']} 超过洞位直径 {self._unilabos_state['diameter']}")
|
||||||
#if len(self.children) >= self._unilabos_state["max_sheets"]:
|
if len(self.children) >= self._unilabos_state["max_sheets"]:
|
||||||
# raise ValueError(f"洞位已满,无法放置更多极片")
|
raise ValueError(f"洞位已满,无法放置更多极片")
|
||||||
super().assign_child_resource(resource, location, reassign)
|
super().assign_child_resource(resource, location, reassign)
|
||||||
|
|
||||||
# 根据children的编号取物料对象。
|
# 根据children的编号取物料对象。
|
||||||
@@ -172,6 +164,8 @@ class MaterialPlateState(TypedDict):
|
|||||||
hole_diameter: float
|
hole_diameter: float
|
||||||
info: Optional[str] # 附加信息
|
info: Optional[str] # 附加信息
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class MaterialPlate(ItemizedResource[MaterialHole]):
|
class MaterialPlate(ItemizedResource[MaterialHole]):
|
||||||
"""料板类 - 4x4个洞位,每个洞位放1个极片"""
|
"""料板类 - 4x4个洞位,每个洞位放1个极片"""
|
||||||
|
|
||||||
@@ -329,13 +323,12 @@ class PlateSlot(ResourceStack):
|
|||||||
|
|
||||||
class ClipMagazineHole(Container):
|
class ClipMagazineHole(Container):
|
||||||
"""子弹夹洞位类"""
|
"""子弹夹洞位类"""
|
||||||
|
children: List[ElectrodeSheet] = []
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
name: str,
|
name: str,
|
||||||
diameter: float,
|
diameter: float,
|
||||||
depth: float,
|
depth: float,
|
||||||
max_sheets: int = 100,
|
|
||||||
category: str = "clip_magazine_hole",
|
category: str = "clip_magazine_hole",
|
||||||
):
|
):
|
||||||
"""初始化子弹夹洞位
|
"""初始化子弹夹洞位
|
||||||
@@ -344,7 +337,6 @@ class ClipMagazineHole(Container):
|
|||||||
name: 洞位名称
|
name: 洞位名称
|
||||||
diameter: 洞直径 (mm)
|
diameter: 洞直径 (mm)
|
||||||
depth: 洞深度 (mm)
|
depth: 洞深度 (mm)
|
||||||
max_sheets: 最大极片数量
|
|
||||||
category: 类别
|
category: 类别
|
||||||
"""
|
"""
|
||||||
super().__init__(
|
super().__init__(
|
||||||
@@ -356,36 +348,131 @@ class ClipMagazineHole(Container):
|
|||||||
)
|
)
|
||||||
self.diameter = diameter
|
self.diameter = diameter
|
||||||
self.depth = depth
|
self.depth = depth
|
||||||
self.max_sheets = max_sheets
|
|
||||||
self._sheets: List[ElectrodeSheet] = []
|
|
||||||
|
|
||||||
def can_add_sheet(self, sheet: ElectrodeSheet) -> bool:
|
def can_add_sheet(self, sheet: ElectrodeSheet) -> bool:
|
||||||
"""检查是否可以添加极片"""
|
"""检查是否可以添加极片
|
||||||
return (len(self._sheets) < self.max_sheets and
|
|
||||||
sheet.diameter <= self.diameter)
|
根据洞的深度和极片的厚度来判断是否可以添加极片
|
||||||
|
"""
|
||||||
|
# 检查极片直径是否适合洞的直径
|
||||||
|
if sheet._unilabos_state["diameter"] > self.diameter:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# 计算当前已添加极片的总厚度
|
||||||
|
current_thickness = sum(s._unilabos_state["thickness"] for s in self.children)
|
||||||
|
|
||||||
|
# 检查添加新极片后总厚度是否超过洞的深度
|
||||||
|
if current_thickness + sheet._unilabos_state["thickness"] > self.depth:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
def add_sheet(self, sheet: ElectrodeSheet) -> None:
|
|
||||||
"""添加极片"""
|
|
||||||
if not self.can_add_sheet(sheet):
|
|
||||||
raise ValueError(f"无法向洞位 {self.name} 添加极片")
|
|
||||||
self._sheets.append(sheet)
|
|
||||||
|
|
||||||
def take_sheet(self) -> ElectrodeSheet:
|
def assign_child_resource(
|
||||||
"""取出极片"""
|
self,
|
||||||
if len(self._sheets) == 0:
|
resource: ElectrodeSheet,
|
||||||
raise ValueError(f"洞位 {self.name} 没有极片")
|
location: Optional[Coordinate] = None,
|
||||||
return self._sheets.pop()
|
reassign: bool = True,
|
||||||
|
):
|
||||||
|
"""放置极片到洞位中
|
||||||
|
|
||||||
|
Args:
|
||||||
|
resource: 要放置的极片
|
||||||
|
location: 极片在洞位中的位置(对于洞位,通常为None)
|
||||||
|
reassign: 是否允许重新分配
|
||||||
|
"""
|
||||||
|
# 检查是否可以添加极片
|
||||||
|
if not self.can_add_sheet(resource):
|
||||||
|
raise ValueError(f"无法向洞位 {self.name} 添加极片:直径或厚度不匹配")
|
||||||
|
|
||||||
|
# 调用父类方法实际执行分配
|
||||||
|
super().assign_child_resource(resource, location, reassign)
|
||||||
|
|
||||||
|
def unassign_child_resource(self, resource: ElectrodeSheet):
|
||||||
|
"""从洞位中移除极片
|
||||||
|
|
||||||
|
Args:
|
||||||
|
resource: 要移除的极片
|
||||||
|
"""
|
||||||
|
if resource not in self.children:
|
||||||
|
raise ValueError(f"极片 {resource.name} 不在洞位 {self.name} 中")
|
||||||
|
|
||||||
|
# 调用父类方法实际执行移除
|
||||||
|
super().unassign_child_resource(resource)
|
||||||
|
|
||||||
|
|
||||||
def get_sheet_count(self) -> int:
|
|
||||||
"""获取极片数量"""
|
|
||||||
return len(self._sheets)
|
|
||||||
|
|
||||||
def serialize_state(self) -> Dict[str, Any]:
|
def serialize_state(self) -> Dict[str, Any]:
|
||||||
return {
|
return {
|
||||||
"sheet_count": len(self._sheets),
|
"sheet_count": len(self.children),
|
||||||
"sheets": [sheet.serialize() for sheet in self._sheets],
|
"sheets": [sheet.serialize() for sheet in self.children],
|
||||||
}
|
}
|
||||||
|
class ClipMagazine_four(ItemizedResource[ClipMagazineHole]):
|
||||||
|
"""子弹夹类 - 有4个洞位,每个洞位放多个极片"""
|
||||||
|
children: List[ClipMagazineHole]
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
name: str,
|
||||||
|
size_x: float,
|
||||||
|
size_y: float,
|
||||||
|
size_z: float,
|
||||||
|
hole_diameter: float = 14.0,
|
||||||
|
hole_depth: float = 10.0,
|
||||||
|
hole_spacing: float = 25.0,
|
||||||
|
max_sheets_per_hole: int = 100,
|
||||||
|
category: str = "clip_magazine_four",
|
||||||
|
model: Optional[str] = None,
|
||||||
|
):
|
||||||
|
"""初始化子弹夹
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name: 子弹夹名称
|
||||||
|
size_x: 长度 (mm)
|
||||||
|
size_y: 宽度 (mm)
|
||||||
|
size_z: 高度 (mm)
|
||||||
|
hole_diameter: 洞直径 (mm)
|
||||||
|
hole_depth: 洞深度 (mm)
|
||||||
|
hole_spacing: 洞位间距 (mm)
|
||||||
|
max_sheets_per_hole: 每个洞位最大极片数量
|
||||||
|
category: 类别
|
||||||
|
model: 型号
|
||||||
|
"""
|
||||||
|
# 创建4个洞位,排成2x2布局
|
||||||
|
holes = create_ordered_items_2d(
|
||||||
|
klass=ClipMagazineHole,
|
||||||
|
num_items_x=2,
|
||||||
|
num_items_y=2,
|
||||||
|
dx=(size_x - 2 * hole_spacing) / 2, # 居中
|
||||||
|
dy=(size_y - hole_spacing) / 2, # 居中
|
||||||
|
dz=size_z - 0,
|
||||||
|
item_dx=hole_spacing,
|
||||||
|
item_dy=hole_spacing,
|
||||||
|
diameter=hole_diameter,
|
||||||
|
depth=hole_depth,
|
||||||
|
)
|
||||||
|
|
||||||
|
super().__init__(
|
||||||
|
name=name,
|
||||||
|
size_x=size_x,
|
||||||
|
size_y=size_y,
|
||||||
|
size_z=size_z,
|
||||||
|
ordered_items=holes,
|
||||||
|
category=category,
|
||||||
|
model=model,
|
||||||
|
)
|
||||||
|
|
||||||
|
# 保存洞位的直径和深度
|
||||||
|
self.hole_diameter = hole_diameter
|
||||||
|
self.hole_depth = hole_depth
|
||||||
|
self.max_sheets_per_hole = max_sheets_per_hole
|
||||||
|
|
||||||
|
def serialize(self) -> dict:
|
||||||
|
return {
|
||||||
|
**super().serialize(),
|
||||||
|
"hole_diameter": self.hole_diameter,
|
||||||
|
"hole_depth": self.hole_depth,
|
||||||
|
"max_sheets_per_hole": self.max_sheets_per_hole,
|
||||||
|
}
|
||||||
# TODO: 这个要改
|
# TODO: 这个要改
|
||||||
class ClipMagazine(ItemizedResource[ClipMagazineHole]):
|
class ClipMagazine(ItemizedResource[ClipMagazineHole]):
|
||||||
"""子弹夹类 - 有6个洞位,每个洞位放多个极片"""
|
"""子弹夹类 - 有6个洞位,每个洞位放多个极片"""
|
||||||
@@ -458,9 +545,9 @@ class BatteryState(TypedDict):
|
|||||||
"""电池状态字典"""
|
"""电池状态字典"""
|
||||||
diameter: float
|
diameter: float
|
||||||
height: float
|
height: float
|
||||||
assembly_pressure: float
|
|
||||||
electrolyte_volume: float
|
|
||||||
electrolyte_name: str
|
electrolyte_name: str
|
||||||
|
electrolyte_volume: float
|
||||||
|
|
||||||
class Battery(Resource):
|
class Battery(Resource):
|
||||||
"""电池类 - 可容纳极片"""
|
"""电池类 - 可容纳极片"""
|
||||||
@@ -469,9 +556,6 @@ class Battery(Resource):
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
name: str,
|
name: str,
|
||||||
size_x=1,
|
|
||||||
size_y=1,
|
|
||||||
size_z=1,
|
|
||||||
category: str = "battery",
|
category: str = "battery",
|
||||||
):
|
):
|
||||||
"""初始化电池
|
"""初始化电池
|
||||||
@@ -492,13 +576,7 @@ class Battery(Resource):
|
|||||||
size_z=1,
|
size_z=1,
|
||||||
category=category,
|
category=category,
|
||||||
)
|
)
|
||||||
self._unilabos_state: BatteryState = BatteryState(
|
self._unilabos_state: BatteryState = BatteryState()
|
||||||
diameter = 1.0,
|
|
||||||
height = 1.0,
|
|
||||||
assembly_pressure = 1.0,
|
|
||||||
electrolyte_volume = 1.0,
|
|
||||||
electrolyte_name = "DP001"
|
|
||||||
)
|
|
||||||
|
|
||||||
def add_electrolyte_with_bottle(self, bottle: Bottle) -> bool:
|
def add_electrolyte_with_bottle(self, bottle: Bottle) -> bool:
|
||||||
to_add_name = bottle._unilabos_state["electrolyte_name"]
|
to_add_name = bottle._unilabos_state["electrolyte_name"]
|
||||||
@@ -586,7 +664,6 @@ class BatteryPressSlot(Resource):
|
|||||||
reassign: bool = True,
|
reassign: bool = True,
|
||||||
):
|
):
|
||||||
"""放置极片"""
|
"""放置极片"""
|
||||||
# TODO: 让高京看下槽位只有一个电池时是否这么写。
|
|
||||||
if self.has_battery():
|
if self.has_battery():
|
||||||
raise ValueError(f"槽位已含有一个电池,无法再放置其他电池")
|
raise ValueError(f"槽位已含有一个电池,无法再放置其他电池")
|
||||||
super().assign_child_resource(resource, location, reassign)
|
super().assign_child_resource(resource, location, reassign)
|
||||||
@@ -595,7 +672,6 @@ class BatteryPressSlot(Resource):
|
|||||||
def get_battery_info(self, index: int) -> Battery:
|
def get_battery_info(self, index: int) -> Battery:
|
||||||
return self.children[0]
|
return self.children[0]
|
||||||
|
|
||||||
# TODO:这个移液枪架子看一下从哪继承
|
|
||||||
class TipBox64State(TypedDict):
|
class TipBox64State(TypedDict):
|
||||||
"""电池状态字典"""
|
"""电池状态字典"""
|
||||||
tip_diameter: float = 5.0
|
tip_diameter: float = 5.0
|
||||||
@@ -654,6 +730,15 @@ class TipBox64(TipRack):
|
|||||||
make_tip=make_tip,
|
make_tip=make_tip,
|
||||||
)
|
)
|
||||||
self._unilabos_state: WasteTipBoxstate = WasteTipBoxstate()
|
self._unilabos_state: WasteTipBoxstate = WasteTipBoxstate()
|
||||||
|
# 记录网格参数用于前端渲染
|
||||||
|
self._grid_params = {
|
||||||
|
"num_items_x": 8,
|
||||||
|
"num_items_y": 8,
|
||||||
|
"dx": 8.0,
|
||||||
|
"dy": 8.0,
|
||||||
|
"item_dx": 9.0,
|
||||||
|
"item_dy": 9.0,
|
||||||
|
}
|
||||||
super().__init__(
|
super().__init__(
|
||||||
name=name,
|
name=name,
|
||||||
size_x=size_x,
|
size_x=size_x,
|
||||||
@@ -665,6 +750,12 @@ class TipBox64(TipRack):
|
|||||||
with_tips=True,
|
with_tips=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def serialize(self) -> dict:
|
||||||
|
return {
|
||||||
|
**super().serialize(),
|
||||||
|
**self._grid_params,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class WasteTipBoxstate(TypedDict):
|
class WasteTipBoxstate(TypedDict):
|
||||||
@@ -742,6 +833,15 @@ class BottleRackState(TypedDict):
|
|||||||
name_to_index: dict
|
name_to_index: dict
|
||||||
|
|
||||||
|
|
||||||
|
class BottleRackState(TypedDict):
|
||||||
|
""" bottle_diameter: 瓶子直径 (mm)
|
||||||
|
bottle_height: 瓶子高度 (mm)
|
||||||
|
position_spacing: 位置间距 (mm)"""
|
||||||
|
bottle_diameter: float
|
||||||
|
bottle_height: float
|
||||||
|
position_spacing: float
|
||||||
|
name_to_index: dict
|
||||||
|
|
||||||
|
|
||||||
class BottleRack(Resource):
|
class BottleRack(Resource):
|
||||||
"""瓶架类 - 12个待配位置+12个已配位置"""
|
"""瓶架类 - 12个待配位置+12个已配位置"""
|
||||||
@@ -861,6 +961,7 @@ class BottleRack(Resource):
|
|||||||
"padding_y": self.padding_y,
|
"padding_y": self.padding_y,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class BottleState(TypedDict):
|
class BottleState(TypedDict):
|
||||||
diameter: float
|
diameter: float
|
||||||
height: float
|
height: float
|
||||||
@@ -913,96 +1014,27 @@ class Bottle(Resource):
|
|||||||
data.update(self._unilabos_state) # Container自身的信息,云端物料将保存这一data,本地也通过这里的data进行读写,当前类用来表示这个物料的长宽高大小的属性,而data(state用来表示物料的内容,细节等)
|
data.update(self._unilabos_state) # Container自身的信息,云端物料将保存这一data,本地也通过这里的data进行读写,当前类用来表示这个物料的长宽高大小的属性,而data(state用来表示物料的内容,细节等)
|
||||||
return data
|
return data
|
||||||
|
|
||||||
class ClipMagazine_four(ItemizedResource[ClipMagazineHole]):
|
|
||||||
"""子弹夹类 - 有4个洞位,每个洞位放多个极片"""
|
|
||||||
children: List[ClipMagazineHole]
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
name: str,
|
|
||||||
size_x: float,
|
|
||||||
size_y: float,
|
|
||||||
size_z: float,
|
|
||||||
hole_diameter: float = 14.0,
|
|
||||||
hole_depth: float = 10.0,
|
|
||||||
hole_spacing: float = 25.0,
|
|
||||||
max_sheets_per_hole: int = 100,
|
|
||||||
category: str = "clip_magazine_four",
|
|
||||||
model: Optional[str] = None,
|
|
||||||
):
|
|
||||||
"""初始化子弹夹
|
|
||||||
|
|
||||||
Args:
|
|
||||||
name: 子弹夹名称
|
|
||||||
size_x: 长度 (mm)
|
|
||||||
size_y: 宽度 (mm)
|
|
||||||
size_z: 高度 (mm)
|
|
||||||
hole_diameter: 洞直径 (mm)
|
|
||||||
hole_depth: 洞深度 (mm)
|
|
||||||
hole_spacing: 洞位间距 (mm)
|
|
||||||
max_sheets_per_hole: 每个洞位最大极片数量
|
|
||||||
category: 类别
|
|
||||||
model: 型号
|
|
||||||
"""
|
|
||||||
# 创建4个洞位,排成2x2布局
|
|
||||||
holes = create_ordered_items_2d(
|
|
||||||
klass=ClipMagazineHole,
|
|
||||||
num_items_x=2,
|
|
||||||
num_items_y=2,
|
|
||||||
dx=(size_x - 2 * hole_spacing) / 2, # 居中
|
|
||||||
dy=(size_y - hole_spacing) / 2, # 居中
|
|
||||||
dz=size_z - 0,
|
|
||||||
item_dx=hole_spacing,
|
|
||||||
item_dy=hole_spacing,
|
|
||||||
diameter=hole_diameter,
|
|
||||||
depth=hole_depth,
|
|
||||||
)
|
|
||||||
|
|
||||||
super().__init__(
|
|
||||||
name=name,
|
|
||||||
size_x=size_x,
|
|
||||||
size_y=size_y,
|
|
||||||
size_z=size_z,
|
|
||||||
ordered_items=holes,
|
|
||||||
category=category,
|
|
||||||
model=model,
|
|
||||||
)
|
|
||||||
|
|
||||||
# 保存洞位的直径和深度
|
|
||||||
self.hole_diameter = hole_diameter
|
|
||||||
self.hole_depth = hole_depth
|
|
||||||
self.max_sheets_per_hole = max_sheets_per_hole
|
|
||||||
|
|
||||||
def serialize(self) -> dict:
|
|
||||||
return {
|
|
||||||
**super().serialize(),
|
|
||||||
"hole_diameter": self.hole_diameter,
|
|
||||||
"hole_depth": self.hole_depth,
|
|
||||||
"max_sheets_per_hole": self.max_sheets_per_hole,
|
|
||||||
}
|
|
||||||
|
|
||||||
class CoincellDeck(Deck):
|
class CoincellDeck(Deck):
|
||||||
"""纽扣电池组装工作站台面类"""
|
"""纽扣电池组装工作站台面类"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
name: str = "coin_cell_deck",
|
name: str = "coin_cell_deck",
|
||||||
size_x: float = 3650.0, # 1m
|
size_x: float = 1620.0, # 3.66m
|
||||||
size_y: float = 1550.0, # 1m
|
size_y: float = 1270.0, # 1.23m
|
||||||
size_z: float = 2100.0, # 0.9m
|
size_z: float = 500.0,
|
||||||
origin: Coordinate = Coordinate(-4000, 2000, 0),
|
origin: Coordinate = Coordinate(0, 0, 0),
|
||||||
category: str = "coin_cell_deck",
|
category: str = "coin_cell_deck",
|
||||||
setup: bool = False, # 是否自动执行 setup
|
|
||||||
):
|
):
|
||||||
"""初始化纽扣电池组装工作站台面
|
"""初始化纽扣电池组装工作站台面
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
name: 台面名称
|
name: 台面名称
|
||||||
size_x: 长度 (mm) - 1m
|
size_x: 长度 (mm) - 3.66m
|
||||||
size_y: 宽度 (mm) - 1m
|
size_y: 宽度 (mm) - 1.23m
|
||||||
size_z: 高度 (mm) - 0.9m
|
size_z: 高度 (mm)
|
||||||
origin: 原点坐标
|
origin: 原点坐标
|
||||||
category: 类别
|
category: 类别
|
||||||
setup: 是否自动执行 setup 配置标准布局
|
|
||||||
"""
|
"""
|
||||||
super().__init__(
|
super().__init__(
|
||||||
name=name,
|
name=name,
|
||||||
@@ -1012,154 +1044,246 @@ class CoincellDeck(Deck):
|
|||||||
origin=origin,
|
origin=origin,
|
||||||
category=category,
|
category=category,
|
||||||
)
|
)
|
||||||
if setup:
|
|
||||||
self.setup()
|
|
||||||
|
|
||||||
def setup(self) -> None:
|
#if __name__ == "__main__":
|
||||||
"""设置工作站的标准布局 - 包含子弹夹、料盘、瓶架等完整配置"""
|
# # 转移极片的测试代码
|
||||||
# ====================================== 子弹夹 ============================================
|
# deck = CoincellDeck("coin_cell_deck")
|
||||||
zip_dan_jia = ClipMagazine_four("zi_dan_jia", 80, 80, 10)
|
# ban_cao_wei = PlateSlot("ban_cao_wei", max_plates=8)
|
||||||
self.assign_child_resource(zip_dan_jia, Coordinate(x=1400, y=50, z=0))
|
# deck.assign_child_resource(ban_cao_wei, Coordinate(x=0, y=0, z=0))
|
||||||
zip_dan_jia2 = ClipMagazine_four("zi_dan_jia2", 80, 80, 10)
|
#
|
||||||
self.assign_child_resource(zip_dan_jia2, Coordinate(x=1600, y=200, z=0))
|
# plate_1 = MaterialPlate("plate_1", 1,1,1, fill=True)
|
||||||
zip_dan_jia3 = ClipMagazine("zi_dan_jia3", 80, 80, 10)
|
# for i, hole in enumerate(plate_1.children):
|
||||||
self.assign_child_resource(zip_dan_jia3, Coordinate(x=1500, y=200, z=0))
|
# sheet = ElectrodeSheet(f"hole_{i}_sheet_1")
|
||||||
zip_dan_jia4 = ClipMagazine("zi_dan_jia4", 80, 80, 10)
|
# sheet._unilabos_state = {
|
||||||
self.assign_child_resource(zip_dan_jia4, Coordinate(x=1500, y=300, z=0))
|
# "diameter": 14,
|
||||||
zip_dan_jia5 = ClipMagazine("zi_dan_jia5", 80, 80, 10)
|
# "info": "NMC",
|
||||||
self.assign_child_resource(zip_dan_jia5, Coordinate(x=1600, y=300, z=0))
|
# "mass": 5.0,
|
||||||
zip_dan_jia6 = ClipMagazine("zi_dan_jia6", 80, 80, 10)
|
# "material_type": "positive_electrode",
|
||||||
self.assign_child_resource(zip_dan_jia6, Coordinate(x=1530, y=500, z=0))
|
# "thickness": 0.1
|
||||||
zip_dan_jia7 = ClipMagazine("zi_dan_jia7", 80, 80, 10)
|
# }
|
||||||
self.assign_child_resource(zip_dan_jia7, Coordinate(x=1180, y=400, z=0))
|
# hole._unilabos_state = {
|
||||||
zip_dan_jia8 = ClipMagazine("zi_dan_jia8", 80, 80, 10)
|
# "depth": 1.0,
|
||||||
self.assign_child_resource(zip_dan_jia8, Coordinate(x=1280, y=400, z=0))
|
# "diameter": 14,
|
||||||
|
# "info": "",
|
||||||
# 为子弹夹添加极片
|
# "max_sheets": 1
|
||||||
for i in range(4):
|
# }
|
||||||
jipian = ElectrodeSheet(name=f"zi_dan_jia_jipian_{i}", size_x=12, size_y=12, size_z=0.1)
|
# hole.assign_child_resource(sheet, Coordinate.zero())
|
||||||
zip_dan_jia2.children[i].assign_child_resource(jipian, location=None)
|
# plate_1._unilabos_state = {
|
||||||
for i in range(4):
|
# "hole_spacing_x": 20.0,
|
||||||
jipian2 = ElectrodeSheet(name=f"zi_dan_jia2_jipian_{i}", size_x=12, size_y=12, size_z=0.1)
|
# "hole_spacing_y": 20.0,
|
||||||
zip_dan_jia.children[i].assign_child_resource(jipian2, location=None)
|
# "hole_diameter": 5,
|
||||||
for i in range(6):
|
# "info": "这是第一块料板"
|
||||||
jipian3 = ElectrodeSheet(name=f"zi_dan_jia3_jipian_{i}", size_x=12, size_y=12, size_z=0.1)
|
# }
|
||||||
zip_dan_jia3.children[i].assign_child_resource(jipian3, location=None)
|
# plate_1.update_locations()
|
||||||
for i in range(6):
|
# ban_cao_wei.assign_child_resource(plate_1, Coordinate.zero())
|
||||||
jipian4 = ElectrodeSheet(name=f"zi_dan_jia4_jipian_{i}", size_x=12, size_y=12, size_z=0.1)
|
# # zi_dan_jia = ClipMagazine("zi_dan_jia", 1, 1, 1)
|
||||||
zip_dan_jia4.children[i].assign_child_resource(jipian4, location=None)
|
# # deck.assign_child_resource(ban_cao_wei, Coordinate(x=200, y=200, z=0))
|
||||||
for i in range(6):
|
#
|
||||||
jipian5 = ElectrodeSheet(name=f"zi_dan_jia5_jipian_{i}", size_x=12, size_y=12, size_z=0.1)
|
# from unilabos.resources.graphio import *
|
||||||
zip_dan_jia5.children[i].assign_child_resource(jipian5, location=None)
|
# A = tree_to_list([resource_plr_to_ulab(deck)])
|
||||||
for i in range(6):
|
# with open("test.json", "w") as f:
|
||||||
jipian6 = ElectrodeSheet(name=f"zi_dan_jia6_jipian_{i}", size_x=12, size_y=12, size_z=0.1)
|
# json.dump(A, f)
|
||||||
zip_dan_jia6.children[i].assign_child_resource(jipian6, location=None)
|
#
|
||||||
for i in range(6):
|
#
|
||||||
jipian7 = ElectrodeSheet(name=f"zi_dan_jia7_jipian_{i}", size_x=12, size_y=12, size_z=0.1)
|
#def get_plate_with_14mm_hole(name=""):
|
||||||
zip_dan_jia7.children[i].assign_child_resource(jipian7, location=None)
|
# plate = MaterialPlate(name=name)
|
||||||
for i in range(6):
|
# for i in range(4):
|
||||||
jipian8 = ElectrodeSheet(name=f"zi_dan_jia8_jipian_{i}", size_x=12, size_y=12, size_z=0.1)
|
# for j in range(4):
|
||||||
zip_dan_jia8.children[i].assign_child_resource(jipian8, location=None)
|
# hole = MaterialHole(f"{i+1}x{j+1}")
|
||||||
|
# hole._unilabos_state["diameter"] = 14
|
||||||
# ====================================== 物料板 ============================================
|
# hole._unilabos_state["max_sheets"] = 1
|
||||||
# 创建6个4*4的物料板
|
# plate.assign_child_resource(hole)
|
||||||
liaopan1 = MaterialPlate(name="liaopan1", size_x=120, size_y=100, size_z=10.0, fill=True)
|
# return plate
|
||||||
self.assign_child_resource(liaopan1, Coordinate(x=1010, y=50, z=0))
|
|
||||||
for i in range(16):
|
|
||||||
jipian_1 = ElectrodeSheet(name=f"{liaopan1.name}_jipian_{i}", size_x=12, size_y=12, size_z=0.1)
|
|
||||||
liaopan1.children[i].assign_child_resource(jipian_1, location=None)
|
|
||||||
|
|
||||||
liaopan2 = MaterialPlate(name="liaopan2", size_x=120, size_y=100, size_z=10.0, fill=True)
|
def create_a_liaopan():
|
||||||
self.assign_child_resource(liaopan2, Coordinate(x=1130, y=50, z=0))
|
liaopan = MaterialPlate(name="liaopan", size_x=120.8, size_y=120.5, size_z=10.0, fill=True)
|
||||||
|
for i in range(16):
|
||||||
|
jipian = ElectrodeSheet(name=f"jipian_{i}", size_x= 12, size_y=12, size_z=0.1)
|
||||||
|
liaopan1.children[i].assign_child_resource(jipian, location=None)
|
||||||
|
return liaopan
|
||||||
|
|
||||||
liaopan3 = MaterialPlate(name="liaopan3", size_x=120, size_y=100, size_z=10.0, fill=True)
|
def create_a_coin_cell_deck():
|
||||||
self.assign_child_resource(liaopan3, Coordinate(x=1250, y=50, z=0))
|
deck = Deck(size_x=1200,
|
||||||
|
size_y=800,
|
||||||
|
size_z=900)
|
||||||
|
|
||||||
liaopan4 = MaterialPlate(name="liaopan4", size_x=120, size_y=100, size_z=10.0, fill=True)
|
#liaopan = TipBox64(name="liaopan")
|
||||||
self.assign_child_resource(liaopan4, Coordinate(x=1010, y=150, z=0))
|
|
||||||
for i in range(16):
|
|
||||||
jipian_4 = ElectrodeSheet(name=f"{liaopan4.name}_jipian_{i}", size_x=12, size_y=12, size_z=0.1)
|
|
||||||
liaopan4.children[i].assign_child_resource(jipian_4, location=None)
|
|
||||||
|
|
||||||
liaopan5 = MaterialPlate(name="liaopan5", size_x=120, size_y=100, size_z=10.0, fill=True)
|
|
||||||
self.assign_child_resource(liaopan5, Coordinate(x=1130, y=150, z=0))
|
|
||||||
|
|
||||||
liaopan6 = MaterialPlate(name="liaopan6", size_x=120, size_y=100, size_z=10.0, fill=True)
|
|
||||||
self.assign_child_resource(liaopan6, Coordinate(x=1250, y=150, z=0))
|
|
||||||
|
|
||||||
# ====================================== 瓶架、移液枪 ============================================
|
|
||||||
# 在台面上放置 3x4 瓶架、6x2 瓶架 与 64孔移液枪头盒
|
|
||||||
bottle_rack_3x4 = BottleRack(
|
|
||||||
name="bottle_rack_3x4",
|
|
||||||
size_x=210.0,
|
|
||||||
size_y=140.0,
|
|
||||||
size_z=100.0,
|
|
||||||
num_items_x=3,
|
|
||||||
num_items_y=4,
|
|
||||||
position_spacing=35.0,
|
|
||||||
orientation="vertical",
|
|
||||||
)
|
|
||||||
self.assign_child_resource(bottle_rack_3x4, Coordinate(x=100, y=200, z=0))
|
|
||||||
|
|
||||||
bottle_rack_6x2 = BottleRack(
|
#创建一个4*4的物料板
|
||||||
name="bottle_rack_6x2",
|
liaopan1 = MaterialPlate(name="liaopan1", size_x=120.8, size_y=120.5, size_z=10.0, fill=True)
|
||||||
size_x=120.0,
|
#把物料板放到桌子上
|
||||||
size_y=250.0,
|
deck.assign_child_resource(liaopan1, Coordinate(x=0, y=0, z=0))
|
||||||
size_z=100.0,
|
#创建一个极片
|
||||||
num_items_x=6,
|
for i in range(16):
|
||||||
num_items_y=2,
|
jipian = ElectrodeSheet(name=f"jipian_{i}", size_x= 12, size_y=12, size_z=0.1)
|
||||||
position_spacing=35.0,
|
liaopan1.children[i].assign_child_resource(jipian, location=None)
|
||||||
orientation="vertical",
|
#创建一个4*4的物料板
|
||||||
)
|
liaopan2 = MaterialPlate(name="liaopan2", size_x=120.8, size_y=120.5, size_z=10.0, fill=True)
|
||||||
self.assign_child_resource(bottle_rack_6x2, Coordinate(x=300, y=300, z=0))
|
#把物料板放到桌子上
|
||||||
|
deck.assign_child_resource(liaopan2, Coordinate(x=500, y=0, z=0))
|
||||||
|
|
||||||
bottle_rack_6x2_2 = BottleRack(
|
#创建一个4*4的物料板
|
||||||
name="bottle_rack_6x2_2",
|
liaopan3 = MaterialPlate(name="liaopan3", size_x=120.8, size_y=120.5, size_z=10.0, fill=True)
|
||||||
size_x=120.0,
|
#把物料板放到桌子上
|
||||||
size_y=250.0,
|
deck.assign_child_resource(liaopan3, Coordinate(x=1000, y=0, z=0))
|
||||||
size_z=100.0,
|
|
||||||
num_items_x=6,
|
|
||||||
num_items_y=2,
|
|
||||||
position_spacing=35.0,
|
|
||||||
orientation="vertical",
|
|
||||||
)
|
|
||||||
self.assign_child_resource(bottle_rack_6x2_2, Coordinate(x=430, y=300, z=0))
|
|
||||||
|
|
||||||
# 将 ElectrodeSheet 放满 3x4 与 6x2 的所有孔位
|
print(deck)
|
||||||
for idx in range(bottle_rack_3x4.num_items_x * bottle_rack_3x4.num_items_y):
|
|
||||||
sheet = ElectrodeSheet(name=f"sheet_3x4_{idx}", size_x=12, size_y=12, size_z=0.1)
|
|
||||||
bottle_rack_3x4.assign_child_resource(sheet, index=idx)
|
|
||||||
|
|
||||||
for idx in range(bottle_rack_6x2.num_items_x * bottle_rack_6x2.num_items_y):
|
|
||||||
sheet = ElectrodeSheet(name=f"sheet_6x2_{idx}", size_x=12, size_y=12, size_z=0.1)
|
|
||||||
bottle_rack_6x2.assign_child_resource(sheet, index=idx)
|
|
||||||
|
|
||||||
tip_box = TipBox64(name="tip_box_64")
|
|
||||||
self.assign_child_resource(tip_box, Coordinate(x=300, y=100, z=0))
|
|
||||||
|
|
||||||
waste_tip_box = WasteTipBox(name="waste_tip_box")
|
|
||||||
self.assign_child_resource(waste_tip_box, Coordinate(x=300, y=200, z=0))
|
|
||||||
|
|
||||||
print(self)
|
|
||||||
|
|
||||||
|
|
||||||
def create_coin_cell_deck(name: str = "coin_cell_deck", size_x: float = 1000.0, size_y: float = 1000.0, size_z: float = 900.0) -> CoincellDeck:
|
|
||||||
"""创建并配置标准的纽扣电池组装工作站台面
|
|
||||||
|
|
||||||
Args:
|
|
||||||
name: 台面名称
|
|
||||||
size_x: 长度 (mm)
|
|
||||||
size_y: 宽度 (mm)
|
|
||||||
size_z: 高度 (mm)
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
已配置好的 CoincellDeck 对象
|
|
||||||
"""
|
|
||||||
# 创建 CoincellDeck 实例并自动执行 setup 配置
|
|
||||||
deck = CoincellDeck(name=name, size_x=size_x, size_y=size_y, size_z=size_z, setup=True)
|
|
||||||
return deck
|
return deck
|
||||||
|
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
deck = create_coin_cell_deck()
|
electrode1 = BatteryPressSlot()
|
||||||
print(deck)
|
#print(electrode1.get_size_x())
|
||||||
|
#print(electrode1.get_size_y())
|
||||||
|
#print(electrode1.get_size_z())
|
||||||
|
#jipian = ElectrodeSheet()
|
||||||
|
#jipian._unilabos_state["diameter"] = 18
|
||||||
|
#print(jipian.serialize())
|
||||||
|
#print(jipian.serialize_state())
|
||||||
|
|
||||||
|
deck = CoincellDeck()
|
||||||
|
"""======================================子弹夹============================================"""
|
||||||
|
zip_dan_jia = ClipMagazine_four("zi_dan_jia", 80, 80, 10)
|
||||||
|
deck.assign_child_resource(zip_dan_jia, Coordinate(x=1400, y=50, z=0))
|
||||||
|
zip_dan_jia2 = ClipMagazine_four("zi_dan_jia2", 80, 80, 10)
|
||||||
|
deck.assign_child_resource(zip_dan_jia2, Coordinate(x=1600, y=200, z=0))
|
||||||
|
zip_dan_jia3 = ClipMagazine("zi_dan_jia3", 80, 80, 10)
|
||||||
|
deck.assign_child_resource(zip_dan_jia3, Coordinate(x=1500, y=200, z=0))
|
||||||
|
zip_dan_jia4 = ClipMagazine("zi_dan_jia4", 80, 80, 10)
|
||||||
|
deck.assign_child_resource(zip_dan_jia4, Coordinate(x=1500, y=300, z=0))
|
||||||
|
zip_dan_jia5 = ClipMagazine("zi_dan_jia5", 80, 80, 10)
|
||||||
|
deck.assign_child_resource(zip_dan_jia5, Coordinate(x=1600, y=300, z=0))
|
||||||
|
zip_dan_jia6 = ClipMagazine("zi_dan_jia6", 80, 80, 10)
|
||||||
|
deck.assign_child_resource(zip_dan_jia6, Coordinate(x=1530, y=500, z=0))
|
||||||
|
zip_dan_jia7 = ClipMagazine("zi_dan_jia7", 80, 80, 10)
|
||||||
|
deck.assign_child_resource(zip_dan_jia7, Coordinate(x=1180, y=400, z=0))
|
||||||
|
zip_dan_jia8 = ClipMagazine("zi_dan_jia8", 80, 80, 10)
|
||||||
|
deck.assign_child_resource(zip_dan_jia8, Coordinate(x=1280, y=400, z=0))
|
||||||
|
for i in range(4):
|
||||||
|
jipian = ElectrodeSheet(name=f"zi_dan_jia_jipian_{i}", size_x=12, size_y=12, size_z=0.1)
|
||||||
|
zip_dan_jia2.children[i].assign_child_resource(jipian, location=None)
|
||||||
|
for i in range(4):
|
||||||
|
jipian2 = ElectrodeSheet(name=f"zi_dan_jia2_jipian_{i}", size_x=12, size_y=12, size_z=0.1)
|
||||||
|
zip_dan_jia.children[i].assign_child_resource(jipian2, location=None)
|
||||||
|
for i in range(6):
|
||||||
|
jipian3 = ElectrodeSheet(name=f"zi_dan_jia3_jipian_{i}", size_x=12, size_y=12, size_z=0.1)
|
||||||
|
zip_dan_jia3.children[i].assign_child_resource(jipian3, location=None)
|
||||||
|
for i in range(6):
|
||||||
|
jipian4 = ElectrodeSheet(name=f"zi_dan_jia4_jipian_{i}", size_x=12, size_y=12, size_z=0.1)
|
||||||
|
zip_dan_jia4.children[i].assign_child_resource(jipian4, location=None)
|
||||||
|
for i in range(6):
|
||||||
|
jipian5 = ElectrodeSheet(name=f"zi_dan_jia5_jipian_{i}", size_x=12, size_y=12, size_z=0.1)
|
||||||
|
zip_dan_jia5.children[i].assign_child_resource(jipian5, location=None)
|
||||||
|
for i in range(6):
|
||||||
|
jipian6 = ElectrodeSheet(name=f"zi_dan_jia6_jipian_{i}", size_x=12, size_y=12, size_z=0.1)
|
||||||
|
zip_dan_jia6.children[i].assign_child_resource(jipian6, location=None)
|
||||||
|
for i in range(6):
|
||||||
|
jipian7 = ElectrodeSheet(name=f"zi_dan_jia7_jipian_{i}", size_x=12, size_y=12, size_z=0.1)
|
||||||
|
zip_dan_jia7.children[i].assign_child_resource(jipian7, location=None)
|
||||||
|
for i in range(6):
|
||||||
|
jipian8 = ElectrodeSheet(name=f"zi_dan_jia8_jipian_{i}", size_x=12, size_y=12, size_z=0.1)
|
||||||
|
zip_dan_jia8.children[i].assign_child_resource(jipian8, location=None)
|
||||||
|
"""======================================子弹夹============================================"""
|
||||||
|
#liaopan = TipBox64(name="liaopan")
|
||||||
|
"""======================================物料板============================================"""
|
||||||
|
#创建一个4*4的物料板
|
||||||
|
liaopan1 = MaterialPlate(name="liaopan1", size_x=120, size_y=100, size_z=10.0, fill=True)
|
||||||
|
deck.assign_child_resource(liaopan1, Coordinate(x=1010, y=50, z=0))
|
||||||
|
for i in range(16):
|
||||||
|
jipian_1 = ElectrodeSheet(name=f"{liaopan1.name}_jipian_{i}", size_x=12, size_y=12, size_z=0.1)
|
||||||
|
liaopan1.children[i].assign_child_resource(jipian_1, location=None)
|
||||||
|
|
||||||
|
liaopan2 = MaterialPlate(name="liaopan2", size_x=120, size_y=100, size_z=10.0, fill=True)
|
||||||
|
deck.assign_child_resource(liaopan2, Coordinate(x=1130, y=50, z=0))
|
||||||
|
|
||||||
|
liaopan3 = MaterialPlate(name="liaopan3", size_x=120, size_y=100, size_z=10.0, fill=True)
|
||||||
|
deck.assign_child_resource(liaopan3, Coordinate(x=1250, y=50, z=0))
|
||||||
|
|
||||||
|
liaopan4 = MaterialPlate(name="liaopan4", size_x=120, size_y=100, size_z=10.0, fill=True)
|
||||||
|
deck.assign_child_resource(liaopan4, Coordinate(x=1010, y=150, z=0))
|
||||||
|
for i in range(16):
|
||||||
|
jipian_4 = ElectrodeSheet(name=f"{liaopan4.name}_jipian_{i}", size_x=12, size_y=12, size_z=0.1)
|
||||||
|
liaopan4.children[i].assign_child_resource(jipian_4, location=None)
|
||||||
|
liaopan5 = MaterialPlate(name="liaopan5", size_x=120, size_y=100, size_z=10.0, fill=True)
|
||||||
|
deck.assign_child_resource(liaopan5, Coordinate(x=1130, y=150, z=0))
|
||||||
|
liaopan6 = MaterialPlate(name="liaopan6", size_x=120, size_y=100, size_z=10.0, fill=True)
|
||||||
|
deck.assign_child_resource(liaopan6, Coordinate(x=1250, y=150, z=0))
|
||||||
|
#liaopan.children[3].assign_child_resource(jipian, location=None)
|
||||||
|
"""======================================物料板============================================"""
|
||||||
|
"""======================================瓶架,移液枪============================================"""
|
||||||
|
# 在台面上放置 3x4 瓶架、6x2 瓶架 与 64孔移液枪头盒
|
||||||
|
bottle_rack_3x4 = BottleRack(
|
||||||
|
name="bottle_rack_3x4",
|
||||||
|
size_x=210.0,
|
||||||
|
size_y=140.0,
|
||||||
|
size_z=100.0,
|
||||||
|
num_items_x=3,
|
||||||
|
num_items_y=4,
|
||||||
|
position_spacing=35.0,
|
||||||
|
orientation="vertical",
|
||||||
|
)
|
||||||
|
deck.assign_child_resource(bottle_rack_3x4, Coordinate(x=100, y=200, z=0))
|
||||||
|
|
||||||
|
bottle_rack_6x2 = BottleRack(
|
||||||
|
name="bottle_rack_6x2",
|
||||||
|
size_x=120.0,
|
||||||
|
size_y=250.0,
|
||||||
|
size_z=100.0,
|
||||||
|
num_items_x=6,
|
||||||
|
num_items_y=2,
|
||||||
|
position_spacing=35.0,
|
||||||
|
orientation="vertical",
|
||||||
|
)
|
||||||
|
deck.assign_child_resource(bottle_rack_6x2, Coordinate(x=300, y=300, z=0))
|
||||||
|
|
||||||
|
bottle_rack_6x2_2 = BottleRack(
|
||||||
|
name="bottle_rack_6x2_2",
|
||||||
|
size_x=120.0,
|
||||||
|
size_y=250.0,
|
||||||
|
size_z=100.0,
|
||||||
|
num_items_x=6,
|
||||||
|
num_items_y=2,
|
||||||
|
position_spacing=35.0,
|
||||||
|
orientation="vertical",
|
||||||
|
)
|
||||||
|
deck.assign_child_resource(bottle_rack_6x2_2, Coordinate(x=430, y=300, z=0))
|
||||||
|
|
||||||
|
|
||||||
|
# 将 ElectrodeSheet 放满 3x4 与 6x2 的所有孔位
|
||||||
|
for idx in range(bottle_rack_3x4.num_items_x * bottle_rack_3x4.num_items_y):
|
||||||
|
sheet = ElectrodeSheet(name=f"sheet_3x4_{idx}", size_x=12, size_y=12, size_z=0.1)
|
||||||
|
bottle_rack_3x4.assign_child_resource(sheet, index=idx)
|
||||||
|
|
||||||
|
for idx in range(bottle_rack_6x2.num_items_x * bottle_rack_6x2.num_items_y):
|
||||||
|
sheet = ElectrodeSheet(name=f"sheet_6x2_{idx}", size_x=12, size_y=12, size_z=0.1)
|
||||||
|
bottle_rack_6x2.assign_child_resource(sheet, index=idx)
|
||||||
|
|
||||||
|
tip_box = TipBox64(name="tip_box_64")
|
||||||
|
deck.assign_child_resource(tip_box, Coordinate(x=300, y=100, z=0))
|
||||||
|
|
||||||
|
waste_tip_box = WasteTipBox(name="waste_tip_box")
|
||||||
|
deck.assign_child_resource(waste_tip_box, Coordinate(x=300, y=200, z=0))
|
||||||
|
"""======================================瓶架,移液枪============================================"""
|
||||||
|
print(deck)
|
||||||
|
|
||||||
|
|
||||||
|
from unilabos.resources.graphio import convert_resources_from_type
|
||||||
|
from unilabos.config.config import BasicConfig
|
||||||
|
BasicConfig.ak = "56bbed5b-6e30-438c-b06d-f69eaa63bb45"
|
||||||
|
BasicConfig.sk = "238222fe-0bf7-4350-a426-e5ced8011dcf"
|
||||||
|
from unilabos.app.web.client import http_client
|
||||||
|
|
||||||
|
resources = convert_resources_from_type([deck], [Resource])
|
||||||
|
|
||||||
|
# 检查序列化后的资源
|
||||||
|
|
||||||
|
json.dump({"nodes": resources, "links": []}, open("button_battery_decks_unilab.json", "w"), indent=2)
|
||||||
|
|
||||||
|
|
||||||
|
#print(resources)
|
||||||
|
http_client.remote_addr = "https://uni-lab.test.bohrium.com/api/v1"
|
||||||
|
|
||||||
|
http_client.resource_add(resources)
|
||||||
@@ -1,133 +1,33 @@
|
|||||||
|
|
||||||
import csv
|
import csv
|
||||||
import inspect
|
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
import types
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Any, Dict, Optional
|
from typing import Any, Dict, Optional
|
||||||
from functools import wraps
|
from pylabrobot.resources import Resource as PLRResource
|
||||||
from pylabrobot.resources import Deck, Resource as PLRResource
|
|
||||||
from unilabos_msgs.msg import Resource
|
from unilabos_msgs.msg import Resource
|
||||||
from unilabos.device_comms.modbus_plc.client import ModbusTcpClient
|
from unilabos.device_comms.modbus_plc.client import ModbusTcpClient
|
||||||
|
from unilabos.devices.workstation.coin_cell_assembly.button_battery_station import MaterialHole, MaterialPlate
|
||||||
from unilabos.devices.workstation.workstation_base import WorkstationBase
|
from unilabos.devices.workstation.workstation_base import WorkstationBase
|
||||||
from unilabos.device_comms.modbus_plc.client import TCPClient, ModbusNode, PLCWorkflow, ModbusWorkflow, WorkflowAction, BaseClient
|
from unilabos.device_comms.modbus_plc.client import TCPClient, ModbusNode, PLCWorkflow, ModbusWorkflow, WorkflowAction, BaseClient
|
||||||
from unilabos.device_comms.modbus_plc.modbus import DeviceType, Base as ModbusNodeBase, DataType, WorderOrder
|
from unilabos.device_comms.modbus_plc.modbus import DeviceType, Base as ModbusNodeBase, DataType, WorderOrder
|
||||||
from unilabos.devices.workstation.coin_cell_assembly.YB_YH_materials import *
|
from unilabos.devices.workstation.coin_cell_assembly.button_battery_station import *
|
||||||
from unilabos.ros.nodes.base_device_node import ROS2DeviceNode, BaseROS2DeviceNode
|
from unilabos.ros.nodes.base_device_node import ROS2DeviceNode, BaseROS2DeviceNode
|
||||||
from unilabos.ros.nodes.presets.workstation import ROS2WorkstationNode
|
from unilabos.ros.nodes.presets.workstation import ROS2WorkstationNode
|
||||||
from unilabos.devices.workstation.coin_cell_assembly.YB_YH_materials import CoincellDeck
|
|
||||||
from unilabos.resources.graphio import convert_resources_to_type
|
|
||||||
from unilabos.utils.log import logger
|
|
||||||
|
|
||||||
|
|
||||||
def _ensure_modbus_slave_kw_alias(modbus_client):
|
|
||||||
if modbus_client is None:
|
|
||||||
return
|
|
||||||
|
|
||||||
method_names = [
|
|
||||||
"read_coils",
|
|
||||||
"write_coils",
|
|
||||||
"write_coil",
|
|
||||||
"read_discrete_inputs",
|
|
||||||
"read_holding_registers",
|
|
||||||
"write_register",
|
|
||||||
"write_registers",
|
|
||||||
]
|
|
||||||
|
|
||||||
def _wrap(func):
|
|
||||||
signature = inspect.signature(func)
|
|
||||||
has_var_kwargs = any(param.kind == param.VAR_KEYWORD for param in signature.parameters.values())
|
|
||||||
accepts_unit = has_var_kwargs or "unit" in signature.parameters
|
|
||||||
accepts_slave = has_var_kwargs or "slave" in signature.parameters
|
|
||||||
|
|
||||||
@wraps(func)
|
|
||||||
def _wrapped(self, *args, **kwargs):
|
|
||||||
if "slave" in kwargs and not accepts_slave:
|
|
||||||
slave_value = kwargs.pop("slave")
|
|
||||||
if accepts_unit and "unit" not in kwargs:
|
|
||||||
kwargs["unit"] = slave_value
|
|
||||||
if "unit" in kwargs and not accepts_unit:
|
|
||||||
unit_value = kwargs.pop("unit")
|
|
||||||
if accepts_slave and "slave" not in kwargs:
|
|
||||||
kwargs["slave"] = unit_value
|
|
||||||
return func(self, *args, **kwargs)
|
|
||||||
|
|
||||||
_wrapped._has_slave_alias = True
|
|
||||||
return _wrapped
|
|
||||||
|
|
||||||
for name in method_names:
|
|
||||||
if not hasattr(modbus_client, name):
|
|
||||||
continue
|
|
||||||
bound_method = getattr(modbus_client, name)
|
|
||||||
func = getattr(bound_method, "__func__", None)
|
|
||||||
if func is None:
|
|
||||||
continue
|
|
||||||
if getattr(func, "_has_slave_alias", False):
|
|
||||||
continue
|
|
||||||
wrapped = _wrap(func)
|
|
||||||
setattr(modbus_client, name, types.MethodType(wrapped, modbus_client))
|
|
||||||
|
|
||||||
|
|
||||||
def _coerce_deck_input(deck: Any) -> Optional[Deck]:
|
|
||||||
if deck is None:
|
|
||||||
return None
|
|
||||||
|
|
||||||
if isinstance(deck, Deck):
|
|
||||||
return deck
|
|
||||||
|
|
||||||
if isinstance(deck, PLRResource):
|
|
||||||
return deck if isinstance(deck, Deck) else None
|
|
||||||
|
|
||||||
candidates = None
|
|
||||||
if isinstance(deck, dict):
|
|
||||||
if "nodes" in deck and isinstance(deck["nodes"], list):
|
|
||||||
candidates = deck["nodes"]
|
|
||||||
else:
|
|
||||||
candidates = [deck]
|
|
||||||
elif isinstance(deck, list):
|
|
||||||
candidates = deck
|
|
||||||
|
|
||||||
if candidates is None:
|
|
||||||
return None
|
|
||||||
|
|
||||||
try:
|
|
||||||
converted = convert_resources_to_type(resources_list=candidates, resource_type=Deck)
|
|
||||||
if isinstance(converted, Deck):
|
|
||||||
return converted
|
|
||||||
if isinstance(converted, list):
|
|
||||||
for item in converted:
|
|
||||||
if isinstance(item, Deck):
|
|
||||||
return item
|
|
||||||
except Exception as exc:
|
|
||||||
logger.warning(f"deck 转换 Deck 失败: {exc}")
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
#构建物料系统
|
#构建物料系统
|
||||||
|
|
||||||
class CoinCellAssemblyWorkstation(WorkstationBase):
|
class CoinCellAssemblyWorkstation(WorkstationBase):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
deck: Deck=None,
|
deck: CoincellDeck,
|
||||||
address: str = "172.16.28.102",
|
address: str = "192.168.1.20",
|
||||||
port: str = "502",
|
port: str = "502",
|
||||||
debug_mode: bool = False,
|
debug_mode: bool = True,
|
||||||
*args,
|
*args,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
):
|
):
|
||||||
if deck is None and "deck" in kwargs:
|
|
||||||
deck = kwargs.pop("deck")
|
|
||||||
else:
|
|
||||||
kwargs.pop("deck", None)
|
|
||||||
|
|
||||||
normalized_deck = _coerce_deck_input(deck)
|
|
||||||
|
|
||||||
if deck is None and isinstance(normalized_deck, Deck):
|
|
||||||
deck = normalized_deck
|
|
||||||
|
|
||||||
super().__init__(
|
super().__init__(
|
||||||
#桌子
|
#桌子
|
||||||
deck=deck,
|
deck=deck,
|
||||||
@@ -135,22 +35,10 @@ class CoinCellAssemblyWorkstation(WorkstationBase):
|
|||||||
**kwargs,
|
**kwargs,
|
||||||
)
|
)
|
||||||
self.debug_mode = debug_mode
|
self.debug_mode = debug_mode
|
||||||
|
self.deck = deck
|
||||||
# 如果没有传入 deck,则创建标准配置的 deck
|
|
||||||
if self.deck is None:
|
|
||||||
self.deck = CoincellDeck(size_x=1000, size_y=1000, size_z=900, origin=Coordinate(-800, 0, 0),setup=True)
|
|
||||||
else:
|
|
||||||
# 如果传入了 deck 但还没有 setup,可以选择是否 setup
|
|
||||||
if self.deck is not None and len(self.deck.children) == 0:
|
|
||||||
# deck 为空,执行 setup
|
|
||||||
self.deck.setup()
|
|
||||||
# 否则使用传入的 deck(可能已经配置好了)
|
|
||||||
self.deck = self.deck
|
|
||||||
|
|
||||||
""" 连接初始化 """
|
""" 连接初始化 """
|
||||||
modbus_client = TCPClient(addr=address, port=port)
|
modbus_client = TCPClient(addr=address, port=port)
|
||||||
logger.debug(f"创建 Modbus 客户端: {modbus_client}")
|
print("modbus_client", modbus_client)
|
||||||
_ensure_modbus_slave_kw_alias(modbus_client.client)
|
|
||||||
if not debug_mode:
|
if not debug_mode:
|
||||||
modbus_client.client.connect()
|
modbus_client.client.connect()
|
||||||
count = 100
|
count = 100
|
||||||
@@ -165,15 +53,16 @@ class CoinCellAssemblyWorkstation(WorkstationBase):
|
|||||||
print("测试模式,跳过连接")
|
print("测试模式,跳过连接")
|
||||||
|
|
||||||
""" 工站的配置 """
|
""" 工站的配置 """
|
||||||
self.nodes = BaseClient.load_csv(os.path.join(os.path.dirname(__file__), 'coin_cell_assembly_1105.csv'))
|
self.nodes = BaseClient.load_csv(os.path.join(os.path.dirname(__file__), 'coin_cell_assembly_a.csv'))
|
||||||
self.client = modbus_client.register_node_list(self.nodes)
|
self.client = modbus_client.register_node_list(self.nodes)
|
||||||
self.success = False
|
self.success = False
|
||||||
self.allow_data_read = False #允许读取函数运行标志位
|
self.allow_data_read = False #允许读取函数运行标志位
|
||||||
self.csv_export_thread = None
|
self.csv_export_thread = None
|
||||||
self.csv_export_running = False
|
self.csv_export_running = False
|
||||||
self.csv_export_file = None
|
self.csv_export_file = None
|
||||||
self.coin_num_N = 0 #已组装电池数量
|
|
||||||
#创建一个物料台面,包含两个极片板
|
#创建一个物料台面,包含两个极片板
|
||||||
|
#self.deck = create_a_coin_cell_deck()
|
||||||
|
|
||||||
#self._ros_node.update_resource(self.deck)
|
#self._ros_node.update_resource(self.deck)
|
||||||
|
|
||||||
#ROS2DeviceNode.run_async_func(self._ros_node.update_resource, True, **{
|
#ROS2DeviceNode.run_async_func(self._ros_node.update_resource, True, **{
|
||||||
@@ -602,11 +491,11 @@ class CoinCellAssemblyWorkstation(WorkstationBase):
|
|||||||
try:
|
try:
|
||||||
# 尝试不同的字节序读取
|
# 尝试不同的字节序读取
|
||||||
code_little, read_err = self.client.use_node('REG_DATA_COIN_CELL_CODE').read(10, word_order=WorderOrder.LITTLE)
|
code_little, read_err = self.client.use_node('REG_DATA_COIN_CELL_CODE').read(10, word_order=WorderOrder.LITTLE)
|
||||||
# logger.debug(f"读取电池二维码原始数据: {code_little}")
|
print(code_little)
|
||||||
clean_code = code_little[-8:][::-1]
|
clean_code = code_little[-8:][::-1]
|
||||||
return clean_code
|
return clean_code
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"读取电池二维码失败: {e}")
|
print(f"读取电池二维码失败: {e}")
|
||||||
return "N/A"
|
return "N/A"
|
||||||
|
|
||||||
|
|
||||||
@@ -615,11 +504,11 @@ class CoinCellAssemblyWorkstation(WorkstationBase):
|
|||||||
try:
|
try:
|
||||||
# 尝试不同的字节序读取
|
# 尝试不同的字节序读取
|
||||||
code_little, read_err = self.client.use_node('REG_DATA_ELECTROLYTE_CODE').read(10, word_order=WorderOrder.LITTLE)
|
code_little, read_err = self.client.use_node('REG_DATA_ELECTROLYTE_CODE').read(10, word_order=WorderOrder.LITTLE)
|
||||||
# logger.debug(f"读取电解液二维码原始数据: {code_little}")
|
print(code_little)
|
||||||
clean_code = code_little[-8:][::-1]
|
clean_code = code_little[-8:][::-1]
|
||||||
return clean_code
|
return clean_code
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"读取电解液二维码失败: {e}")
|
print(f"读取电解液二维码失败: {e}")
|
||||||
return "N/A"
|
return "N/A"
|
||||||
|
|
||||||
# ===================== 环境监控区 ======================
|
# ===================== 环境监控区 ======================
|
||||||
@@ -717,8 +606,7 @@ class CoinCellAssemblyWorkstation(WorkstationBase):
|
|||||||
print("waiting for start_cmd")
|
print("waiting for start_cmd")
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
|
|
||||||
def func_pack_send_bottle_num(self, bottle_num):
|
def func_pack_send_bottle_num(self, bottle_num: int):
|
||||||
bottle_num = int(bottle_num)
|
|
||||||
#发送电解液平台数
|
#发送电解液平台数
|
||||||
print("启动")
|
print("启动")
|
||||||
while (self._unilab_rece_electrolyte_bottle_num()) == False:
|
while (self._unilab_rece_electrolyte_bottle_num()) == False:
|
||||||
@@ -766,25 +654,16 @@ class CoinCellAssemblyWorkstation(WorkstationBase):
|
|||||||
# self.success = True
|
# self.success = True
|
||||||
# return self.success
|
# return self.success
|
||||||
|
|
||||||
def func_pack_send_msg_cmd(self, elec_use_num, elec_vol, assembly_type, assembly_pressure) -> bool:
|
def func_pack_send_msg_cmd(self, elec_use_num) -> bool:
|
||||||
"""UNILAB写参数"""
|
"""UNILAB写参数"""
|
||||||
while (self.request_rec_msg_status) == False:
|
while (self.request_rec_msg_status) == False:
|
||||||
print("wait for request_rec_msg_status to True")
|
print("wait for request_rec_msg_status to True")
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
self.success = False
|
self.success = False
|
||||||
#self._unilab_send_msg_electrolyte_num(elec_num)
|
#self._unilab_send_msg_electrolyte_num(elec_num)
|
||||||
#设置平行样数目
|
time.sleep(1)
|
||||||
self._unilab_send_msg_electrolyte_use_num(elec_use_num)
|
self._unilab_send_msg_electrolyte_use_num(elec_use_num)
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
#发送电解液加注量
|
|
||||||
self._unilab_send_msg_electrolyte_vol(elec_vol)
|
|
||||||
time.sleep(1)
|
|
||||||
#发送电解液组装类型
|
|
||||||
self._unilab_send_msg_assembly_type(assembly_type)
|
|
||||||
time.sleep(1)
|
|
||||||
#发送电池压制力
|
|
||||||
self._unilab_send_msg_assembly_pressure(assembly_pressure)
|
|
||||||
time.sleep(1)
|
|
||||||
self._unilab_send_msg_succ_cmd(True)
|
self._unilab_send_msg_succ_cmd(True)
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
while (self.request_rec_msg_status) == True:
|
while (self.request_rec_msg_status) == True:
|
||||||
@@ -809,32 +688,15 @@ class CoinCellAssemblyWorkstation(WorkstationBase):
|
|||||||
data_coin_num = self.data_coin_num
|
data_coin_num = self.data_coin_num
|
||||||
data_electrolyte_code = self.data_electrolyte_code
|
data_electrolyte_code = self.data_electrolyte_code
|
||||||
data_coin_cell_code = self.data_coin_cell_code
|
data_coin_cell_code = self.data_coin_cell_code
|
||||||
logger.debug(f"data_open_circuit_voltage: {data_open_circuit_voltage}")
|
print("data_open_circuit_voltage", data_open_circuit_voltage)
|
||||||
logger.debug(f"data_pole_weight: {data_pole_weight}")
|
print("data_pole_weight", data_pole_weight)
|
||||||
logger.debug(f"data_assembly_time: {data_assembly_time}")
|
print("data_assembly_time", data_assembly_time)
|
||||||
logger.debug(f"data_assembly_pressure: {data_assembly_pressure}")
|
print("data_assembly_pressure", data_assembly_pressure)
|
||||||
logger.debug(f"data_electrolyte_volume: {data_electrolyte_volume}")
|
print("data_electrolyte_volume", data_electrolyte_volume)
|
||||||
logger.debug(f"data_coin_num: {data_coin_num}")
|
print("data_coin_num", data_coin_num)
|
||||||
logger.debug(f"data_electrolyte_code: {data_electrolyte_code}")
|
print("data_electrolyte_code", data_electrolyte_code)
|
||||||
logger.debug(f"data_coin_cell_code: {data_coin_cell_code}")
|
print("data_coin_cell_code", data_coin_cell_code)
|
||||||
#接收完信息后,读取完毕标志位置True
|
#接收完信息后,读取完毕标志位置True
|
||||||
liaopan3 = self.deck.get_resource("\u7535\u6c60\u6599\u76d8")
|
|
||||||
#把物料解绑后放到另一盘上
|
|
||||||
battery = ElectrodeSheet(name=f"battery_{self.coin_num_N}", size_x=14, size_y=14, size_z=2)
|
|
||||||
battery._unilabos_state = {
|
|
||||||
"electrolyte_name": data_coin_cell_code,
|
|
||||||
"data_electrolyte_code": data_electrolyte_code,
|
|
||||||
"open_circuit_voltage": data_open_circuit_voltage,
|
|
||||||
"assembly_pressure": data_assembly_pressure,
|
|
||||||
"electrolyte_volume": data_electrolyte_volume
|
|
||||||
}
|
|
||||||
liaopan3.children[self.coin_num_N].assign_child_resource(battery, location=None)
|
|
||||||
#print(jipian2.parent)
|
|
||||||
ROS2DeviceNode.run_async_func(self._ros_node.update_resource, True, **{
|
|
||||||
"resources": [self.deck]
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
self._unilab_rec_msg_succ_cmd(True)
|
self._unilab_rec_msg_succ_cmd(True)
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
#等待允许读取标志位置False
|
#等待允许读取标志位置False
|
||||||
@@ -892,25 +754,11 @@ class CoinCellAssemblyWorkstation(WorkstationBase):
|
|||||||
self.success = True
|
self.success = True
|
||||||
return self.success
|
return self.success
|
||||||
|
|
||||||
def qiming_coin_cell_code(self, fujipian_panshu:int, fujipian_juzhendianwei:int=0, gemopanshu:int=0, gemo_juzhendianwei:int=0, lvbodian:bool=True, battery_pressure_mode:bool=True, battery_pressure:int=4000, battery_clean_ignore:bool=False) -> bool:
|
|
||||||
self.success = False
|
|
||||||
self.client.use_node('REG_MSG_NE_PLATE_NUM').write(fujipian_panshu)
|
|
||||||
self.client.use_node('REG_MSG_NE_PLATE_MATRIX').write(fujipian_juzhendianwei)
|
|
||||||
self.client.use_node('REG_MSG_SEPARATOR_PLATE_NUM').write(gemopanshu)
|
|
||||||
self.client.use_node('REG_MSG_SEPARATOR_PLATE_MATRIX').write(gemo_juzhendianwei)
|
|
||||||
self.client.use_node('COIL_ALUMINUM_FOIL').write(not lvbodian)
|
|
||||||
self.client.use_node('REG_MSG_PRESS_MODE').write(not battery_pressure_mode)
|
|
||||||
# self.client.use_node('REG_MSG_ASSEMBLY_PRESSURE').write(battery_pressure)
|
|
||||||
self.client.use_node('REG_MSG_BATTERY_CLEAN_IGNORE').write(battery_clean_ignore)
|
|
||||||
self.success = True
|
|
||||||
|
|
||||||
return self.success
|
|
||||||
|
|
||||||
def func_allpack_cmd(self, elec_num, elec_use_num, elec_vol:int=50, assembly_type:int=7, assembly_pressure:int=4200, file_path: str="/Users/sml/work") -> bool:
|
|
||||||
elec_num, elec_use_num, elec_vol, assembly_type, assembly_pressure = int(elec_num), int(elec_use_num), int(elec_vol), int(assembly_type), int(assembly_pressure)
|
def func_allpack_cmd(self, elec_num, elec_use_num, file_path: str="D:\\coin_cell_data") -> bool:
|
||||||
summary_csv_file = os.path.join(file_path, "duandian.csv")
|
summary_csv_file = os.path.join(file_path, "duandian.csv")
|
||||||
# 如果断点文件存在,先读取之前的进度
|
# 如果断点文件存在,先读取之前的进度
|
||||||
|
|
||||||
if os.path.exists(summary_csv_file):
|
if os.path.exists(summary_csv_file):
|
||||||
read_status_flag = True
|
read_status_flag = True
|
||||||
with open(summary_csv_file, 'r', newline='', encoding='utf-8') as csvfile:
|
with open(summary_csv_file, 'r', newline='', encoding='utf-8') as csvfile:
|
||||||
@@ -936,38 +784,54 @@ class CoinCellAssemblyWorkstation(WorkstationBase):
|
|||||||
elec_num_N = 0
|
elec_num_N = 0
|
||||||
elec_use_num_N = 0
|
elec_use_num_N = 0
|
||||||
coin_num_N = 0
|
coin_num_N = 0
|
||||||
for i in range(20):
|
|
||||||
print(f"剩余电解液瓶数: {elec_num}, 已组装电池数: {elec_use_num}")
|
print(f"剩余电解液瓶数: {elec_num}, 已组装电池数: {elec_use_num}")
|
||||||
print(f"剩余电解液瓶数: {type(elec_num)}, 已组装电池数: {type(elec_use_num)}")
|
|
||||||
print(f"剩余电解液瓶数: {type(int(elec_num))}, 已组装电池数: {type(int(elec_use_num))}")
|
|
||||||
|
|
||||||
#如果是第一次运行,则进行初始化、切换自动、启动, 如果是断点重启则跳过。
|
#如果是第一次运行,则进行初始化、切换自动、启动, 如果是断点重启则跳过。
|
||||||
if read_status_flag == False:
|
if read_status_flag == False:
|
||||||
pass
|
|
||||||
#初始化
|
#初始化
|
||||||
#self.func_pack_device_init()
|
self.func_pack_device_init()
|
||||||
#切换自动
|
#切换自动
|
||||||
#self.func_pack_device_auto()
|
self.func_pack_device_auto()
|
||||||
#启动,小车收回
|
#启动,小车收回
|
||||||
#self.func_pack_device_start()
|
self.func_pack_device_start()
|
||||||
#发送电解液瓶数量,启动搬运,多搬运没事
|
#发送电解液瓶数量,启动搬运,多搬运没事
|
||||||
#self.func_pack_send_bottle_num(elec_num)
|
self.func_pack_send_bottle_num(elec_num)
|
||||||
last_i = elec_num_N
|
last_i = elec_num_N
|
||||||
last_j = elec_use_num_N
|
last_j = elec_use_num_N
|
||||||
for i in range(last_i, elec_num):
|
for i in range(last_i, elec_num):
|
||||||
print(f"开始第{last_i+i+1}瓶电解液的组装")
|
print(f"开始第{last_i+i+1}瓶电解液的组装")
|
||||||
#第一个循环从上次断点继续,后续循环从0开始
|
#第一个循环从上次断点继续,后续循环从0开始
|
||||||
j_start = last_j if i == last_i else 0
|
j_start = last_j if i == last_i else 0
|
||||||
self.func_pack_send_msg_cmd(elec_use_num-j_start, elec_vol, assembly_type, assembly_pressure)
|
self.func_pack_send_msg_cmd(elec_use_num-j_start)
|
||||||
|
|
||||||
for j in range(j_start, elec_use_num):
|
for j in range(j_start, elec_use_num):
|
||||||
print(f"开始第{last_i+i+1}瓶电解液的第{j+j_start+1}个电池组装")
|
print(f"开始第{last_i+i+1}瓶电解液的第{j+j_start+1}个电池组装")
|
||||||
#读取电池组装数据并存入csv
|
#读取电池组装数据并存入csv
|
||||||
self.func_pack_get_msg_cmd(file_path)
|
self.func_pack_get_msg_cmd(file_path)
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
|
|
||||||
|
#这里定义物料系统
|
||||||
# TODO:读完再将电池数加一还是进入循环就将电池数加一需要考虑
|
# TODO:读完再将电池数加一还是进入循环就将电池数加一需要考虑
|
||||||
|
liaopan1 = self.deck.get_resource("liaopan1")
|
||||||
|
liaopan4 = self.deck.get_resource("liaopan4")
|
||||||
|
jipian1 = liaopan1.children[coin_num_N].children[0]
|
||||||
|
jipian4 = liaopan4.children[coin_num_N].children[0]
|
||||||
|
#print(jipian1)
|
||||||
|
#从料盘上去物料解绑后放到另一盘上
|
||||||
|
jipian1.parent.unassign_child_resource(jipian1)
|
||||||
|
jipian4.parent.unassign_child_resource(jipian4)
|
||||||
|
|
||||||
|
#print(jipian2.parent)
|
||||||
|
battery = Battery(name = f"battery_{coin_num_N}")
|
||||||
|
battery.assign_child_resource(jipian1, location=None)
|
||||||
|
battery.assign_child_resource(jipian4, location=None)
|
||||||
|
|
||||||
|
zidanjia6 = self.deck.get_resource("zi_dan_jia6")
|
||||||
|
|
||||||
|
zidanjia6.children[0].assign_child_resource(battery, location=None)
|
||||||
|
|
||||||
|
|
||||||
# 生成断点文件
|
# 生成断点文件
|
||||||
# 生成包含elec_num_N、coin_num_N、timestamp的CSV文件
|
# 生成包含elec_num_N、coin_num_N、timestamp的CSV文件
|
||||||
@@ -978,7 +842,6 @@ class CoinCellAssemblyWorkstation(WorkstationBase):
|
|||||||
writer.writerow([elec_num, elec_use_num, elec_num_N, elec_use_num_N, coin_num_N, timestamp])
|
writer.writerow([elec_num, elec_use_num, elec_num_N, elec_use_num_N, coin_num_N, timestamp])
|
||||||
csvfile.flush()
|
csvfile.flush()
|
||||||
coin_num_N += 1
|
coin_num_N += 1
|
||||||
self.coin_num_N = coin_num_N
|
|
||||||
elec_use_num_N += 1
|
elec_use_num_N += 1
|
||||||
elec_num_N += 1
|
elec_num_N += 1
|
||||||
elec_use_num_N = 0
|
elec_use_num_N = 0
|
||||||
@@ -1015,25 +878,34 @@ class CoinCellAssemblyWorkstation(WorkstationBase):
|
|||||||
|
|
||||||
def fun_wuliao_test(self) -> bool:
|
def fun_wuliao_test(self) -> bool:
|
||||||
#找到data_init中构建的2个物料盘
|
#找到data_init中构建的2个物料盘
|
||||||
liaopan3 = self.deck.get_resource("\u7535\u6c60\u6599\u76d8")
|
#liaopan1 = self.deck.get_resource("liaopan1")
|
||||||
for i in range(16):
|
#liaopan4 = self.deck.get_resource("liaopan4")
|
||||||
battery = ElectrodeSheet(name=f"battery_{i}", size_x=16, size_y=16, size_z=2)
|
#for coin_num_N in range(16):
|
||||||
battery._unilabos_state = {
|
# liaopan1 = self.deck.get_resource("liaopan1")
|
||||||
"diameter": 20.0,
|
# liaopan4 = self.deck.get_resource("liaopan4")
|
||||||
"height": 20.0,
|
# jipian1 = liaopan1.children[coin_num_N].children[0]
|
||||||
"assembly_pressure": i,
|
# jipian4 = liaopan4.children[coin_num_N].children[0]
|
||||||
"electrolyte_volume": 20.0,
|
# #print(jipian1)
|
||||||
"electrolyte_name": f"DP{i}"
|
# #从料盘上去物料解绑后放到另一盘上
|
||||||
}
|
# jipian1.parent.unassign_child_resource(jipian1)
|
||||||
liaopan3.children[i].assign_child_resource(battery, location=None)
|
# jipian4.parent.unassign_child_resource(jipian4)
|
||||||
|
#
|
||||||
|
# #print(jipian2.parent)
|
||||||
|
# battery = Battery(name = f"battery_{coin_num_N}")
|
||||||
|
# battery.assign_child_resource(jipian1, location=None)
|
||||||
|
# battery.assign_child_resource(jipian4, location=None)
|
||||||
|
#
|
||||||
|
# zidanjia6 = self.deck.get_resource("zi_dan_jia6")
|
||||||
|
# zidanjia6.children[0].assign_child_resource(battery, location=None)
|
||||||
|
# ROS2DeviceNode.run_async_func(self._ros_node.update_resource, True, **{
|
||||||
|
# "resources": [self.deck]
|
||||||
|
# })
|
||||||
|
# time.sleep(2)
|
||||||
|
for i in range(20):
|
||||||
|
print(f"输出{i}")
|
||||||
|
time.sleep(2)
|
||||||
|
|
||||||
|
|
||||||
ROS2DeviceNode.run_async_func(self._ros_node.update_resource, True, **{
|
|
||||||
"resources": [self.deck]
|
|
||||||
})
|
|
||||||
# for i in range(40):
|
|
||||||
# print(f"fun_wuliao_test 运行结束{i}")
|
|
||||||
# time.sleep(1)
|
|
||||||
# time.sleep(40)
|
|
||||||
# 数据读取与输出
|
# 数据读取与输出
|
||||||
def func_read_data_and_output(self, file_path: str="D:\\coin_cell_data"):
|
def func_read_data_and_output(self, file_path: str="D:\\coin_cell_data"):
|
||||||
# 检查CSV导出是否正在运行,已运行则跳出,防止同时启动两个while循环
|
# 检查CSV导出是否正在运行,已运行则跳出,防止同时启动两个while循环
|
||||||
@@ -1140,7 +1012,7 @@ class CoinCellAssemblyWorkstation(WorkstationBase):
|
|||||||
# else:
|
# else:
|
||||||
# print("子弹夹洞位0没有极片")
|
# print("子弹夹洞位0没有极片")
|
||||||
#
|
#
|
||||||
# # TODO:#把电解液从瓶中取到电池夹子中
|
# #把电解液从瓶中取到电池夹子中
|
||||||
# battery_site = deck.get_resource("battery_press_1")
|
# battery_site = deck.get_resource("battery_press_1")
|
||||||
# clip_magazine_battery = deck.get_resource("clip_magazine_battery")
|
# clip_magazine_battery = deck.get_resource("clip_magazine_battery")
|
||||||
# if battery_site.has_battery():
|
# if battery_site.has_battery():
|
||||||
@@ -1230,10 +1102,41 @@ class CoinCellAssemblyWorkstation(WorkstationBase):
|
|||||||
'''
|
'''
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
# 简单测试
|
from pylabrobot.resources import Resource
|
||||||
workstation = CoinCellAssemblyWorkstation()
|
Coin_Cell = CoinCellAssemblyWorkstation(Resource("1", 1, 1, 1), debug_mode=True)
|
||||||
workstation.qiming_coin_cell_code(fujipian_panshu=1, fujipian_juzhendianwei=2, gemopanshu=3, gemo_juzhendianwei=4, lvbodian=False, battery_pressure_mode=False, battery_pressure=4200, battery_clean_ignore=False)
|
#Coin_Cell.func_pack_device_init()
|
||||||
print(f"工作站创建成功: {workstation.deck.name}")
|
#Coin_Cell.func_pack_device_auto()
|
||||||
print(f"料盘数量: {len(workstation.deck.children)}")
|
#Coin_Cell.func_pack_device_start()
|
||||||
|
#Coin_Cell.func_pack_send_bottle_num(2)
|
||||||
|
#Coin_Cell.func_pack_send_msg_cmd(2)
|
||||||
|
#Coin_Cell.func_pack_get_msg_cmd()
|
||||||
|
#Coin_Cell.func_pack_get_msg_cmd()
|
||||||
|
#Coin_Cell.func_pack_send_finished_cmd()
|
||||||
|
#
|
||||||
|
#Coin_Cell.func_allpack_cmd(3, 2)
|
||||||
|
#print(Coin_Cell.data_stack_vision_code)
|
||||||
|
#print("success")
|
||||||
|
#创建一个物料台面
|
||||||
|
|
||||||
|
#deck = create_a_coin_cell_deck()
|
||||||
|
|
||||||
|
##在台面上找到料盘和极片
|
||||||
|
#liaopan1 = deck.get_resource("liaopan1")
|
||||||
|
#liaopan2 = deck.get_resource("liaopan2")
|
||||||
|
#jipian1 = liaopan1.children[1].children[0]
|
||||||
|
#
|
||||||
|
##print(jipian1)
|
||||||
|
##把物料解绑后放到另一盘上
|
||||||
|
#jipian1.parent.unassign_child_resource(jipian1)
|
||||||
|
#liaopan2.children[1].assign_child_resource(jipian1, location=None)
|
||||||
|
##print(jipian2.parent)
|
||||||
|
from unilabos.resources.graphio import resource_ulab_to_plr, convert_resources_to_type
|
||||||
|
|
||||||
|
with open("./button_battery_decks_unilab.json", "r", encoding="utf-8") as f:
|
||||||
|
bioyond_resources_unilab = json.load(f)
|
||||||
|
print(f"成功读取 JSON 文件,包含 {len(bioyond_resources_unilab)} 个资源")
|
||||||
|
ulab_resources = convert_resources_to_type(bioyond_resources_unilab, List[PLRResource])
|
||||||
|
print(f"转换结果类型: {type(ulab_resources)}")
|
||||||
|
print(ulab_resources)
|
||||||
|
|
||||||
|
|||||||
@@ -1,64 +0,0 @@
|
|||||||
Name,DataType,InitValue,Comment,Attribute,DeviceType,Address,
|
|
||||||
COIL_SYS_START_CMD,BOOL,,,,coil,9010,
|
|
||||||
COIL_SYS_STOP_CMD,BOOL,,,,coil,9020,
|
|
||||||
COIL_SYS_RESET_CMD,BOOL,,,,coil,9030,
|
|
||||||
COIL_SYS_HAND_CMD,BOOL,,,,coil,9040,
|
|
||||||
COIL_SYS_AUTO_CMD,BOOL,,,,coil,9050,
|
|
||||||
COIL_SYS_INIT_CMD,BOOL,,,,coil,9060,
|
|
||||||
COIL_UNILAB_SEND_MSG_SUCC_CMD,BOOL,,,,coil,9700,
|
|
||||||
COIL_UNILAB_REC_MSG_SUCC_CMD,BOOL,,,,coil,9710,unilab_rec_msg_succ_cmd
|
|
||||||
COIL_SYS_START_STATUS,BOOL,,,,coil,9210,
|
|
||||||
COIL_SYS_STOP_STATUS,BOOL,,,,coil,9220,
|
|
||||||
COIL_SYS_RESET_STATUS,BOOL,,,,coil,9230,
|
|
||||||
COIL_SYS_HAND_STATUS,BOOL,,,,coil,9240,
|
|
||||||
COIL_SYS_AUTO_STATUS,BOOL,,,,coil,9250,
|
|
||||||
COIL_SYS_INIT_STATUS,BOOL,,,,coil,9260,
|
|
||||||
COIL_REQUEST_REC_MSG_STATUS,BOOL,,,,coil,9500,
|
|
||||||
COIL_REQUEST_SEND_MSG_STATUS,BOOL,,,,coil,9510,request_send_msg_status
|
|
||||||
REG_MSG_ELECTROLYTE_USE_NUM,INT16,,,,hold_register,17000,
|
|
||||||
REG_MSG_ELECTROLYTE_NUM,INT16,,,,hold_register,17002,unilab_send_msg_electrolyte_num
|
|
||||||
REG_MSG_ELECTROLYTE_VOLUME,INT16,,,,hold_register,17004,unilab_send_msg_electrolyte_vol
|
|
||||||
REG_MSG_ASSEMBLY_TYPE,INT16,,,,hold_register,17006,unilab_send_msg_assembly_type
|
|
||||||
REG_MSG_ASSEMBLY_PRESSURE,INT16,,,,hold_register,17008,unilab_send_msg_assembly_pressure
|
|
||||||
REG_DATA_ASSEMBLY_COIN_CELL_NUM,INT16,,,,hold_register,16000,data_assembly_coin_cell_num
|
|
||||||
REG_DATA_OPEN_CIRCUIT_VOLTAGE,FLOAT32,,,,hold_register,16002,data_open_circuit_voltage
|
|
||||||
REG_DATA_AXIS_X_POS,FLOAT32,,,,hold_register,16004,
|
|
||||||
REG_DATA_AXIS_Y_POS,FLOAT32,,,,hold_register,16006,
|
|
||||||
REG_DATA_AXIS_Z_POS,FLOAT32,,,,hold_register,16008,
|
|
||||||
REG_DATA_POLE_WEIGHT,FLOAT32,,,,hold_register,16010,data_pole_weight
|
|
||||||
REG_DATA_ASSEMBLY_PER_TIME,FLOAT32,,,,hold_register,16012,data_assembly_time
|
|
||||||
REG_DATA_ASSEMBLY_PRESSURE,INT16,,,,hold_register,16014,data_assembly_pressure
|
|
||||||
REG_DATA_ELECTROLYTE_VOLUME,INT16,,,,hold_register,16016,data_electrolyte_volume
|
|
||||||
REG_DATA_COIN_NUM,INT16,,,,hold_register,16018,data_coin_num
|
|
||||||
REG_DATA_ELECTROLYTE_CODE,STRING,,,,hold_register,16020,data_electrolyte_code()
|
|
||||||
REG_DATA_COIN_CELL_CODE,STRING,,,,hold_register,16030,data_coin_cell_code()
|
|
||||||
REG_DATA_STACK_VISON_CODE,STRING,,,,hold_register,18004,data_stack_vision_code()
|
|
||||||
REG_DATA_GLOVE_BOX_PRESSURE,FLOAT32,,,,hold_register,16050,data_glove_box_pressure
|
|
||||||
REG_DATA_GLOVE_BOX_WATER_CONTENT,FLOAT32,,,,hold_register,16052,data_glove_box_water_content
|
|
||||||
REG_DATA_GLOVE_BOX_O2_CONTENT,FLOAT32,,,,hold_register,16054,data_glove_box_o2_content
|
|
||||||
UNILAB_SEND_ELECTROLYTE_BOTTLE_NUM,BOOL,,,,coil,9720,
|
|
||||||
UNILAB_RECE_ELECTROLYTE_BOTTLE_NUM,BOOL,,,,coil,9520,
|
|
||||||
REG_MSG_ELECTROLYTE_NUM_USED,INT16,,,,hold_register,17496,
|
|
||||||
REG_DATA_ELECTROLYTE_USE_NUM,INT16,,,,hold_register,16000,
|
|
||||||
UNILAB_SEND_FINISHED_CMD,BOOL,,,,coil,9730,
|
|
||||||
UNILAB_RECE_FINISHED_CMD,BOOL,,,,coil,9530,
|
|
||||||
REG_DATA_ASSEMBLY_TYPE,INT16,,,,hold_register,16018,ASSEMBLY_TYPE7or8
|
|
||||||
COIL_ALUMINUM_FOIL,BOOL,,使用铝箔垫,,coil,9340,
|
|
||||||
REG_MSG_NE_PLATE_MATRIX,INT16,,负极片矩阵点位,,hold_register,17440,
|
|
||||||
REG_MSG_SEPARATOR_PLATE_MATRIX,INT16,,隔膜矩阵点位,,hold_register,17450,
|
|
||||||
REG_MSG_TIP_BOX_MATRIX,INT16,,移液枪头矩阵点位,,hold_register,17480,
|
|
||||||
REG_MSG_NE_PLATE_NUM,INT16,,负极片盘数,,hold_register,17443,
|
|
||||||
REG_MSG_SEPARATOR_PLATE_NUM,INT16,,隔膜盘数,,hold_register,17453,
|
|
||||||
REG_MSG_PRESS_MODE,BOOL,,压制模式(false:压力检测模式,True:距离模式),,coil,9360,电池压制模式
|
|
||||||
,,,,,,,
|
|
||||||
,BOOL,,视觉对位(false:使用,true:忽略),,coil,9300,视觉对位
|
|
||||||
,BOOL,,复检(false:使用,true:忽略),,coil,9310,视觉复检
|
|
||||||
,BOOL,,手套箱_左仓(false:使用,true:忽略),,coil,9320,手套箱左仓
|
|
||||||
,BOOL,,手套箱_右仓(false:使用,true:忽略),,coil,9420,手套箱右仓
|
|
||||||
,BOOL,,真空检知(false:使用,true:忽略),,coil,9350,真空检知
|
|
||||||
,BOOL,,电解液添加模式(false:单次滴液,true:二次滴液),,coil,9370,滴液模式
|
|
||||||
,BOOL,,正极片称重(false:使用,true:忽略),,coil,9380,正极片称重
|
|
||||||
,BOOL,,正负极片组装方式(false:正装,true:倒装),,coil,9390,正负极反装
|
|
||||||
,BOOL,,压制清洁(false:使用,true:忽略),,coil,9400,压制清洁
|
|
||||||
,BOOL,,物料盘摆盘方式(false:水平摆盘,true:堆叠摆盘),,coil,9410,负极片摆盘方式
|
|
||||||
REG_MSG_BATTERY_CLEAN_IGNORE,BOOL,,忽略电池清洁(false:使用,true:忽略),,coil,9460,
|
|
||||||
|
@@ -1,64 +0,0 @@
|
|||||||
Name,DataType,InitValue,Comment,Attribute,DeviceType,Address,
|
|
||||||
COIL_SYS_START_CMD,BOOL,,,,coil,8010,
|
|
||||||
COIL_SYS_STOP_CMD,BOOL,,,,coil,8020,
|
|
||||||
COIL_SYS_RESET_CMD,BOOL,,,,coil,8030,
|
|
||||||
COIL_SYS_HAND_CMD,BOOL,,,,coil,8040,
|
|
||||||
COIL_SYS_AUTO_CMD,BOOL,,,,coil,8050,
|
|
||||||
COIL_SYS_INIT_CMD,BOOL,,,,coil,8060,
|
|
||||||
COIL_UNILAB_SEND_MSG_SUCC_CMD,BOOL,,,,coil,8700,
|
|
||||||
COIL_UNILAB_REC_MSG_SUCC_CMD,BOOL,,,,coil,8710,unilab_rec_msg_succ_cmd
|
|
||||||
COIL_SYS_START_STATUS,BOOL,,,,coil,8210,
|
|
||||||
COIL_SYS_STOP_STATUS,BOOL,,,,coil,8220,
|
|
||||||
COIL_SYS_RESET_STATUS,BOOL,,,,coil,8230,
|
|
||||||
COIL_SYS_HAND_STATUS,BOOL,,,,coil,8240,
|
|
||||||
COIL_SYS_AUTO_STATUS,BOOL,,,,coil,8250,
|
|
||||||
COIL_SYS_INIT_STATUS,BOOL,,,,coil,8260,
|
|
||||||
COIL_REQUEST_REC_MSG_STATUS,BOOL,,,,coil,8500,
|
|
||||||
COIL_REQUEST_SEND_MSG_STATUS,BOOL,,,,coil,8510,request_send_msg_status
|
|
||||||
REG_MSG_ELECTROLYTE_USE_NUM,INT16,,,,hold_register,11000,
|
|
||||||
REG_MSG_ELECTROLYTE_NUM,INT16,,,,hold_register,11002,unilab_send_msg_electrolyte_num
|
|
||||||
REG_MSG_ELECTROLYTE_VOLUME,INT16,,,,hold_register,11004,unilab_send_msg_electrolyte_vol
|
|
||||||
REG_MSG_ASSEMBLY_TYPE,INT16,,,,hold_register,11006,unilab_send_msg_assembly_type
|
|
||||||
REG_MSG_ASSEMBLY_PRESSURE,INT16,,,,hold_register,11008,unilab_send_msg_assembly_pressure
|
|
||||||
REG_DATA_ASSEMBLY_COIN_CELL_NUM,INT16,,,,hold_register,10000,data_assembly_coin_cell_num
|
|
||||||
REG_DATA_OPEN_CIRCUIT_VOLTAGE,FLOAT32,,,,hold_register,10002,data_open_circuit_voltage
|
|
||||||
REG_DATA_AXIS_X_POS,FLOAT32,,,,hold_register,10004,
|
|
||||||
REG_DATA_AXIS_Y_POS,FLOAT32,,,,hold_register,10006,
|
|
||||||
REG_DATA_AXIS_Z_POS,FLOAT32,,,,hold_register,10008,
|
|
||||||
REG_DATA_POLE_WEIGHT,FLOAT32,,,,hold_register,10010,data_pole_weight
|
|
||||||
REG_DATA_ASSEMBLY_PER_TIME,FLOAT32,,,,hold_register,10012,data_assembly_time
|
|
||||||
REG_DATA_ASSEMBLY_PRESSURE,INT16,,,,hold_register,10014,data_assembly_pressure
|
|
||||||
REG_DATA_ELECTROLYTE_VOLUME,INT16,,,,hold_register,10016,data_electrolyte_volume
|
|
||||||
REG_DATA_COIN_NUM,INT16,,,,hold_register,10018,data_coin_num
|
|
||||||
REG_DATA_ELECTROLYTE_CODE,STRING,,,,hold_register,10020,data_electrolyte_code()
|
|
||||||
REG_DATA_COIN_CELL_CODE,STRING,,,,hold_register,10030,data_coin_cell_code()
|
|
||||||
REG_DATA_STACK_VISON_CODE,STRING,,,,hold_register,12004,data_stack_vision_code()
|
|
||||||
REG_DATA_GLOVE_BOX_PRESSURE,FLOAT32,,,,hold_register,10050,data_glove_box_pressure
|
|
||||||
REG_DATA_GLOVE_BOX_WATER_CONTENT,FLOAT32,,,,hold_register,10052,data_glove_box_water_content
|
|
||||||
REG_DATA_GLOVE_BOX_O2_CONTENT,FLOAT32,,,,hold_register,10054,data_glove_box_o2_content
|
|
||||||
UNILAB_SEND_ELECTROLYTE_BOTTLE_NUM,BOOL,,,,coil,8720,
|
|
||||||
UNILAB_RECE_ELECTROLYTE_BOTTLE_NUM,BOOL,,,,coil,8520,
|
|
||||||
REG_MSG_ELECTROLYTE_NUM_USED,INT16,,,,hold_register,496,
|
|
||||||
REG_DATA_ELECTROLYTE_USE_NUM,INT16,,,,hold_register,10000,
|
|
||||||
UNILAB_SEND_FINISHED_CMD,BOOL,,,,coil,8730,
|
|
||||||
UNILAB_RECE_FINISHED_CMD,BOOL,,,,coil,8530,
|
|
||||||
REG_DATA_ASSEMBLY_TYPE,INT16,,,,hold_register,10018,ASSEMBLY_TYPE7or8
|
|
||||||
COIL_ALUMINUM_FOIL,BOOL,,使用铝箔垫,,coil,8340,
|
|
||||||
REG_MSG_NE_PLATE_MATRIX,INT16,,负极片矩阵点位,,hold_register,440,
|
|
||||||
REG_MSG_SEPARATOR_PLATE_MATRIX,INT16,,隔膜矩阵点位,,hold_register,450,
|
|
||||||
REG_MSG_TIP_BOX_MATRIX,INT16,,移液枪头矩阵点位,,hold_register,480,
|
|
||||||
REG_MSG_NE_PLATE_NUM,INT16,,负极片盘数,,hold_register,443,
|
|
||||||
REG_MSG_SEPARATOR_PLATE_NUM,INT16,,隔膜盘数,,hold_register,453,
|
|
||||||
REG_MSG_PRESS_MODE,BOOL,,压制模式(false:压力检测模式,True:距离模式),,coil,8360,电池压制模式
|
|
||||||
,,,,,,,
|
|
||||||
,BOOL,,视觉对位(false:使用,true:忽略),,coil,8300,视觉对位
|
|
||||||
,BOOL,,复检(false:使用,true:忽略),,coil,8310,视觉复检
|
|
||||||
,BOOL,,手套箱_左仓(false:使用,true:忽略),,coil,8320,手套箱左仓
|
|
||||||
,BOOL,,手套箱_右仓(false:使用,true:忽略),,coil,8420,手套箱右仓
|
|
||||||
,BOOL,,真空检知(false:使用,true:忽略),,coil,8350,真空检知
|
|
||||||
,BOOL,,电解液添加模式(false:单次滴液,true:二次滴液),,coil,8370,滴液模式
|
|
||||||
,BOOL,,正极片称重(false:使用,true:忽略),,coil,8380,正极片称重
|
|
||||||
,BOOL,,正负极片组装方式(false:正装,true:倒装),,coil,8390,正负极反装
|
|
||||||
,BOOL,,压制清洁(false:使用,true:忽略),,coil,8400,压制清洁
|
|
||||||
,BOOL,,物料盘摆盘方式(false:水平摆盘,true:堆叠摆盘),,coil,8410,负极片摆盘方式
|
|
||||||
REG_MSG_BATTERY_CLEAN_IGNORE,BOOL,,忽略电池清洁(false:使用,true:忽略),,coil,8460,
|
|
||||||
|
@@ -1,39 +0,0 @@
|
|||||||
{
|
|
||||||
"nodes": [
|
|
||||||
{
|
|
||||||
"id": "bioyond_cell_workstation",
|
|
||||||
"name": "配液分液工站",
|
|
||||||
"children": [
|
|
||||||
],
|
|
||||||
"parent": null,
|
|
||||||
"type": "device",
|
|
||||||
"class": "bioyond_cell",
|
|
||||||
"config": {
|
|
||||||
"protocol_type": [],
|
|
||||||
"station_resource": {}
|
|
||||||
},
|
|
||||||
"data": {}
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
"id": "BatteryStation",
|
|
||||||
"name": "扣电工作站",
|
|
||||||
"children": [
|
|
||||||
"coin_cell_deck"
|
|
||||||
],
|
|
||||||
"parent": null,
|
|
||||||
"type": "device",
|
|
||||||
"class": "coincellassemblyworkstation_device",
|
|
||||||
"position": {
|
|
||||||
"x": -600,
|
|
||||||
"y": -400,
|
|
||||||
"z": 0
|
|
||||||
},
|
|
||||||
"config": {
|
|
||||||
"debug_mode": false,
|
|
||||||
"protocol_type": []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"links": []
|
|
||||||
}
|
|
||||||
@@ -1,23 +1,8 @@
|
|||||||
{
|
{
|
||||||
"nodes": [
|
"nodes": [
|
||||||
{
|
|
||||||
"id": "bioyond_cell_workstation",
|
|
||||||
"name": "配液分液工站",
|
|
||||||
"children": [
|
|
||||||
],
|
|
||||||
"parent": null,
|
|
||||||
"type": "device",
|
|
||||||
"class": "bioyond_cell",
|
|
||||||
"config": {
|
|
||||||
"protocol_type": [],
|
|
||||||
"station_resource": {}
|
|
||||||
|
|
||||||
},
|
|
||||||
"data": {}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"id": "BatteryStation",
|
"id": "BatteryStation",
|
||||||
"name": "扣电组装工作站",
|
"name": "扣电工作站",
|
||||||
"children": [
|
"children": [
|
||||||
"coin_cell_deck"
|
"coin_cell_deck"
|
||||||
],
|
],
|
||||||
@@ -171,6 +171,7 @@ class WorkstationBase(ABC):
|
|||||||
def post_init(self, ros_node: ROS2WorkstationNode) -> None:
|
def post_init(self, ros_node: ROS2WorkstationNode) -> None:
|
||||||
# 初始化物料系统
|
# 初始化物料系统
|
||||||
self._ros_node = ros_node
|
self._ros_node = ros_node
|
||||||
|
self._ros_node.update_resource([self.deck])
|
||||||
|
|
||||||
def _build_resource_mappings(self, deck: Deck):
|
def _build_resource_mappings(self, deck: Deck):
|
||||||
"""递归构建资源映射"""
|
"""递归构建资源映射"""
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,3 +1,231 @@
|
|||||||
|
hplc.agilent:
|
||||||
|
category:
|
||||||
|
- characterization_chromatic
|
||||||
|
class:
|
||||||
|
action_value_mappings:
|
||||||
|
auto-check_status:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default: {}
|
||||||
|
handles: {}
|
||||||
|
placeholder_keys: {}
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: 检查安捷伦HPLC设备状态的函数。用于监控设备的运行状态、连接状态、错误信息等关键指标。该函数定期查询设备状态,确保系统稳定运行,及时发现和报告设备异常。适用于自动化流程中的设备监控、故障诊断、系统维护等场景。
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: check_status参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-extract_data_from_txt:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
file_path: null
|
||||||
|
handles: {}
|
||||||
|
placeholder_keys: {}
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: 从文本文件中提取分析数据的函数。用于解析安捷伦HPLC生成的结果文件,提取峰面积、保留时间、浓度等关键分析数据。支持多种文件格式的自动识别和数据结构化处理,为后续数据分析和报告生成提供标准化的数据格式。适用于批量数据处理、结果验证、质量控制等分析工作流程。
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
file_path:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- file_path
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: extract_data_from_txt参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-start_sequence:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
params: null
|
||||||
|
resource: null
|
||||||
|
wf_name: null
|
||||||
|
handles: {}
|
||||||
|
placeholder_keys: {}
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: 启动安捷伦HPLC分析序列的函数。用于执行预定义的分析方法序列,包括样品进样、色谱分离、检测等完整的分析流程。支持参数配置、资源分配、工作流程管理等功能,实现全自动的样品分析。适用于批量样品处理、标准化分析、质量检测等需要连续自动分析的应用场景。
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
params:
|
||||||
|
type: string
|
||||||
|
resource:
|
||||||
|
type: object
|
||||||
|
wf_name:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- wf_name
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: start_sequence参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-try_close_sub_device:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
device_name: null
|
||||||
|
handles: {}
|
||||||
|
placeholder_keys: {}
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: 尝试关闭HPLC子设备的函数。用于安全地关闭泵、检测器、进样器等各个子模块,确保设备正常断开连接并保护硬件安全。该函数提供错误处理和状态确认机制,避免强制关闭可能造成的设备损坏。适用于设备维护、系统重启、紧急停机等需要安全关闭设备的场景。
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
device_name:
|
||||||
|
type: string
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: try_close_sub_device参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-try_open_sub_device:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
device_name: null
|
||||||
|
handles: {}
|
||||||
|
placeholder_keys: {}
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: 尝试打开HPLC子设备的函数。用于初始化和连接泵、检测器、进样器等各个子模块,建立设备通信并进行自检。该函数提供连接验证和错误恢复机制,确保子设备正常启动并准备就绪。适用于设备初始化、系统启动、设备重连等需要建立设备连接的场景。
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
device_name:
|
||||||
|
type: string
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: try_open_sub_device参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
execute_command_from_outer:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
command: command
|
||||||
|
goal_default:
|
||||||
|
command: ''
|
||||||
|
handles: {}
|
||||||
|
result:
|
||||||
|
success: success
|
||||||
|
schema:
|
||||||
|
description: ''
|
||||||
|
properties:
|
||||||
|
feedback:
|
||||||
|
properties:
|
||||||
|
status:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- status
|
||||||
|
title: SendCmd_Feedback
|
||||||
|
type: object
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
command:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- command
|
||||||
|
title: SendCmd_Goal
|
||||||
|
type: object
|
||||||
|
result:
|
||||||
|
properties:
|
||||||
|
return_info:
|
||||||
|
type: string
|
||||||
|
success:
|
||||||
|
type: boolean
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
|
- success
|
||||||
|
title: SendCmd_Result
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: SendCmd
|
||||||
|
type: object
|
||||||
|
type: SendCmd
|
||||||
|
module: unilabos.devices.hplc.AgilentHPLC:HPLCDriver
|
||||||
|
status_types:
|
||||||
|
could_run: bool
|
||||||
|
data_file: String
|
||||||
|
device_status: str
|
||||||
|
driver_init_ok: bool
|
||||||
|
finish_status: str
|
||||||
|
is_running: bool
|
||||||
|
status_text: str
|
||||||
|
success: bool
|
||||||
|
type: python
|
||||||
|
config_info: []
|
||||||
|
description: 安捷伦高效液相色谱(HPLC)分析设备,用于复杂化合物的分离、检测和定量分析。该设备通过UI自动化技术控制安捷伦ChemStation软件,实现全自动的样品分析流程。具备序列启动、设备状态监控、数据文件提取、结果处理等功能。支持多样品批量处理和实时状态反馈,适用于药物分析、环境检测、食品安全、化学研究等需要高精度色谱分析的实验室应用。
|
||||||
|
handles: []
|
||||||
|
icon: ''
|
||||||
|
init_param_schema:
|
||||||
|
config:
|
||||||
|
properties:
|
||||||
|
driver_debug:
|
||||||
|
default: false
|
||||||
|
type: string
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
data:
|
||||||
|
properties:
|
||||||
|
could_run:
|
||||||
|
type: boolean
|
||||||
|
data_file:
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
type: array
|
||||||
|
device_status:
|
||||||
|
type: string
|
||||||
|
driver_init_ok:
|
||||||
|
type: boolean
|
||||||
|
finish_status:
|
||||||
|
type: string
|
||||||
|
is_running:
|
||||||
|
type: boolean
|
||||||
|
status_text:
|
||||||
|
type: string
|
||||||
|
success:
|
||||||
|
type: boolean
|
||||||
|
required:
|
||||||
|
- status_text
|
||||||
|
- device_status
|
||||||
|
- could_run
|
||||||
|
- driver_init_ok
|
||||||
|
- is_running
|
||||||
|
- success
|
||||||
|
- finish_status
|
||||||
|
- data_file
|
||||||
|
type: object
|
||||||
|
version: 1.0.0
|
||||||
hplc.agilent-zhida:
|
hplc.agilent-zhida:
|
||||||
category:
|
category:
|
||||||
- characterization_chromatic
|
- characterization_chromatic
|
||||||
|
|||||||
@@ -1 +1,194 @@
|
|||||||
{}
|
raman.home_made:
|
||||||
|
category:
|
||||||
|
- characterization_optic
|
||||||
|
class:
|
||||||
|
action_value_mappings:
|
||||||
|
auto-ccd_time:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
int_time: null
|
||||||
|
handles: {}
|
||||||
|
placeholder_keys: {}
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: 设置CCD检测器积分时间的函数。用于配置拉曼光谱仪的信号采集时间,控制光谱数据的质量和信噪比。较长的积分时间可获得更高的信号强度和更好的光谱质量,但会增加测量时间。该函数允许根据样品特性和测量要求动态调整检测参数,优化测量效果。
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
int_time:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- int_time
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: ccd_time参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-laser_on_power:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
output_voltage_laser: null
|
||||||
|
handles: {}
|
||||||
|
placeholder_keys: {}
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: 设置激光器输出功率的函数。用于控制拉曼光谱仪激光器的功率输出,调节激光强度以适应不同样品的测量需求。适当的激光功率能够获得良好的拉曼信号同时避免样品损伤。该函数支持精确的功率控制,确保测量结果的稳定性和重现性。
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
output_voltage_laser:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- output_voltage_laser
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: laser_on_power参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-raman_without_background:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
int_time: null
|
||||||
|
laser_power: null
|
||||||
|
handles: {}
|
||||||
|
placeholder_keys: {}
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: 执行无背景扣除的拉曼光谱测量函数。用于直接采集样品的拉曼光谱信号,不进行背景校正处理。该函数配置积分时间和激光功率参数,获取原始光谱数据用于后续的数据处理分析。适用于对光谱数据质量要求较高或需要自定义背景处理流程的测量场景。
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
int_time:
|
||||||
|
type: string
|
||||||
|
laser_power:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- int_time
|
||||||
|
- laser_power
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: raman_without_background参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-raman_without_background_average:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
average: null
|
||||||
|
int_time: null
|
||||||
|
laser_power: null
|
||||||
|
sample_name: null
|
||||||
|
handles: {}
|
||||||
|
placeholder_keys: {}
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: 执行多次平均的无背景拉曼光谱测量函数。通过多次测量取平均值来提高光谱数据的信噪比和测量精度,减少随机噪声影响。该函数支持自定义平均次数、积分时间、激光功率等参数,并可为样品指定名称便于数据管理。适用于对测量精度要求较高的定量分析和研究应用。
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
average:
|
||||||
|
type: string
|
||||||
|
int_time:
|
||||||
|
type: string
|
||||||
|
laser_power:
|
||||||
|
type: string
|
||||||
|
sample_name:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- sample_name
|
||||||
|
- int_time
|
||||||
|
- laser_power
|
||||||
|
- average
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: raman_without_background_average参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
raman_cmd:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
command: command
|
||||||
|
goal_default:
|
||||||
|
command: ''
|
||||||
|
handles: {}
|
||||||
|
result:
|
||||||
|
success: success
|
||||||
|
schema:
|
||||||
|
description: ''
|
||||||
|
properties:
|
||||||
|
feedback:
|
||||||
|
properties:
|
||||||
|
status:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- status
|
||||||
|
title: SendCmd_Feedback
|
||||||
|
type: object
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
command:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- command
|
||||||
|
title: SendCmd_Goal
|
||||||
|
type: object
|
||||||
|
result:
|
||||||
|
properties:
|
||||||
|
return_info:
|
||||||
|
type: string
|
||||||
|
success:
|
||||||
|
type: boolean
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
|
- success
|
||||||
|
title: SendCmd_Result
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: SendCmd
|
||||||
|
type: object
|
||||||
|
type: SendCmd
|
||||||
|
module: unilabos.devices.raman_uv.home_made_raman:RamanObj
|
||||||
|
status_types: {}
|
||||||
|
type: python
|
||||||
|
config_info: []
|
||||||
|
description: 拉曼光谱分析设备,用于物质的分子结构和化学成分表征。该设备集成激光器和CCD检测器,通过串口通信控制激光功率和光谱采集。具备背景扣除、多次平均、自动数据处理等功能,支持高精度的拉曼光谱测量。适用于材料表征、化学分析、质量控制、研究开发等需要分子指纹识别和结构分析的实验应用。
|
||||||
|
handles: []
|
||||||
|
icon: ''
|
||||||
|
init_param_schema:
|
||||||
|
config:
|
||||||
|
properties:
|
||||||
|
baudrate_ccd:
|
||||||
|
default: 921600
|
||||||
|
type: string
|
||||||
|
baudrate_laser:
|
||||||
|
default: 9600
|
||||||
|
type: string
|
||||||
|
port_ccd:
|
||||||
|
type: string
|
||||||
|
port_laser:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- port_laser
|
||||||
|
- port_ccd
|
||||||
|
type: object
|
||||||
|
data:
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
version: 1.0.0
|
||||||
|
|||||||
@@ -1,584 +0,0 @@
|
|||||||
coincellassemblyworkstation_device:
|
|
||||||
category:
|
|
||||||
- coin_cell_workstation
|
|
||||||
class:
|
|
||||||
action_value_mappings:
|
|
||||||
auto-change_hole_sheet_to_2:
|
|
||||||
feedback: {}
|
|
||||||
goal: {}
|
|
||||||
goal_default:
|
|
||||||
hole: null
|
|
||||||
handles: {}
|
|
||||||
placeholder_keys: {}
|
|
||||||
result: {}
|
|
||||||
schema:
|
|
||||||
description: ''
|
|
||||||
properties:
|
|
||||||
feedback: {}
|
|
||||||
goal:
|
|
||||||
properties:
|
|
||||||
hole:
|
|
||||||
type: object
|
|
||||||
required:
|
|
||||||
- hole
|
|
||||||
type: object
|
|
||||||
result: {}
|
|
||||||
required:
|
|
||||||
- goal
|
|
||||||
title: change_hole_sheet_to_2参数
|
|
||||||
type: object
|
|
||||||
type: UniLabJsonCommandAsync
|
|
||||||
auto-fill_plate:
|
|
||||||
feedback: {}
|
|
||||||
goal: {}
|
|
||||||
goal_default: {}
|
|
||||||
handles: {}
|
|
||||||
placeholder_keys: {}
|
|
||||||
result: {}
|
|
||||||
schema:
|
|
||||||
description: ''
|
|
||||||
properties:
|
|
||||||
feedback: {}
|
|
||||||
goal:
|
|
||||||
properties: {}
|
|
||||||
required: []
|
|
||||||
type: object
|
|
||||||
result: {}
|
|
||||||
required:
|
|
||||||
- goal
|
|
||||||
title: fill_plate参数
|
|
||||||
type: object
|
|
||||||
type: UniLabJsonCommandAsync
|
|
||||||
auto-fun_wuliao_test:
|
|
||||||
feedback: {}
|
|
||||||
goal: {}
|
|
||||||
goal_default: {}
|
|
||||||
handles: {}
|
|
||||||
placeholder_keys: {}
|
|
||||||
result: {}
|
|
||||||
schema:
|
|
||||||
description: ''
|
|
||||||
properties:
|
|
||||||
feedback: {}
|
|
||||||
goal:
|
|
||||||
properties: {}
|
|
||||||
required: []
|
|
||||||
type: object
|
|
||||||
result: {}
|
|
||||||
required:
|
|
||||||
- goal
|
|
||||||
title: fun_wuliao_test参数
|
|
||||||
type: object
|
|
||||||
type: UniLabJsonCommand
|
|
||||||
auto-func_allpack_cmd:
|
|
||||||
feedback: {}
|
|
||||||
goal: {}
|
|
||||||
goal_default:
|
|
||||||
assembly_pressure: 4200
|
|
||||||
assembly_type: 7
|
|
||||||
elec_num: null
|
|
||||||
elec_use_num: null
|
|
||||||
elec_vol: 50
|
|
||||||
file_path: C:\Users\67484\Desktop
|
|
||||||
handles: {}
|
|
||||||
placeholder_keys: {}
|
|
||||||
result: {}
|
|
||||||
schema:
|
|
||||||
description: ''
|
|
||||||
properties:
|
|
||||||
feedback: {}
|
|
||||||
goal:
|
|
||||||
properties:
|
|
||||||
assembly_pressure:
|
|
||||||
default: 4200
|
|
||||||
type: integer
|
|
||||||
assembly_type:
|
|
||||||
default: 7
|
|
||||||
type: integer
|
|
||||||
elec_num:
|
|
||||||
type: string
|
|
||||||
elec_use_num:
|
|
||||||
type: string
|
|
||||||
elec_vol:
|
|
||||||
default: 50
|
|
||||||
type: integer
|
|
||||||
file_path:
|
|
||||||
default: C:\Users\67484\Desktop
|
|
||||||
type: string
|
|
||||||
required:
|
|
||||||
- elec_num
|
|
||||||
- elec_use_num
|
|
||||||
type: object
|
|
||||||
result: {}
|
|
||||||
required:
|
|
||||||
- goal
|
|
||||||
title: func_allpack_cmd参数
|
|
||||||
type: object
|
|
||||||
type: UniLabJsonCommand
|
|
||||||
auto-func_get_csv_export_status:
|
|
||||||
feedback: {}
|
|
||||||
goal: {}
|
|
||||||
goal_default: {}
|
|
||||||
handles: {}
|
|
||||||
placeholder_keys: {}
|
|
||||||
result: {}
|
|
||||||
schema:
|
|
||||||
description: ''
|
|
||||||
properties:
|
|
||||||
feedback: {}
|
|
||||||
goal:
|
|
||||||
properties: {}
|
|
||||||
required: []
|
|
||||||
type: object
|
|
||||||
result: {}
|
|
||||||
required:
|
|
||||||
- goal
|
|
||||||
title: func_get_csv_export_status参数
|
|
||||||
type: object
|
|
||||||
type: UniLabJsonCommand
|
|
||||||
auto-func_pack_device_auto:
|
|
||||||
feedback: {}
|
|
||||||
goal: {}
|
|
||||||
goal_default: {}
|
|
||||||
handles: {}
|
|
||||||
placeholder_keys: {}
|
|
||||||
result: {}
|
|
||||||
schema:
|
|
||||||
description: ''
|
|
||||||
properties:
|
|
||||||
feedback: {}
|
|
||||||
goal:
|
|
||||||
properties: {}
|
|
||||||
required: []
|
|
||||||
type: object
|
|
||||||
result: {}
|
|
||||||
required:
|
|
||||||
- goal
|
|
||||||
title: func_pack_device_auto参数
|
|
||||||
type: object
|
|
||||||
type: UniLabJsonCommand
|
|
||||||
auto-func_pack_device_init:
|
|
||||||
feedback: {}
|
|
||||||
goal: {}
|
|
||||||
goal_default: {}
|
|
||||||
handles: {}
|
|
||||||
placeholder_keys: {}
|
|
||||||
result: {}
|
|
||||||
schema:
|
|
||||||
description: ''
|
|
||||||
properties:
|
|
||||||
feedback: {}
|
|
||||||
goal:
|
|
||||||
properties: {}
|
|
||||||
required: []
|
|
||||||
type: object
|
|
||||||
result: {}
|
|
||||||
required:
|
|
||||||
- goal
|
|
||||||
title: func_pack_device_init参数
|
|
||||||
type: object
|
|
||||||
type: UniLabJsonCommand
|
|
||||||
auto-func_pack_device_start:
|
|
||||||
feedback: {}
|
|
||||||
goal: {}
|
|
||||||
goal_default: {}
|
|
||||||
handles: {}
|
|
||||||
placeholder_keys: {}
|
|
||||||
result: {}
|
|
||||||
schema:
|
|
||||||
description: ''
|
|
||||||
properties:
|
|
||||||
feedback: {}
|
|
||||||
goal:
|
|
||||||
properties: {}
|
|
||||||
required: []
|
|
||||||
type: object
|
|
||||||
result: {}
|
|
||||||
required:
|
|
||||||
- goal
|
|
||||||
title: func_pack_device_start参数
|
|
||||||
type: object
|
|
||||||
type: UniLabJsonCommand
|
|
||||||
auto-func_pack_device_stop:
|
|
||||||
feedback: {}
|
|
||||||
goal: {}
|
|
||||||
goal_default: {}
|
|
||||||
handles: {}
|
|
||||||
placeholder_keys: {}
|
|
||||||
result: {}
|
|
||||||
schema:
|
|
||||||
description: ''
|
|
||||||
properties:
|
|
||||||
feedback: {}
|
|
||||||
goal:
|
|
||||||
properties: {}
|
|
||||||
required: []
|
|
||||||
type: object
|
|
||||||
result: {}
|
|
||||||
required:
|
|
||||||
- goal
|
|
||||||
title: func_pack_device_stop参数
|
|
||||||
type: object
|
|
||||||
type: UniLabJsonCommand
|
|
||||||
auto-func_pack_get_msg_cmd:
|
|
||||||
feedback: {}
|
|
||||||
goal: {}
|
|
||||||
goal_default:
|
|
||||||
file_path: D:\coin_cell_data
|
|
||||||
handles: {}
|
|
||||||
placeholder_keys: {}
|
|
||||||
result: {}
|
|
||||||
schema:
|
|
||||||
description: ''
|
|
||||||
properties:
|
|
||||||
feedback: {}
|
|
||||||
goal:
|
|
||||||
properties:
|
|
||||||
file_path:
|
|
||||||
default: D:\coin_cell_data
|
|
||||||
type: string
|
|
||||||
required: []
|
|
||||||
type: object
|
|
||||||
result: {}
|
|
||||||
required:
|
|
||||||
- goal
|
|
||||||
title: func_pack_get_msg_cmd参数
|
|
||||||
type: object
|
|
||||||
type: UniLabJsonCommand
|
|
||||||
auto-func_pack_send_bottle_num:
|
|
||||||
feedback: {}
|
|
||||||
goal: {}
|
|
||||||
goal_default:
|
|
||||||
bottle_num: null
|
|
||||||
handles: {}
|
|
||||||
placeholder_keys: {}
|
|
||||||
result: {}
|
|
||||||
schema:
|
|
||||||
description: ''
|
|
||||||
properties:
|
|
||||||
feedback: {}
|
|
||||||
goal:
|
|
||||||
properties:
|
|
||||||
bottle_num:
|
|
||||||
type: string
|
|
||||||
required:
|
|
||||||
- bottle_num
|
|
||||||
type: object
|
|
||||||
result: {}
|
|
||||||
required:
|
|
||||||
- goal
|
|
||||||
title: func_pack_send_bottle_num参数
|
|
||||||
type: object
|
|
||||||
type: UniLabJsonCommand
|
|
||||||
auto-func_pack_send_finished_cmd:
|
|
||||||
feedback: {}
|
|
||||||
goal: {}
|
|
||||||
goal_default: {}
|
|
||||||
handles: {}
|
|
||||||
placeholder_keys: {}
|
|
||||||
result: {}
|
|
||||||
schema:
|
|
||||||
description: ''
|
|
||||||
properties:
|
|
||||||
feedback: {}
|
|
||||||
goal:
|
|
||||||
properties: {}
|
|
||||||
required: []
|
|
||||||
type: object
|
|
||||||
result: {}
|
|
||||||
required:
|
|
||||||
- goal
|
|
||||||
title: func_pack_send_finished_cmd参数
|
|
||||||
type: object
|
|
||||||
type: UniLabJsonCommand
|
|
||||||
auto-func_pack_send_msg_cmd:
|
|
||||||
feedback: {}
|
|
||||||
goal: {}
|
|
||||||
goal_default:
|
|
||||||
assembly_pressure: null
|
|
||||||
assembly_type: null
|
|
||||||
elec_use_num: null
|
|
||||||
elec_vol: null
|
|
||||||
handles: {}
|
|
||||||
placeholder_keys: {}
|
|
||||||
result: {}
|
|
||||||
schema:
|
|
||||||
description: ''
|
|
||||||
properties:
|
|
||||||
feedback: {}
|
|
||||||
goal:
|
|
||||||
properties:
|
|
||||||
assembly_pressure:
|
|
||||||
type: string
|
|
||||||
assembly_type:
|
|
||||||
type: string
|
|
||||||
elec_use_num:
|
|
||||||
type: string
|
|
||||||
elec_vol:
|
|
||||||
type: string
|
|
||||||
required:
|
|
||||||
- elec_use_num
|
|
||||||
- elec_vol
|
|
||||||
- assembly_type
|
|
||||||
- assembly_pressure
|
|
||||||
type: object
|
|
||||||
result: {}
|
|
||||||
required:
|
|
||||||
- goal
|
|
||||||
title: func_pack_send_msg_cmd参数
|
|
||||||
type: object
|
|
||||||
type: UniLabJsonCommand
|
|
||||||
auto-func_read_data_and_output:
|
|
||||||
feedback: {}
|
|
||||||
goal: {}
|
|
||||||
goal_default:
|
|
||||||
file_path: D:\coin_cell_data
|
|
||||||
handles: {}
|
|
||||||
placeholder_keys: {}
|
|
||||||
result: {}
|
|
||||||
schema:
|
|
||||||
description: ''
|
|
||||||
properties:
|
|
||||||
feedback: {}
|
|
||||||
goal:
|
|
||||||
properties:
|
|
||||||
file_path:
|
|
||||||
default: D:\coin_cell_data
|
|
||||||
type: string
|
|
||||||
required: []
|
|
||||||
type: object
|
|
||||||
result: {}
|
|
||||||
required:
|
|
||||||
- goal
|
|
||||||
title: func_read_data_and_output参数
|
|
||||||
type: object
|
|
||||||
type: UniLabJsonCommand
|
|
||||||
auto-func_stop_read_data:
|
|
||||||
feedback: {}
|
|
||||||
goal: {}
|
|
||||||
goal_default: {}
|
|
||||||
handles: {}
|
|
||||||
placeholder_keys: {}
|
|
||||||
result: {}
|
|
||||||
schema:
|
|
||||||
description: ''
|
|
||||||
properties:
|
|
||||||
feedback: {}
|
|
||||||
goal:
|
|
||||||
properties: {}
|
|
||||||
required: []
|
|
||||||
type: object
|
|
||||||
result: {}
|
|
||||||
required:
|
|
||||||
- goal
|
|
||||||
title: func_stop_read_data参数
|
|
||||||
type: object
|
|
||||||
type: UniLabJsonCommand
|
|
||||||
auto-modify_deck_name:
|
|
||||||
feedback: {}
|
|
||||||
goal: {}
|
|
||||||
goal_default:
|
|
||||||
resource_name: null
|
|
||||||
handles: {}
|
|
||||||
placeholder_keys: {}
|
|
||||||
result: {}
|
|
||||||
schema:
|
|
||||||
description: ''
|
|
||||||
properties:
|
|
||||||
feedback: {}
|
|
||||||
goal:
|
|
||||||
properties:
|
|
||||||
resource_name:
|
|
||||||
type: string
|
|
||||||
required:
|
|
||||||
- resource_name
|
|
||||||
type: object
|
|
||||||
result: {}
|
|
||||||
required:
|
|
||||||
- goal
|
|
||||||
title: modify_deck_name参数
|
|
||||||
type: object
|
|
||||||
type: UniLabJsonCommand
|
|
||||||
auto-post_init:
|
|
||||||
feedback: {}
|
|
||||||
goal: {}
|
|
||||||
goal_default:
|
|
||||||
ros_node: null
|
|
||||||
handles: {}
|
|
||||||
placeholder_keys: {}
|
|
||||||
result: {}
|
|
||||||
schema:
|
|
||||||
description: ''
|
|
||||||
properties:
|
|
||||||
feedback: {}
|
|
||||||
goal:
|
|
||||||
properties:
|
|
||||||
ros_node:
|
|
||||||
type: object
|
|
||||||
required:
|
|
||||||
- ros_node
|
|
||||||
type: object
|
|
||||||
result: {}
|
|
||||||
required:
|
|
||||||
- goal
|
|
||||||
title: post_init参数
|
|
||||||
type: object
|
|
||||||
type: UniLabJsonCommand
|
|
||||||
auto-qiming_coin_cell_code:
|
|
||||||
feedback: {}
|
|
||||||
goal: {}
|
|
||||||
goal_default:
|
|
||||||
battery_clean_ignore: false
|
|
||||||
battery_pressure: 4000
|
|
||||||
battery_pressure_mode: true
|
|
||||||
fujipian_juzhendianwei: 0
|
|
||||||
fujipian_panshu: null
|
|
||||||
gemo_juzhendianwei: 0
|
|
||||||
gemopanshu: 0
|
|
||||||
lvbodian: true
|
|
||||||
handles: {}
|
|
||||||
placeholder_keys: {}
|
|
||||||
result: {}
|
|
||||||
schema:
|
|
||||||
description: ''
|
|
||||||
properties:
|
|
||||||
feedback: {}
|
|
||||||
goal:
|
|
||||||
properties:
|
|
||||||
battery_clean_ignore:
|
|
||||||
default: false
|
|
||||||
type: boolean
|
|
||||||
battery_pressure:
|
|
||||||
default: 4000
|
|
||||||
type: integer
|
|
||||||
battery_pressure_mode:
|
|
||||||
default: true
|
|
||||||
type: boolean
|
|
||||||
fujipian_juzhendianwei:
|
|
||||||
default: 0
|
|
||||||
type: integer
|
|
||||||
fujipian_panshu:
|
|
||||||
type: integer
|
|
||||||
gemo_juzhendianwei:
|
|
||||||
default: 0
|
|
||||||
type: integer
|
|
||||||
gemopanshu:
|
|
||||||
default: 0
|
|
||||||
type: integer
|
|
||||||
lvbodian:
|
|
||||||
default: true
|
|
||||||
type: boolean
|
|
||||||
required:
|
|
||||||
- fujipian_panshu
|
|
||||||
type: object
|
|
||||||
result: {}
|
|
||||||
required:
|
|
||||||
- goal
|
|
||||||
title: qiming_coin_cell_code参数
|
|
||||||
type: object
|
|
||||||
type: UniLabJsonCommand
|
|
||||||
module: unilabos.devices.workstation.coin_cell_assembly.coin_cell_assembly:CoinCellAssemblyWorkstation
|
|
||||||
status_types:
|
|
||||||
data_assembly_coin_cell_num: int
|
|
||||||
data_assembly_pressure: int
|
|
||||||
data_assembly_time: float
|
|
||||||
data_axis_x_pos: float
|
|
||||||
data_axis_y_pos: float
|
|
||||||
data_axis_z_pos: float
|
|
||||||
data_coin_cell_code: str
|
|
||||||
data_coin_num: int
|
|
||||||
data_electrolyte_code: str
|
|
||||||
data_electrolyte_volume: int
|
|
||||||
data_glove_box_o2_content: float
|
|
||||||
data_glove_box_pressure: float
|
|
||||||
data_glove_box_water_content: float
|
|
||||||
data_open_circuit_voltage: float
|
|
||||||
data_pole_weight: float
|
|
||||||
request_rec_msg_status: bool
|
|
||||||
request_send_msg_status: bool
|
|
||||||
sys_mode: str
|
|
||||||
sys_status: str
|
|
||||||
type: python
|
|
||||||
config_info: []
|
|
||||||
description: ''
|
|
||||||
handles: []
|
|
||||||
icon: coin_cell_assembly_picture.webp
|
|
||||||
init_param_schema:
|
|
||||||
config:
|
|
||||||
properties:
|
|
||||||
address:
|
|
||||||
default: 172.21.32.111
|
|
||||||
type: string
|
|
||||||
debug_mode:
|
|
||||||
default: false
|
|
||||||
type: boolean
|
|
||||||
deck:
|
|
||||||
type: object
|
|
||||||
port:
|
|
||||||
default: '502'
|
|
||||||
type: string
|
|
||||||
required: []
|
|
||||||
type: object
|
|
||||||
data:
|
|
||||||
properties:
|
|
||||||
data_assembly_coin_cell_num:
|
|
||||||
type: integer
|
|
||||||
data_assembly_pressure:
|
|
||||||
type: integer
|
|
||||||
data_assembly_time:
|
|
||||||
type: number
|
|
||||||
data_axis_x_pos:
|
|
||||||
type: number
|
|
||||||
data_axis_y_pos:
|
|
||||||
type: number
|
|
||||||
data_axis_z_pos:
|
|
||||||
type: number
|
|
||||||
data_coin_cell_code:
|
|
||||||
type: string
|
|
||||||
data_coin_num:
|
|
||||||
type: integer
|
|
||||||
data_electrolyte_code:
|
|
||||||
type: string
|
|
||||||
data_electrolyte_volume:
|
|
||||||
type: integer
|
|
||||||
data_glove_box_o2_content:
|
|
||||||
type: number
|
|
||||||
data_glove_box_pressure:
|
|
||||||
type: number
|
|
||||||
data_glove_box_water_content:
|
|
||||||
type: number
|
|
||||||
data_open_circuit_voltage:
|
|
||||||
type: number
|
|
||||||
data_pole_weight:
|
|
||||||
type: number
|
|
||||||
request_rec_msg_status:
|
|
||||||
type: boolean
|
|
||||||
request_send_msg_status:
|
|
||||||
type: boolean
|
|
||||||
sys_mode:
|
|
||||||
type: string
|
|
||||||
sys_status:
|
|
||||||
type: string
|
|
||||||
required:
|
|
||||||
- sys_status
|
|
||||||
- sys_mode
|
|
||||||
- request_rec_msg_status
|
|
||||||
- request_send_msg_status
|
|
||||||
- data_assembly_coin_cell_num
|
|
||||||
- data_assembly_time
|
|
||||||
- data_open_circuit_voltage
|
|
||||||
- data_axis_x_pos
|
|
||||||
- data_axis_y_pos
|
|
||||||
- data_axis_z_pos
|
|
||||||
- data_pole_weight
|
|
||||||
- data_assembly_pressure
|
|
||||||
- data_electrolyte_volume
|
|
||||||
- data_coin_num
|
|
||||||
- data_coin_cell_code
|
|
||||||
- data_electrolyte_code
|
|
||||||
- data_glove_box_pressure
|
|
||||||
- data_glove_box_o2_content
|
|
||||||
- data_glove_box_water_content
|
|
||||||
type: object
|
|
||||||
registry_type: device
|
|
||||||
version: 1.0.0
|
|
||||||
1582
unilabos/registry/devices/dispensing_station_bioyond.yaml
Normal file
1582
unilabos/registry/devices/dispensing_station_bioyond.yaml
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1361,7 +1361,8 @@ laiyu_liquid:
|
|||||||
mix_liquid_height: 0.0
|
mix_liquid_height: 0.0
|
||||||
mix_rate: 0
|
mix_rate: 0
|
||||||
mix_stage: ''
|
mix_stage: ''
|
||||||
mix_times: 0
|
mix_times:
|
||||||
|
- 0
|
||||||
mix_vol: 0
|
mix_vol: 0
|
||||||
none_keys:
|
none_keys:
|
||||||
- ''
|
- ''
|
||||||
@@ -1491,9 +1492,11 @@ laiyu_liquid:
|
|||||||
mix_stage:
|
mix_stage:
|
||||||
type: string
|
type: string
|
||||||
mix_times:
|
mix_times:
|
||||||
maximum: 2147483647
|
items:
|
||||||
minimum: -2147483648
|
maximum: 2147483647
|
||||||
type: integer
|
minimum: -2147483648
|
||||||
|
type: integer
|
||||||
|
type: array
|
||||||
mix_vol:
|
mix_vol:
|
||||||
maximum: 2147483647
|
maximum: 2147483647
|
||||||
minimum: -2147483648
|
minimum: -2147483648
|
||||||
|
|||||||
@@ -3994,7 +3994,8 @@ liquid_handler:
|
|||||||
mix_liquid_height: 0.0
|
mix_liquid_height: 0.0
|
||||||
mix_rate: 0
|
mix_rate: 0
|
||||||
mix_stage: ''
|
mix_stage: ''
|
||||||
mix_times: 0
|
mix_times:
|
||||||
|
- 0
|
||||||
mix_vol: 0
|
mix_vol: 0
|
||||||
none_keys:
|
none_keys:
|
||||||
- ''
|
- ''
|
||||||
@@ -4150,9 +4151,11 @@ liquid_handler:
|
|||||||
mix_stage:
|
mix_stage:
|
||||||
type: string
|
type: string
|
||||||
mix_times:
|
mix_times:
|
||||||
maximum: 2147483647
|
items:
|
||||||
minimum: -2147483648
|
maximum: 2147483647
|
||||||
type: integer
|
minimum: -2147483648
|
||||||
|
type: integer
|
||||||
|
type: array
|
||||||
mix_vol:
|
mix_vol:
|
||||||
maximum: 2147483647
|
maximum: 2147483647
|
||||||
minimum: -2147483648
|
minimum: -2147483648
|
||||||
@@ -5012,7 +5015,8 @@ liquid_handler.biomek:
|
|||||||
mix_liquid_height: 0.0
|
mix_liquid_height: 0.0
|
||||||
mix_rate: 0
|
mix_rate: 0
|
||||||
mix_stage: ''
|
mix_stage: ''
|
||||||
mix_times: 0
|
mix_times:
|
||||||
|
- 0
|
||||||
mix_vol: 0
|
mix_vol: 0
|
||||||
none_keys:
|
none_keys:
|
||||||
- ''
|
- ''
|
||||||
@@ -5155,9 +5159,11 @@ liquid_handler.biomek:
|
|||||||
mix_stage:
|
mix_stage:
|
||||||
type: string
|
type: string
|
||||||
mix_times:
|
mix_times:
|
||||||
maximum: 2147483647
|
items:
|
||||||
minimum: -2147483648
|
maximum: 2147483647
|
||||||
type: integer
|
minimum: -2147483648
|
||||||
|
type: integer
|
||||||
|
type: array
|
||||||
mix_vol:
|
mix_vol:
|
||||||
maximum: 2147483647
|
maximum: 2147483647
|
||||||
minimum: -2147483648
|
minimum: -2147483648
|
||||||
@@ -7801,7 +7807,8 @@ liquid_handler.prcxi:
|
|||||||
mix_liquid_height: 0.0
|
mix_liquid_height: 0.0
|
||||||
mix_rate: 0
|
mix_rate: 0
|
||||||
mix_stage: ''
|
mix_stage: ''
|
||||||
mix_times: 0
|
mix_times:
|
||||||
|
- 0
|
||||||
mix_vol: 0
|
mix_vol: 0
|
||||||
none_keys:
|
none_keys:
|
||||||
- ''
|
- ''
|
||||||
@@ -7930,9 +7937,11 @@ liquid_handler.prcxi:
|
|||||||
mix_stage:
|
mix_stage:
|
||||||
type: string
|
type: string
|
||||||
mix_times:
|
mix_times:
|
||||||
maximum: 2147483647
|
items:
|
||||||
minimum: -2147483648
|
maximum: 2147483647
|
||||||
type: integer
|
minimum: -2147483648
|
||||||
|
type: integer
|
||||||
|
type: array
|
||||||
mix_vol:
|
mix_vol:
|
||||||
maximum: 2147483647
|
maximum: 2147483647
|
||||||
minimum: -2147483648
|
minimum: -2147483648
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -834,3 +834,174 @@ linear_motion.toyo_xyz.sim:
|
|||||||
mesh: toyo_xyz
|
mesh: toyo_xyz
|
||||||
type: device
|
type: device
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
|
motor.iCL42:
|
||||||
|
category:
|
||||||
|
- robot_linear_motion
|
||||||
|
class:
|
||||||
|
action_value_mappings:
|
||||||
|
auto-execute_run_motor:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
mode: null
|
||||||
|
position: null
|
||||||
|
velocity: null
|
||||||
|
handles: {}
|
||||||
|
placeholder_keys: {}
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: 步进电机执行运动函数。直接执行电机运动命令,包括位置设定、速度控制和路径规划。该函数处理底层的电机控制协议,消除警告信息,设置运动参数并启动电机运行。适用于需要直接控制电机运动的应用场景。
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
mode:
|
||||||
|
type: string
|
||||||
|
position:
|
||||||
|
type: number
|
||||||
|
velocity:
|
||||||
|
type: integer
|
||||||
|
required:
|
||||||
|
- mode
|
||||||
|
- position
|
||||||
|
- velocity
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: execute_run_motor参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-init_device:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default: {}
|
||||||
|
handles: {}
|
||||||
|
placeholder_keys: {}
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: iCL42电机设备初始化函数。建立与iCL42步进电机驱动器的串口通信连接,配置通信参数包括波特率、数据位、校验位等。该函数是电机使用前的必要步骤,确保驱动器处于可控状态并准备接收运动指令。
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: init_device参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-run_motor:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
mode: null
|
||||||
|
position: null
|
||||||
|
velocity: null
|
||||||
|
handles: {}
|
||||||
|
placeholder_keys: {}
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: 步进电机运动控制函数。根据指定的运动模式、目标位置和速度参数控制电机运动。支持多种运动模式和精确的位置控制,自动处理运动轨迹规划和执行。该函数提供异步执行和状态反馈,确保运动的准确性和可靠性。
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
mode:
|
||||||
|
type: string
|
||||||
|
position:
|
||||||
|
type: number
|
||||||
|
velocity:
|
||||||
|
type: integer
|
||||||
|
required:
|
||||||
|
- mode
|
||||||
|
- position
|
||||||
|
- velocity
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: run_motor参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
execute_command_from_outer:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
command: command
|
||||||
|
goal_default:
|
||||||
|
command: ''
|
||||||
|
handles: {}
|
||||||
|
result:
|
||||||
|
success: success
|
||||||
|
schema:
|
||||||
|
description: ''
|
||||||
|
properties:
|
||||||
|
feedback:
|
||||||
|
properties:
|
||||||
|
status:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- status
|
||||||
|
title: SendCmd_Feedback
|
||||||
|
type: object
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
command:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- command
|
||||||
|
title: SendCmd_Goal
|
||||||
|
type: object
|
||||||
|
result:
|
||||||
|
properties:
|
||||||
|
return_info:
|
||||||
|
type: string
|
||||||
|
success:
|
||||||
|
type: boolean
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
|
- success
|
||||||
|
title: SendCmd_Result
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: SendCmd
|
||||||
|
type: object
|
||||||
|
type: SendCmd
|
||||||
|
module: unilabos.devices.motor.iCL42:iCL42Driver
|
||||||
|
status_types:
|
||||||
|
is_executing_run: bool
|
||||||
|
motor_position: int
|
||||||
|
success: bool
|
||||||
|
type: python
|
||||||
|
config_info: []
|
||||||
|
description: iCL42步进电机驱动器,用于实验室设备的精密线性运动控制。该设备通过串口通信控制iCL42型步进电机驱动器,支持多种运动模式和精确的位置、速度控制。具备位置反馈、运行状态监控和故障检测功能。适用于自动进样器、样品传送、精密定位平台等需要准确线性运动控制的实验室自动化设备。
|
||||||
|
handles: []
|
||||||
|
icon: ''
|
||||||
|
init_param_schema:
|
||||||
|
config:
|
||||||
|
properties:
|
||||||
|
device_address:
|
||||||
|
default: 1
|
||||||
|
type: integer
|
||||||
|
device_com:
|
||||||
|
default: COM9
|
||||||
|
type: string
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
data:
|
||||||
|
properties:
|
||||||
|
is_executing_run:
|
||||||
|
type: boolean
|
||||||
|
motor_position:
|
||||||
|
type: integer
|
||||||
|
success:
|
||||||
|
type: boolean
|
||||||
|
required:
|
||||||
|
- motor_position
|
||||||
|
- is_executing_run
|
||||||
|
- success
|
||||||
|
type: object
|
||||||
|
version: 1.0.0
|
||||||
|
|||||||
7391
unilabos/registry/devices/work_station.yaml
Normal file
7391
unilabos/registry/devices/work_station.yaml
Normal file
File diff suppressed because it is too large
Load Diff
@@ -708,8 +708,6 @@ class Registry:
|
|||||||
for status_name, status_type in device_config["class"]["status_types"].items():
|
for status_name, status_type in device_config["class"]["status_types"].items():
|
||||||
device_config["class"]["status_types"][status_name] = status_str_type_mapping[status_type]
|
device_config["class"]["status_types"][status_name] = status_str_type_mapping[status_type]
|
||||||
for action_name, action_config in device_config["class"]["action_value_mappings"].items():
|
for action_name, action_config in device_config["class"]["action_value_mappings"].items():
|
||||||
if action_config["type"] not in action_str_type_mapping:
|
|
||||||
continue
|
|
||||||
action_config["type"] = action_str_type_mapping[action_config["type"]]
|
action_config["type"] = action_str_type_mapping[action_config["type"]]
|
||||||
# 添加内置的驱动命令动作
|
# 添加内置的驱动命令动作
|
||||||
self._add_builtin_actions(device_config, device_id)
|
self._add_builtin_actions(device_config, device_id)
|
||||||
|
|||||||
@@ -1,65 +0,0 @@
|
|||||||
YB_qiang_tou:
|
|
||||||
category:
|
|
||||||
- yb3
|
|
||||||
- YB_bottle
|
|
||||||
class:
|
|
||||||
module: unilabos.resources.bioyond.YB_bottles:YB_qiang_tou
|
|
||||||
type: pylabrobot
|
|
||||||
description: YB_qiang_tou
|
|
||||||
handles: []
|
|
||||||
icon: ''
|
|
||||||
init_param_schema: {}
|
|
||||||
registry_type: resource
|
|
||||||
version: 1.0.0
|
|
||||||
YB_20ml_fenyeping:
|
|
||||||
category:
|
|
||||||
- yb3
|
|
||||||
- YB_bottle
|
|
||||||
class:
|
|
||||||
module: unilabos.resources.bioyond.YB_bottles:YB_20ml_fenyeping
|
|
||||||
type: pylabrobot
|
|
||||||
description: YB_20ml_fenyeping
|
|
||||||
handles: []
|
|
||||||
icon: ''
|
|
||||||
init_param_schema: {}
|
|
||||||
registry_type: resource
|
|
||||||
version: 1.0.0
|
|
||||||
YB_5ml_fenyeping:
|
|
||||||
category:
|
|
||||||
- yb3
|
|
||||||
- YB_bottle
|
|
||||||
class:
|
|
||||||
module: unilabos.resources.bioyond.YB_bottles:YB_5ml_fenyeping
|
|
||||||
type: pylabrobot
|
|
||||||
description: YB_5ml_fenyeping
|
|
||||||
handles: []
|
|
||||||
icon: ''
|
|
||||||
init_param_schema: {}
|
|
||||||
registry_type: resource
|
|
||||||
version: 1.0.0
|
|
||||||
YB_pei_ye_da_Bottle:
|
|
||||||
category:
|
|
||||||
- yb3
|
|
||||||
- YB_bottle
|
|
||||||
class:
|
|
||||||
module: unilabos.resources.bioyond.YB_bottles:YB_pei_ye_da_Bottle
|
|
||||||
type: pylabrobot
|
|
||||||
description: YB_pei_ye_da_Bottle
|
|
||||||
handles: []
|
|
||||||
icon: ''
|
|
||||||
init_param_schema: {}
|
|
||||||
registry_type: resource
|
|
||||||
version: 1.0.0
|
|
||||||
YB_pei_ye_xiao_Bottle:
|
|
||||||
category:
|
|
||||||
- yb3
|
|
||||||
- YB_bottle
|
|
||||||
class:
|
|
||||||
module: unilabos.resources.bioyond.YB_bottles:YB_pei_ye_xiao_Bottle
|
|
||||||
type: pylabrobot
|
|
||||||
description: YB_pei_ye_xiao_Bottle
|
|
||||||
handles: []
|
|
||||||
icon: ''
|
|
||||||
init_param_schema: {}
|
|
||||||
registry_type: resource
|
|
||||||
version: 1.0.0
|
|
||||||
@@ -1,208 +0,0 @@
|
|||||||
YB_100ml_yeti:
|
|
||||||
category:
|
|
||||||
- yb3
|
|
||||||
- YB_bottle_carriers
|
|
||||||
class:
|
|
||||||
module: unilabos.resources.bioyond.YB_bottle_carriers:YB_100ml_yeti
|
|
||||||
type: pylabrobot
|
|
||||||
description: YB_100ml_yeti
|
|
||||||
handles: []
|
|
||||||
icon: ''
|
|
||||||
init_param_schema: {}
|
|
||||||
registry_type: resource
|
|
||||||
version: 1.0.0
|
|
||||||
YB_1BottleCarrier:
|
|
||||||
category:
|
|
||||||
- yb3
|
|
||||||
- YB_bottle_carriers
|
|
||||||
class:
|
|
||||||
module: unilabos.resources.bioyond.YB_bottle_carriers:YB_1BottleCarrier
|
|
||||||
type: pylabrobot
|
|
||||||
description: YB_1BottleCarrier
|
|
||||||
handles: []
|
|
||||||
icon: ''
|
|
||||||
init_param_schema: {}
|
|
||||||
registry_type: resource
|
|
||||||
version: 1.0.0
|
|
||||||
YB_gaonianye:
|
|
||||||
category:
|
|
||||||
- yb3
|
|
||||||
- YB_bottle_carriers
|
|
||||||
class:
|
|
||||||
module: unilabos.resources.bioyond.YB_bottle_carriers:YB_gaonianye
|
|
||||||
type: pylabrobot
|
|
||||||
description: YB_gaonianye
|
|
||||||
handles: []
|
|
||||||
icon: ''
|
|
||||||
init_param_schema: {}
|
|
||||||
registry_type: resource
|
|
||||||
version: 1.0.0
|
|
||||||
YB_peiyepingdaban:
|
|
||||||
category:
|
|
||||||
- yb3
|
|
||||||
- YB_bottle_carriers
|
|
||||||
class:
|
|
||||||
module: unilabos.resources.bioyond.YB_bottle_carriers:YB_peiyepingdaban
|
|
||||||
type: pylabrobot
|
|
||||||
description: YB_peiyepingdaban
|
|
||||||
handles: []
|
|
||||||
icon: ''
|
|
||||||
init_param_schema: {}
|
|
||||||
registry_type: resource
|
|
||||||
version: 1.0.0
|
|
||||||
YB_6StockCarrier:
|
|
||||||
category:
|
|
||||||
- yb3
|
|
||||||
- YB_bottle_carriers
|
|
||||||
class:
|
|
||||||
module: unilabos.resources.bioyond.YB_bottle_carriers:YB_6StockCarrier
|
|
||||||
type: pylabrobot
|
|
||||||
description: YB_6StockCarrier
|
|
||||||
handles: []
|
|
||||||
icon: ''
|
|
||||||
init_param_schema: {}
|
|
||||||
registry_type: resource
|
|
||||||
version: 1.0.0
|
|
||||||
YB_6VialCarrier:
|
|
||||||
category:
|
|
||||||
- yb3
|
|
||||||
- YB_bottle_carriers
|
|
||||||
class:
|
|
||||||
module: unilabos.resources.bioyond.YB_bottle_carriers:YB_6VialCarrier
|
|
||||||
type: pylabrobot
|
|
||||||
description: YB_6VialCarrier
|
|
||||||
handles: []
|
|
||||||
icon: ''
|
|
||||||
init_param_schema: {}
|
|
||||||
registry_type: resource
|
|
||||||
version: 1.0.0
|
|
||||||
YB_20ml_fenyepingban:
|
|
||||||
category:
|
|
||||||
- yb3
|
|
||||||
- YB_bottle_carriers
|
|
||||||
class:
|
|
||||||
module: unilabos.resources.bioyond.YB_bottle_carriers:YB_20ml_fenyepingban
|
|
||||||
type: pylabrobot
|
|
||||||
description: YB_20ml_fenyepingban
|
|
||||||
handles: []
|
|
||||||
icon: ''
|
|
||||||
init_param_schema: {}
|
|
||||||
registry_type: resource
|
|
||||||
version: 1.0.0
|
|
||||||
YB_5ml_fenyepingban:
|
|
||||||
category:
|
|
||||||
- yb3
|
|
||||||
- YB_bottle_carriers
|
|
||||||
class:
|
|
||||||
module: unilabos.resources.bioyond.YB_bottle_carriers:YB_5ml_fenyepingban
|
|
||||||
type: pylabrobot
|
|
||||||
description: YB_5ml_fenyepingban
|
|
||||||
handles: []
|
|
||||||
icon: ''
|
|
||||||
init_param_schema: {}
|
|
||||||
registry_type: resource
|
|
||||||
version: 1.0.0
|
|
||||||
YB_peiyepingxiaoban:
|
|
||||||
category:
|
|
||||||
- yb3
|
|
||||||
- YB_bottle_carriers
|
|
||||||
class:
|
|
||||||
module: unilabos.resources.bioyond.YB_bottle_carriers:YB_peiyepingxiaoban
|
|
||||||
type: pylabrobot
|
|
||||||
description: YB_peiyepingxiaoban
|
|
||||||
handles: []
|
|
||||||
icon: ''
|
|
||||||
init_param_schema: {}
|
|
||||||
registry_type: resource
|
|
||||||
version: 1.0.0
|
|
||||||
YB_shi_pei_qi_kuai:
|
|
||||||
category:
|
|
||||||
- yb3
|
|
||||||
- YB_bottle_carriers
|
|
||||||
class:
|
|
||||||
module: unilabos.resources.bioyond.YB_bottle_carriers:YB_shi_pei_qi_kuai
|
|
||||||
type: pylabrobot
|
|
||||||
description: YB_shi_pei_qi_kuai
|
|
||||||
handles: []
|
|
||||||
icon: ''
|
|
||||||
init_param_schema: {}
|
|
||||||
registry_type: resource
|
|
||||||
version: 1.0.0
|
|
||||||
YB_qiang_tou_he:
|
|
||||||
category:
|
|
||||||
- yb3
|
|
||||||
- YB_bottle_carriers
|
|
||||||
class:
|
|
||||||
module: unilabos.resources.bioyond.YB_bottle_carriers:YB_qiang_tou_he
|
|
||||||
type: pylabrobot
|
|
||||||
description: YB_qiang_tou_he
|
|
||||||
handles: []
|
|
||||||
icon: ''
|
|
||||||
init_param_schema: {}
|
|
||||||
registry_type: resource
|
|
||||||
version: 1.0.0
|
|
||||||
YB_gao_nian_ye_Bottle:
|
|
||||||
category:
|
|
||||||
- yb3
|
|
||||||
- YB_bottle_carriers
|
|
||||||
class:
|
|
||||||
module: unilabos.resources.bioyond.YB_bottles:YB_gao_nian_ye_Bottle
|
|
||||||
type: pylabrobot
|
|
||||||
description: YB_gao_nian_ye_Bottle
|
|
||||||
handles: []
|
|
||||||
icon: ''
|
|
||||||
init_param_schema: {}
|
|
||||||
registry_type: resource
|
|
||||||
version: 1.0.0
|
|
||||||
YB_jia_yang_tou_da:
|
|
||||||
category:
|
|
||||||
- yb3
|
|
||||||
- YB_bottle_carriers
|
|
||||||
class:
|
|
||||||
module: unilabos.resources.bioyond.YB_bottles:YB_jia_yang_tou_da
|
|
||||||
type: pylabrobot
|
|
||||||
description: YB_jia_yang_tou_da
|
|
||||||
handles: []
|
|
||||||
icon: ''
|
|
||||||
init_param_schema: {}
|
|
||||||
registry_type: resource
|
|
||||||
version: 1.0.0
|
|
||||||
YB_jia_yang_tou_da_Carrier:
|
|
||||||
category:
|
|
||||||
- yb3
|
|
||||||
- YB_bottle_carriers
|
|
||||||
class:
|
|
||||||
module: unilabos.resources.bioyond.YB_bottle_carriers:YB_jia_yang_tou_da_Carrier
|
|
||||||
type: pylabrobot
|
|
||||||
description: YB_jia_yang_tou_da_Carrier
|
|
||||||
handles: []
|
|
||||||
icon: ''
|
|
||||||
init_param_schema: {}
|
|
||||||
registry_type: resource
|
|
||||||
version: 1.0.0
|
|
||||||
YB_ye_100ml_Bottle:
|
|
||||||
category:
|
|
||||||
- yb3
|
|
||||||
- YB_bottle_carriers
|
|
||||||
class:
|
|
||||||
module: unilabos.resources.bioyond.YB_bottles:YB_ye_100ml_Bottle
|
|
||||||
type: pylabrobot
|
|
||||||
description: YB_ye_100ml_Bottle
|
|
||||||
handles: []
|
|
||||||
icon: ''
|
|
||||||
init_param_schema: {}
|
|
||||||
registry_type: resource
|
|
||||||
version: 1.0.0
|
|
||||||
YB_ye_Bottle:
|
|
||||||
category:
|
|
||||||
- yb3
|
|
||||||
- YB_bottle_carriers
|
|
||||||
class:
|
|
||||||
module: unilabos.resources.bioyond.YB_bottles:YB_ye_Bottle
|
|
||||||
type: pylabrobot
|
|
||||||
description: YB_ye_Bottle
|
|
||||||
handles: []
|
|
||||||
icon: ''
|
|
||||||
init_param_schema: {}
|
|
||||||
registry_type: resource
|
|
||||||
version: 1.0.0
|
|
||||||
48
unilabos/registry/resources/bioyond/bottle_carriers.yaml
Normal file
48
unilabos/registry/resources/bioyond/bottle_carriers.yaml
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
BIOYOND_PolymerStation_1BottleCarrier:
|
||||||
|
category:
|
||||||
|
- bottle_carriers
|
||||||
|
class:
|
||||||
|
module: unilabos.resources.bioyond.bottle_carriers:BIOYOND_PolymerStation_1BottleCarrier
|
||||||
|
type: pylabrobot
|
||||||
|
description: BIOYOND_PolymerStation_1BottleCarrier
|
||||||
|
handles: []
|
||||||
|
icon: ''
|
||||||
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
|
version: 1.0.0
|
||||||
|
BIOYOND_PolymerStation_1FlaskCarrier:
|
||||||
|
category:
|
||||||
|
- bottle_carriers
|
||||||
|
class:
|
||||||
|
module: unilabos.resources.bioyond.bottle_carriers:BIOYOND_PolymerStation_1FlaskCarrier
|
||||||
|
type: pylabrobot
|
||||||
|
description: BIOYOND_PolymerStation_1FlaskCarrier
|
||||||
|
handles: []
|
||||||
|
icon: ''
|
||||||
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
|
version: 1.0.0
|
||||||
|
BIOYOND_PolymerStation_6StockCarrier:
|
||||||
|
category:
|
||||||
|
- bottle_carriers
|
||||||
|
class:
|
||||||
|
module: unilabos.resources.bioyond.bottle_carriers:BIOYOND_PolymerStation_6StockCarrier
|
||||||
|
type: pylabrobot
|
||||||
|
description: BIOYOND_PolymerStation_6StockCarrier
|
||||||
|
handles: []
|
||||||
|
icon: ''
|
||||||
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
|
version: 1.0.0
|
||||||
|
BIOYOND_PolymerStation_6VialCarrier:
|
||||||
|
category:
|
||||||
|
- bottle_carriers
|
||||||
|
class:
|
||||||
|
module: unilabos.resources.bioyond.bottle_carriers:BIOYOND_PolymerStation_6VialCarrier
|
||||||
|
type: pylabrobot
|
||||||
|
description: BIOYOND_PolymerStation_6VialCarrier
|
||||||
|
handles: []
|
||||||
|
icon: ''
|
||||||
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
|
version: 1.0.0
|
||||||
50
unilabos/registry/resources/bioyond/bottles.yaml
Normal file
50
unilabos/registry/resources/bioyond/bottles.yaml
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
BIOYOND_PolymerStation_Liquid_Vial:
|
||||||
|
category:
|
||||||
|
- bottles
|
||||||
|
class:
|
||||||
|
module: unilabos.resources.bioyond.bottles:BIOYOND_PolymerStation_Liquid_Vial
|
||||||
|
type: pylabrobot
|
||||||
|
handles: []
|
||||||
|
icon: ''
|
||||||
|
init_param_schema: {}
|
||||||
|
version: 1.0.0
|
||||||
|
BIOYOND_PolymerStation_Reagent_Bottle:
|
||||||
|
category:
|
||||||
|
- bottles
|
||||||
|
class:
|
||||||
|
module: unilabos.resources.bioyond.bottles:BIOYOND_PolymerStation_Reagent_Bottle
|
||||||
|
type: pylabrobot
|
||||||
|
handles: []
|
||||||
|
icon: ''
|
||||||
|
init_param_schema: {}
|
||||||
|
version: 1.0.0
|
||||||
|
BIOYOND_PolymerStation_Solid_Stock:
|
||||||
|
category:
|
||||||
|
- bottles
|
||||||
|
class:
|
||||||
|
module: unilabos.resources.bioyond.bottles:BIOYOND_PolymerStation_Solid_Stock
|
||||||
|
type: pylabrobot
|
||||||
|
handles: []
|
||||||
|
icon: ''
|
||||||
|
init_param_schema: {}
|
||||||
|
version: 1.0.0
|
||||||
|
BIOYOND_PolymerStation_Solid_Vial:
|
||||||
|
category:
|
||||||
|
- bottles
|
||||||
|
class:
|
||||||
|
module: unilabos.resources.bioyond.bottles:BIOYOND_PolymerStation_Solid_Vial
|
||||||
|
type: pylabrobot
|
||||||
|
handles: []
|
||||||
|
icon: ''
|
||||||
|
init_param_schema: {}
|
||||||
|
version: 1.0.0
|
||||||
|
BIOYOND_PolymerStation_Solution_Beaker:
|
||||||
|
category:
|
||||||
|
- bottles
|
||||||
|
class:
|
||||||
|
module: unilabos.resources.bioyond.bottles:BIOYOND_PolymerStation_Solution_Beaker
|
||||||
|
type: pylabrobot
|
||||||
|
handles: []
|
||||||
|
icon: ''
|
||||||
|
init_param_schema: {}
|
||||||
|
version: 1.0.0
|
||||||
@@ -22,15 +22,3 @@ BIOYOND_PolymerReactionStation_Deck:
|
|||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
registry_type: resource
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
BIOYOND_YB_Deck:
|
|
||||||
category:
|
|
||||||
- deck
|
|
||||||
class:
|
|
||||||
module: unilabos.resources.bioyond.decks:BIOYOND_YB_Deck
|
|
||||||
type: pylabrobot
|
|
||||||
description: BIOYOND_YB_Deck
|
|
||||||
handles: []
|
|
||||||
icon: 配液站.webp
|
|
||||||
init_param_schema: {}
|
|
||||||
registry_type: resource
|
|
||||||
version: 1.0.0
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ container:
|
|||||||
- container
|
- container
|
||||||
class:
|
class:
|
||||||
module: unilabos.resources.container:RegularContainer
|
module: unilabos.resources.container:RegularContainer
|
||||||
type: pylabrobot
|
type: unilabos
|
||||||
description: regular organic container
|
description: regular organic container
|
||||||
handles:
|
handles:
|
||||||
- data_key: fluid_in
|
- data_key: fluid_in
|
||||||
|
|||||||
@@ -1,653 +0,0 @@
|
|||||||
from pylabrobot.resources import create_homogeneous_resources, Coordinate, ResourceHolder, create_ordered_items_2d
|
|
||||||
|
|
||||||
from unilabos.resources.itemized_carrier import Bottle, BottleCarrier
|
|
||||||
from unilabos.resources.bioyond.YB_bottles import (
|
|
||||||
YB_jia_yang_tou_da,
|
|
||||||
YB_ye_Bottle,
|
|
||||||
YB_ye_100ml_Bottle,
|
|
||||||
YB_gao_nian_ye_Bottle,
|
|
||||||
YB_5ml_fenyeping,
|
|
||||||
YB_20ml_fenyeping,
|
|
||||||
YB_pei_ye_xiao_Bottle,
|
|
||||||
YB_pei_ye_da_Bottle,
|
|
||||||
YB_qiang_tou,
|
|
||||||
)
|
|
||||||
# 命名约定:试剂瓶-Bottle,烧杯-Beaker,烧瓶-Flask,小瓶-Vial
|
|
||||||
|
|
||||||
|
|
||||||
def BIOYOND_Electrolyte_6VialCarrier(name: str) -> BottleCarrier:
|
|
||||||
"""6瓶载架 - 2x3布局"""
|
|
||||||
|
|
||||||
# 载架尺寸 (mm)
|
|
||||||
carrier_size_x = 127.8
|
|
||||||
carrier_size_y = 85.5
|
|
||||||
carrier_size_z = 50.0
|
|
||||||
|
|
||||||
# 瓶位尺寸
|
|
||||||
bottle_diameter = 30.0
|
|
||||||
bottle_spacing_x = 42.0 # X方向间距
|
|
||||||
bottle_spacing_y = 35.0 # Y方向间距
|
|
||||||
|
|
||||||
# 计算起始位置 (居中排列)
|
|
||||||
start_x = (carrier_size_x - (3 - 1) * bottle_spacing_x - bottle_diameter) / 2
|
|
||||||
start_y = (carrier_size_y - (2 - 1) * bottle_spacing_y - bottle_diameter) / 2
|
|
||||||
|
|
||||||
sites = create_ordered_items_2d(
|
|
||||||
klass=ResourceHolder,
|
|
||||||
num_items_x=3,
|
|
||||||
num_items_y=2,
|
|
||||||
dx=start_x,
|
|
||||||
dy=start_y,
|
|
||||||
dz=5.0,
|
|
||||||
item_dx=bottle_spacing_x,
|
|
||||||
item_dy=bottle_spacing_y,
|
|
||||||
|
|
||||||
size_x=bottle_diameter,
|
|
||||||
size_y=bottle_diameter,
|
|
||||||
size_z=carrier_size_z,
|
|
||||||
)
|
|
||||||
for k, v in sites.items():
|
|
||||||
v.name = f"{name}_{v.name}"
|
|
||||||
|
|
||||||
carrier = BottleCarrier(
|
|
||||||
name=name,
|
|
||||||
size_x=carrier_size_x,
|
|
||||||
size_y=carrier_size_y,
|
|
||||||
size_z=carrier_size_z,
|
|
||||||
sites=sites,
|
|
||||||
model="Electrolyte_6VialCarrier",
|
|
||||||
)
|
|
||||||
carrier.num_items_x = 3
|
|
||||||
carrier.num_items_y = 2
|
|
||||||
carrier.num_items_z = 1
|
|
||||||
# for i in range(6):
|
|
||||||
# carrier[i] = YB_Solid_Vial(f"{name}_vial_{i+1}")
|
|
||||||
return carrier
|
|
||||||
|
|
||||||
|
|
||||||
def BIOYOND_Electrolyte_1BottleCarrier(name: str) -> BottleCarrier:
|
|
||||||
"""1瓶载架 - 单个中央位置"""
|
|
||||||
|
|
||||||
# 载架尺寸 (mm)
|
|
||||||
carrier_size_x = 127.8
|
|
||||||
carrier_size_y = 85.5
|
|
||||||
carrier_size_z = 100.0
|
|
||||||
|
|
||||||
# 烧杯尺寸
|
|
||||||
beaker_diameter = 80.0
|
|
||||||
|
|
||||||
# 计算中央位置
|
|
||||||
center_x = (carrier_size_x - beaker_diameter) / 2
|
|
||||||
center_y = (carrier_size_y - beaker_diameter) / 2
|
|
||||||
center_z = 5.0
|
|
||||||
|
|
||||||
carrier = BottleCarrier(
|
|
||||||
name=name,
|
|
||||||
size_x=carrier_size_x,
|
|
||||||
size_y=carrier_size_y,
|
|
||||||
size_z=carrier_size_z,
|
|
||||||
sites=create_homogeneous_resources(
|
|
||||||
klass=ResourceHolder,
|
|
||||||
locations=[Coordinate(center_x, center_y, center_z)],
|
|
||||||
resource_size_x=beaker_diameter,
|
|
||||||
resource_size_y=beaker_diameter,
|
|
||||||
name_prefix=name,
|
|
||||||
),
|
|
||||||
model="Electrolyte_1BottleCarrier",
|
|
||||||
)
|
|
||||||
carrier.num_items_x = 1
|
|
||||||
carrier.num_items_y = 1
|
|
||||||
carrier.num_items_z = 1
|
|
||||||
# carrier[0] = YB_Solution_Beaker(f"{name}_beaker_1")
|
|
||||||
return carrier
|
|
||||||
|
|
||||||
|
|
||||||
def YB_6StockCarrier(name: str) -> BottleCarrier:
|
|
||||||
"""6瓶载架 - 2x3布局"""
|
|
||||||
|
|
||||||
# 载架尺寸 (mm)
|
|
||||||
carrier_size_x = 127.8
|
|
||||||
carrier_size_y = 85.5
|
|
||||||
carrier_size_z = 50.0
|
|
||||||
|
|
||||||
# 瓶位尺寸
|
|
||||||
bottle_diameter = 20.0
|
|
||||||
bottle_spacing_x = 42.0 # X方向间距
|
|
||||||
bottle_spacing_y = 35.0 # Y方向间距
|
|
||||||
|
|
||||||
# 计算起始位置 (居中排列)
|
|
||||||
start_x = (carrier_size_x - (3 - 1) * bottle_spacing_x - bottle_diameter) / 2
|
|
||||||
start_y = (carrier_size_y - (2 - 1) * bottle_spacing_y - bottle_diameter) / 2
|
|
||||||
|
|
||||||
sites = create_ordered_items_2d(
|
|
||||||
klass=ResourceHolder,
|
|
||||||
num_items_x=3,
|
|
||||||
num_items_y=2,
|
|
||||||
dx=start_x,
|
|
||||||
dy=start_y,
|
|
||||||
dz=5.0,
|
|
||||||
item_dx=bottle_spacing_x,
|
|
||||||
item_dy=bottle_spacing_y,
|
|
||||||
|
|
||||||
size_x=bottle_diameter,
|
|
||||||
size_y=bottle_diameter,
|
|
||||||
size_z=carrier_size_z,
|
|
||||||
)
|
|
||||||
for k, v in sites.items():
|
|
||||||
v.name = f"{name}_{v.name}"
|
|
||||||
|
|
||||||
carrier = BottleCarrier(
|
|
||||||
name=name,
|
|
||||||
size_x=carrier_size_x,
|
|
||||||
size_y=carrier_size_y,
|
|
||||||
size_z=carrier_size_z,
|
|
||||||
sites=sites,
|
|
||||||
model="6StockCarrier",
|
|
||||||
)
|
|
||||||
carrier.num_items_x = 3
|
|
||||||
carrier.num_items_y = 2
|
|
||||||
carrier.num_items_z = 1
|
|
||||||
ordering = ["A1", "A2", "A3", "B1", "B2", "B3"] # 自定义顺序
|
|
||||||
# for i in range(6):
|
|
||||||
# carrier[i] = YB_Solid_Stock(f"{name}_vial_{ordering[i]}")
|
|
||||||
return carrier
|
|
||||||
|
|
||||||
|
|
||||||
def YB_6VialCarrier(name: str) -> BottleCarrier:
|
|
||||||
"""6瓶载架 - 2x3布局"""
|
|
||||||
|
|
||||||
# 载架尺寸 (mm)
|
|
||||||
carrier_size_x = 127.8
|
|
||||||
carrier_size_y = 85.5
|
|
||||||
carrier_size_z = 50.0
|
|
||||||
|
|
||||||
# 瓶位尺寸
|
|
||||||
bottle_diameter = 30.0
|
|
||||||
bottle_spacing_x = 42.0 # X方向间距
|
|
||||||
bottle_spacing_y = 35.0 # Y方向间距
|
|
||||||
|
|
||||||
# 计算起始位置 (居中排列)
|
|
||||||
start_x = (carrier_size_x - (3 - 1) * bottle_spacing_x - bottle_diameter) / 2
|
|
||||||
start_y = (carrier_size_y - (2 - 1) * bottle_spacing_y - bottle_diameter) / 2
|
|
||||||
|
|
||||||
sites = create_ordered_items_2d(
|
|
||||||
klass=ResourceHolder,
|
|
||||||
num_items_x=3,
|
|
||||||
num_items_y=2,
|
|
||||||
dx=start_x,
|
|
||||||
dy=start_y,
|
|
||||||
dz=5.0,
|
|
||||||
item_dx=bottle_spacing_x,
|
|
||||||
item_dy=bottle_spacing_y,
|
|
||||||
|
|
||||||
size_x=bottle_diameter,
|
|
||||||
size_y=bottle_diameter,
|
|
||||||
size_z=carrier_size_z,
|
|
||||||
)
|
|
||||||
for k, v in sites.items():
|
|
||||||
v.name = f"{name}_{v.name}"
|
|
||||||
|
|
||||||
carrier = BottleCarrier(
|
|
||||||
name=name,
|
|
||||||
size_x=carrier_size_x,
|
|
||||||
size_y=carrier_size_y,
|
|
||||||
size_z=carrier_size_z,
|
|
||||||
sites=sites,
|
|
||||||
model="6VialCarrier",
|
|
||||||
)
|
|
||||||
carrier.num_items_x = 3
|
|
||||||
carrier.num_items_y = 2
|
|
||||||
carrier.num_items_z = 1
|
|
||||||
ordering = ["A1", "A2", "A3", "B1", "B2", "B3"] # 自定义顺序
|
|
||||||
# for i in range(3):
|
|
||||||
# carrier[i] = YB_Solid_Vial(f"{name}_solidvial_{ordering[i]}")
|
|
||||||
# for i in range(3, 6):
|
|
||||||
# carrier[i] = YB_Liquid_Vial(f"{name}_liquidvial_{ordering[i]}")
|
|
||||||
return carrier
|
|
||||||
|
|
||||||
# 1瓶载架 - 单个中央位置
|
|
||||||
def YB_1BottleCarrier(name: str) -> BottleCarrier:
|
|
||||||
|
|
||||||
# 载架尺寸 (mm)
|
|
||||||
carrier_size_x = 127.8
|
|
||||||
carrier_size_y = 85.5
|
|
||||||
carrier_size_z = 20.0
|
|
||||||
|
|
||||||
# 烧杯尺寸
|
|
||||||
beaker_diameter = 60.0
|
|
||||||
|
|
||||||
# 计算中央位置
|
|
||||||
center_x = (carrier_size_x - beaker_diameter) / 2
|
|
||||||
center_y = (carrier_size_y - beaker_diameter) / 2
|
|
||||||
center_z = 5.0
|
|
||||||
|
|
||||||
carrier = BottleCarrier(
|
|
||||||
name=name,
|
|
||||||
size_x=carrier_size_x,
|
|
||||||
size_y=carrier_size_y,
|
|
||||||
size_z=carrier_size_z,
|
|
||||||
sites=create_homogeneous_resources(
|
|
||||||
klass=ResourceHolder,
|
|
||||||
locations=[Coordinate(center_x, center_y, center_z)],
|
|
||||||
resource_size_x=beaker_diameter,
|
|
||||||
resource_size_y=beaker_diameter,
|
|
||||||
name_prefix=name,
|
|
||||||
),
|
|
||||||
model="YB_1BottleCarrier",
|
|
||||||
)
|
|
||||||
carrier.num_items_x = 1
|
|
||||||
carrier.num_items_y = 1
|
|
||||||
carrier.num_items_z = 1
|
|
||||||
carrier[0] = YB_ye_Bottle(f"{name}_flask_1")
|
|
||||||
return carrier
|
|
||||||
|
|
||||||
|
|
||||||
# 高粘液瓶载架 - 单个中央位置
|
|
||||||
def YB_gaonianye(name: str) -> BottleCarrier:
|
|
||||||
|
|
||||||
# 载架尺寸 (mm)
|
|
||||||
carrier_size_x = 127.8
|
|
||||||
carrier_size_y = 85.5
|
|
||||||
carrier_size_z = 20.0
|
|
||||||
|
|
||||||
# 烧杯尺寸
|
|
||||||
beaker_diameter = 60.0
|
|
||||||
|
|
||||||
# 计算中央位置
|
|
||||||
center_x = (carrier_size_x - beaker_diameter) / 2
|
|
||||||
center_y = (carrier_size_y - beaker_diameter) / 2
|
|
||||||
center_z = 5.0
|
|
||||||
|
|
||||||
carrier = BottleCarrier(
|
|
||||||
name=name,
|
|
||||||
size_x=carrier_size_x,
|
|
||||||
size_y=carrier_size_y,
|
|
||||||
size_z=carrier_size_z,
|
|
||||||
sites=create_homogeneous_resources(
|
|
||||||
klass=ResourceHolder,
|
|
||||||
locations=[Coordinate(center_x, center_y, center_z)],
|
|
||||||
resource_size_x=beaker_diameter,
|
|
||||||
resource_size_y=beaker_diameter,
|
|
||||||
name_prefix=name,
|
|
||||||
),
|
|
||||||
model="YB_gaonianye",
|
|
||||||
)
|
|
||||||
carrier.num_items_x = 1
|
|
||||||
carrier.num_items_y = 1
|
|
||||||
carrier.num_items_z = 1
|
|
||||||
carrier[0] = YB_gao_nian_ye_Bottle(f"{name}_flask_1")
|
|
||||||
return carrier
|
|
||||||
|
|
||||||
|
|
||||||
# 100ml液体瓶载架 - 单个中央位置
|
|
||||||
def YB_100ml_yeti(name: str) -> BottleCarrier:
|
|
||||||
|
|
||||||
# 载架尺寸 (mm)
|
|
||||||
carrier_size_x = 127.8
|
|
||||||
carrier_size_y = 85.5
|
|
||||||
carrier_size_z = 20.0
|
|
||||||
|
|
||||||
# 烧杯尺寸
|
|
||||||
beaker_diameter = 60.0
|
|
||||||
|
|
||||||
# 计算中央位置
|
|
||||||
center_x = (carrier_size_x - beaker_diameter) / 2
|
|
||||||
center_y = (carrier_size_y - beaker_diameter) / 2
|
|
||||||
center_z = 5.0
|
|
||||||
|
|
||||||
carrier = BottleCarrier(
|
|
||||||
name=name,
|
|
||||||
size_x=carrier_size_x,
|
|
||||||
size_y=carrier_size_y,
|
|
||||||
size_z=carrier_size_z,
|
|
||||||
sites=create_homogeneous_resources(
|
|
||||||
klass=ResourceHolder,
|
|
||||||
locations=[Coordinate(center_x, center_y, center_z)],
|
|
||||||
resource_size_x=beaker_diameter,
|
|
||||||
resource_size_y=beaker_diameter,
|
|
||||||
name_prefix=name,
|
|
||||||
),
|
|
||||||
model="YB_100ml_yeti",
|
|
||||||
)
|
|
||||||
carrier.num_items_x = 1
|
|
||||||
carrier.num_items_y = 1
|
|
||||||
carrier.num_items_z = 1
|
|
||||||
carrier[0] = YB_ye_100ml_Bottle(f"{name}_flask_1")
|
|
||||||
return carrier
|
|
||||||
|
|
||||||
# 5ml分液瓶板 - 4x2布局,8个位置
|
|
||||||
def YB_5ml_fenyepingban(name: str) -> BottleCarrier:
|
|
||||||
|
|
||||||
|
|
||||||
# 载架尺寸 (mm)
|
|
||||||
carrier_size_x = 127.8
|
|
||||||
carrier_size_y = 85.5
|
|
||||||
carrier_size_z = 50.0
|
|
||||||
|
|
||||||
# 瓶位尺寸
|
|
||||||
bottle_diameter = 15.0
|
|
||||||
bottle_spacing_x = 42.0 # X方向间距
|
|
||||||
bottle_spacing_y = 35.0 # Y方向间距
|
|
||||||
|
|
||||||
# 计算起始位置 (居中排列)
|
|
||||||
start_x = (carrier_size_x - (4 - 1) * bottle_spacing_x - bottle_diameter) / 2
|
|
||||||
start_y = (carrier_size_y - (2 - 1) * bottle_spacing_y - bottle_diameter) / 2
|
|
||||||
|
|
||||||
sites = create_ordered_items_2d(
|
|
||||||
klass=ResourceHolder,
|
|
||||||
num_items_x=4,
|
|
||||||
num_items_y=2,
|
|
||||||
dx=start_x,
|
|
||||||
dy=start_y,
|
|
||||||
dz=5.0,
|
|
||||||
item_dx=bottle_spacing_x,
|
|
||||||
item_dy=bottle_spacing_y,
|
|
||||||
size_x=bottle_diameter,
|
|
||||||
size_y=bottle_diameter,
|
|
||||||
size_z=carrier_size_z,
|
|
||||||
)
|
|
||||||
for k, v in sites.items():
|
|
||||||
v.name = f"{name}_{v.name}"
|
|
||||||
|
|
||||||
carrier = BottleCarrier(
|
|
||||||
name=name,
|
|
||||||
size_x=carrier_size_x,
|
|
||||||
size_y=carrier_size_y,
|
|
||||||
size_z=carrier_size_z,
|
|
||||||
sites=sites,
|
|
||||||
model="YB_5ml_fenyepingban",
|
|
||||||
)
|
|
||||||
carrier.num_items_x = 4
|
|
||||||
carrier.num_items_y = 2
|
|
||||||
carrier.num_items_z = 1
|
|
||||||
ordering = ["A1", "A2", "A3", "A4", "B1", "B2", "B3", "B4"]
|
|
||||||
for i in range(8):
|
|
||||||
carrier[i] = YB_5ml_fenyeping(f"{name}_vial_{ordering[i]}")
|
|
||||||
return carrier
|
|
||||||
|
|
||||||
# 20ml分液瓶板 - 4x2布局,8个位置
|
|
||||||
def YB_20ml_fenyepingban(name: str) -> BottleCarrier:
|
|
||||||
|
|
||||||
|
|
||||||
# 载架尺寸 (mm)
|
|
||||||
carrier_size_x = 127.8
|
|
||||||
carrier_size_y = 85.5
|
|
||||||
carrier_size_z = 70.0
|
|
||||||
|
|
||||||
# 瓶位尺寸
|
|
||||||
bottle_diameter = 20.0
|
|
||||||
bottle_spacing_x = 42.0 # X方向间距
|
|
||||||
bottle_spacing_y = 35.0 # Y方向间距
|
|
||||||
|
|
||||||
# 计算起始位置 (居中排列)
|
|
||||||
start_x = (carrier_size_x - (4 - 1) * bottle_spacing_x - bottle_diameter) / 2
|
|
||||||
start_y = (carrier_size_y - (2 - 1) * bottle_spacing_y - bottle_diameter) / 2
|
|
||||||
|
|
||||||
sites = create_ordered_items_2d(
|
|
||||||
klass=ResourceHolder,
|
|
||||||
num_items_x=4,
|
|
||||||
num_items_y=2,
|
|
||||||
dx=start_x,
|
|
||||||
dy=start_y,
|
|
||||||
dz=5.0,
|
|
||||||
item_dx=bottle_spacing_x,
|
|
||||||
item_dy=bottle_spacing_y,
|
|
||||||
size_x=bottle_diameter,
|
|
||||||
size_y=bottle_diameter,
|
|
||||||
size_z=carrier_size_z,
|
|
||||||
)
|
|
||||||
for k, v in sites.items():
|
|
||||||
v.name = f"{name}_{v.name}"
|
|
||||||
|
|
||||||
carrier = BottleCarrier(
|
|
||||||
name=name,
|
|
||||||
size_x=carrier_size_x,
|
|
||||||
size_y=carrier_size_y,
|
|
||||||
size_z=carrier_size_z,
|
|
||||||
sites=sites,
|
|
||||||
model="YB_20ml_fenyepingban",
|
|
||||||
)
|
|
||||||
carrier.num_items_x = 4
|
|
||||||
carrier.num_items_y = 2
|
|
||||||
carrier.num_items_z = 1
|
|
||||||
ordering = ["A1", "A2", "A3", "A4", "B1", "B2", "B3", "B4"]
|
|
||||||
for i in range(8):
|
|
||||||
carrier[i] = YB_20ml_fenyeping(f"{name}_vial_{ordering[i]}")
|
|
||||||
return carrier
|
|
||||||
|
|
||||||
# 配液瓶(小)板 - 4x2布局,8个位置
|
|
||||||
def YB_peiyepingxiaoban(name: str) -> BottleCarrier:
|
|
||||||
|
|
||||||
|
|
||||||
# 载架尺寸 (mm)
|
|
||||||
carrier_size_x = 127.8
|
|
||||||
carrier_size_y = 85.5
|
|
||||||
carrier_size_z = 65.0
|
|
||||||
|
|
||||||
# 瓶位尺寸
|
|
||||||
bottle_diameter = 35.0
|
|
||||||
bottle_spacing_x = 42.0 # X方向间距
|
|
||||||
bottle_spacing_y = 35.0 # Y方向间距
|
|
||||||
|
|
||||||
# 计算起始位置 (居中排列)
|
|
||||||
start_x = (carrier_size_x - (4 - 1) * bottle_spacing_x - bottle_diameter) / 2
|
|
||||||
start_y = (carrier_size_y - (2 - 1) * bottle_spacing_y - bottle_diameter) / 2
|
|
||||||
|
|
||||||
sites = create_ordered_items_2d(
|
|
||||||
klass=ResourceHolder,
|
|
||||||
num_items_x=4,
|
|
||||||
num_items_y=2,
|
|
||||||
dx=start_x,
|
|
||||||
dy=start_y,
|
|
||||||
dz=5.0,
|
|
||||||
item_dx=bottle_spacing_x,
|
|
||||||
item_dy=bottle_spacing_y,
|
|
||||||
size_x=bottle_diameter,
|
|
||||||
size_y=bottle_diameter,
|
|
||||||
size_z=carrier_size_z,
|
|
||||||
)
|
|
||||||
for k, v in sites.items():
|
|
||||||
v.name = f"{name}_{v.name}"
|
|
||||||
|
|
||||||
carrier = BottleCarrier(
|
|
||||||
name=name,
|
|
||||||
size_x=carrier_size_x,
|
|
||||||
size_y=carrier_size_y,
|
|
||||||
size_z=carrier_size_z,
|
|
||||||
sites=sites,
|
|
||||||
model="YB_peiyepingxiaoban",
|
|
||||||
)
|
|
||||||
carrier.num_items_x = 4
|
|
||||||
carrier.num_items_y = 2
|
|
||||||
carrier.num_items_z = 1
|
|
||||||
ordering = ["A1", "A2", "A3", "A4", "B1", "B2", "B3", "B4"]
|
|
||||||
for i in range(8):
|
|
||||||
carrier[i] = YB_pei_ye_xiao_Bottle(f"{name}_bottle_{ordering[i]}")
|
|
||||||
return carrier
|
|
||||||
|
|
||||||
|
|
||||||
# 配液瓶(大)板 - 2x2布局,4个位置
|
|
||||||
def YB_peiyepingdaban(name: str) -> BottleCarrier:
|
|
||||||
|
|
||||||
# 载架尺寸 (mm)
|
|
||||||
carrier_size_x = 127.8
|
|
||||||
carrier_size_y = 85.5
|
|
||||||
carrier_size_z = 95.0
|
|
||||||
|
|
||||||
# 瓶位尺寸
|
|
||||||
bottle_diameter = 55.0
|
|
||||||
bottle_spacing_x = 60.0 # X方向间距
|
|
||||||
bottle_spacing_y = 60.0 # Y方向间距
|
|
||||||
|
|
||||||
# 计算起始位置 (居中排列)
|
|
||||||
start_x = (carrier_size_x - (2 - 1) * bottle_spacing_x - bottle_diameter) / 2
|
|
||||||
start_y = (carrier_size_y - (2 - 1) * bottle_spacing_y - bottle_diameter) / 2
|
|
||||||
|
|
||||||
sites = create_ordered_items_2d(
|
|
||||||
klass=ResourceHolder,
|
|
||||||
num_items_x=2,
|
|
||||||
num_items_y=2,
|
|
||||||
dx=start_x,
|
|
||||||
dy=start_y,
|
|
||||||
dz=5.0,
|
|
||||||
item_dx=bottle_spacing_x,
|
|
||||||
item_dy=bottle_spacing_y,
|
|
||||||
size_x=bottle_diameter,
|
|
||||||
size_y=bottle_diameter,
|
|
||||||
size_z=carrier_size_z,
|
|
||||||
)
|
|
||||||
for k, v in sites.items():
|
|
||||||
v.name = f"{name}_{v.name}"
|
|
||||||
|
|
||||||
carrier = BottleCarrier(
|
|
||||||
name=name,
|
|
||||||
size_x=carrier_size_x,
|
|
||||||
size_y=carrier_size_y,
|
|
||||||
size_z=carrier_size_z,
|
|
||||||
sites=sites,
|
|
||||||
model="YB_peiyepingdaban",
|
|
||||||
)
|
|
||||||
carrier.num_items_x = 2
|
|
||||||
carrier.num_items_y = 2
|
|
||||||
carrier.num_items_z = 1
|
|
||||||
ordering = ["A1", "A2", "B1", "B2"]
|
|
||||||
for i in range(4):
|
|
||||||
carrier[i] = YB_pei_ye_da_Bottle(f"{name}_bottle_{ordering[i]}")
|
|
||||||
return carrier
|
|
||||||
|
|
||||||
# 加样头(大)板 - 1x1布局,1个位置
|
|
||||||
def YB_jia_yang_tou_da_Carrier(name: str) -> BottleCarrier:
|
|
||||||
|
|
||||||
# 载架尺寸 (mm)
|
|
||||||
carrier_size_x = 127.8
|
|
||||||
carrier_size_y = 85.5
|
|
||||||
carrier_size_z = 95.0
|
|
||||||
|
|
||||||
# 瓶位尺寸
|
|
||||||
bottle_diameter = 35.0
|
|
||||||
bottle_spacing_x = 42.0 # X方向间距
|
|
||||||
bottle_spacing_y = 35.0 # Y方向间距
|
|
||||||
|
|
||||||
# 计算起始位置 (居中排列)
|
|
||||||
start_x = (carrier_size_x - (1 - 1) * bottle_spacing_x - bottle_diameter) / 2
|
|
||||||
start_y = (carrier_size_y - (1 - 1) * bottle_spacing_y - bottle_diameter) / 2
|
|
||||||
|
|
||||||
sites = create_ordered_items_2d(
|
|
||||||
klass=ResourceHolder,
|
|
||||||
num_items_x=1,
|
|
||||||
num_items_y=1,
|
|
||||||
dx=start_x,
|
|
||||||
dy=start_y,
|
|
||||||
dz=5.0,
|
|
||||||
item_dx=bottle_spacing_x,
|
|
||||||
item_dy=bottle_spacing_y,
|
|
||||||
size_x=bottle_diameter,
|
|
||||||
size_y=bottle_diameter,
|
|
||||||
size_z=carrier_size_z,
|
|
||||||
)
|
|
||||||
for k, v in sites.items():
|
|
||||||
v.name = f"{name}_{v.name}"
|
|
||||||
|
|
||||||
carrier = BottleCarrier(
|
|
||||||
name=name,
|
|
||||||
size_x=carrier_size_x,
|
|
||||||
size_y=carrier_size_y,
|
|
||||||
size_z=carrier_size_z,
|
|
||||||
sites=sites,
|
|
||||||
model="YB_jia_yang_tou_da_Carrier",
|
|
||||||
)
|
|
||||||
carrier.num_items_x = 1
|
|
||||||
carrier.num_items_y = 1
|
|
||||||
carrier.num_items_z = 1
|
|
||||||
carrier[0] = YB_jia_yang_tou_da(f"{name}_head_1")
|
|
||||||
return carrier
|
|
||||||
|
|
||||||
|
|
||||||
def YB_shi_pei_qi_kuai(name: str) -> BottleCarrier:
|
|
||||||
"""适配器块 - 单个中央位置"""
|
|
||||||
|
|
||||||
# 载架尺寸 (mm)
|
|
||||||
carrier_size_x = 127.8
|
|
||||||
carrier_size_y = 85.5
|
|
||||||
carrier_size_z = 30.0
|
|
||||||
|
|
||||||
# 适配器尺寸
|
|
||||||
adapter_diameter = 80.0
|
|
||||||
|
|
||||||
# 计算中央位置
|
|
||||||
center_x = (carrier_size_x - adapter_diameter) / 2
|
|
||||||
center_y = (carrier_size_y - adapter_diameter) / 2
|
|
||||||
center_z = 0.0
|
|
||||||
|
|
||||||
carrier = BottleCarrier(
|
|
||||||
name=name,
|
|
||||||
size_x=carrier_size_x,
|
|
||||||
size_y=carrier_size_y,
|
|
||||||
size_z=carrier_size_z,
|
|
||||||
sites=create_homogeneous_resources(
|
|
||||||
klass=ResourceHolder,
|
|
||||||
locations=[Coordinate(center_x, center_y, center_z)],
|
|
||||||
resource_size_x=adapter_diameter,
|
|
||||||
resource_size_y=adapter_diameter,
|
|
||||||
name_prefix=name,
|
|
||||||
),
|
|
||||||
model="YB_shi_pei_qi_kuai",
|
|
||||||
)
|
|
||||||
carrier.num_items_x = 1
|
|
||||||
carrier.num_items_y = 1
|
|
||||||
carrier.num_items_z = 1
|
|
||||||
# 适配器块本身不包含瓶子,只是一个支撑结构
|
|
||||||
return carrier
|
|
||||||
|
|
||||||
|
|
||||||
def YB_qiang_tou_he(name: str) -> BottleCarrier:
|
|
||||||
"""枪头盒 - 8x12布局,96个位置"""
|
|
||||||
|
|
||||||
# 载架尺寸 (mm)
|
|
||||||
carrier_size_x = 127.8
|
|
||||||
carrier_size_y = 85.5
|
|
||||||
carrier_size_z = 55.0
|
|
||||||
|
|
||||||
# 枪头尺寸
|
|
||||||
tip_diameter = 10.0
|
|
||||||
tip_spacing_x = 9.0 # X方向间距
|
|
||||||
tip_spacing_y = 9.0 # Y方向间距
|
|
||||||
|
|
||||||
# 计算起始位置 (居中排列)
|
|
||||||
start_x = (carrier_size_x - (12 - 1) * tip_spacing_x - tip_diameter) / 2
|
|
||||||
start_y = (carrier_size_y - (8 - 1) * tip_spacing_y - tip_diameter) / 2
|
|
||||||
|
|
||||||
sites = create_ordered_items_2d(
|
|
||||||
klass=ResourceHolder,
|
|
||||||
num_items_x=12,
|
|
||||||
num_items_y=8,
|
|
||||||
dx=start_x,
|
|
||||||
dy=start_y,
|
|
||||||
dz=5.0,
|
|
||||||
item_dx=tip_spacing_x,
|
|
||||||
item_dy=tip_spacing_y,
|
|
||||||
size_x=tip_diameter,
|
|
||||||
size_y=tip_diameter,
|
|
||||||
size_z=carrier_size_z,
|
|
||||||
)
|
|
||||||
for k, v in sites.items():
|
|
||||||
v.name = f"{name}_{v.name}"
|
|
||||||
|
|
||||||
carrier = BottleCarrier(
|
|
||||||
name=name,
|
|
||||||
size_x=carrier_size_x,
|
|
||||||
size_y=carrier_size_y,
|
|
||||||
size_z=carrier_size_z,
|
|
||||||
sites=sites,
|
|
||||||
model="YB_qiang_tou_he",
|
|
||||||
)
|
|
||||||
carrier.num_items_x = 12
|
|
||||||
carrier.num_items_y = 8
|
|
||||||
carrier.num_items_z = 1
|
|
||||||
# 创建96个枪头
|
|
||||||
for i in range(96):
|
|
||||||
row = chr(65 + i // 12) # A-H
|
|
||||||
col = (i % 12) + 1 # 1-12
|
|
||||||
carrier[i] = YB_qiang_tou(f"{name}_tip_{row}{col}")
|
|
||||||
return carrier
|
|
||||||
|
|
||||||
@@ -1,163 +0,0 @@
|
|||||||
from unilabos.resources.itemized_carrier import Bottle, BottleCarrier
|
|
||||||
# 工厂函数
|
|
||||||
"""加样头(大)"""
|
|
||||||
def YB_jia_yang_tou_da(
|
|
||||||
name: str,
|
|
||||||
diameter: float = 20.0,
|
|
||||||
height: float = 100.0,
|
|
||||||
max_volume: float = 30000.0, # 30mL
|
|
||||||
barcode: str = None,
|
|
||||||
) -> Bottle:
|
|
||||||
"""创建粉末瓶"""
|
|
||||||
return Bottle(
|
|
||||||
name=name,
|
|
||||||
diameter=diameter,# 未知
|
|
||||||
height=height,
|
|
||||||
max_volume=max_volume,
|
|
||||||
barcode=barcode,
|
|
||||||
model="YB_jia_yang_tou_da",
|
|
||||||
)
|
|
||||||
|
|
||||||
"""液1x1"""
|
|
||||||
def YB_ye_Bottle(
|
|
||||||
name: str,
|
|
||||||
diameter: float = 40.0,
|
|
||||||
height: float = 70.0,
|
|
||||||
max_volume: float = 50000.0, # 50mL
|
|
||||||
barcode: str = None,
|
|
||||||
) -> Bottle:
|
|
||||||
"""创建液体瓶"""
|
|
||||||
return Bottle(
|
|
||||||
name=name,
|
|
||||||
diameter=diameter,
|
|
||||||
height=height,
|
|
||||||
max_volume=max_volume,
|
|
||||||
barcode=barcode,
|
|
||||||
model="Liquid_Bottle",
|
|
||||||
)
|
|
||||||
|
|
||||||
"""100ml液体"""
|
|
||||||
def YB_ye_100ml_Bottle(
|
|
||||||
name: str,
|
|
||||||
diameter: float = 50.0,
|
|
||||||
height: float = 90.0,
|
|
||||||
max_volume: float = 100000.0, # 100mL
|
|
||||||
barcode: str = None,
|
|
||||||
) -> Bottle:
|
|
||||||
"""创建100ml液体瓶"""
|
|
||||||
return Bottle(
|
|
||||||
name=name,
|
|
||||||
diameter=diameter,
|
|
||||||
height=height,
|
|
||||||
max_volume=max_volume,
|
|
||||||
barcode=barcode,
|
|
||||||
model="YB_100ml_yeti",
|
|
||||||
)
|
|
||||||
|
|
||||||
"""高粘液"""
|
|
||||||
def YB_gao_nian_ye_Bottle(
|
|
||||||
name: str,
|
|
||||||
diameter: float = 40.0,
|
|
||||||
height: float = 70.0,
|
|
||||||
max_volume: float = 50000.0, # 50mL
|
|
||||||
barcode: str = None,
|
|
||||||
) -> Bottle:
|
|
||||||
"""创建高粘液瓶"""
|
|
||||||
return Bottle(
|
|
||||||
name=name,
|
|
||||||
diameter=diameter,
|
|
||||||
height=height,
|
|
||||||
max_volume=max_volume,
|
|
||||||
barcode=barcode,
|
|
||||||
model="High_Viscosity_Liquid",
|
|
||||||
)
|
|
||||||
|
|
||||||
"""5ml分液瓶"""
|
|
||||||
def YB_5ml_fenyeping(
|
|
||||||
name: str,
|
|
||||||
diameter: float = 20.0,
|
|
||||||
height: float = 50.0,
|
|
||||||
max_volume: float = 5000.0, # 5mL
|
|
||||||
barcode: str = None,
|
|
||||||
) -> Bottle:
|
|
||||||
"""创建5ml分液瓶"""
|
|
||||||
return Bottle(
|
|
||||||
name=name,
|
|
||||||
diameter=diameter,
|
|
||||||
height=height,
|
|
||||||
max_volume=max_volume,
|
|
||||||
barcode=barcode,
|
|
||||||
model="YB_5ml_fenyeping",
|
|
||||||
)
|
|
||||||
|
|
||||||
"""20ml分液瓶"""
|
|
||||||
def YB_20ml_fenyeping(
|
|
||||||
name: str,
|
|
||||||
diameter: float = 30.0,
|
|
||||||
height: float = 65.0,
|
|
||||||
max_volume: float = 20000.0, # 20mL
|
|
||||||
barcode: str = None,
|
|
||||||
) -> Bottle:
|
|
||||||
"""创建20ml分液瓶"""
|
|
||||||
return Bottle(
|
|
||||||
name=name,
|
|
||||||
diameter=diameter,
|
|
||||||
height=height,
|
|
||||||
max_volume=max_volume,
|
|
||||||
barcode=barcode,
|
|
||||||
model="YB_20ml_fenyeping",
|
|
||||||
)
|
|
||||||
|
|
||||||
"""配液瓶(小)"""
|
|
||||||
def YB_pei_ye_xiao_Bottle(
|
|
||||||
name: str,
|
|
||||||
diameter: float = 35.0,
|
|
||||||
height: float = 60.0,
|
|
||||||
max_volume: float = 30000.0, # 30mL
|
|
||||||
barcode: str = None,
|
|
||||||
) -> Bottle:
|
|
||||||
"""创建配液瓶(小)"""
|
|
||||||
return Bottle(
|
|
||||||
name=name,
|
|
||||||
diameter=diameter,
|
|
||||||
height=height,
|
|
||||||
max_volume=max_volume,
|
|
||||||
barcode=barcode,
|
|
||||||
model="YB_pei_ye_xiao_Bottle",
|
|
||||||
)
|
|
||||||
|
|
||||||
"""配液瓶(大)"""
|
|
||||||
def YB_pei_ye_da_Bottle(
|
|
||||||
name: str,
|
|
||||||
diameter: float = 55.0,
|
|
||||||
height: float = 100.0,
|
|
||||||
max_volume: float = 150000.0, # 150mL
|
|
||||||
barcode: str = None,
|
|
||||||
) -> Bottle:
|
|
||||||
"""创建配液瓶(大)"""
|
|
||||||
return Bottle(
|
|
||||||
name=name,
|
|
||||||
diameter=diameter,
|
|
||||||
height=height,
|
|
||||||
max_volume=max_volume,
|
|
||||||
barcode=barcode,
|
|
||||||
model="YB_pei_ye_da_Bottle",
|
|
||||||
)
|
|
||||||
|
|
||||||
"""枪头"""
|
|
||||||
def YB_qiang_tou(
|
|
||||||
name: str,
|
|
||||||
diameter: float = 10.0,
|
|
||||||
height: float = 50.0,
|
|
||||||
max_volume: float = 1000.0, # 1mL
|
|
||||||
barcode: str = None,
|
|
||||||
) -> Bottle:
|
|
||||||
"""创建枪头"""
|
|
||||||
return Bottle(
|
|
||||||
name=name,
|
|
||||||
diameter=diameter,
|
|
||||||
height=height,
|
|
||||||
max_volume=max_volume,
|
|
||||||
barcode=barcode,
|
|
||||||
model="YB_qiang_tou",
|
|
||||||
)
|
|
||||||
@@ -1,209 +0,0 @@
|
|||||||
from unilabos.resources.warehouse import WareHouse, YB_warehouse_factory
|
|
||||||
|
|
||||||
|
|
||||||
def bioyond_warehouse_1x4x4(name: str) -> WareHouse:
|
|
||||||
"""创建BioYond 4x1x4仓库"""
|
|
||||||
return YB_warehouse_factory(
|
|
||||||
name=name,
|
|
||||||
num_items_x=1,
|
|
||||||
num_items_y=4,
|
|
||||||
num_items_z=4,
|
|
||||||
dx=10.0,
|
|
||||||
dy=10.0,
|
|
||||||
dz=10.0,
|
|
||||||
item_dx=137.0,
|
|
||||||
item_dy=96.0,
|
|
||||||
item_dz=120.0,
|
|
||||||
category="warehouse",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def bioyond_warehouse_1x4x2(name: str) -> WareHouse:
|
|
||||||
"""创建BioYond 4x1x2仓库"""
|
|
||||||
return YB_warehouse_factory(
|
|
||||||
name=name,
|
|
||||||
num_items_x=1,
|
|
||||||
num_items_y=4,
|
|
||||||
num_items_z=2,
|
|
||||||
dx=10.0,
|
|
||||||
dy=10.0,
|
|
||||||
dz=10.0,
|
|
||||||
item_dx=137.0,
|
|
||||||
item_dy=96.0,
|
|
||||||
item_dz=120.0,
|
|
||||||
category="warehouse",
|
|
||||||
removed_positions=None
|
|
||||||
)
|
|
||||||
# 定义benyond的堆栈
|
|
||||||
def bioyond_warehouse_1x2x2(name: str) -> WareHouse:
|
|
||||||
"""创建BioYond 4x1x4仓库"""
|
|
||||||
return YB_warehouse_factory(
|
|
||||||
name=name,
|
|
||||||
num_items_x=2,
|
|
||||||
num_items_y=2,
|
|
||||||
num_items_z=1,
|
|
||||||
dx=10.0,
|
|
||||||
dy=10.0,
|
|
||||||
dz=10.0,
|
|
||||||
item_dx=137.0,
|
|
||||||
item_dy=96.0,
|
|
||||||
item_dz=120.0,
|
|
||||||
category="YB_warehouse",
|
|
||||||
)
|
|
||||||
|
|
||||||
def bioyond_warehouse_2x2x1(name: str) -> WareHouse:
|
|
||||||
"""创建BioYond 2x2x1仓库(自动堆栈)"""
|
|
||||||
return YB_warehouse_factory(
|
|
||||||
name=name,
|
|
||||||
num_items_x=2,
|
|
||||||
num_items_y=2,
|
|
||||||
num_items_z=1,
|
|
||||||
dx=10.0,
|
|
||||||
dy=10.0,
|
|
||||||
dz=10.0,
|
|
||||||
item_dx=137.0,
|
|
||||||
item_dy=96.0,
|
|
||||||
item_dz=120.0,
|
|
||||||
category="YB_warehouse",
|
|
||||||
)
|
|
||||||
|
|
||||||
def bioyond_warehouse_10x1x1(name: str) -> WareHouse:
|
|
||||||
"""创建BioYond 4x1x4仓库"""
|
|
||||||
return YB_warehouse_factory(
|
|
||||||
name=name,
|
|
||||||
num_items_x=10,
|
|
||||||
num_items_y=1,
|
|
||||||
num_items_z=1,
|
|
||||||
dx=10.0,
|
|
||||||
dy=10.0,
|
|
||||||
dz=10.0,
|
|
||||||
item_dx=137.0,
|
|
||||||
item_dy=96.0,
|
|
||||||
item_dz=120.0,
|
|
||||||
category="warehouse",
|
|
||||||
)
|
|
||||||
def bioyond_warehouse_1x3x3(name: str) -> WareHouse:
|
|
||||||
"""创建BioYond 4x1x4仓库"""
|
|
||||||
return YB_warehouse_factory(
|
|
||||||
name=name,
|
|
||||||
num_items_x=1,
|
|
||||||
num_items_y=3,
|
|
||||||
num_items_z=3,
|
|
||||||
dx=10.0,
|
|
||||||
dy=10.0,
|
|
||||||
dz=10.0,
|
|
||||||
item_dx=137.0,
|
|
||||||
item_dy=96.0,
|
|
||||||
item_dz=120.0,
|
|
||||||
category="warehouse",
|
|
||||||
)
|
|
||||||
def bioyond_warehouse_2x1x3(name: str) -> WareHouse:
|
|
||||||
"""创建BioYond 4x1x4仓库"""
|
|
||||||
return YB_warehouse_factory(
|
|
||||||
name=name,
|
|
||||||
num_items_x=2,
|
|
||||||
num_items_y=1,
|
|
||||||
num_items_z=3,
|
|
||||||
dx=10.0,
|
|
||||||
dy=10.0,
|
|
||||||
dz=10.0,
|
|
||||||
item_dx=137.0,
|
|
||||||
item_dy=96.0,
|
|
||||||
item_dz=120.0,
|
|
||||||
category="warehouse",
|
|
||||||
)
|
|
||||||
|
|
||||||
def bioyond_warehouse_3x3x1(name: str) -> WareHouse:
|
|
||||||
"""创建BioYond 4x1x4仓库"""
|
|
||||||
return YB_warehouse_factory(
|
|
||||||
name=name,
|
|
||||||
num_items_x=3,
|
|
||||||
num_items_y=3,
|
|
||||||
num_items_z=1,
|
|
||||||
dx=10.0,
|
|
||||||
dy=10.0,
|
|
||||||
dz=10.0,
|
|
||||||
item_dx=137.0,
|
|
||||||
item_dy=96.0,
|
|
||||||
item_dz=120.0,
|
|
||||||
category="warehouse",
|
|
||||||
)
|
|
||||||
def bioyond_warehouse_5x1x1(name: str) -> WareHouse:
|
|
||||||
"""创建BioYond 4x1x4仓库"""
|
|
||||||
return YB_warehouse_factory(
|
|
||||||
name=name,
|
|
||||||
num_items_x=5,
|
|
||||||
num_items_y=1,
|
|
||||||
num_items_z=1,
|
|
||||||
dx=10.0,
|
|
||||||
dy=10.0,
|
|
||||||
dz=10.0,
|
|
||||||
item_dx=137.0,
|
|
||||||
item_dy=96.0,
|
|
||||||
item_dz=120.0,
|
|
||||||
category="warehouse",
|
|
||||||
)
|
|
||||||
def bioyond_warehouse_3x3x1_2(name: str) -> WareHouse:
|
|
||||||
"""创建BioYond 4x1x4仓库"""
|
|
||||||
return YB_warehouse_factory(
|
|
||||||
name=name,
|
|
||||||
num_items_x=3,
|
|
||||||
num_items_y=3,
|
|
||||||
num_items_z=1,
|
|
||||||
dx=12.0,
|
|
||||||
dy=12.0,
|
|
||||||
dz=12.0,
|
|
||||||
item_dx=137.0,
|
|
||||||
item_dy=96.0,
|
|
||||||
item_dz=120.0,
|
|
||||||
category="warehouse",
|
|
||||||
)
|
|
||||||
|
|
||||||
def bioyond_warehouse_liquid_and_lid_handling(name: str) -> WareHouse:
|
|
||||||
"""创建BioYond开关盖加液模块台面"""
|
|
||||||
return YB_warehouse_factory(
|
|
||||||
name=name,
|
|
||||||
num_items_x=2,
|
|
||||||
num_items_y=5,
|
|
||||||
num_items_z=1,
|
|
||||||
dx=10.0,
|
|
||||||
dy=10.0,
|
|
||||||
dz=10.0,
|
|
||||||
item_dx=137.0,
|
|
||||||
item_dy=96.0,
|
|
||||||
item_dz=120.0,
|
|
||||||
category="warehouse",
|
|
||||||
removed_positions=None
|
|
||||||
)
|
|
||||||
|
|
||||||
def bioyond_warehouse_3x5x1(name: str) -> WareHouse:
|
|
||||||
"""创建BioYond 3x5x1仓库(手动堆栈)"""
|
|
||||||
return YB_warehouse_factory(
|
|
||||||
name=name,
|
|
||||||
num_items_x=3,
|
|
||||||
num_items_y=5,
|
|
||||||
num_items_z=1,
|
|
||||||
dx=10.0,
|
|
||||||
dy=10.0,
|
|
||||||
dz=10.0,
|
|
||||||
item_dx=137.0,
|
|
||||||
item_dy=96.0,
|
|
||||||
item_dz=120.0,
|
|
||||||
category="warehouse",
|
|
||||||
)
|
|
||||||
|
|
||||||
def bioyond_warehouse_20x1x1(name: str) -> WareHouse:
|
|
||||||
"""创建BioYond 20x1x1仓库(粉末加样头堆栈)"""
|
|
||||||
return YB_warehouse_factory(
|
|
||||||
name=name,
|
|
||||||
num_items_x=20,
|
|
||||||
num_items_y=1,
|
|
||||||
num_items_z=1,
|
|
||||||
dx=10.0,
|
|
||||||
dy=10.0,
|
|
||||||
dz=10.0,
|
|
||||||
item_dx=137.0,
|
|
||||||
item_dy=96.0,
|
|
||||||
item_dz=120.0,
|
|
||||||
category="warehouse",
|
|
||||||
)
|
|
||||||
276
unilabos/resources/bioyond/bottle_carriers.py
Normal file
276
unilabos/resources/bioyond/bottle_carriers.py
Normal file
@@ -0,0 +1,276 @@
|
|||||||
|
from pylabrobot.resources import create_homogeneous_resources, Coordinate, ResourceHolder, create_ordered_items_2d
|
||||||
|
|
||||||
|
from unilabos.resources.itemized_carrier import Bottle, BottleCarrier
|
||||||
|
from unilabos.resources.bioyond.bottles import (
|
||||||
|
BIOYOND_PolymerStation_Solid_Stock,
|
||||||
|
BIOYOND_PolymerStation_Solid_Vial,
|
||||||
|
BIOYOND_PolymerStation_Liquid_Vial,
|
||||||
|
BIOYOND_PolymerStation_Solution_Beaker,
|
||||||
|
BIOYOND_PolymerStation_Reagent_Bottle
|
||||||
|
)
|
||||||
|
# 命名约定:试剂瓶-Bottle,烧杯-Beaker,烧瓶-Flask,小瓶-Vial
|
||||||
|
|
||||||
|
|
||||||
|
def BIOYOND_Electrolyte_6VialCarrier(name: str) -> BottleCarrier:
|
||||||
|
"""6瓶载架 - 2x3布局"""
|
||||||
|
|
||||||
|
# 载架尺寸 (mm)
|
||||||
|
carrier_size_x = 127.8
|
||||||
|
carrier_size_y = 85.5
|
||||||
|
carrier_size_z = 50.0
|
||||||
|
|
||||||
|
# 瓶位尺寸
|
||||||
|
bottle_diameter = 30.0
|
||||||
|
bottle_spacing_x = 42.0 # X方向间距
|
||||||
|
bottle_spacing_y = 35.0 # Y方向间距
|
||||||
|
|
||||||
|
# 计算起始位置 (居中排列)
|
||||||
|
start_x = (carrier_size_x - (3 - 1) * bottle_spacing_x - bottle_diameter) / 2
|
||||||
|
start_y = (carrier_size_y - (2 - 1) * bottle_spacing_y - bottle_diameter) / 2
|
||||||
|
|
||||||
|
sites = create_ordered_items_2d(
|
||||||
|
klass=ResourceHolder,
|
||||||
|
num_items_x=3,
|
||||||
|
num_items_y=2,
|
||||||
|
dx=start_x,
|
||||||
|
dy=start_y,
|
||||||
|
dz=5.0,
|
||||||
|
item_dx=bottle_spacing_x,
|
||||||
|
item_dy=bottle_spacing_y,
|
||||||
|
|
||||||
|
size_x=bottle_diameter,
|
||||||
|
size_y=bottle_diameter,
|
||||||
|
size_z=carrier_size_z,
|
||||||
|
)
|
||||||
|
for k, v in sites.items():
|
||||||
|
v.name = f"{name}_{v.name}"
|
||||||
|
|
||||||
|
carrier = BottleCarrier(
|
||||||
|
name=name,
|
||||||
|
size_x=carrier_size_x,
|
||||||
|
size_y=carrier_size_y,
|
||||||
|
size_z=carrier_size_z,
|
||||||
|
sites=sites,
|
||||||
|
model="BIOYOND_Electrolyte_6VialCarrier",
|
||||||
|
)
|
||||||
|
carrier.num_items_x = 3
|
||||||
|
carrier.num_items_y = 2
|
||||||
|
carrier.num_items_z = 1
|
||||||
|
for i in range(6):
|
||||||
|
carrier[i] = BIOYOND_PolymerStation_Solid_Vial(f"{name}_vial_{i+1}")
|
||||||
|
return carrier
|
||||||
|
|
||||||
|
|
||||||
|
def BIOYOND_Electrolyte_1BottleCarrier(name: str) -> BottleCarrier:
|
||||||
|
"""1瓶载架 - 单个中央位置"""
|
||||||
|
|
||||||
|
# 载架尺寸 (mm)
|
||||||
|
carrier_size_x = 127.8
|
||||||
|
carrier_size_y = 85.5
|
||||||
|
carrier_size_z = 100.0
|
||||||
|
|
||||||
|
# 烧杯尺寸
|
||||||
|
beaker_diameter = 80.0
|
||||||
|
|
||||||
|
# 计算中央位置
|
||||||
|
center_x = (carrier_size_x - beaker_diameter) / 2
|
||||||
|
center_y = (carrier_size_y - beaker_diameter) / 2
|
||||||
|
center_z = 5.0
|
||||||
|
|
||||||
|
carrier = BottleCarrier(
|
||||||
|
name=name,
|
||||||
|
size_x=carrier_size_x,
|
||||||
|
size_y=carrier_size_y,
|
||||||
|
size_z=carrier_size_z,
|
||||||
|
sites=create_homogeneous_resources(
|
||||||
|
klass=ResourceHolder,
|
||||||
|
locations=[Coordinate(center_x, center_y, center_z)],
|
||||||
|
resource_size_x=beaker_diameter,
|
||||||
|
resource_size_y=beaker_diameter,
|
||||||
|
name_prefix=name,
|
||||||
|
),
|
||||||
|
model="BIOYOND_Electrolyte_1BottleCarrier",
|
||||||
|
)
|
||||||
|
carrier.num_items_x = 1
|
||||||
|
carrier.num_items_y = 1
|
||||||
|
carrier.num_items_z = 1
|
||||||
|
carrier[0] = BIOYOND_PolymerStation_Solution_Beaker(f"{name}_beaker_1")
|
||||||
|
return carrier
|
||||||
|
|
||||||
|
|
||||||
|
def BIOYOND_PolymerStation_6StockCarrier(name: str) -> BottleCarrier:
|
||||||
|
"""6瓶载架 - 2x3布局"""
|
||||||
|
|
||||||
|
# 载架尺寸 (mm)
|
||||||
|
carrier_size_x = 127.8
|
||||||
|
carrier_size_y = 85.5
|
||||||
|
carrier_size_z = 50.0
|
||||||
|
|
||||||
|
# 瓶位尺寸
|
||||||
|
bottle_diameter = 20.0
|
||||||
|
bottle_spacing_x = 42.0 # X方向间距
|
||||||
|
bottle_spacing_y = 35.0 # Y方向间距
|
||||||
|
|
||||||
|
# 计算起始位置 (居中排列)
|
||||||
|
start_x = (carrier_size_x - (3 - 1) * bottle_spacing_x - bottle_diameter) / 2
|
||||||
|
start_y = (carrier_size_y - (2 - 1) * bottle_spacing_y - bottle_diameter) / 2
|
||||||
|
|
||||||
|
sites = create_ordered_items_2d(
|
||||||
|
klass=ResourceHolder,
|
||||||
|
num_items_x=3,
|
||||||
|
num_items_y=2,
|
||||||
|
dx=start_x,
|
||||||
|
dy=start_y,
|
||||||
|
dz=5.0,
|
||||||
|
item_dx=bottle_spacing_x,
|
||||||
|
item_dy=bottle_spacing_y,
|
||||||
|
|
||||||
|
size_x=bottle_diameter,
|
||||||
|
size_y=bottle_diameter,
|
||||||
|
size_z=carrier_size_z,
|
||||||
|
)
|
||||||
|
for k, v in sites.items():
|
||||||
|
v.name = f"{name}_{v.name}"
|
||||||
|
|
||||||
|
carrier = BottleCarrier(
|
||||||
|
name=name,
|
||||||
|
size_x=carrier_size_x,
|
||||||
|
size_y=carrier_size_y,
|
||||||
|
size_z=carrier_size_z,
|
||||||
|
sites=sites,
|
||||||
|
model="BIOYOND_PolymerStation_6VialCarrier",
|
||||||
|
)
|
||||||
|
carrier.num_items_x = 3
|
||||||
|
carrier.num_items_y = 2
|
||||||
|
carrier.num_items_z = 1
|
||||||
|
ordering = ["A1", "A2", "A3", "B1", "B2", "B3"] # 自定义顺序
|
||||||
|
for i in range(6):
|
||||||
|
carrier[i] = BIOYOND_PolymerStation_Solid_Stock(f"{name}_vial_{ordering[i]}")
|
||||||
|
return carrier
|
||||||
|
|
||||||
|
|
||||||
|
def BIOYOND_PolymerStation_6VialCarrier(name: str) -> BottleCarrier:
|
||||||
|
"""6瓶载架 - 2x3布局"""
|
||||||
|
|
||||||
|
# 载架尺寸 (mm)
|
||||||
|
carrier_size_x = 127.8
|
||||||
|
carrier_size_y = 85.5
|
||||||
|
carrier_size_z = 50.0
|
||||||
|
|
||||||
|
# 瓶位尺寸
|
||||||
|
bottle_diameter = 30.0
|
||||||
|
bottle_spacing_x = 42.0 # X方向间距
|
||||||
|
bottle_spacing_y = 35.0 # Y方向间距
|
||||||
|
|
||||||
|
# 计算起始位置 (居中排列)
|
||||||
|
start_x = (carrier_size_x - (3 - 1) * bottle_spacing_x - bottle_diameter) / 2
|
||||||
|
start_y = (carrier_size_y - (2 - 1) * bottle_spacing_y - bottle_diameter) / 2
|
||||||
|
|
||||||
|
sites = create_ordered_items_2d(
|
||||||
|
klass=ResourceHolder,
|
||||||
|
num_items_x=3,
|
||||||
|
num_items_y=2,
|
||||||
|
dx=start_x,
|
||||||
|
dy=start_y,
|
||||||
|
dz=5.0,
|
||||||
|
item_dx=bottle_spacing_x,
|
||||||
|
item_dy=bottle_spacing_y,
|
||||||
|
|
||||||
|
size_x=bottle_diameter,
|
||||||
|
size_y=bottle_diameter,
|
||||||
|
size_z=carrier_size_z,
|
||||||
|
)
|
||||||
|
for k, v in sites.items():
|
||||||
|
v.name = f"{name}_{v.name}"
|
||||||
|
|
||||||
|
carrier = BottleCarrier(
|
||||||
|
name=name,
|
||||||
|
size_x=carrier_size_x,
|
||||||
|
size_y=carrier_size_y,
|
||||||
|
size_z=carrier_size_z,
|
||||||
|
sites=sites,
|
||||||
|
model="BIOYOND_PolymerStation_6VialCarrier",
|
||||||
|
)
|
||||||
|
carrier.num_items_x = 3
|
||||||
|
carrier.num_items_y = 2
|
||||||
|
carrier.num_items_z = 1
|
||||||
|
ordering = ["A1", "A2", "A3", "B1", "B2", "B3"] # 自定义顺序
|
||||||
|
for i in range(3):
|
||||||
|
carrier[i] = BIOYOND_PolymerStation_Solid_Vial(f"{name}_solidvial_{ordering[i]}")
|
||||||
|
for i in range(3, 6):
|
||||||
|
carrier[i] = BIOYOND_PolymerStation_Liquid_Vial(f"{name}_liquidvial_{ordering[i]}")
|
||||||
|
return carrier
|
||||||
|
|
||||||
|
|
||||||
|
def BIOYOND_PolymerStation_1BottleCarrier(name: str) -> BottleCarrier:
|
||||||
|
"""1瓶载架 - 单个中央位置"""
|
||||||
|
|
||||||
|
# 载架尺寸 (mm)
|
||||||
|
carrier_size_x = 127.8
|
||||||
|
carrier_size_y = 85.5
|
||||||
|
carrier_size_z = 20.0
|
||||||
|
|
||||||
|
# 烧杯尺寸
|
||||||
|
beaker_diameter = 60.0
|
||||||
|
|
||||||
|
# 计算中央位置
|
||||||
|
center_x = (carrier_size_x - beaker_diameter) / 2
|
||||||
|
center_y = (carrier_size_y - beaker_diameter) / 2
|
||||||
|
center_z = 5.0
|
||||||
|
|
||||||
|
carrier = BottleCarrier(
|
||||||
|
name=name,
|
||||||
|
size_x=carrier_size_x,
|
||||||
|
size_y=carrier_size_y,
|
||||||
|
size_z=carrier_size_z,
|
||||||
|
sites=create_homogeneous_resources(
|
||||||
|
klass=ResourceHolder,
|
||||||
|
locations=[Coordinate(center_x, center_y, center_z)],
|
||||||
|
resource_size_x=beaker_diameter,
|
||||||
|
resource_size_y=beaker_diameter,
|
||||||
|
name_prefix=name,
|
||||||
|
),
|
||||||
|
model="BIOYOND_PolymerStation_1BottleCarrier",
|
||||||
|
)
|
||||||
|
carrier.num_items_x = 1
|
||||||
|
carrier.num_items_y = 1
|
||||||
|
carrier.num_items_z = 1
|
||||||
|
carrier[0] = BIOYOND_PolymerStation_Reagent_Bottle(f"{name}_flask_1")
|
||||||
|
return carrier
|
||||||
|
|
||||||
|
|
||||||
|
def BIOYOND_PolymerStation_1FlaskCarrier(name: str) -> BottleCarrier:
|
||||||
|
"""1瓶载架 - 单个中央位置"""
|
||||||
|
|
||||||
|
# 载架尺寸 (mm)
|
||||||
|
carrier_size_x = 127.8
|
||||||
|
carrier_size_y = 85.5
|
||||||
|
carrier_size_z = 20.0
|
||||||
|
|
||||||
|
# 烧杯尺寸
|
||||||
|
beaker_diameter = 70.0
|
||||||
|
|
||||||
|
# 计算中央位置
|
||||||
|
center_x = (carrier_size_x - beaker_diameter) / 2
|
||||||
|
center_y = (carrier_size_y - beaker_diameter) / 2
|
||||||
|
center_z = 5.0
|
||||||
|
|
||||||
|
carrier = BottleCarrier(
|
||||||
|
name=name,
|
||||||
|
size_x=carrier_size_x,
|
||||||
|
size_y=carrier_size_y,
|
||||||
|
size_z=carrier_size_z,
|
||||||
|
sites=create_homogeneous_resources(
|
||||||
|
klass=ResourceHolder,
|
||||||
|
locations=[Coordinate(center_x, center_y, center_z)],
|
||||||
|
resource_size_x=beaker_diameter,
|
||||||
|
resource_size_y=beaker_diameter,
|
||||||
|
name_prefix=name,
|
||||||
|
),
|
||||||
|
model="BIOYOND_PolymerStation_1FlaskCarrier",
|
||||||
|
)
|
||||||
|
carrier.num_items_x = 1
|
||||||
|
carrier.num_items_y = 1
|
||||||
|
carrier.num_items_z = 1
|
||||||
|
carrier[0] = BIOYOND_PolymerStation_Reagent_Bottle(f"{name}_bottle_1")
|
||||||
|
return carrier
|
||||||
92
unilabos/resources/bioyond/bottles.py
Normal file
92
unilabos/resources/bioyond/bottles.py
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
from unilabos.resources.itemized_carrier import Bottle, BottleCarrier
|
||||||
|
# 工厂函数
|
||||||
|
|
||||||
|
|
||||||
|
def BIOYOND_PolymerStation_Solid_Stock(
|
||||||
|
name: str,
|
||||||
|
diameter: float = 20.0,
|
||||||
|
height: float = 100.0,
|
||||||
|
max_volume: float = 30000.0, # 30mL
|
||||||
|
barcode: str = None,
|
||||||
|
) -> Bottle:
|
||||||
|
"""创建粉末瓶"""
|
||||||
|
return Bottle(
|
||||||
|
name=name,
|
||||||
|
diameter=diameter,
|
||||||
|
height=height,
|
||||||
|
max_volume=max_volume,
|
||||||
|
barcode=barcode,
|
||||||
|
model="BIOYOND_PolymerStation_Solid_Stock",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def BIOYOND_PolymerStation_Solid_Vial(
|
||||||
|
name: str,
|
||||||
|
diameter: float = 25.0,
|
||||||
|
height: float = 60.0,
|
||||||
|
max_volume: float = 30000.0, # 30mL
|
||||||
|
barcode: str = None,
|
||||||
|
) -> Bottle:
|
||||||
|
"""创建粉末瓶"""
|
||||||
|
return Bottle(
|
||||||
|
name=name,
|
||||||
|
diameter=diameter,
|
||||||
|
height=height,
|
||||||
|
max_volume=max_volume,
|
||||||
|
barcode=barcode,
|
||||||
|
model="BIOYOND_PolymerStation_Solid_Vial",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def BIOYOND_PolymerStation_Liquid_Vial(
|
||||||
|
name: str,
|
||||||
|
diameter: float = 25.0,
|
||||||
|
height: float = 60.0,
|
||||||
|
max_volume: float = 30000.0, # 30mL
|
||||||
|
barcode: str = None,
|
||||||
|
) -> Bottle:
|
||||||
|
"""创建滴定液瓶"""
|
||||||
|
return Bottle(
|
||||||
|
name=name,
|
||||||
|
diameter=diameter,
|
||||||
|
height=height,
|
||||||
|
max_volume=max_volume,
|
||||||
|
barcode=barcode,
|
||||||
|
model="BIOYOND_PolymerStation_Liquid_Vial",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def BIOYOND_PolymerStation_Solution_Beaker(
|
||||||
|
name: str,
|
||||||
|
diameter: float = 60.0,
|
||||||
|
height: float = 70.0,
|
||||||
|
max_volume: float = 200000.0, # 200mL
|
||||||
|
barcode: str = None,
|
||||||
|
) -> Bottle:
|
||||||
|
"""创建溶液烧杯"""
|
||||||
|
return Bottle(
|
||||||
|
name=name,
|
||||||
|
diameter=diameter,
|
||||||
|
height=height,
|
||||||
|
max_volume=max_volume,
|
||||||
|
barcode=barcode,
|
||||||
|
model="BIOYOND_PolymerStation_Solution_Beaker",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def BIOYOND_PolymerStation_Reagent_Bottle(
|
||||||
|
name: str,
|
||||||
|
diameter: float = 70.0,
|
||||||
|
height: float = 120.0,
|
||||||
|
max_volume: float = 500000.0, # 500mL
|
||||||
|
barcode: str = None,
|
||||||
|
) -> Bottle:
|
||||||
|
"""创建试剂瓶"""
|
||||||
|
return Bottle(
|
||||||
|
name=name,
|
||||||
|
diameter=diameter,
|
||||||
|
height=height,
|
||||||
|
max_volume=max_volume,
|
||||||
|
barcode=barcode,
|
||||||
|
model="BIOYOND_PolymerStation_Reagent_Bottle",
|
||||||
|
)
|
||||||
@@ -1,8 +1,6 @@
|
|||||||
from os import name
|
|
||||||
from pickle import TRUE
|
|
||||||
from pylabrobot.resources import Deck, Coordinate, Rotation
|
from pylabrobot.resources import Deck, Coordinate, Rotation
|
||||||
|
|
||||||
from unilabos.resources.bioyond.YB_warehouses import bioyond_warehouse_1x4x4, bioyond_warehouse_1x4x2, bioyond_warehouse_liquid_and_lid_handling, bioyond_warehouse_1x2x2, bioyond_warehouse_1x3x3, bioyond_warehouse_10x1x1, bioyond_warehouse_3x3x1, bioyond_warehouse_3x3x1_2, bioyond_warehouse_5x1x1, bioyond_warehouse_20x1x1, bioyond_warehouse_2x2x1, bioyond_warehouse_3x5x1
|
from unilabos.resources.bioyond.warehouses import bioyond_warehouse_1x4x4, bioyond_warehouse_1x4x2, bioyond_warehouse_liquid_and_lid_handling
|
||||||
|
|
||||||
|
|
||||||
class BIOYOND_PolymerReactionStation_Deck(Deck):
|
class BIOYOND_PolymerReactionStation_Deck(Deck):
|
||||||
@@ -36,6 +34,7 @@ class BIOYOND_PolymerReactionStation_Deck(Deck):
|
|||||||
for warehouse_name, warehouse in self.warehouses.items():
|
for warehouse_name, warehouse in self.warehouses.items():
|
||||||
self.assign_child_resource(warehouse, location=self.warehouse_locations[warehouse_name])
|
self.assign_child_resource(warehouse, location=self.warehouse_locations[warehouse_name])
|
||||||
|
|
||||||
|
|
||||||
class BIOYOND_PolymerPreparationStation_Deck(Deck):
|
class BIOYOND_PolymerPreparationStation_Deck(Deck):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
@@ -67,56 +66,3 @@ class BIOYOND_PolymerPreparationStation_Deck(Deck):
|
|||||||
|
|
||||||
for warehouse_name, warehouse in self.warehouses.items():
|
for warehouse_name, warehouse in self.warehouses.items():
|
||||||
self.assign_child_resource(warehouse, location=self.warehouse_locations[warehouse_name])
|
self.assign_child_resource(warehouse, location=self.warehouse_locations[warehouse_name])
|
||||||
|
|
||||||
class BIOYOND_YB_Deck(Deck):
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
name: str = "YB_Deck",
|
|
||||||
size_x: float = 4150,
|
|
||||||
size_y: float = 1400.0,
|
|
||||||
size_z: float = 2670.0,
|
|
||||||
category: str = "deck",
|
|
||||||
setup: bool = False
|
|
||||||
) -> None:
|
|
||||||
super().__init__(name=name, size_x=4150.0, size_y=1400.0, size_z=2670.0)
|
|
||||||
if setup:
|
|
||||||
self.setup()
|
|
||||||
|
|
||||||
def setup(self) -> None:
|
|
||||||
# 添加仓库
|
|
||||||
self.warehouses = {
|
|
||||||
"自动堆栈-左": bioyond_warehouse_2x2x1("自动堆栈-左"),
|
|
||||||
"自动堆栈-右": bioyond_warehouse_2x2x1("自动堆栈-右"),
|
|
||||||
"手动堆栈-左": bioyond_warehouse_3x5x1("手动堆栈-左"),
|
|
||||||
"手动堆栈-右": bioyond_warehouse_3x5x1("手动堆栈-右"),
|
|
||||||
"粉末加样头堆栈-左": bioyond_warehouse_10x1x1("粉末加样头堆栈-左"),
|
|
||||||
"粉末加样头堆栈-右": bioyond_warehouse_10x1x1("粉末加样头堆栈-右"),
|
|
||||||
"配液站内试剂仓库": bioyond_warehouse_3x3x1("配液站内试剂仓库"),
|
|
||||||
"试剂替换仓库-左": bioyond_warehouse_5x1x1("试剂替换仓库-左"),
|
|
||||||
"试剂替换仓库-右": bioyond_warehouse_5x1x1("试剂替换仓库-右"),
|
|
||||||
}
|
|
||||||
# warehouse 的位置
|
|
||||||
self.warehouse_locations = {
|
|
||||||
"自动堆栈-左": Coordinate(-300.0, 158.0, 0.0),
|
|
||||||
"自动堆栈-右": Coordinate(4160.0, 158.0, 0.0),
|
|
||||||
"手动堆栈-左": Coordinate(-400.0, 877.0, 0.0),
|
|
||||||
"手动堆栈-右": Coordinate(4160.0, 877.0, 0.0),
|
|
||||||
"粉末加样头堆栈-左": Coordinate(415.0, 1301.0, 0.0),
|
|
||||||
"粉末加样头堆栈-右": Coordinate(2200.0, 1304.0, 0.0),
|
|
||||||
"配液站内试剂仓库": Coordinate(2162.0, 337.0, 0.0),
|
|
||||||
"试剂替换仓库-左": Coordinate(1173.0, 702.0, 0.0),
|
|
||||||
"试剂替换仓库-右": Coordinate(2721.0, 739.0, 0.0),
|
|
||||||
}
|
|
||||||
|
|
||||||
for warehouse_name, warehouse in self.warehouses.items():
|
|
||||||
self.assign_child_resource(warehouse, location=self.warehouse_locations[warehouse_name])
|
|
||||||
|
|
||||||
# def YB_Deck(name: str) -> Deck:
|
|
||||||
# # by=BIOYOND_YB_Deck(name=name)
|
|
||||||
# # by.setup()
|
|
||||||
# return None
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
54
unilabos/resources/bioyond/warehouses.py
Normal file
54
unilabos/resources/bioyond/warehouses.py
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
from unilabos.resources.warehouse import WareHouse, warehouse_factory
|
||||||
|
|
||||||
|
|
||||||
|
def bioyond_warehouse_1x4x4(name: str) -> WareHouse:
|
||||||
|
"""创建BioYond 4x1x4仓库"""
|
||||||
|
return warehouse_factory(
|
||||||
|
name=name,
|
||||||
|
num_items_x=1,
|
||||||
|
num_items_y=4,
|
||||||
|
num_items_z=4,
|
||||||
|
dx=10.0,
|
||||||
|
dy=10.0,
|
||||||
|
dz=10.0,
|
||||||
|
item_dx=137.0,
|
||||||
|
item_dy=96.0,
|
||||||
|
item_dz=120.0,
|
||||||
|
category="warehouse",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def bioyond_warehouse_1x4x2(name: str) -> WareHouse:
|
||||||
|
"""创建BioYond 4x1x2仓库"""
|
||||||
|
return warehouse_factory(
|
||||||
|
name=name,
|
||||||
|
num_items_x=1,
|
||||||
|
num_items_y=4,
|
||||||
|
num_items_z=2,
|
||||||
|
dx=10.0,
|
||||||
|
dy=10.0,
|
||||||
|
dz=10.0,
|
||||||
|
item_dx=137.0,
|
||||||
|
item_dy=96.0,
|
||||||
|
item_dz=120.0,
|
||||||
|
category="warehouse",
|
||||||
|
removed_positions=None
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def bioyond_warehouse_liquid_and_lid_handling(name: str) -> WareHouse:
|
||||||
|
"""创建BioYond开关盖加液模块台面"""
|
||||||
|
return warehouse_factory(
|
||||||
|
name=name,
|
||||||
|
num_items_x=2,
|
||||||
|
num_items_y=5,
|
||||||
|
num_items_z=1,
|
||||||
|
dx=10.0,
|
||||||
|
dy=10.0,
|
||||||
|
dz=10.0,
|
||||||
|
item_dx=137.0,
|
||||||
|
item_dy=96.0,
|
||||||
|
item_dz=120.0,
|
||||||
|
category="warehouse",
|
||||||
|
removed_positions=None
|
||||||
|
)
|
||||||
@@ -1,84 +1,67 @@
|
|||||||
import json
|
import json
|
||||||
from typing import Dict, Any
|
|
||||||
|
|
||||||
from pylabrobot.resources import Container
|
|
||||||
from unilabos_msgs.msg import Resource
|
from unilabos_msgs.msg import Resource
|
||||||
|
|
||||||
from unilabos.ros.msgs.message_converter import convert_from_ros_msg
|
from unilabos.ros.msgs.message_converter import convert_from_ros_msg
|
||||||
|
|
||||||
|
|
||||||
class RegularContainer(Container):
|
class RegularContainer(object):
|
||||||
def __init__(self, *args, **kwargs):
|
# 第一个参数必须是id传入
|
||||||
if "size_x" not in kwargs:
|
# noinspection PyShadowingBuiltins
|
||||||
kwargs["size_x"] = 0
|
def __init__(self, id: str):
|
||||||
if "size_y" not in kwargs:
|
self.id = id
|
||||||
kwargs["size_y"] = 0
|
self.ulr_resource = Resource()
|
||||||
if "size_z" not in kwargs:
|
self._data = None
|
||||||
kwargs["size_z"] = 0
|
|
||||||
self.kwargs = kwargs
|
|
||||||
self.state = {}
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
def load_state(self, state: Dict[str, Any]):
|
@property
|
||||||
self.state = state
|
def ulr_resource_data(self):
|
||||||
#
|
if self._data is None:
|
||||||
# class RegularContainer(object):
|
self._data = json.loads(self.ulr_resource.data) if self.ulr_resource.data else {}
|
||||||
# # 第一个参数必须是id传入
|
return self._data
|
||||||
# # noinspection PyShadowingBuiltins
|
|
||||||
# def __init__(self, id: str):
|
@ulr_resource_data.setter
|
||||||
# self.id = id
|
def ulr_resource_data(self, value: dict):
|
||||||
# self.ulr_resource = Resource()
|
self._data = value
|
||||||
# self._data = None
|
self.ulr_resource.data = json.dumps(self._data)
|
||||||
#
|
|
||||||
# @property
|
@property
|
||||||
# def ulr_resource_data(self):
|
def liquid_type(self):
|
||||||
# if self._data is None:
|
return self.ulr_resource_data.get("liquid_type", None)
|
||||||
# self._data = json.loads(self.ulr_resource.data) if self.ulr_resource.data else {}
|
|
||||||
# return self._data
|
@liquid_type.setter
|
||||||
#
|
def liquid_type(self, value: str):
|
||||||
# @ulr_resource_data.setter
|
if value is not None:
|
||||||
# def ulr_resource_data(self, value: dict):
|
self.ulr_resource_data["liquid_type"] = value
|
||||||
# self._data = value
|
else:
|
||||||
# self.ulr_resource.data = json.dumps(self._data)
|
self.ulr_resource_data.pop("liquid_type", None)
|
||||||
#
|
|
||||||
# @property
|
@property
|
||||||
# def liquid_type(self):
|
def liquid_volume(self):
|
||||||
# return self.ulr_resource_data.get("liquid_type", None)
|
return self.ulr_resource_data.get("liquid_volume", None)
|
||||||
#
|
|
||||||
# @liquid_type.setter
|
@liquid_volume.setter
|
||||||
# def liquid_type(self, value: str):
|
def liquid_volume(self, value: float):
|
||||||
# if value is not None:
|
if value is not None:
|
||||||
# self.ulr_resource_data["liquid_type"] = value
|
self.ulr_resource_data["liquid_volume"] = value
|
||||||
# else:
|
else:
|
||||||
# self.ulr_resource_data.pop("liquid_type", None)
|
self.ulr_resource_data.pop("liquid_volume", None)
|
||||||
#
|
|
||||||
# @property
|
def get_ulr_resource(self) -> Resource:
|
||||||
# def liquid_volume(self):
|
"""
|
||||||
# return self.ulr_resource_data.get("liquid_volume", None)
|
获取UlrResource对象
|
||||||
#
|
:return: UlrResource对象
|
||||||
# @liquid_volume.setter
|
"""
|
||||||
# def liquid_volume(self, value: float):
|
self.ulr_resource_data = self.ulr_resource_data # 确保数据被更新
|
||||||
# if value is not None:
|
return self.ulr_resource
|
||||||
# self.ulr_resource_data["liquid_volume"] = value
|
|
||||||
# else:
|
def get_ulr_resource_as_dict(self) -> Resource:
|
||||||
# self.ulr_resource_data.pop("liquid_volume", None)
|
"""
|
||||||
#
|
获取UlrResource对象
|
||||||
# def get_ulr_resource(self) -> Resource:
|
:return: UlrResource对象
|
||||||
# """
|
"""
|
||||||
# 获取UlrResource对象
|
to_dict = convert_from_ros_msg(self.get_ulr_resource())
|
||||||
# :return: UlrResource对象
|
to_dict["type"] = "container"
|
||||||
# """
|
return to_dict
|
||||||
# self.ulr_resource_data = self.ulr_resource_data # 确保数据被更新
|
|
||||||
# return self.ulr_resource
|
def __str__(self):
|
||||||
#
|
return f"{self.id}"
|
||||||
# def get_ulr_resource_as_dict(self) -> Resource:
|
|
||||||
# """
|
|
||||||
# 获取UlrResource对象
|
|
||||||
# :return: UlrResource对象
|
|
||||||
# """
|
|
||||||
# to_dict = convert_from_ros_msg(self.get_ulr_resource())
|
|
||||||
# to_dict["type"] = "container"
|
|
||||||
# return to_dict
|
|
||||||
#
|
|
||||||
# def __str__(self):
|
|
||||||
# return f"{self.id}"
|
|
||||||
@@ -1,23 +1,18 @@
|
|||||||
import importlib
|
import importlib
|
||||||
import inspect
|
import inspect
|
||||||
import json
|
import json
|
||||||
import os.path
|
|
||||||
import traceback
|
import traceback
|
||||||
from typing import Union, Any, Dict, List, Tuple
|
from typing import Union, Any, Dict, List
|
||||||
import uuid
|
|
||||||
import networkx as nx
|
import networkx as nx
|
||||||
from pylabrobot.resources import ResourceHolder
|
from pylabrobot.resources import ResourceHolder
|
||||||
from unilabos_msgs.msg import Resource
|
from unilabos_msgs.msg import Resource
|
||||||
|
|
||||||
from unilabos.config.config import BasicConfig
|
|
||||||
from unilabos.resources.container import RegularContainer
|
from unilabos.resources.container import RegularContainer
|
||||||
from unilabos.resources.itemized_carrier import ItemizedCarrier
|
|
||||||
from unilabos.ros.msgs.message_converter import convert_to_ros_msg
|
from unilabos.ros.msgs.message_converter import convert_to_ros_msg
|
||||||
from unilabos.ros.nodes.resource_tracker import (
|
from unilabos.ros.nodes.resource_tracker import (
|
||||||
ResourceDictInstance,
|
ResourceDictInstance,
|
||||||
ResourceTreeSet,
|
ResourceTreeSet,
|
||||||
)
|
)
|
||||||
from unilabos.utils import logger
|
|
||||||
from unilabos.utils.banner_print import print_status
|
from unilabos.utils.banner_print import print_status
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -49,33 +44,6 @@ def canonicalize_nodes_data(
|
|||||||
if node.get("label") is not None:
|
if node.get("label") is not None:
|
||||||
node_id = node.pop("label")
|
node_id = node.pop("label")
|
||||||
node["id"] = node["name"] = node_id
|
node["id"] = node["name"] = node_id
|
||||||
if not isinstance(node.get("config"), dict):
|
|
||||||
node["config"] = {}
|
|
||||||
if not node.get("type"):
|
|
||||||
node["type"] = "device"
|
|
||||||
print_status(f"Warning: Node {node.get('id', 'unknown')} missing 'type', defaulting to 'device'", "warning")
|
|
||||||
if node.get("name", None) is None:
|
|
||||||
node["name"] = node.get("id")
|
|
||||||
print_status(f"Warning: Node {node.get('id', 'unknown')} missing 'name', defaulting to {node['name']}", "warning")
|
|
||||||
if not isinstance(node.get("position"), dict):
|
|
||||||
node["position"] = {"position": {}}
|
|
||||||
x = node.pop("x", None)
|
|
||||||
if x is not None:
|
|
||||||
node["position"]["position"]["x"] = x
|
|
||||||
y = node.pop("y", None)
|
|
||||||
if y is not None:
|
|
||||||
node["position"]["position"]["y"] = y
|
|
||||||
z = node.pop("z", None)
|
|
||||||
if z is not None:
|
|
||||||
node["position"]["position"]["z"] = z
|
|
||||||
if "sample_id" in node:
|
|
||||||
sample_id = node.pop("sample_id")
|
|
||||||
if sample_id:
|
|
||||||
logger.error(f"{node}的sample_id参数已弃用,sample_id: {sample_id}")
|
|
||||||
for k in list(node.keys()):
|
|
||||||
if k not in ["id", "uuid", "name", "description", "schema", "model", "icon", "parent_uuid", "parent", "type", "class", "position", "config", "data", "children"]:
|
|
||||||
v = node.pop(k)
|
|
||||||
node["config"][k] = v
|
|
||||||
|
|
||||||
# 第二步:处理parent_relation
|
# 第二步:处理parent_relation
|
||||||
id2idx = {node["id"]: idx for idx, node in enumerate(nodes)}
|
id2idx = {node["id"]: idx for idx, node in enumerate(nodes)}
|
||||||
@@ -333,10 +301,6 @@ def read_graphml(graphml_file: str) -> tuple[nx.Graph, ResourceTreeSet, List[Dic
|
|||||||
"nodes": [node.res_content.model_dump(by_alias=True) for node in resource_tree_set.all_nodes],
|
"nodes": [node.res_content.model_dump(by_alias=True) for node in resource_tree_set.all_nodes],
|
||||||
"links": standardized_links,
|
"links": standardized_links,
|
||||||
}
|
}
|
||||||
dump_json_path = os.path.join(BasicConfig.working_dir, os.path.basename(graphml_file).rsplit(".")[0] + ".json")
|
|
||||||
with open(dump_json_path, "w", encoding="utf-8") as f:
|
|
||||||
f.write(json.dumps(graph_data, indent=4, ensure_ascii=False))
|
|
||||||
print_status(f"GraphML converted to JSON and saved to {dump_json_path}", "info")
|
|
||||||
physical_setup_graph = nx.node_link_graph(graph_data, link="links", multigraph=False)
|
physical_setup_graph = nx.node_link_graph(graph_data, link="links", multigraph=False)
|
||||||
handle_communications(physical_setup_graph)
|
handle_communications(physical_setup_graph)
|
||||||
|
|
||||||
@@ -535,7 +499,6 @@ def resource_ulab_to_plr(resource: dict, plr_model=False) -> "ResourcePLR":
|
|||||||
|
|
||||||
def resource_ulab_to_plr_inner(resource: dict):
|
def resource_ulab_to_plr_inner(resource: dict):
|
||||||
all_states[resource["name"]] = resource["data"]
|
all_states[resource["name"]] = resource["data"]
|
||||||
extra = resource.pop("extra", {})
|
|
||||||
d = {
|
d = {
|
||||||
"name": resource["name"],
|
"name": resource["name"],
|
||||||
"type": resource["type"],
|
"type": resource["type"],
|
||||||
@@ -576,16 +539,16 @@ def resource_plr_to_ulab(resource_plr: "ResourcePLR", parent_name: str = None, w
|
|||||||
replace_info = {
|
replace_info = {
|
||||||
"plate": "plate",
|
"plate": "plate",
|
||||||
"well": "well",
|
"well": "well",
|
||||||
"tip_spot": "tip_spot",
|
"tip_spot": "container",
|
||||||
"trash": "trash",
|
"trash": "container",
|
||||||
"deck": "deck",
|
"deck": "deck",
|
||||||
"tip_rack": "tip_rack",
|
"tip_rack": "container",
|
||||||
}
|
}
|
||||||
if source in replace_info:
|
if source in replace_info:
|
||||||
return replace_info[source]
|
return replace_info[source]
|
||||||
else:
|
else:
|
||||||
print("转换pylabrobot的时候,出现未知类型", source)
|
print("转换pylabrobot的时候,出现未知类型", source)
|
||||||
return source
|
return "container"
|
||||||
|
|
||||||
def resource_plr_to_ulab_inner(d: dict, all_states: dict, child=True) -> dict:
|
def resource_plr_to_ulab_inner(d: dict, all_states: dict, child=True) -> dict:
|
||||||
r = {
|
r = {
|
||||||
@@ -613,55 +576,51 @@ def resource_plr_to_ulab(resource_plr: "ResourcePLR", parent_name: str = None, w
|
|||||||
return r
|
return r
|
||||||
|
|
||||||
|
|
||||||
def resource_bioyond_to_plr(bioyond_materials: list[dict], type_mapping: Dict[str, Tuple[str, str]] = {}, deck: Any = None) -> list[dict]:
|
def resource_bioyond_to_plr(bioyond_materials: list[dict], type_mapping: dict = {}, deck: Any = None) -> list[dict]:
|
||||||
"""
|
"""
|
||||||
将 bioyond 物料格式转换为 ulab 物料格式
|
将 bioyond 物料格式转换为 ulab 物料格式
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
bioyond_materials: bioyond 系统的物料查询结果列表
|
bioyond_materials: bioyond 系统的物料查询结果列表
|
||||||
type_mapping: 物料类型映射字典,格式 {bioyond_type: [plr_class_name, class_uuid]}
|
type_mapping: 物料类型映射字典,格式 {bioyond_type: plr_class_name}
|
||||||
location_id_mapping: 库位 ID 到名称的映射字典,格式 {location_id: location_name}
|
location_id_mapping: 库位 ID 到名称的映射字典,格式 {location_id: location_name}
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
pylabrobot 格式的物料列表
|
pylabrobot 格式的物料列表
|
||||||
"""
|
"""
|
||||||
print("1:bioyond_materials:",bioyond_materials)
|
|
||||||
# print("2:type_mapping:",type_mapping)
|
|
||||||
plr_materials = []
|
plr_materials = []
|
||||||
|
|
||||||
for material in bioyond_materials:
|
for material in bioyond_materials:
|
||||||
className = (
|
className = (
|
||||||
type_mapping.get(material.get("typeName"), ("RegularContainer", ""))[0] if type_mapping else "RegularContainer"
|
type_mapping.get(material.get("typeName"), "RegularContainer") if type_mapping else "RegularContainer"
|
||||||
)
|
)
|
||||||
|
|
||||||
plr_material: ResourcePLR = initialize_resource(
|
plr_material: ResourcePLR = initialize_resource(
|
||||||
{"name": material["name"], "class": className}, resource_type=ResourcePLR
|
{"name": material["name"], "class": className}, resource_type=ResourcePLR
|
||||||
)
|
)
|
||||||
print("plr_material:",plr_material)
|
|
||||||
print("code:",material.get("code", ""))
|
|
||||||
plr_material.code = material.get("code", "") and material.get("barCode", "") or ""
|
plr_material.code = material.get("code", "") and material.get("barCode", "") or ""
|
||||||
plr_material.unilabos_uuid = str(uuid.uuid4())
|
|
||||||
|
|
||||||
# 处理子物料(detail)
|
# 处理子物料(detail)
|
||||||
if material.get("detail") and len(material["detail"]) > 0:
|
if material.get("detail") and len(material["detail"]) > 0:
|
||||||
for bottle in reversed(plr_material.children):
|
|
||||||
plr_material.unassign_child_resource(bottle)
|
|
||||||
child_ids = []
|
child_ids = []
|
||||||
for detail in material["detail"]:
|
for detail in material["detail"]:
|
||||||
number = (
|
number = (
|
||||||
(detail.get("z", 0) - 1) * plr_material.num_items_x * plr_material.num_items_y
|
(detail.get("z", 0) - 1) * plr_material.num_items_x * plr_material.num_items_y
|
||||||
+ (detail.get("y", 0) - 1) * plr_material.num_items_y
|
+ (detail.get("x", 0) - 1) * plr_material.num_items_x
|
||||||
+ (detail.get("x", 0) - 1)
|
+ (detail.get("y", 0) - 1)
|
||||||
)
|
)
|
||||||
typeName = detail.get("typeName", detail.get("name", ""))
|
bottle = plr_material[number]
|
||||||
if typeName in type_mapping:
|
if detail["name"] in type_mapping:
|
||||||
bottle = plr_material[number] = initialize_resource(
|
# plr_material.unassign_child_resource(bottle)
|
||||||
{"name": f'{detail["name"]}_{number}', "class": type_mapping[typeName][0]}, resource_type=ResourcePLR
|
plr_material.sites[number] = None
|
||||||
|
plr_material[number] = initialize_resource(
|
||||||
|
{"name": f'{detail["name"]}_{number}', "class": type_mapping[detail["name"]]}, resource_type=ResourcePLR
|
||||||
)
|
)
|
||||||
|
else:
|
||||||
bottle.tracker.liquids = [
|
bottle.tracker.liquids = [
|
||||||
(detail["name"], float(detail.get("quantity", 0)) if detail.get("quantity") else 0)
|
(detail["name"], float(detail.get("quantity", 0)) if detail.get("quantity") else 0)
|
||||||
]
|
]
|
||||||
bottle.code = detail.get("code", "")
|
bottle.code = detail.get("code", "")
|
||||||
else:
|
else:
|
||||||
bottle = plr_material[0] if plr_material.capacity > 0 else plr_material
|
bottle = plr_material[0] if plr_material.capacity > 0 else plr_material
|
||||||
bottle.tracker.liquids = [
|
bottle.tracker.liquids = [
|
||||||
@@ -686,59 +645,32 @@ def resource_bioyond_to_plr(bioyond_materials: list[dict], type_mapping: Dict[st
|
|||||||
return plr_materials
|
return plr_materials
|
||||||
|
|
||||||
|
|
||||||
def resource_plr_to_bioyond(plr_resources: list[ResourcePLR], type_mapping: dict = {}, warehouse_mapping: dict = {}) -> list[dict]:
|
def resource_plr_to_bioyond(plr_materials: list[ResourcePLR], type_mapping: dict = {}, warehouse_mapping: dict = {}) -> list[dict]:
|
||||||
bioyond_materials = []
|
bioyond_materials = []
|
||||||
for resource in plr_resources:
|
for plr_material in plr_materials:
|
||||||
if hasattr(resource, "capacity") and resource.capacity > 1:
|
material = {
|
||||||
material = {
|
"name": plr_material.name,
|
||||||
"typeId": type_mapping.get(resource.model)[1],
|
"typeName": plr_material.__class__.__name__,
|
||||||
"name": resource.name,
|
"code": plr_material.code,
|
||||||
"unit": "个",
|
"quantity": 0,
|
||||||
"quantity": 1,
|
"detail": [],
|
||||||
"details": [],
|
"locations": [],
|
||||||
"Parameters": "{}"
|
}
|
||||||
}
|
if hasattr(plr_material, "capacity") and plr_material.capacity > 1:
|
||||||
for bottle in resource.children:
|
for idx in range(plr_material.capacity):
|
||||||
if isinstance(resource, ItemizedCarrier):
|
bottle = plr_material[idx]
|
||||||
site = resource.get_child_identifier(bottle)
|
detail = {
|
||||||
else:
|
"x": (idx // (plr_material.num_items_x * plr_material.num_items_y)) + 1,
|
||||||
site = {"x": bottle.location.x - 1, "y": bottle.location.y - 1}
|
"y": ((idx % (plr_material.num_items_x * plr_material.num_items_y)) // plr_material.num_items_x) + 1,
|
||||||
detail_item = {
|
"z": (idx % plr_material.num_items_x) + 1,
|
||||||
"typeId": type_mapping.get(bottle.model)[1],
|
|
||||||
"name": bottle.name,
|
|
||||||
"code": bottle.code if hasattr(bottle, "code") else "",
|
"code": bottle.code if hasattr(bottle, "code") else "",
|
||||||
"quantity": sum(qty for _, qty in bottle.tracker.liquids) if hasattr(bottle, "tracker") else 0,
|
"quantity": sum(qty for _, qty in bottle.tracker.liquids) if hasattr(bottle, "tracker") else 0,
|
||||||
"x": site["x"] + 1,
|
|
||||||
"y": site["y"] + 1,
|
|
||||||
"molecular": 1,
|
|
||||||
"Parameters": json.dumps({"molecular": 1})
|
|
||||||
}
|
}
|
||||||
material["details"].append(detail_item)
|
material["detail"].append(detail)
|
||||||
|
material["quantity"] = 1.0
|
||||||
else:
|
else:
|
||||||
bottle = resource[0] if resource.capacity > 0 else resource
|
bottle = plr_material[0] if plr_material.capacity > 0 else plr_material
|
||||||
material = {
|
material["quantity"] = sum(qty for _, qty in bottle.tracker.liquids) if hasattr(plr_material, "tracker") else 0
|
||||||
"typeId": "3a14196b-24f2-ca49-9081-0cab8021bf1a",
|
|
||||||
"name": resource.get("name", ""),
|
|
||||||
"unit": "",
|
|
||||||
"quantity": sum(qty for _, qty in bottle.tracker.liquids) if hasattr(bottle, "tracker") else 0,
|
|
||||||
"Parameters": "{}"
|
|
||||||
}
|
|
||||||
|
|
||||||
if resource.parent is not None and isinstance(resource.parent, ItemizedCarrier):
|
|
||||||
site_in_parent = resource.parent.get_child_identifier(resource)
|
|
||||||
material["locations"] = [
|
|
||||||
{
|
|
||||||
"id": warehouse_mapping[resource.parent.name]["site_uuids"][site_in_parent["identifier"]],
|
|
||||||
"whid": warehouse_mapping[resource.parent.name]["uuid"],
|
|
||||||
"whName": resource.parent.name,
|
|
||||||
"x": site_in_parent["z"] + 1,
|
|
||||||
"y": site_in_parent["y"] + 1,
|
|
||||||
"z": 1,
|
|
||||||
"quantity": 0
|
|
||||||
}
|
|
||||||
],
|
|
||||||
|
|
||||||
print(f"material_data: {material}")
|
|
||||||
bioyond_materials.append(material)
|
bioyond_materials.append(material)
|
||||||
return bioyond_materials
|
return bioyond_materials
|
||||||
|
|
||||||
@@ -785,7 +717,6 @@ def initialize_resource(resource_config: dict, resource_type: Any = None) -> Uni
|
|||||||
else:
|
else:
|
||||||
r = resource_plr
|
r = resource_plr
|
||||||
elif resource_class_config["type"] == "unilabos":
|
elif resource_class_config["type"] == "unilabos":
|
||||||
raise ValueError(f"No more support for unilabos Resource class {resource_class_config}")
|
|
||||||
res_instance: RegularContainer = RESOURCE(id=resource_config["name"])
|
res_instance: RegularContainer = RESOURCE(id=resource_config["name"])
|
||||||
res_instance.ulr_resource = convert_to_ros_msg(
|
res_instance.ulr_resource = convert_to_ros_msg(
|
||||||
Resource, {k: v for k, v in resource_config.items() if k != "class"}
|
Resource, {k: v for k, v in resource_config.items() if k != "class"}
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ class Bottle(Well):
|
|||||||
barcode: Optional[str] = "",
|
barcode: Optional[str] = "",
|
||||||
category: str = "container",
|
category: str = "container",
|
||||||
model: Optional[str] = None,
|
model: Optional[str] = None,
|
||||||
**kwargs,
|
|
||||||
):
|
):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
name=name,
|
name=name,
|
||||||
@@ -74,11 +73,9 @@ class ItemizedCarrier(ResourcePLR):
|
|||||||
num_items_x: int = 0,
|
num_items_x: int = 0,
|
||||||
num_items_y: int = 0,
|
num_items_y: int = 0,
|
||||||
num_items_z: int = 0,
|
num_items_z: int = 0,
|
||||||
layout: str = "x-y",
|
|
||||||
sites: Optional[Dict[Union[int, str], Optional[ResourcePLR]]] = None,
|
sites: Optional[Dict[Union[int, str], Optional[ResourcePLR]]] = None,
|
||||||
category: Optional[str] = "carrier",
|
category: Optional[str] = "carrier",
|
||||||
model: Optional[str] = None,
|
model: Optional[str] = None,
|
||||||
invisible_slots: Optional[str] = None,
|
|
||||||
):
|
):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
name=name,
|
name=name,
|
||||||
@@ -90,9 +87,6 @@ class ItemizedCarrier(ResourcePLR):
|
|||||||
)
|
)
|
||||||
self.num_items = len(sites)
|
self.num_items = len(sites)
|
||||||
self.num_items_x, self.num_items_y, self.num_items_z = num_items_x, num_items_y, num_items_z
|
self.num_items_x, self.num_items_y, self.num_items_z = num_items_x, num_items_y, num_items_z
|
||||||
self.invisible_slots = [] if invisible_slots is None else invisible_slots
|
|
||||||
self.layout = "z-y" if self.num_items_z > 1 and self.num_items_x == 1 else "x-z" if self.num_items_z > 1 and self.num_items_y == 1 else "x-y"
|
|
||||||
|
|
||||||
if isinstance(sites, dict):
|
if isinstance(sites, dict):
|
||||||
sites = sites or {}
|
sites = sites or {}
|
||||||
self.sites: List[Optional[ResourcePLR]] = list(sites.values())
|
self.sites: List[Optional[ResourcePLR]] = list(sites.values())
|
||||||
@@ -155,7 +149,7 @@ class ItemizedCarrier(ResourcePLR):
|
|||||||
def assign_resource_to_site(self, resource: ResourcePLR, spot: int):
|
def assign_resource_to_site(self, resource: ResourcePLR, spot: int):
|
||||||
if self.sites[spot] is not None and not isinstance(self.sites[spot], ResourceHolder):
|
if self.sites[spot] is not None and not isinstance(self.sites[spot], ResourceHolder):
|
||||||
raise ValueError(f"spot {spot} already has a resource, {resource}")
|
raise ValueError(f"spot {spot} already has a resource, {resource}")
|
||||||
self.assign_child_resource(resource, location=self.child_locations.get(list(self._ordering.keys())[spot]), spot=spot)
|
self.assign_child_resource(resource, location=self.child_locations.get(str(spot)), spot=spot)
|
||||||
|
|
||||||
def unassign_child_resource(self, resource: ResourcePLR):
|
def unassign_child_resource(self, resource: ResourcePLR):
|
||||||
found = False
|
found = False
|
||||||
@@ -166,92 +160,8 @@ class ItemizedCarrier(ResourcePLR):
|
|||||||
break
|
break
|
||||||
if not found:
|
if not found:
|
||||||
raise ValueError(f"Resource {resource} is not assigned to this carrier")
|
raise ValueError(f"Resource {resource} is not assigned to this carrier")
|
||||||
super().unassign_child_resource(resource)
|
if hasattr(resource, "unassign"):
|
||||||
# if hasattr(resource, "unassign"):
|
resource.unassign()
|
||||||
# resource.unassign()
|
|
||||||
|
|
||||||
def get_child_identifier(self, child: ResourcePLR):
|
|
||||||
"""Get the identifier information for a given child resource.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
child: The Resource object to find the identifier for
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
dict: A dictionary containing:
|
|
||||||
- identifier: The string identifier (e.g. "A1", "B2")
|
|
||||||
- idx: The integer index in the sites list
|
|
||||||
- x: The x index (column index, 0-based)
|
|
||||||
- y: The y index (row index, 0-based)
|
|
||||||
- z: The z index (layer index, 0-based)
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
ValueError: If the child resource is not found in this carrier
|
|
||||||
"""
|
|
||||||
# Find the child resource in sites
|
|
||||||
for idx, resource in enumerate(self.sites):
|
|
||||||
if resource is child:
|
|
||||||
# Get the identifier from ordering keys
|
|
||||||
identifier = list(self._ordering.keys())[idx]
|
|
||||||
|
|
||||||
# Parse identifier to get x, y, z indices
|
|
||||||
x_idx, y_idx, z_idx = self._parse_identifier_to_indices(identifier, idx)
|
|
||||||
|
|
||||||
return {
|
|
||||||
"identifier": identifier,
|
|
||||||
"idx": idx,
|
|
||||||
"x": x_idx,
|
|
||||||
"y": y_idx,
|
|
||||||
"z": z_idx
|
|
||||||
}
|
|
||||||
|
|
||||||
# If not found, raise an error
|
|
||||||
raise ValueError(f"Resource {child} is not assigned to this carrier")
|
|
||||||
|
|
||||||
def _parse_identifier_to_indices(self, identifier: str, idx: int) -> Tuple[int, int, int]:
|
|
||||||
"""Parse identifier string to get x, y, z indices.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
identifier: String identifier like "A1", "B2", etc.
|
|
||||||
idx: Linear index as fallback for calculation
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Tuple of (x_idx, y_idx, z_idx)
|
|
||||||
"""
|
|
||||||
# If we have explicit dimensions, calculate from idx
|
|
||||||
if self.num_items_x > 0 and self.num_items_y > 0:
|
|
||||||
# Calculate 3D indices from linear index
|
|
||||||
z_idx = idx // (self.num_items_x * self.num_items_y) if self.num_items_z > 0 else 0
|
|
||||||
remaining = idx % (self.num_items_x * self.num_items_y)
|
|
||||||
y_idx = remaining // self.num_items_x
|
|
||||||
x_idx = remaining % self.num_items_x
|
|
||||||
return x_idx, y_idx, z_idx
|
|
||||||
|
|
||||||
# Fallback: parse from Excel-style identifier
|
|
||||||
if isinstance(identifier, str) and len(identifier) >= 2:
|
|
||||||
# Extract row (letter) and column (number)
|
|
||||||
row_letters = ""
|
|
||||||
col_numbers = ""
|
|
||||||
|
|
||||||
for char in identifier:
|
|
||||||
if char.isalpha():
|
|
||||||
row_letters += char
|
|
||||||
elif char.isdigit():
|
|
||||||
col_numbers += char
|
|
||||||
|
|
||||||
if row_letters and col_numbers:
|
|
||||||
# Convert letter(s) to row index (A=0, B=1, etc.)
|
|
||||||
y_idx = 0
|
|
||||||
for char in row_letters:
|
|
||||||
y_idx = y_idx * 26 + (ord(char.upper()) - ord('A'))
|
|
||||||
|
|
||||||
# Convert number to column index (1-based to 0-based)
|
|
||||||
x_idx = int(col_numbers) - 1
|
|
||||||
z_idx = 0 # Default layer
|
|
||||||
|
|
||||||
return x_idx, y_idx, z_idx
|
|
||||||
|
|
||||||
# If all else fails, assume linear arrangement
|
|
||||||
return idx, 0, 0
|
|
||||||
|
|
||||||
def __getitem__(
|
def __getitem__(
|
||||||
self,
|
self,
|
||||||
@@ -409,10 +319,9 @@ class ItemizedCarrier(ResourcePLR):
|
|||||||
"num_items_x": self.num_items_x,
|
"num_items_x": self.num_items_x,
|
||||||
"num_items_y": self.num_items_y,
|
"num_items_y": self.num_items_y,
|
||||||
"num_items_z": self.num_items_z,
|
"num_items_z": self.num_items_z,
|
||||||
"layout": self.layout,
|
|
||||||
"sites": [{
|
"sites": [{
|
||||||
"label": str(identifier),
|
"label": str(identifier),
|
||||||
"visible": False if identifier in self.invisible_slots else True,
|
"visible": True if self[identifier] is not None else False,
|
||||||
"occupied_by": self[identifier].name
|
"occupied_by": self[identifier].name
|
||||||
if isinstance(self[identifier], ResourcePLR) and not isinstance(self[identifier], ResourceHolder) else
|
if isinstance(self[identifier], ResourcePLR) and not isinstance(self[identifier], ResourceHolder) else
|
||||||
self[identifier] if isinstance(self[identifier], str) else None,
|
self[identifier] if isinstance(self[identifier], str) else None,
|
||||||
@@ -435,8 +344,6 @@ class BottleCarrier(ItemizedCarrier):
|
|||||||
sites: Optional[Dict[Union[int, str], ResourceHolder]] = None,
|
sites: Optional[Dict[Union[int, str], ResourceHolder]] = None,
|
||||||
category: str = "bottle_carrier",
|
category: str = "bottle_carrier",
|
||||||
model: Optional[str] = None,
|
model: Optional[str] = None,
|
||||||
invisible_slots: List[str] = None,
|
|
||||||
**kwargs,
|
|
||||||
):
|
):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
name=name,
|
name=name,
|
||||||
@@ -446,5 +353,4 @@ class BottleCarrier(ItemizedCarrier):
|
|||||||
sites=sites,
|
sites=sites,
|
||||||
category=category,
|
category=category,
|
||||||
model=model,
|
model=model,
|
||||||
invisible_slots=invisible_slots,
|
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -5,13 +5,10 @@ from pylabrobot.resources.carrier import ResourceHolder, create_homogeneous_reso
|
|||||||
from unilabos.resources.itemized_carrier import ItemizedCarrier, ResourcePLR
|
from unilabos.resources.itemized_carrier import ItemizedCarrier, ResourcePLR
|
||||||
|
|
||||||
|
|
||||||
LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
def warehouse_factory(
|
||||||
|
|
||||||
|
|
||||||
def YB_warehouse_factory(
|
|
||||||
name: str,
|
name: str,
|
||||||
num_items_x: int = 1,
|
num_items_x: int = 4,
|
||||||
num_items_y: int = 4,
|
num_items_y: int = 1,
|
||||||
num_items_z: int = 4,
|
num_items_z: int = 4,
|
||||||
dx: float = 137.0,
|
dx: float = 137.0,
|
||||||
dy: float = 96.0,
|
dy: float = 96.0,
|
||||||
@@ -36,17 +33,13 @@ def YB_warehouse_factory(
|
|||||||
locations.append(Coordinate(x, y, z))
|
locations.append(Coordinate(x, y, z))
|
||||||
if removed_positions:
|
if removed_positions:
|
||||||
locations = [loc for i, loc in enumerate(locations) if i not in removed_positions]
|
locations = [loc for i, loc in enumerate(locations) if i not in removed_positions]
|
||||||
_sites = create_homogeneous_resources(
|
sites = create_homogeneous_resources(
|
||||||
klass=ResourceHolder,
|
klass=ResourceHolder,
|
||||||
locations=locations,
|
locations=locations,
|
||||||
resource_size_x=127.0,
|
resource_size_x=127.0,
|
||||||
resource_size_y=86.0,
|
resource_size_y=86.0,
|
||||||
name_prefix=name,
|
name_prefix=name,
|
||||||
)
|
)
|
||||||
len_x, len_y = (num_items_x, num_items_y) if num_items_z == 1 else (num_items_y, num_items_z) if num_items_x == 1 else (num_items_x, num_items_z)
|
|
||||||
|
|
||||||
keys = [f"{LETTERS[len_y-1-j]}{str(i+1).zfill(2)}" for j in range(len_y) for i in range(len_x)]
|
|
||||||
sites = {i: site for i, site in zip(keys, _sites.values())}
|
|
||||||
|
|
||||||
return WareHouse(
|
return WareHouse(
|
||||||
name=name,
|
name=name,
|
||||||
@@ -75,7 +68,6 @@ class WareHouse(ItemizedCarrier):
|
|||||||
num_items_x: int,
|
num_items_x: int,
|
||||||
num_items_y: int,
|
num_items_y: int,
|
||||||
num_items_z: int,
|
num_items_z: int,
|
||||||
layout: str = "x-y",
|
|
||||||
sites: Optional[Dict[Union[int, str], Optional[ResourcePLR]]] = None,
|
sites: Optional[Dict[Union[int, str], Optional[ResourcePLR]]] = None,
|
||||||
category: str = "warehouse",
|
category: str = "warehouse",
|
||||||
model: Optional[str] = None,
|
model: Optional[str] = None,
|
||||||
@@ -91,7 +83,6 @@ class WareHouse(ItemizedCarrier):
|
|||||||
num_items_x=num_items_x,
|
num_items_x=num_items_x,
|
||||||
num_items_y=num_items_y,
|
num_items_y=num_items_y,
|
||||||
num_items_z=num_items_z,
|
num_items_z=num_items_z,
|
||||||
layout=layout,
|
|
||||||
sites=sites,
|
sites=sites,
|
||||||
category=category,
|
category=category,
|
||||||
model=model,
|
model=model,
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ def initialize_device_from_dict(device_id, device_config) -> Optional[ROS2Device
|
|||||||
d = None
|
d = None
|
||||||
original_device_config = copy.deepcopy(device_config)
|
original_device_config = copy.deepcopy(device_config)
|
||||||
device_class_config = device_config["class"]
|
device_class_config = device_config["class"]
|
||||||
uid = device_config["uuid"]
|
|
||||||
if isinstance(device_class_config, str): # 如果是字符串,则直接去lab_registry中查找,获取class
|
if isinstance(device_class_config, str): # 如果是字符串,则直接去lab_registry中查找,获取class
|
||||||
if len(device_class_config) == 0:
|
if len(device_class_config) == 0:
|
||||||
raise DeviceClassInvalid(f"Device [{device_id}] class cannot be an empty string. {device_config}")
|
raise DeviceClassInvalid(f"Device [{device_id}] class cannot be an empty string. {device_config}")
|
||||||
@@ -51,7 +50,7 @@ def initialize_device_from_dict(device_id, device_config) -> Optional[ROS2Device
|
|||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
d = DEVICE(
|
d = DEVICE(
|
||||||
device_id=device_id, device_uuid=uid, driver_is_ros=device_class_config["type"] == "ros2", driver_params=device_config.get("config", {})
|
device_id=device_id, driver_is_ros=device_class_config["type"] == "ros2", driver_params=device_config.get("config", {})
|
||||||
)
|
)
|
||||||
except DeviceInitError as ex:
|
except DeviceInitError as ex:
|
||||||
return d
|
return d
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user