Compare commits
1 Commits
d266d21104
...
v0.10.7
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9aeffebde1 |
@@ -1,6 +1,6 @@
|
||||
package:
|
||||
name: unilabos
|
||||
version: 0.10.10
|
||||
version: 0.10.7
|
||||
|
||||
source:
|
||||
path: ../unilabos
|
||||
|
||||
114
.github/workflows/conda-pack-build.yml
vendored
@@ -42,7 +42,7 @@ jobs:
|
||||
defaults:
|
||||
run:
|
||||
# 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:
|
||||
- name: Check if platform should be built
|
||||
@@ -73,6 +73,7 @@ jobs:
|
||||
channels: conda-forge,robostack-staging,uni-lab,defaults
|
||||
channel-priority: flexible
|
||||
activate-environment: unilab
|
||||
auto-activate-base: true
|
||||
auto-update-conda: false
|
||||
show-channel-urls: true
|
||||
|
||||
@@ -81,7 +82,7 @@ jobs:
|
||||
run: |
|
||||
echo Installing unilabos and dependencies to unilab environment...
|
||||
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)
|
||||
if: steps.should_build.outputs.should_build == 'true' && matrix.platform != 'win-64'
|
||||
@@ -89,15 +90,15 @@ jobs:
|
||||
run: |
|
||||
echo "Installing unilabos and dependencies to unilab environment..."
|
||||
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)
|
||||
if: steps.should_build.outputs.should_build == 'true' && matrix.platform == 'win-64'
|
||||
id: msgs_version_win
|
||||
run: |
|
||||
echo Checking installed ros-humble-unilabos-msgs version...
|
||||
conda list -n unilab 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
|
||||
conda list ros-humble-unilabos-msgs
|
||||
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 ros-humble-unilabos-msgs version: %VERSION%
|
||||
|
||||
@@ -107,7 +108,7 @@ jobs:
|
||||
shell: bash
|
||||
run: |
|
||||
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 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
|
||||
echo.
|
||||
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)
|
||||
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"
|
||||
echo ""
|
||||
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)
|
||||
if: steps.should_build.outputs.should_build == 'true' && matrix.platform == 'win-64'
|
||||
run: |
|
||||
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 }})...
|
||||
mamba run -n unilab pip install .
|
||||
pip install .
|
||||
echo Verifying installation...
|
||||
mamba run -n unilab pip show unilabos
|
||||
pip show unilabos
|
||||
|
||||
- name: Install latest unilabos from source (Unix)
|
||||
if: steps.should_build.outputs.should_build == 'true' && matrix.platform != 'win-64'
|
||||
shell: bash
|
||||
run: |
|
||||
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 }})..."
|
||||
mamba run -n unilab pip install .
|
||||
pip install .
|
||||
echo "Verifying installation..."
|
||||
mamba run -n unilab pip show unilabos
|
||||
pip show unilabos
|
||||
|
||||
- name: Display environment info (Windows)
|
||||
if: steps.should_build.outputs.should_build == 'true' && matrix.platform == 'win-64'
|
||||
run: |
|
||||
echo === Environment Information ===
|
||||
mamba env list
|
||||
conda env list
|
||||
echo.
|
||||
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 === 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)
|
||||
if: steps.should_build.outputs.should_build == 'true' && matrix.platform != 'win-64'
|
||||
shell: bash
|
||||
run: |
|
||||
echo "=== Environment Information ==="
|
||||
mamba env list
|
||||
conda env list
|
||||
echo ""
|
||||
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 "=== 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)
|
||||
if: steps.should_build.outputs.should_build == 'true' && matrix.platform == 'win-64'
|
||||
run: |
|
||||
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...
|
||||
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...
|
||||
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...
|
||||
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!
|
||||
|
||||
- name: Verify environment integrity (Unix)
|
||||
@@ -194,20 +195,20 @@ jobs:
|
||||
shell: bash
|
||||
run: |
|
||||
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..."
|
||||
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..."
|
||||
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..."
|
||||
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!"
|
||||
|
||||
- name: Pack conda environment (Windows)
|
||||
if: steps.should_build.outputs.should_build == 'true' && matrix.platform == 'win-64'
|
||||
run: |
|
||||
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:
|
||||
dir unilab-env-${{ matrix.platform }}.tar.gz
|
||||
|
||||
@@ -216,7 +217,6 @@ jobs:
|
||||
shell: bash
|
||||
run: |
|
||||
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
|
||||
echo "Pack file created:"
|
||||
ls -lh unilab-env-${{ matrix.platform }}.tar.gz
|
||||
@@ -242,10 +242,6 @@ jobs:
|
||||
echo Adding: verify_installation.py
|
||||
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
|
||||
echo Creating: 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"
|
||||
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
|
||||
echo "Creating: 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/
|
||||
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
|
||||
if: steps.should_build.outputs.should_build == 'true'
|
||||
uses: actions/upload-artifact@v4
|
||||
@@ -333,8 +365,12 @@ jobs:
|
||||
echo "Distribution package contents:"
|
||||
ls -lh dist-package/
|
||||
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 ""
|
||||
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 "=========================================="
|
||||
|
||||
43
.github/workflows/deploy-docs.yml
vendored
@@ -39,39 +39,24 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.inputs.branch || github.ref }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Miniforge (with mamba)
|
||||
uses: conda-incubator/setup-miniconda@v3
|
||||
- name: Setup Python environment
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
miniforge-version: latest
|
||||
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
|
||||
python-version: '3.10'
|
||||
|
||||
- name: Install unilabos and dependencies
|
||||
- name: Install system dependencies
|
||||
run: |
|
||||
echo "Installing unilabos and dependencies to unilab environment..."
|
||||
echo "Using mamba for faster and more reliable dependency resolution..."
|
||||
mamba install -n unilab uni-lab::unilabos -c uni-lab -c robostack-staging -c conda-forge -y
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y pandoc
|
||||
|
||||
- name: Install latest unilabos from source
|
||||
- name: Install Python dependencies
|
||||
run: |
|
||||
echo "Uninstalling existing unilabos..."
|
||||
mamba run -n unilab pip uninstall unilabos -y || echo "unilabos not installed via pip"
|
||||
echo "Installing unilabos from source..."
|
||||
mamba run -n unilab pip install .
|
||||
echo "Verifying installation..."
|
||||
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
|
||||
python -m pip install --upgrade pip
|
||||
# Install package in development mode to get version info
|
||||
pip install -e .
|
||||
# Install documentation dependencies
|
||||
pip install -r docs/requirements.txt
|
||||
|
||||
- name: Setup Pages
|
||||
id: pages
|
||||
@@ -83,8 +68,8 @@ jobs:
|
||||
cd docs
|
||||
# Clean previous builds
|
||||
rm -rf _build
|
||||
# Build HTML documentation in conda environment
|
||||
mamba run -n unilab python -m sphinx -b html . _build/html -v
|
||||
# Build HTML documentation
|
||||
python -m sphinx -b html . _build/html -v
|
||||
|
||||
- name: Check build results
|
||||
run: |
|
||||
|
||||
19
CONTRIBUTORS
@@ -1,18 +1,15 @@
|
||||
56 Xuwznln <18435084+Xuwznln@users.noreply.github.com>
|
||||
10 wznln <18435084+Xuwznln@users.noreply.github.com>
|
||||
6 Junhan Chang <changjh@dp.tech>
|
||||
156 Xuwznln <18435084+Xuwznln@users.noreply.github.com>
|
||||
39 Junhan Chang <changjh@dp.tech>
|
||||
9 wznln <18435084+Xuwznln@users.noreply.github.com>
|
||||
8 Guangxin Zhang <guangxin.zhang.bio@gmail.com>
|
||||
5 ZiWei <131428629+ZiWei09@users.noreply.github.com>
|
||||
2 Guangxin Zhang <guangxin.zhang.bio@gmail.com>
|
||||
2 Junhan Chang <changjh@pku.edu.cn>
|
||||
2 WenzheG <wenzheguo32@gmail.com>
|
||||
1 Harry Liu <113173203+ALITTLELZ@users.noreply.github.com>
|
||||
2 Xie Qiming <97236197+Andy6M@users.noreply.github.com>
|
||||
1 Harvey Que <103566763+Mile-Away@users.noreply.github.com>
|
||||
1 Junhan Chang <1700011741@pku.edu.cn>
|
||||
1 Xianwei Qi <qxw@stu.pku.edu.cn>
|
||||
1 hh.(SII) <103566763+Mile-Away@users.noreply.github.com>
|
||||
1 LccLink <1951855008@qq.com>
|
||||
1 h840473807 <47357934+h840473807@users.noreply.github.com>
|
||||
1 lixinyu1011 <61094742+lixinyu1011@users.noreply.github.com>
|
||||
1 q434343 <73513873+q434343@users.noreply.github.com>
|
||||
1 tt <166512503+tt11142023@users.noreply.github.com>
|
||||
1 xyc <49015816+xiaoyu10031@users.noreply.github.com>
|
||||
1 shiyubo0410 <shiyubo@dp.tech>
|
||||
1 王俊杰 <1800011822@pku.edu.cn>
|
||||
1 王俊杰 <43375851+wjjxxx@users.noreply.github.com>
|
||||
|
||||
@@ -31,7 +31,7 @@ Join the [Intelligent Organic Chemistry Synthesis Competition](https://bohrium.d
|
||||
|
||||
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
|
||||
|
||||
@@ -55,7 +55,7 @@ pip install .
|
||||
|
||||
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
|
||||
|
||||
|
||||
@@ -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 系统:
|
||||
|
||||
请见[文档-启动样例](https://xuwznln.github.io/Uni-Lab-OS-Doc/boot_examples/index.html)
|
||||
请见[文档-启动样例](https://dptech-corp.github.io/Uni-Lab-OS/boot_examples/index.html)
|
||||
|
||||
## 消息格式
|
||||
|
||||
|
||||
@@ -91,7 +91,7 @@
|
||||
使用以下命令启动模拟反应器:
|
||||
|
||||
```bash
|
||||
unilab -g test/experiments/mock_reactor.json
|
||||
unilab -g test/experiments/mock_reactor.json --app_bridges ""
|
||||
```
|
||||
|
||||
### 2. 执行抽真空和充气操作
|
||||
|
||||
@@ -23,8 +23,7 @@ extensions = [
|
||||
"myst_parser",
|
||||
"sphinx.ext.autodoc",
|
||||
"sphinx.ext.napoleon", # 如果您使用 Google 或 NumPy 风格的 docstrings
|
||||
"sphinx_rtd_theme",
|
||||
"sphinxcontrib.mermaid"
|
||||
"sphinx_rtd_theme"
|
||||
]
|
||||
|
||||
source_suffix = {
|
||||
@@ -43,8 +42,6 @@ myst_enable_extensions = [
|
||||
"substitution",
|
||||
]
|
||||
|
||||
myst_fence_as_directive = ["mermaid"]
|
||||
|
||||
templates_path = ["_templates"]
|
||||
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
|
||||
|
||||
@@ -206,5 +203,3 @@ def generate_action_includes(app):
|
||||
|
||||
def setup(app):
|
||||
app.connect("builder-inited", generate_action_includes)
|
||||
app.add_js_file("https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js")
|
||||
app.add_js_file(None, body="mermaid.initialize({startOnLoad:true});")
|
||||
|
||||
@@ -1,26 +1,88 @@
|
||||
## 简单单变量动作函数
|
||||
|
||||
|
||||
### `SendCmd`
|
||||
|
||||
```{literalinclude} ../../unilabos_msgs/action/SendCmd.action
|
||||
:language: yaml
|
||||
```
|
||||
|
||||
----
|
||||
---
|
||||
|
||||
### `StrSingleInput`
|
||||
|
||||
```{literalinclude} ../../unilabos_msgs/action/StrSingleInput.action
|
||||
:language: yaml
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `IntSingleInput`
|
||||
|
||||
```{literalinclude} ../../unilabos_msgs/action/IntSingleInput.action
|
||||
:language: yaml
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `FloatSingleInput`
|
||||
|
||||
```{literalinclude} ../../unilabos_msgs/action/FloatSingleInput.action
|
||||
:language: yaml
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `Point3DSeparateInput`
|
||||
|
||||
```{literalinclude} ../../unilabos_msgs/action/Point3DSeparateInput.action
|
||||
:language: yaml
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `Wait`
|
||||
|
||||
```{literalinclude} ../../unilabos_msgs/action/Wait.action
|
||||
:language: yaml
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 常量有机化学操作
|
||||
|
||||
Uni-Lab 常量有机化学指令集多数来自 [XDL](https://croningroup.gitlab.io/chemputer/xdl/standard/full_steps_specification.html#),包含有机合成实验中常见的操作,如加热、搅拌、冷却等。
|
||||
|
||||
|
||||
|
||||
### `Clean`
|
||||
|
||||
```{literalinclude} ../../unilabos_msgs/action/Clean.action
|
||||
:language: yaml
|
||||
```
|
||||
|
||||
----
|
||||
---
|
||||
|
||||
### `EvacuateAndRefill`
|
||||
|
||||
```{literalinclude} ../../unilabos_msgs/action/EvacuateAndRefill.action
|
||||
:language: yaml
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `Evaporate`
|
||||
|
||||
```{literalinclude} ../../unilabos_msgs/action/Evaporate.action
|
||||
:language: yaml
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `HeatChill`
|
||||
|
||||
```{literalinclude} ../../unilabos_msgs/action/HeatChill.action
|
||||
:language: yaml
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `HeatChillStart`
|
||||
|
||||
@@ -28,7 +90,7 @@ Uni-Lab 常量有机化学指令集多数来自 [XDL](https://croningroup.gitlab
|
||||
:language: yaml
|
||||
```
|
||||
|
||||
----
|
||||
---
|
||||
|
||||
### `HeatChillStop`
|
||||
|
||||
@@ -36,7 +98,7 @@ Uni-Lab 常量有机化学指令集多数来自 [XDL](https://croningroup.gitlab
|
||||
:language: yaml
|
||||
```
|
||||
|
||||
----
|
||||
---
|
||||
|
||||
### `PumpTransfer`
|
||||
|
||||
@@ -44,12 +106,195 @@ Uni-Lab 常量有机化学指令集多数来自 [XDL](https://croningroup.gitlab
|
||||
:language: yaml
|
||||
```
|
||||
|
||||
----
|
||||
---
|
||||
|
||||
### `Separate`
|
||||
|
||||
```{literalinclude} ../../unilabos_msgs/action/Separate.action
|
||||
:language: yaml
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `Stir`
|
||||
|
||||
```{literalinclude} ../../unilabos_msgs/action/Stir.action
|
||||
:language: yaml
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `Add`
|
||||
|
||||
```{literalinclude} ../../unilabos_msgs/action/Add.action
|
||||
:language: yaml
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `AddSolid`
|
||||
|
||||
```{literalinclude} ../../unilabos_msgs/action/AddSolid.action
|
||||
:language: yaml
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `AdjustPH`
|
||||
|
||||
```{literalinclude} ../../unilabos_msgs/action/AdjustPH.action
|
||||
:language: yaml
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `Centrifuge`
|
||||
|
||||
```{literalinclude} ../../unilabos_msgs/action/Centrifuge.action
|
||||
:language: yaml
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `CleanVessel`
|
||||
|
||||
```{literalinclude} ../../unilabos_msgs/action/CleanVessel.action
|
||||
:language: yaml
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `Crystallize`
|
||||
|
||||
```{literalinclude} ../../unilabos_msgs/action/Crystallize.action
|
||||
:language: yaml
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `Dissolve`
|
||||
|
||||
```{literalinclude} ../../unilabos_msgs/action/Dissolve.action
|
||||
:language: yaml
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `Dry`
|
||||
|
||||
```{literalinclude} ../../unilabos_msgs/action/Dry.action
|
||||
:language: yaml
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `Filter`
|
||||
|
||||
```{literalinclude} ../../unilabos_msgs/action/Filter.action
|
||||
:language: yaml
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `FilterThrough`
|
||||
|
||||
```{literalinclude} ../../unilabos_msgs/action/FilterThrough.action
|
||||
:language: yaml
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `Hydrogenate`
|
||||
|
||||
```{literalinclude} ../../unilabos_msgs/action/Hydrogenate.action
|
||||
:language: yaml
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `Purge`
|
||||
|
||||
```{literalinclude} ../../unilabos_msgs/action/Purge.action
|
||||
:language: yaml
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `Recrystallize`
|
||||
|
||||
```{literalinclude} ../../unilabos_msgs/action/Recrystallize.action
|
||||
:language: yaml
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `RunColumn`
|
||||
|
||||
```{literalinclude} ../../unilabos_msgs/action/RunColumn.action
|
||||
:language: yaml
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `StartPurge`
|
||||
|
||||
```{literalinclude} ../../unilabos_msgs/action/StartPurge.action
|
||||
:language: yaml
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `StartStir`
|
||||
|
||||
```{literalinclude} ../../unilabos_msgs/action/StartStir.action
|
||||
:language: yaml
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `StopPurge`
|
||||
|
||||
```{literalinclude} ../../unilabos_msgs/action/StopPurge.action
|
||||
:language: yaml
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `StopStir`
|
||||
|
||||
```{literalinclude} ../../unilabos_msgs/action/StopStir.action
|
||||
:language: yaml
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `Transfer`
|
||||
|
||||
```{literalinclude} ../../unilabos_msgs/action/Transfer.action
|
||||
:language: yaml
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `WashSolid`
|
||||
|
||||
```{literalinclude} ../../unilabos_msgs/action/WashSolid.action
|
||||
:language: yaml
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 移液工作站及相关生物自动化设备操作
|
||||
|
||||
Uni-Lab 生物操作指令集多数来自 [PyLabRobot](https://docs.pylabrobot.org/user_guide/index.html),包含生物实验中常见的操作,如移液、混匀、离心等。
|
||||
|
||||
### `LiquidHandlerAspirate`
|
||||
|
||||
```{literalinclude} ../../unilabos_msgs/action/LiquidHandlerAspirate.action
|
||||
:language: yaml
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `LiquidHandlerDiscardTips`
|
||||
|
||||
@@ -57,7 +302,15 @@ Uni-Lab 生物操作指令集多数来自 [PyLabRobot](https://docs.pylabrobot.o
|
||||
:language: yaml
|
||||
```
|
||||
|
||||
----
|
||||
---
|
||||
|
||||
### `LiquidHandlerDispense`
|
||||
|
||||
```{literalinclude} ../../unilabos_msgs/action/LiquidHandlerDispense.action
|
||||
:language: yaml
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `LiquidHandlerDropTips`
|
||||
|
||||
@@ -65,7 +318,7 @@ Uni-Lab 生物操作指令集多数来自 [PyLabRobot](https://docs.pylabrobot.o
|
||||
:language: yaml
|
||||
```
|
||||
|
||||
----
|
||||
---
|
||||
|
||||
### `LiquidHandlerDropTips96`
|
||||
|
||||
@@ -73,7 +326,7 @@ Uni-Lab 生物操作指令集多数来自 [PyLabRobot](https://docs.pylabrobot.o
|
||||
:language: yaml
|
||||
```
|
||||
|
||||
----
|
||||
---
|
||||
|
||||
### `LiquidHandlerMoveLid`
|
||||
|
||||
@@ -81,7 +334,7 @@ Uni-Lab 生物操作指令集多数来自 [PyLabRobot](https://docs.pylabrobot.o
|
||||
:language: yaml
|
||||
```
|
||||
|
||||
----
|
||||
---
|
||||
|
||||
### `LiquidHandlerMovePlate`
|
||||
|
||||
@@ -89,7 +342,7 @@ Uni-Lab 生物操作指令集多数来自 [PyLabRobot](https://docs.pylabrobot.o
|
||||
:language: yaml
|
||||
```
|
||||
|
||||
----
|
||||
---
|
||||
|
||||
### `LiquidHandlerMoveResource`
|
||||
|
||||
@@ -97,7 +350,7 @@ Uni-Lab 生物操作指令集多数来自 [PyLabRobot](https://docs.pylabrobot.o
|
||||
:language: yaml
|
||||
```
|
||||
|
||||
----
|
||||
---
|
||||
|
||||
### `LiquidHandlerPickUpTips`
|
||||
|
||||
@@ -105,7 +358,7 @@ Uni-Lab 生物操作指令集多数来自 [PyLabRobot](https://docs.pylabrobot.o
|
||||
:language: yaml
|
||||
```
|
||||
|
||||
----
|
||||
---
|
||||
|
||||
### `LiquidHandlerPickUpTips96`
|
||||
|
||||
@@ -113,7 +366,7 @@ Uni-Lab 生物操作指令集多数来自 [PyLabRobot](https://docs.pylabrobot.o
|
||||
:language: yaml
|
||||
```
|
||||
|
||||
----
|
||||
---
|
||||
|
||||
### `LiquidHandlerReturnTips`
|
||||
|
||||
@@ -121,7 +374,7 @@ Uni-Lab 生物操作指令集多数来自 [PyLabRobot](https://docs.pylabrobot.o
|
||||
:language: yaml
|
||||
```
|
||||
|
||||
----
|
||||
---
|
||||
|
||||
### `LiquidHandlerReturnTips96`
|
||||
|
||||
@@ -129,7 +382,7 @@ Uni-Lab 生物操作指令集多数来自 [PyLabRobot](https://docs.pylabrobot.o
|
||||
:language: yaml
|
||||
```
|
||||
|
||||
----
|
||||
---
|
||||
|
||||
### `LiquidHandlerStamp`
|
||||
|
||||
@@ -137,9 +390,121 @@ Uni-Lab 生物操作指令集多数来自 [PyLabRobot](https://docs.pylabrobot.o
|
||||
:language: yaml
|
||||
```
|
||||
|
||||
----
|
||||
## 多工作站及小车运行、物料转移
|
||||
---
|
||||
|
||||
### `LiquidHandlerTransfer`
|
||||
|
||||
```{literalinclude} ../../unilabos_msgs/action/LiquidHandlerTransfer.action
|
||||
:language: yaml
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `LiquidHandlerAdd`
|
||||
|
||||
```{literalinclude} ../../unilabos_msgs/action/LiquidHandlerAdd.action
|
||||
:language: yaml
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `LiquidHandlerIncubateBiomek`
|
||||
|
||||
```{literalinclude} ../../unilabos_msgs/action/LiquidHandlerIncubateBiomek.action
|
||||
:language: yaml
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `LiquidHandlerMix`
|
||||
|
||||
```{literalinclude} ../../unilabos_msgs/action/LiquidHandlerMix.action
|
||||
:language: yaml
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `LiquidHandlerMoveBiomek`
|
||||
|
||||
```{literalinclude} ../../unilabos_msgs/action/LiquidHandlerMoveBiomek.action
|
||||
:language: yaml
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `LiquidHandlerMoveTo`
|
||||
|
||||
```{literalinclude} ../../unilabos_msgs/action/LiquidHandlerMoveTo.action
|
||||
:language: yaml
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `LiquidHandlerOscillateBiomek`
|
||||
|
||||
```{literalinclude} ../../unilabos_msgs/action/LiquidHandlerOscillateBiomek.action
|
||||
:language: yaml
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `LiquidHandlerProtocolCreation`
|
||||
|
||||
```{literalinclude} ../../unilabos_msgs/action/LiquidHandlerProtocolCreation.action
|
||||
:language: yaml
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `LiquidHandlerRemove`
|
||||
|
||||
```{literalinclude} ../../unilabos_msgs/action/LiquidHandlerRemove.action
|
||||
:language: yaml
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `LiquidHandlerSetGroup`
|
||||
|
||||
```{literalinclude} ../../unilabos_msgs/action/LiquidHandlerSetGroup.action
|
||||
:language: yaml
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `LiquidHandlerSetLiquid`
|
||||
|
||||
```{literalinclude} ../../unilabos_msgs/action/LiquidHandlerSetLiquid.action
|
||||
:language: yaml
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `LiquidHandlerSetTipRack`
|
||||
|
||||
```{literalinclude} ../../unilabos_msgs/action/LiquidHandlerSetTipRack.action
|
||||
:language: yaml
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `LiquidHandlerTransferBiomek`
|
||||
|
||||
```{literalinclude} ../../unilabos_msgs/action/LiquidHandlerTransferBiomek.action
|
||||
:language: yaml
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `LiquidHandlerTransferGroup`
|
||||
|
||||
```{literalinclude} ../../unilabos_msgs/action/LiquidHandlerTransferGroup.action
|
||||
:language: yaml
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 多工作站及小车运行、物料转移
|
||||
|
||||
### `AGVTransfer`
|
||||
|
||||
@@ -147,7 +512,7 @@ Uni-Lab 生物操作指令集多数来自 [PyLabRobot](https://docs.pylabrobot.o
|
||||
:language: yaml
|
||||
```
|
||||
|
||||
----
|
||||
---
|
||||
|
||||
### `WorkStationRun`
|
||||
|
||||
@@ -155,12 +520,64 @@ Uni-Lab 生物操作指令集多数来自 [PyLabRobot](https://docs.pylabrobot.o
|
||||
:language: yaml
|
||||
```
|
||||
|
||||
----
|
||||
---
|
||||
|
||||
### `ResetHandling`
|
||||
|
||||
```{literalinclude} ../../unilabos_msgs/action/ResetHandling.action
|
||||
:language: yaml
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `ResourceCreateFromOuter`
|
||||
|
||||
```{literalinclude} ../../unilabos_msgs/action/ResourceCreateFromOuter.action
|
||||
:language: yaml
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `ResourceCreateFromOuterEasy`
|
||||
|
||||
```{literalinclude} ../../unilabos_msgs/action/ResourceCreateFromOuterEasy.action
|
||||
:language: yaml
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `SetPumpPosition`
|
||||
|
||||
```{literalinclude} ../../unilabos_msgs/action/SetPumpPosition.action
|
||||
:language: yaml
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 固体分配与处理设备操作
|
||||
|
||||
### `SolidDispenseAddPowderTube`
|
||||
|
||||
```{literalinclude} ../../unilabos_msgs/action/SolidDispenseAddPowderTube.action
|
||||
:language: yaml
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 其他设备操作
|
||||
|
||||
### `EmptyIn`
|
||||
|
||||
```{literalinclude} ../../unilabos_msgs/action/EmptyIn.action
|
||||
:language: yaml
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 机械臂、夹爪等机器人设备
|
||||
|
||||
Uni-Lab 机械臂、机器人、夹爪和导航指令集沿用 ROS2 的 `control_msgs` 和 `nav2_msgs`:
|
||||
|
||||
|
||||
### `FollowJointTrajectory`
|
||||
|
||||
```yaml
|
||||
@@ -228,7 +645,8 @@ trajectory_msgs/MultiDOFJointTrajectoryPoint multi_dof_error
|
||||
|
||||
```
|
||||
|
||||
----
|
||||
---
|
||||
|
||||
### `GripperCommand`
|
||||
|
||||
```yaml
|
||||
@@ -246,42 +664,19 @@ bool reached_goal # True iff the gripper position has reached the commanded setp
|
||||
|
||||
```
|
||||
|
||||
----
|
||||
---
|
||||
|
||||
### `JointTrajectory`
|
||||
|
||||
```yaml
|
||||
trajectory_msgs/JointTrajectory trajectory
|
||||
---
|
||||
---
|
||||
|
||||
---
|
||||
```
|
||||
|
||||
----
|
||||
### `ParallelGripperCommand`
|
||||
|
||||
```yaml
|
||||
# Parallel grippers refer to an end effector where two opposing fingers grasp an object from opposite sides.
|
||||
sensor_msgs/JointState command
|
||||
# name: the name(s) of the joint this command is requesting
|
||||
# position: desired position of each gripper joint (radians or meters)
|
||||
# velocity: (optional, not used if empty) max velocity of the joint allowed while moving (radians or meters / second)
|
||||
# effort: (optional, not used if empty) max effort of the joint allowed while moving (Newtons or Newton-meters)
|
||||
---
|
||||
sensor_msgs/JointState state # The current gripper state.
|
||||
# position of each joint (radians or meters)
|
||||
# optional: velocity of each joint (radians or meters / second)
|
||||
# optional: effort of each joint (Newtons or Newton-meters)
|
||||
bool stalled # True if the gripper is exerting max effort and not moving
|
||||
bool reached_goal # True if the gripper position has reached the commanded setpoint
|
||||
---
|
||||
sensor_msgs/JointState state # The current gripper state.
|
||||
# position of each joint (radians or meters)
|
||||
# optional: velocity of each joint (radians or meters / second)
|
||||
# optional: effort of each joint (Newtons or Newton-meters)
|
||||
|
||||
```
|
||||
|
||||
----
|
||||
### `PointHead`
|
||||
|
||||
```yaml
|
||||
@@ -291,12 +686,13 @@ string pointing_frame
|
||||
builtin_interfaces/Duration min_duration
|
||||
float64 max_velocity
|
||||
---
|
||||
|
||||
---
|
||||
float64 pointing_angle_error
|
||||
|
||||
```
|
||||
|
||||
----
|
||||
---
|
||||
|
||||
### `SingleJointPosition`
|
||||
|
||||
```yaml
|
||||
@@ -304,15 +700,16 @@ float64 position
|
||||
builtin_interfaces/Duration min_duration
|
||||
float64 max_velocity
|
||||
---
|
||||
|
||||
---
|
||||
std_msgs/Header header
|
||||
float64 position
|
||||
float64 velocity
|
||||
float64 error
|
||||
|
||||
```
|
||||
|
||||
----
|
||||
---
|
||||
|
||||
### `AssistedTeleop`
|
||||
|
||||
```yaml
|
||||
@@ -324,10 +721,10 @@ builtin_interfaces/Duration total_elapsed_time
|
||||
---
|
||||
#feedback
|
||||
builtin_interfaces/Duration current_teleop_duration
|
||||
|
||||
```
|
||||
|
||||
----
|
||||
---
|
||||
|
||||
### `BackUp`
|
||||
|
||||
```yaml
|
||||
@@ -341,10 +738,10 @@ builtin_interfaces/Duration total_elapsed_time
|
||||
---
|
||||
#feedback definition
|
||||
float32 distance_traveled
|
||||
|
||||
```
|
||||
|
||||
----
|
||||
---
|
||||
|
||||
### `ComputePathThroughPoses`
|
||||
|
||||
```yaml
|
||||
@@ -359,10 +756,10 @@ nav_msgs/Path path
|
||||
builtin_interfaces/Duration planning_time
|
||||
---
|
||||
#feedback definition
|
||||
|
||||
```
|
||||
|
||||
----
|
||||
---
|
||||
|
||||
### `ComputePathToPose`
|
||||
|
||||
```yaml
|
||||
@@ -377,10 +774,10 @@ nav_msgs/Path path
|
||||
builtin_interfaces/Duration planning_time
|
||||
---
|
||||
#feedback definition
|
||||
|
||||
```
|
||||
|
||||
----
|
||||
---
|
||||
|
||||
### `DriveOnHeading`
|
||||
|
||||
```yaml
|
||||
@@ -394,10 +791,10 @@ builtin_interfaces/Duration total_elapsed_time
|
||||
---
|
||||
#feedback definition
|
||||
float32 distance_traveled
|
||||
|
||||
```
|
||||
|
||||
----
|
||||
---
|
||||
|
||||
### `DummyBehavior`
|
||||
|
||||
```yaml
|
||||
@@ -408,10 +805,10 @@ std_msgs/String command
|
||||
builtin_interfaces/Duration total_elapsed_time
|
||||
---
|
||||
#feedback definition
|
||||
|
||||
```
|
||||
|
||||
----
|
||||
---
|
||||
|
||||
### `FollowPath`
|
||||
|
||||
```yaml
|
||||
@@ -426,10 +823,10 @@ std_msgs/Empty result
|
||||
#feedback definition
|
||||
float32 distance_to_goal
|
||||
float32 speed
|
||||
|
||||
```
|
||||
|
||||
----
|
||||
---
|
||||
|
||||
### `FollowWaypoints`
|
||||
|
||||
```yaml
|
||||
@@ -441,10 +838,10 @@ int32[] missed_waypoints
|
||||
---
|
||||
#feedback definition
|
||||
uint32 current_waypoint
|
||||
|
||||
```
|
||||
|
||||
----
|
||||
---
|
||||
|
||||
### `NavigateThroughPoses`
|
||||
|
||||
```yaml
|
||||
@@ -462,10 +859,10 @@ builtin_interfaces/Duration estimated_time_remaining
|
||||
int16 number_of_recoveries
|
||||
float32 distance_remaining
|
||||
int16 number_of_poses_remaining
|
||||
|
||||
```
|
||||
|
||||
----
|
||||
---
|
||||
|
||||
### `NavigateToPose`
|
||||
|
||||
```yaml
|
||||
@@ -482,10 +879,10 @@ builtin_interfaces/Duration navigation_time
|
||||
builtin_interfaces/Duration estimated_time_remaining
|
||||
int16 number_of_recoveries
|
||||
float32 distance_remaining
|
||||
|
||||
```
|
||||
|
||||
----
|
||||
---
|
||||
|
||||
### `SmoothPath`
|
||||
|
||||
```yaml
|
||||
@@ -501,10 +898,10 @@ builtin_interfaces/Duration smoothing_duration
|
||||
bool was_completed
|
||||
---
|
||||
#feedback definition
|
||||
|
||||
```
|
||||
|
||||
----
|
||||
---
|
||||
|
||||
### `Spin`
|
||||
|
||||
```yaml
|
||||
@@ -517,10 +914,10 @@ builtin_interfaces/Duration total_elapsed_time
|
||||
---
|
||||
#feedback definition
|
||||
float32 angular_distance_traveled
|
||||
|
||||
```
|
||||
|
||||
----
|
||||
---
|
||||
|
||||
### `Wait`
|
||||
|
||||
```yaml
|
||||
@@ -532,7 +929,6 @@ builtin_interfaces/Duration total_elapsed_time
|
||||
---
|
||||
#feedback definition
|
||||
builtin_interfaces/Duration time_left
|
||||
|
||||
```
|
||||
|
||||
----
|
||||
---
|
||||
|
||||
|
Before Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 629 KiB |
|
Before Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 269 KiB |
@@ -1,405 +0,0 @@
|
||||
# 物料构建指南
|
||||
|
||||
## 概述
|
||||
|
||||
在UniLab-OS系统中,任何工作站中所需要用到的物料主要包括四个核心组件:
|
||||
|
||||
1. **桌子(Deck)** - 工作台面,定义整个工作空间的布局
|
||||
2. **堆栈(Warehouse)** - 存储区域,用于放置载具和物料
|
||||
3. **载具(Carriers)** - 承载瓶子等物料的容器架
|
||||
4. **瓶子(Bottles)** - 实际的物料容器
|
||||
|
||||
本文档以BioYond工作站为例,详细说明如何构建这些物料组件。
|
||||
|
||||
## 文件结构
|
||||
|
||||
物料定义文件位于 `unilabos/resources/` 文件夹中:
|
||||
|
||||
```
|
||||
unilabos/resources/bioyond/
|
||||
├── decks.py # 桌子定义
|
||||
├── YB_warehouses.py # 堆栈定义
|
||||
├── YB_bottle_carriers.py # 载具定义
|
||||
└── YB_bottles.py # 瓶子定义
|
||||
```
|
||||
|
||||
对应的注册表文件位于 `unilabos/registry/resources/bioyond/` 文件夹中:
|
||||
|
||||
```
|
||||
unilabos/registry/resources/bioyond/
|
||||
├── deck.yaml # 桌子注册表
|
||||
├── YB_bottle_carriers.yaml # 载具注册表
|
||||
└── YB_bottle.yaml # 瓶子注册表
|
||||
```
|
||||
|
||||
## 1. 桌子(Deck)构建
|
||||
|
||||
桌子是整个工作站的基础,定义了工作空间的尺寸和各个组件的位置。
|
||||
|
||||
### 代码示例 (decks.py)
|
||||
|
||||
```python
|
||||
from pylabrobot.resources import Coordinate, Deck
|
||||
from unilabos.resources.bioyond.YB_warehouses import (
|
||||
bioyond_warehouse_2x2x1,
|
||||
bioyond_warehouse_3x5x1,
|
||||
bioyond_warehouse_20x1x1,
|
||||
bioyond_warehouse_3x3x1,
|
||||
bioyond_warehouse_10x1x1
|
||||
)
|
||||
|
||||
class BIOYOND_YB_Deck(Deck):
|
||||
def __init__(
|
||||
self,
|
||||
name: str = "YB_Deck",
|
||||
size_x: float = 4150, # 桌子X方向尺寸 (mm)
|
||||
size_y: float = 1400.0, # 桌子Y方向尺寸 (mm)
|
||||
size_z: float = 2670.0, # 桌子Z方向尺寸 (mm)
|
||||
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() # 当在工作站配置中setup为True时,自动创建并放置所有预定义的堆栈
|
||||
|
||||
def setup(self) -> None:
|
||||
# 定义桌子上的各个仓库区域
|
||||
self.warehouses = {
|
||||
"自动堆栈-左": bioyond_warehouse_2x2x1("自动堆栈-左"),
|
||||
"自动堆栈-右": bioyond_warehouse_2x2x1("自动堆栈-右"),
|
||||
"手动堆栈-左": bioyond_warehouse_3x5x1("手动堆栈-左"),
|
||||
"手动堆栈-右": bioyond_warehouse_3x5x1("手动堆栈-右"),
|
||||
"粉末加样头堆栈": bioyond_warehouse_20x1x1("粉末加样头堆栈"),
|
||||
"配液站内试剂仓库": bioyond_warehouse_3x3x1("配液站内试剂仓库"),
|
||||
"试剂替换仓库": bioyond_warehouse_10x1x1("试剂替换仓库"),
|
||||
}
|
||||
|
||||
# 定义各个仓库在桌子上的坐标位置
|
||||
self.warehouse_locations = {
|
||||
"自动堆栈-左": Coordinate(-100.3, 171.5, 0.0),
|
||||
"自动堆栈-右": Coordinate(3960.1, 155.9, 0.0),
|
||||
"手动堆栈-左": Coordinate(-213.3, 804.4, 0.0),
|
||||
"手动堆栈-右": Coordinate(3960.1, 807.6, 0.0),
|
||||
"粉末加样头堆栈": Coordinate(415.0, 1301.0, 0.0),
|
||||
"配液站内试剂仓库": Coordinate(2162.0, 437.0, 0.0),
|
||||
"试剂替换仓库": Coordinate(1173.0, 802.0, 0.0),
|
||||
}
|
||||
|
||||
# 将仓库分配到桌子的指定位置
|
||||
for warehouse_name, warehouse in self.warehouses.items():
|
||||
self.assign_child_resource(warehouse, location=self.warehouse_locations[warehouse_name])
|
||||
```
|
||||
|
||||
### 在工作站配置中的使用
|
||||
|
||||
当在工作站配置文件中定义桌子时,可以通过`setup`参数控制是否自动建立所有堆栈:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "YB_Bioyond_Deck",
|
||||
"name": "YB_Bioyond_Deck",
|
||||
"children": [],
|
||||
"parent": "bioyond_cell_workstation",
|
||||
"type": "deck",
|
||||
"class": "BIOYOND_YB_Deck",
|
||||
"config": {
|
||||
"type": "BIOYOND_YB_Deck",
|
||||
"setup": true
|
||||
},
|
||||
"data": {}
|
||||
}
|
||||
```
|
||||
|
||||
**重要说明**:
|
||||
- 当 `"setup": true` 时,系统会自动调用桌子的 `setup()` 方法
|
||||
- 这将创建并放置所有预定义的堆栈到桌子上的指定位置
|
||||
- 如果 `"setup": false` 或省略该参数,则只创建空桌子,需要手动添加堆栈
|
||||
|
||||
### 关键要点注释
|
||||
|
||||
- `size_x`, `size_y`, `size_z`: 定义桌子的物理尺寸
|
||||
- `warehouses`: 字典类型,包含桌子上所有的仓库区域
|
||||
- `warehouse_locations`: 定义每个仓库在桌子坐标系中的位置
|
||||
- `assign_child_resource()`: 将仓库资源分配到桌子的指定位置
|
||||
- `setup()`: 可选的自动设置方法,初始化时可调用
|
||||
|
||||
## 2. 堆栈(Warehouse)构建
|
||||
|
||||
堆栈定义了存储区域的规格和布局,用于放置载具。
|
||||
|
||||
### 代码示例 (YB_warehouses.py)
|
||||
|
||||
```python
|
||||
from unilabos.resources.warehouse import WareHouse, YB_warehouse_factory
|
||||
|
||||
def bioyond_warehouse_1x4x4(name: str) -> WareHouse:
|
||||
"""创建BioYond 1x4x4仓库
|
||||
|
||||
Args:
|
||||
name: 仓库名称
|
||||
|
||||
Returns:
|
||||
WareHouse: 仓库对象
|
||||
"""
|
||||
return YB_warehouse_factory(
|
||||
name=name,
|
||||
num_items_x=1, # X方向位置数量
|
||||
num_items_y=4, # Y方向位置数量
|
||||
num_items_z=4, # Z方向位置数量(层数)
|
||||
dx=10.0, # X方向起始偏移
|
||||
dy=10.0, # Y方向起始偏移
|
||||
dz=10.0, # Z方向起始偏移
|
||||
item_dx=137.0, # X方向间距
|
||||
item_dy=96.0, # Y方向间距
|
||||
item_dz=120.0, # Z方向间距(层高)
|
||||
category="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",
|
||||
)
|
||||
```
|
||||
|
||||
### 关键要点注释
|
||||
|
||||
- `num_items_x/y/z`: 定义仓库在各个方向的位置数量
|
||||
- `dx/dy/dz`: 第一个位置的起始偏移坐标
|
||||
- `item_dx/dy/dz`: 相邻位置之间的间距
|
||||
- `category`: 仓库类别,用于分类管理
|
||||
- `YB_warehouse_factory`: 统一的仓库创建工厂函数
|
||||
|
||||
## 3. 载具(Carriers)构建
|
||||
|
||||
载具是承载瓶子的容器架,定义了瓶子的排列方式和位置。
|
||||
|
||||
### 代码示例 (YB_bottle_carriers.py)
|
||||
|
||||
```python
|
||||
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_pei_ye_xiao_Bottle
|
||||
|
||||
def YB_peiyepingxiaoban(name: str) -> BottleCarrier:
|
||||
"""配液瓶(小)板 - 4x2布局,8个位置
|
||||
|
||||
Args:
|
||||
name: 载具名称
|
||||
|
||||
Returns:
|
||||
BottleCarrier: 载具对象,包含8个配液瓶位置
|
||||
"""
|
||||
|
||||
# 载具物理尺寸 (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
|
||||
|
||||
# 创建瓶位布局:4列x2行
|
||||
sites = create_ordered_items_2d(
|
||||
klass=ResourceHolder,
|
||||
num_items_x=4, # 4列
|
||||
num_items_y=2, # 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
|
||||
```
|
||||
|
||||
### 关键要点注释
|
||||
|
||||
- `carrier_size_x/y/z`: 载具的物理尺寸
|
||||
- `bottle_diameter`: 瓶子的直径,用于计算瓶位大小
|
||||
- `bottle_spacing_x/y`: 瓶子之间的间距
|
||||
- `create_ordered_items_2d`: 创建二维排列的瓶位
|
||||
- `sites`: 瓶位字典,存储所有瓶子位置信息
|
||||
- `ordering`: 定义瓶位的命名规则(如A1, A2, B1等)
|
||||
|
||||
## 4. 瓶子(Bottles)构建
|
||||
|
||||
瓶子是最终的物料容器,定义了容器的物理属性。
|
||||
|
||||
### 代码示例 (YB_bottles.py)
|
||||
|
||||
```python
|
||||
from unilabos.resources.itemized_carrier import Bottle
|
||||
|
||||
def YB_pei_ye_xiao_Bottle(
|
||||
name: str,
|
||||
diameter: float = 35.0, # 瓶子直径 (mm)
|
||||
height: float = 60.0, # 瓶子高度 (mm)
|
||||
max_volume: float = 30000.0, # 最大容量 (μL) - 30mL
|
||||
barcode: str = None, # 条码
|
||||
) -> Bottle:
|
||||
"""创建配液瓶(小)
|
||||
|
||||
Args:
|
||||
name: 瓶子名称
|
||||
diameter: 瓶子直径
|
||||
height: 瓶子高度
|
||||
max_volume: 最大容量(微升)
|
||||
barcode: 条码标识
|
||||
|
||||
Returns:
|
||||
Bottle: 瓶子对象
|
||||
"""
|
||||
return Bottle(
|
||||
name=name,
|
||||
diameter=diameter,
|
||||
height=height,
|
||||
max_volume=max_volume,
|
||||
barcode=barcode,
|
||||
model="YB_pei_ye_xiao_Bottle",
|
||||
)
|
||||
|
||||
def YB_ye_Bottle(
|
||||
name: str,
|
||||
diameter: float = 40.0,
|
||||
height: float = 70.0,
|
||||
max_volume: float = 50000.0, # 最大容量
|
||||
barcode: str = None,
|
||||
) -> Bottle:
|
||||
"""创建液体瓶"""
|
||||
return Bottle(
|
||||
name=name,
|
||||
diameter=diameter,
|
||||
height=height,
|
||||
max_volume=max_volume,
|
||||
barcode=barcode,
|
||||
model="YB_ye_Bottle",
|
||||
)
|
||||
```
|
||||
|
||||
### 关键要点注释
|
||||
|
||||
- `diameter`: 瓶子直径,影响瓶位大小计算
|
||||
- `height`: 瓶子高度,用于碰撞检测和移液计算
|
||||
- `max_volume`: 最大容量,单位为微升(μL)
|
||||
- `barcode`: 条码标识,用于瓶子追踪
|
||||
- `model`: 型号标识,用于区分不同类型的瓶子
|
||||
|
||||
## 5. 注册表配置
|
||||
|
||||
创建完物料定义后,需要在注册表中注册这些物料,使系统能够识别和使用它们。
|
||||
|
||||
在 `unilabos/registry/resources/bioyond/` 目录下创建:
|
||||
|
||||
- `deck.yaml` - 桌子注册表
|
||||
- `YB_bottle_carriers.yaml` - 载具注册表
|
||||
- `YB_bottle.yaml` - 瓶子注册表
|
||||
|
||||
### 5.1 桌子注册表 (deck.yaml)
|
||||
|
||||
```yaml
|
||||
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 # 版本号
|
||||
```
|
||||
|
||||
### 5.2 载具注册表 (YB_bottle_carriers.yaml)
|
||||
|
||||
```yaml
|
||||
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
|
||||
```
|
||||
|
||||
### 5.3 瓶子注册表 (YB_bottle.yaml)
|
||||
|
||||
```yaml
|
||||
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
|
||||
```
|
||||
|
||||
### 注册表关键要点注释
|
||||
|
||||
- `category`: 物料分类,用于在云端(网页界面)中的分类中显示
|
||||
- `module`: Python模块路径,格式为 `模块路径:类名`
|
||||
- `type`: 框架类型,通常为 `pylabrobot`(默认即可)
|
||||
- `description`: 描述信息,显示在用户界面中
|
||||
- `icon`: (名称唯一自动匹配后端上传的图标文件名,显示在云端)
|
||||
- `registry_type`: 固定为 `resource`
|
||||
- `version`: 版本号,用于版本管理
|
||||
@@ -26,19 +26,15 @@ boot_examples/index.md
|
||||
## 开发者指南
|
||||
|
||||
```{toctree}
|
||||
|
||||
:maxdepth: 2
|
||||
|
||||
developer_guide/device_driver
|
||||
developer_guide/add_device
|
||||
developer_guide/add_action
|
||||
developer_guide/actions
|
||||
developer_guide/workstation_architecture
|
||||
developer_guide/add_protocol
|
||||
developer_guide/add_batteryPLC
|
||||
developer_guide/materials_tutorial
|
||||
developer_guide/materials_construction_guide
|
||||
|
||||
developer_guide/materials_tutorial.md
|
||||
```
|
||||
|
||||
## 接口文档
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
sphinx>=7.0.0
|
||||
sphinx-rtd-theme>=2.0.0
|
||||
myst-parser>=2.0.0
|
||||
sphinxcontrib-mermaid
|
||||
|
||||
# 用于支持Jupyter notebook文档
|
||||
myst-nb>=1.0.0
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package:
|
||||
name: ros-humble-unilabos-msgs
|
||||
version: 0.10.10
|
||||
version: 0.10.7
|
||||
source:
|
||||
path: ../../unilabos_msgs
|
||||
target_directory: src
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
:: Generated by vinca http://github.com/RoboStack/vinca.
|
||||
:: DO NOT EDIT!
|
||||
setlocal EnableDelayedExpansion
|
||||
|
||||
set "PYTHONPATH=%LIBRARY_PREFIX%\lib\site-packages;%SP_DIR%"
|
||||
|
||||
:: MSVC is preferred.
|
||||
set CC=cl.exe
|
||||
set CXX=cl.exe
|
||||
|
||||
rd /s /q build
|
||||
mkdir build
|
||||
pushd build
|
||||
|
||||
:: set "CMAKE_GENERATOR=Ninja"
|
||||
|
||||
:: try to fix long paths issues by using default generator
|
||||
set "CMAKE_GENERATOR=Visual Studio %VS_MAJOR% %VS_YEAR%"
|
||||
set "SP_DIR_FORWARDSLASHES=%SP_DIR:\=/%"
|
||||
|
||||
set PYTHON="%PREFIX%\python.exe"
|
||||
|
||||
cmake ^
|
||||
-G "%CMAKE_GENERATOR%" ^
|
||||
-DCMAKE_INSTALL_PREFIX=%LIBRARY_PREFIX% ^
|
||||
-DCMAKE_BUILD_TYPE=Release ^
|
||||
-DCMAKE_INSTALL_SYSTEM_RUNTIME_LIBS_SKIP=True ^
|
||||
-DPYTHON_EXECUTABLE=%PYTHON% ^
|
||||
-DPython_EXECUTABLE=%PYTHON% ^
|
||||
-DPython3_EXECUTABLE=%PYTHON% ^
|
||||
-DSETUPTOOLS_DEB_LAYOUT=OFF ^
|
||||
-DBUILD_SHARED_LIBS=ON ^
|
||||
-DBUILD_TESTING=OFF ^
|
||||
-DCMAKE_OBJECT_PATH_MAX=255 ^
|
||||
-DPYTHON_INSTALL_DIR=%SP_DIR_FORWARDSLASHES% ^
|
||||
--compile-no-warning-as-error ^
|
||||
%SRC_DIR%\%PKG_NAME%\src\work
|
||||
if errorlevel 1 exit 1
|
||||
|
||||
cmake --build . --config Release --target install
|
||||
if errorlevel 1 exit 1
|
||||
@@ -1,71 +0,0 @@
|
||||
# Generated by vinca http://github.com/RoboStack/vinca.
|
||||
# DO NOT EDIT!
|
||||
|
||||
rm -rf build
|
||||
mkdir build
|
||||
cd build
|
||||
|
||||
# necessary for correctly linking SIP files (from python_qt_bindings)
|
||||
export LINK=$CXX
|
||||
|
||||
if [[ "$CONDA_BUILD_CROSS_COMPILATION" != "1" ]]; then
|
||||
PYTHON_EXECUTABLE=$PREFIX/bin/python
|
||||
PKG_CONFIG_EXECUTABLE=$PREFIX/bin/pkg-config
|
||||
OSX_DEPLOYMENT_TARGET="10.15"
|
||||
else
|
||||
PYTHON_EXECUTABLE=$BUILD_PREFIX/bin/python
|
||||
PKG_CONFIG_EXECUTABLE=$BUILD_PREFIX/bin/pkg-config
|
||||
OSX_DEPLOYMENT_TARGET="11.0"
|
||||
fi
|
||||
|
||||
echo "USING PYTHON_EXECUTABLE=${PYTHON_EXECUTABLE}"
|
||||
echo "USING PKG_CONFIG_EXECUTABLE=${PKG_CONFIG_EXECUTABLE}"
|
||||
|
||||
export ROS_PYTHON_VERSION=`$PYTHON_EXECUTABLE -c "import sys; print('%i.%i' % (sys.version_info[0:2]))"`
|
||||
echo "Using Python ${ROS_PYTHON_VERSION}"
|
||||
# Fix up SP_DIR which for some reason might contain a path to a wrong Python version
|
||||
FIXED_SP_DIR=$(echo $SP_DIR | sed -E "s/python[0-9]+\.[0-9]+/python$ROS_PYTHON_VERSION/")
|
||||
echo "Using site-package dir ${FIXED_SP_DIR}"
|
||||
|
||||
# see https://github.com/conda-forge/cross-python-feedstock/issues/24
|
||||
if [[ "$CONDA_BUILD_CROSS_COMPILATION" == "1" ]]; then
|
||||
find $PREFIX/lib/cmake -type f -exec sed -i "s~\${_IMPORT_PREFIX}/lib/python${ROS_PYTHON_VERSION}/site-packages~${BUILD_PREFIX}/lib/python${ROS_PYTHON_VERSION}/site-packages~g" {} + || true
|
||||
find $PREFIX/share/rosidl* -type f -exec sed -i "s~$PREFIX/lib/python${ROS_PYTHON_VERSION}/site-packages~${BUILD_PREFIX}/lib/python${ROS_PYTHON_VERSION}/site-packages~g" {} + || true
|
||||
find $PREFIX/share/rosidl* -type f -exec sed -i "s~\${_IMPORT_PREFIX}/lib/python${ROS_PYTHON_VERSION}/site-packages~${BUILD_PREFIX}/lib/python${ROS_PYTHON_VERSION}/site-packages~g" {} + || true
|
||||
find $PREFIX/lib/cmake -type f -exec sed -i "s~message(FATAL_ERROR \"The imported target~message(WARNING \"The imported target~g" {} + || true
|
||||
fi
|
||||
|
||||
if [[ $target_platform =~ linux.* ]]; then
|
||||
export CFLAGS="${CFLAGS} -D__STDC_FORMAT_MACROS=1"
|
||||
export CXXFLAGS="${CXXFLAGS} -D__STDC_FORMAT_MACROS=1"
|
||||
fi;
|
||||
|
||||
# Needed for qt-gui-cpp ..
|
||||
if [[ $target_platform =~ linux.* ]]; then
|
||||
ln -s $GCC ${BUILD_PREFIX}/bin/gcc
|
||||
ln -s $GXX ${BUILD_PREFIX}/bin/g++
|
||||
fi;
|
||||
|
||||
cmake \
|
||||
-G "Ninja" \
|
||||
-DCMAKE_INSTALL_PREFIX=$PREFIX \
|
||||
-DCMAKE_PREFIX_PATH=$PREFIX \
|
||||
-DAMENT_PREFIX_PATH=$PREFIX \
|
||||
-DCMAKE_INSTALL_LIBDIR=lib \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DPYTHON_EXECUTABLE=$PYTHON_EXECUTABLE \
|
||||
-DPython_EXECUTABLE=$PYTHON_EXECUTABLE \
|
||||
-DPython3_EXECUTABLE=$PYTHON_EXECUTABLE \
|
||||
-DPython3_FIND_STRATEGY=LOCATION \
|
||||
-DPKG_CONFIG_EXECUTABLE=$PKG_CONFIG_EXECUTABLE \
|
||||
-DPYTHON_INSTALL_DIR=$FIXED_SP_DIR \
|
||||
-DSETUPTOOLS_DEB_LAYOUT=OFF \
|
||||
-DCATKIN_SKIP_TESTING=$SKIP_TESTING \
|
||||
-DCMAKE_INSTALL_SYSTEM_RUNTIME_LIBS_SKIP=True \
|
||||
-DBUILD_SHARED_LIBS=ON \
|
||||
-DBUILD_TESTING=OFF \
|
||||
-DCMAKE_OSX_DEPLOYMENT_TARGET=$OSX_DEPLOYMENT_TARGET \
|
||||
--compile-no-warning-as-error \
|
||||
$SRC_DIR/$PKG_NAME/src/work
|
||||
|
||||
cmake --build . --config Release --target install
|
||||
@@ -1,61 +0,0 @@
|
||||
package:
|
||||
name: ros-humble-unilabos-msgs
|
||||
version: 0.9.7
|
||||
source:
|
||||
path: ../../unilabos_msgs
|
||||
folder: ros-humble-unilabos-msgs/src/work
|
||||
|
||||
build:
|
||||
script:
|
||||
sel(win): bld_ament_cmake.bat
|
||||
sel(unix): build_ament_cmake.sh
|
||||
number: 5
|
||||
about:
|
||||
home: https://www.ros.org/
|
||||
license: BSD-3-Clause
|
||||
summary: |
|
||||
Robot Operating System
|
||||
|
||||
extra:
|
||||
recipe-maintainers:
|
||||
- ros-forge
|
||||
|
||||
requirements:
|
||||
build:
|
||||
- "{{ compiler('cxx') }}"
|
||||
- "{{ compiler('c') }}"
|
||||
- sel(linux64): sysroot_linux-64 2.17
|
||||
- ninja
|
||||
- setuptools
|
||||
- sel(unix): make
|
||||
- sel(unix): coreutils
|
||||
- sel(osx): tapi
|
||||
- sel(build_platform != target_platform): pkg-config
|
||||
- cmake
|
||||
- cython
|
||||
- sel(win): vs2022_win-64
|
||||
- sel(build_platform != target_platform): python
|
||||
- sel(build_platform != target_platform): cross-python_{{ target_platform }}
|
||||
- sel(build_platform != target_platform): numpy
|
||||
host:
|
||||
- numpy
|
||||
- pip
|
||||
- sel(build_platform == target_platform): pkg-config
|
||||
- robostack-staging::ros-humble-action-msgs
|
||||
- robostack-staging::ros-humble-ament-cmake
|
||||
- robostack-staging::ros-humble-ament-lint-auto
|
||||
- robostack-staging::ros-humble-ament-lint-common
|
||||
- robostack-staging::ros-humble-ros-environment
|
||||
- robostack-staging::ros-humble-ros-workspace
|
||||
- robostack-staging::ros-humble-rosidl-default-generators
|
||||
- robostack-staging::ros-humble-std-msgs
|
||||
- robostack-staging::ros-humble-geometry-msgs
|
||||
- robostack-staging::ros2-distro-mutex=0.5.*
|
||||
run:
|
||||
- robostack-staging::ros-humble-action-msgs
|
||||
- robostack-staging::ros-humble-ros-workspace
|
||||
- robostack-staging::ros-humble-rosidl-default-runtime
|
||||
- robostack-staging::ros-humble-std-msgs
|
||||
- robostack-staging::ros-humble-geometry-msgs
|
||||
# - robostack-staging::ros2-distro-mutex=0.6.*
|
||||
- sel(osx and x86_64): __osx >={{ MACOSX_DEPLOYMENT_TARGET|default('10.14') }}
|
||||
@@ -1,6 +1,6 @@
|
||||
package:
|
||||
name: unilabos
|
||||
version: "0.10.10"
|
||||
version: "0.10.7"
|
||||
|
||||
source:
|
||||
path: ../..
|
||||
|
||||
@@ -172,7 +172,7 @@ Examples:
|
||||
with open(output_path, "w", encoding="utf-8") as f:
|
||||
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" 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.
|
||||
|
||||
Usage:
|
||||
python verify_installation.py [--auto-install]
|
||||
|
||||
Options:
|
||||
--auto-install Automatically install missing packages
|
||||
python verify_installation.py
|
||||
|
||||
Or in the conda environment:
|
||||
conda activate unilab
|
||||
@@ -20,15 +17,14 @@ Usage:
|
||||
|
||||
import sys
|
||||
import os
|
||||
import argparse
|
||||
|
||||
# IMPORTANT: Set UTF-8 encoding BEFORE any other imports
|
||||
# This ensures all subsequent imports (including unilabos) can output UTF-8 characters
|
||||
if sys.platform == "win32":
|
||||
# Method 1: Reconfigure stdout/stderr to use UTF-8 with error handling
|
||||
try:
|
||||
sys.stdout.reconfigure(encoding="utf-8", errors="replace") # type: ignore
|
||||
sys.stderr.reconfigure(encoding="utf-8", errors="replace") # type: ignore
|
||||
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
|
||||
sys.stderr.reconfigure(encoding="utf-8", errors="replace")
|
||||
except (AttributeError, OSError):
|
||||
pass
|
||||
|
||||
@@ -53,7 +49,7 @@ CHECK_MARK = "[OK]"
|
||||
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.
|
||||
|
||||
@@ -91,25 +87,9 @@ def check_python_version() -> bool:
|
||||
|
||||
def main():
|
||||
"""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("UniLabOS Installation Verification")
|
||||
print("=" * 60)
|
||||
if args.auto_install:
|
||||
print("Mode: Auto-install missing packages")
|
||||
else:
|
||||
print("Mode: Verification only")
|
||||
print()
|
||||
|
||||
all_passed = True
|
||||
@@ -133,16 +113,14 @@ def main():
|
||||
|
||||
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
|
||||
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:
|
||||
print(f" {CHECK_MARK} All required packages available")
|
||||
else:
|
||||
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:
|
||||
print(f" {CROSS_MARK} UniLabOS not installed")
|
||||
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}
|
||||
2
setup.py
@@ -4,7 +4,7 @@ package_name = 'unilabos'
|
||||
|
||||
setup(
|
||||
name=package_name,
|
||||
version='0.10.10',
|
||||
version='0.10.7',
|
||||
packages=find_packages(),
|
||||
include_package_data=True,
|
||||
install_requires=['setuptools'],
|
||||
|
||||
@@ -1,171 +0,0 @@
|
||||
{
|
||||
"nodes": [
|
||||
{
|
||||
"id": "dispensing_station_bioyond",
|
||||
"name": "dispensing_station_bioyond",
|
||||
"children": [
|
||||
"Bioyond_Dispensing_Deck"
|
||||
],
|
||||
"parent": null,
|
||||
"type": "device",
|
||||
"class": "bioyond_dispensing_station",
|
||||
"config": {
|
||||
"config": {
|
||||
"api_key": "DE9BDDA0",
|
||||
"api_host": "http://192.168.1.200:44400",
|
||||
"material_type_mappings": {
|
||||
"BIOYOND_PolymerStation_1FlaskCarrier": [
|
||||
"烧杯",
|
||||
"3a14196b-24f2-ca49-9081-0cab8021bf1a"
|
||||
],
|
||||
"BIOYOND_PolymerStation_1BottleCarrier": [
|
||||
"试剂瓶",
|
||||
"3a14196b-8bcf-a460-4f74-23f21ca79e72"
|
||||
],
|
||||
"BIOYOND_PolymerStation_6StockCarrier": [
|
||||
"分装板",
|
||||
"3a14196e-5dfe-6e21-0c79-fe2036d052c4"
|
||||
],
|
||||
"BIOYOND_PolymerStation_Liquid_Vial": [
|
||||
"10%分装小瓶",
|
||||
"3a14196c-76be-2279-4e22-7310d69aed68"
|
||||
],
|
||||
"BIOYOND_PolymerStation_Solid_Vial": [
|
||||
"90%分装小瓶",
|
||||
"3a14196c-cdcf-088d-dc7d-5cf38f0ad9ea"
|
||||
],
|
||||
"BIOYOND_PolymerStation_8StockCarrier": [
|
||||
"样品板",
|
||||
"3a14196e-b7a0-a5da-1931-35f3000281e9"
|
||||
],
|
||||
"BIOYOND_PolymerStation_Solid_Stock": [
|
||||
"样品瓶",
|
||||
"3a14196a-cf7d-8aea-48d8-b9662c7dba94"
|
||||
]
|
||||
}
|
||||
},
|
||||
"deck": {
|
||||
"data": {
|
||||
"_resource_child_name": "Bioyond_Dispensing_Deck",
|
||||
"_resource_type": "unilabos.resources.bioyond.decks:BIOYOND_PolymerPreparationStation_Deck"
|
||||
}
|
||||
},
|
||||
"protocol_type": []
|
||||
},
|
||||
"data": {}
|
||||
},
|
||||
{
|
||||
"id": "Bioyond_Dispensing_Deck",
|
||||
"name": "Bioyond_Dispensing_Deck",
|
||||
"sample_id": null,
|
||||
"children": [],
|
||||
"parent": "dispensing_station_bioyond",
|
||||
"type": "deck",
|
||||
"class": "BIOYOND_PolymerPreparationStation_Deck",
|
||||
"position": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"type": "BIOYOND_PolymerPreparationStation_Deck",
|
||||
"setup": true,
|
||||
"rotation": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0,
|
||||
"type": "Rotation"
|
||||
}
|
||||
},
|
||||
"data": {}
|
||||
},
|
||||
{
|
||||
"id": "reaction_station_bioyond",
|
||||
"name": "reaction_station_bioyond",
|
||||
"parent": null,
|
||||
"children": [
|
||||
"Bioyond_Deck"
|
||||
],
|
||||
"type": "device",
|
||||
"class": "reaction_station.bioyond",
|
||||
"config": {
|
||||
"config": {
|
||||
"api_key": "DE9BDDA0",
|
||||
"api_host": "http://192.168.1.200:44402",
|
||||
"workflow_mappings": {
|
||||
"reactor_taken_out": "3a16081e-4788-ca37-eff4-ceed8d7019d1",
|
||||
"reactor_taken_in": "3a160df6-76b3-0957-9eb0-cb496d5721c6",
|
||||
"Solid_feeding_vials": "3a160877-87e7-7699-7bc6-ec72b05eb5e6",
|
||||
"Liquid_feeding_vials(non-titration)": "3a167d99-6158-c6f0-15b5-eb030f7d8e47",
|
||||
"Liquid_feeding_solvents": "3a160824-0665-01ed-285a-51ef817a9046",
|
||||
"Liquid_feeding(titration)": "3a16082a-96ac-0449-446a-4ed39f3365b6",
|
||||
"liquid_feeding_beaker": "3a16087e-124f-8ddb-8ec1-c2dff09ca784",
|
||||
"Drip_back": "3a162cf9-6aac-565a-ddd7-682ba1796a4a"
|
||||
},
|
||||
"material_type_mappings": {
|
||||
"BIOYOND_PolymerStation_Reactor": [
|
||||
"反应器",
|
||||
"3a14233b-902d-0d7b-4533-3f60f1c41c1b"
|
||||
],
|
||||
"BIOYOND_PolymerStation_1BottleCarrier": [
|
||||
"试剂瓶",
|
||||
"3a14233b-56e3-6c53-a8ab-fcaac163a9ba"
|
||||
],
|
||||
"BIOYOND_PolymerStation_1FlaskCarrier": [
|
||||
"烧杯",
|
||||
"3a14233b-f0a9-ba84-eaa9-0d4718b361b6"
|
||||
],
|
||||
"BIOYOND_PolymerStation_6StockCarrier": [
|
||||
"样品板",
|
||||
"3a142339-80de-8f25-6093-1b1b1b6c322e"
|
||||
],
|
||||
"BIOYOND_PolymerStation_Solid_Vial": [
|
||||
"90%分装小瓶",
|
||||
"3a14233a-26e1-28f8-af6a-60ca06ba0165"
|
||||
],
|
||||
"BIOYOND_PolymerStation_Liquid_Vial": [
|
||||
"10%分装小瓶",
|
||||
"3a14233a-84a3-088d-6676-7cb4acd57c64"
|
||||
],
|
||||
"BIOYOND_PolymerStation_TipBox": [
|
||||
"枪头盒",
|
||||
"3a143890-9d51-60ac-6d6f-6edb43c12041"
|
||||
]
|
||||
}
|
||||
},
|
||||
"deck": {
|
||||
"data": {
|
||||
"_resource_child_name": "Bioyond_Deck",
|
||||
"_resource_type": "unilabos.resources.bioyond.decks:BIOYOND_PolymerReactionStation_Deck"
|
||||
}
|
||||
},
|
||||
"protocol_type": []
|
||||
},
|
||||
"data": {}
|
||||
},
|
||||
{
|
||||
"id": "Bioyond_Deck",
|
||||
"name": "Bioyond_Deck",
|
||||
"children": [],
|
||||
"parent": "reaction_station_bioyond",
|
||||
"type": "deck",
|
||||
"class": "BIOYOND_PolymerReactionStation_Deck",
|
||||
"position": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"type": "BIOYOND_PolymerReactionStation_Deck",
|
||||
"setup": true,
|
||||
"rotation": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0,
|
||||
"type": "Rotation"
|
||||
}
|
||||
},
|
||||
"data": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -170,16 +170,15 @@
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"max_volume": 1000.0,
|
||||
"type": "RegularContainer",
|
||||
"category": "container",
|
||||
"size_x": 200,
|
||||
"size_y": 150,
|
||||
"size_z": 0
|
||||
"max_volume": 1000.0
|
||||
},
|
||||
"data": {
|
||||
"liquids": [["DMF", 500.0]],
|
||||
"pending_liquids": [["DMF", 500.0]]
|
||||
"liquids": [
|
||||
{
|
||||
"liquid_type": "DMF",
|
||||
"liquid_volume": 1000.0
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -195,16 +194,15 @@
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"max_volume": 1000.0,
|
||||
"type": "RegularContainer",
|
||||
"category": "container",
|
||||
"size_x": 200,
|
||||
"size_y": 150,
|
||||
"size_z": 0
|
||||
"max_volume": 1000.0
|
||||
},
|
||||
"data": {
|
||||
"liquids": [["ethyl_acetate", 1000.0]],
|
||||
"pending_liquids": [["ethyl_acetate", 1000.0]]
|
||||
"liquids": [
|
||||
{
|
||||
"liquid_type": "ethyl_acetate",
|
||||
"liquid_volume": 1000.0
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -220,16 +218,15 @@
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"max_volume": 1000.0,
|
||||
"type": "RegularContainer",
|
||||
"category": "container",
|
||||
"size_x": 300,
|
||||
"size_y": 150,
|
||||
"size_z": 0
|
||||
"max_volume": 1000.0
|
||||
},
|
||||
"data": {
|
||||
"liquids": [["hexane", 1000.0]],
|
||||
"pending_liquids": [["hexane", 1000.0]]
|
||||
"liquids": [
|
||||
{
|
||||
"liquid_type": "hexane",
|
||||
"liquid_volume": 1000.0
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -245,16 +242,15 @@
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"max_volume": 1000.0,
|
||||
"type": "RegularContainer",
|
||||
"category": "container",
|
||||
"size_x": 900,
|
||||
"size_y": 150,
|
||||
"size_z": 0
|
||||
"max_volume": 1000.0
|
||||
},
|
||||
"data": {
|
||||
"liquids": [["methanol", 1000.0]],
|
||||
"pending_liquids": [["methanol", 1000.0]]
|
||||
"liquids": [
|
||||
{
|
||||
"liquid_type": "methanol",
|
||||
"liquid_volume": 1000.0
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -270,16 +266,15 @@
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"max_volume": 1000.0,
|
||||
"type": "RegularContainer",
|
||||
"category": "container",
|
||||
"size_x": 950,
|
||||
"size_y": 150,
|
||||
"size_z": 0
|
||||
"max_volume": 1000.0
|
||||
},
|
||||
"data": {
|
||||
"liquids": [["water", 1000.0]],
|
||||
"pending_liquids": [["water", 1000.0]]
|
||||
"liquids": [
|
||||
{
|
||||
"liquid_type": "water",
|
||||
"liquid_volume": 1000.0
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -340,16 +335,14 @@
|
||||
},
|
||||
"config": {
|
||||
"max_volume": 500.0,
|
||||
"type": "RegularContainer",
|
||||
"category": "container",
|
||||
"max_temp": 200.0,
|
||||
"min_temp": -20.0,
|
||||
"has_stirrer": true,
|
||||
"has_heater": true
|
||||
},
|
||||
"data": {
|
||||
"liquids": [],
|
||||
"pending_liquids": []
|
||||
"liquids": [
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -426,16 +419,11 @@
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"max_volume": 2000.0,
|
||||
"type": "RegularContainer",
|
||||
"category": "container",
|
||||
"size_x": 500,
|
||||
"size_y": 400,
|
||||
"size_z": 0
|
||||
"max_volume": 2000.0
|
||||
},
|
||||
"data": {
|
||||
"liquids": [],
|
||||
"pending_liquids": []
|
||||
"liquids": [
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -451,16 +439,11 @@
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"max_volume": 2000.0,
|
||||
"type": "RegularContainer",
|
||||
"category": "container",
|
||||
"size_x": 1100,
|
||||
"size_y": 500,
|
||||
"size_z": 0
|
||||
"max_volume": 2000.0
|
||||
},
|
||||
"data": {
|
||||
"liquids": [],
|
||||
"pending_liquids": []
|
||||
"liquids": [
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -666,16 +649,11 @@
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"max_volume": 250.0,
|
||||
"type": "RegularContainer",
|
||||
"category": "container",
|
||||
"size_x": 900,
|
||||
"size_y": 500,
|
||||
"size_z": 0
|
||||
"max_volume": 250.0
|
||||
},
|
||||
"data": {
|
||||
"liquids": [],
|
||||
"pending_liquids": []
|
||||
"liquids": [
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -691,16 +669,11 @@
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"max_volume": 250.0,
|
||||
"type": "RegularContainer",
|
||||
"category": "container",
|
||||
"size_x": 950,
|
||||
"size_y": 500,
|
||||
"size_z": 0
|
||||
"max_volume": 250.0
|
||||
},
|
||||
"data": {
|
||||
"liquids": [],
|
||||
"pending_liquids": []
|
||||
"liquids": [
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -716,16 +689,11 @@
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"max_volume": 250.0,
|
||||
"type": "RegularContainer",
|
||||
"category": "container",
|
||||
"size_x": 1050,
|
||||
"size_y": 500,
|
||||
"size_z": 0
|
||||
"max_volume": 250.0
|
||||
},
|
||||
"data": {
|
||||
"liquids": [],
|
||||
"pending_liquids": []
|
||||
"liquids": [
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -765,11 +733,6 @@
|
||||
},
|
||||
"config": {
|
||||
"max_volume": 500.0,
|
||||
"size_x": 550,
|
||||
"size_y": 250,
|
||||
"size_z": 0,
|
||||
"type": "RegularContainer",
|
||||
"category": "container",
|
||||
"reagent": "sodium_chloride",
|
||||
"physical_state": "solid"
|
||||
},
|
||||
@@ -793,11 +756,6 @@
|
||||
},
|
||||
"config": {
|
||||
"volume": 500.0,
|
||||
"size_x": 600,
|
||||
"size_y": 250,
|
||||
"size_z": 0,
|
||||
"type": "RegularContainer",
|
||||
"category": "container",
|
||||
"reagent": "sodium_carbonate",
|
||||
"physical_state": "solid"
|
||||
},
|
||||
@@ -821,11 +779,6 @@
|
||||
},
|
||||
"config": {
|
||||
"volume": 500.0,
|
||||
"size_x": 650,
|
||||
"size_y": 250,
|
||||
"size_z": 0,
|
||||
"type": "RegularContainer",
|
||||
"category": "container",
|
||||
"reagent": "magnesium_chloride",
|
||||
"physical_state": "solid"
|
||||
},
|
||||
|
||||
@@ -8,41 +8,11 @@
|
||||
],
|
||||
"parent": null,
|
||||
"type": "device",
|
||||
"class": "bioyond_dispensing_station",
|
||||
"class": "dispensing_station.bioyond",
|
||||
"config": {
|
||||
"config": {
|
||||
"api_key": "DE9BDDA0",
|
||||
"api_host": "http://192.168.1.200:44388",
|
||||
"material_type_mappings": {
|
||||
"BIOYOND_PolymerStation_1FlaskCarrier": [
|
||||
"烧杯",
|
||||
"3a14196b-24f2-ca49-9081-0cab8021bf1a"
|
||||
],
|
||||
"BIOYOND_PolymerStation_1BottleCarrier": [
|
||||
"试剂瓶",
|
||||
"3a14196b-8bcf-a460-4f74-23f21ca79e72"
|
||||
],
|
||||
"BIOYOND_PolymerStation_6StockCarrier": [
|
||||
"分装板",
|
||||
"3a14196e-5dfe-6e21-0c79-fe2036d052c4"
|
||||
],
|
||||
"BIOYOND_PolymerStation_Liquid_Vial": [
|
||||
"10%分装小瓶",
|
||||
"3a14196c-76be-2279-4e22-7310d69aed68"
|
||||
],
|
||||
"BIOYOND_PolymerStation_Solid_Vial": [
|
||||
"90%分装小瓶",
|
||||
"3a14196c-cdcf-088d-dc7d-5cf38f0ad9ea"
|
||||
],
|
||||
"BIOYOND_PolymerStation_8StockCarrier": [
|
||||
"样品板",
|
||||
"3a14196e-b7a0-a5da-1931-35f3000281e9"
|
||||
],
|
||||
"BIOYOND_PolymerStation_Solid_Stock": [
|
||||
"样品瓶",
|
||||
"3a14196a-cf7d-8aea-48d8-b9662c7dba94"
|
||||
]
|
||||
}
|
||||
"api_host": "http://192.168.1.200:44388"
|
||||
},
|
||||
"deck": {
|
||||
"data": {
|
||||
@@ -50,6 +20,13 @@
|
||||
"_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": []
|
||||
},
|
||||
"data": {}
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
{
|
||||
"nodes": [
|
||||
{
|
||||
"id": "id",
|
||||
"name": "name",
|
||||
"children": [
|
||||
],
|
||||
"parent": null,
|
||||
"type": "device",
|
||||
"class": "opcua_example",
|
||||
"config": {
|
||||
"url": "url",
|
||||
"config_path": "unilabos/device_comms/opcua_client/opcua_workflow_example.json"
|
||||
},
|
||||
"data": {
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -10,22 +10,24 @@
|
||||
"x": 620.6111111111111,
|
||||
"y": 171,
|
||||
"z": 0
|
||||
},
|
||||
},
|
||||
"config": {
|
||||
"data": {
|
||||
"children": [
|
||||
{
|
||||
"_resource_child_name": "deck",
|
||||
"_resource_type": "pylabrobot.resources.opentrons.deck:OTDeck"
|
||||
}
|
||||
],
|
||||
"backend": {
|
||||
"type": "LiquidHandlerRvizBackend"
|
||||
}
|
||||
}
|
||||
},
|
||||
"data": {},
|
||||
"children": [
|
||||
"deck"
|
||||
],
|
||||
"config": {
|
||||
"deck": {
|
||||
"_resource_child_name": "deck",
|
||||
"_resource_type": "pylabrobot.resources.opentrons.deck:OTDeck"
|
||||
},
|
||||
"backend": {
|
||||
"type": "UniLiquidHandlerRvizBackend"
|
||||
},
|
||||
"simulator": true
|
||||
}
|
||||
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "deck",
|
||||
@@ -9648,7 +9650,7 @@
|
||||
"children": [],
|
||||
"parent": null,
|
||||
"type": "device",
|
||||
"class": "robotic_arm.SCARA_with_slider.moveit.virtual",
|
||||
"class": "robotic_arm.SCARA_with_slider.virtual",
|
||||
"position": {
|
||||
"x": -500,
|
||||
"y": 1000,
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
"type": "device",
|
||||
"class": "reaction_station.bioyond",
|
||||
"config": {
|
||||
"config": {
|
||||
"bioyond_config": {
|
||||
"api_key": "DE9BDDA0",
|
||||
"api_host": "http://192.168.1.200:44402",
|
||||
"workflow_mappings": {
|
||||
@@ -19,39 +19,14 @@
|
||||
"Solid_feeding_vials": "3a160877-87e7-7699-7bc6-ec72b05eb5e6",
|
||||
"Liquid_feeding_vials(non-titration)": "3a167d99-6158-c6f0-15b5-eb030f7d8e47",
|
||||
"Liquid_feeding_solvents": "3a160824-0665-01ed-285a-51ef817a9046",
|
||||
"Liquid_feeding(titration)": "3a16082a-96ac-0449-446a-4ed39f3365b6",
|
||||
"liquid_feeding_beaker": "3a16087e-124f-8ddb-8ec1-c2dff09ca784",
|
||||
"Liquid_feeding(titration)": "3a160824-0665-01ed-285a-51ef817a9046",
|
||||
"Liquid_feeding_beaker": "3a16087e-124f-8ddb-8ec1-c2dff09ca784",
|
||||
"Drip_back": "3a162cf9-6aac-565a-ddd7-682ba1796a4a"
|
||||
},
|
||||
"material_type_mappings": {
|
||||
"BIOYOND_PolymerStation_Reactor": [
|
||||
"反应器",
|
||||
"3a14233b-902d-0d7b-4533-3f60f1c41c1b"
|
||||
],
|
||||
"BIOYOND_PolymerStation_1BottleCarrier": [
|
||||
"试剂瓶",
|
||||
"3a14233b-56e3-6c53-a8ab-fcaac163a9ba"
|
||||
],
|
||||
"BIOYOND_PolymerStation_1FlaskCarrier": [
|
||||
"烧杯",
|
||||
"3a14233b-f0a9-ba84-eaa9-0d4718b361b6"
|
||||
],
|
||||
"BIOYOND_PolymerStation_6StockCarrier": [
|
||||
"样品板",
|
||||
"3a142339-80de-8f25-6093-1b1b1b6c322e"
|
||||
],
|
||||
"BIOYOND_PolymerStation_Solid_Vial": [
|
||||
"90%分装小瓶",
|
||||
"3a14233a-26e1-28f8-af6a-60ca06ba0165"
|
||||
],
|
||||
"BIOYOND_PolymerStation_Liquid_Vial": [
|
||||
"10%分装小瓶",
|
||||
"3a14233a-84a3-088d-6676-7cb4acd57c64"
|
||||
],
|
||||
"BIOYOND_PolymerStation_TipBox": [
|
||||
"枪头盒",
|
||||
"3a143890-9d51-60ac-6d6f-6edb43c12041"
|
||||
]
|
||||
"烧杯": "BIOYOND_PolymerStation_1FlaskCarrier",
|
||||
"试剂瓶": "BIOYOND_PolymerStation_1BottleCarrier",
|
||||
"样品板": "BIOYOND_PolymerStation_6VialCarrier"
|
||||
}
|
||||
},
|
||||
"deck": {
|
||||
@@ -67,7 +42,9 @@
|
||||
{
|
||||
"id": "Bioyond_Deck",
|
||||
"name": "Bioyond_Deck",
|
||||
"children": [],
|
||||
"sample_id": null,
|
||||
"children": [
|
||||
],
|
||||
"parent": "reaction_station_bioyond",
|
||||
"type": "deck",
|
||||
"class": "BIOYOND_PolymerReactionStation_Deck",
|
||||
|
||||
@@ -18,21 +18,21 @@
|
||||
"config": {
|
||||
"deck": {
|
||||
"_resource_child_name": "deck",
|
||||
"_resource_type": "unilabos.devices.liquid_handling.laiyu.laiyu:TransformXYZDeck",
|
||||
"_resource_type": "pylabrobot.resources.opentrons.deck:OTDeck",
|
||||
"name": "deck"
|
||||
},
|
||||
"backend": {
|
||||
"type": "UniLiquidHandlerLaiyuBackend",
|
||||
"port": "/dev/ttyUSB_CH340"
|
||||
"type": "UniLiquidHandlerRvizBackend"
|
||||
|
||||
},
|
||||
"simulator": false,
|
||||
"total_height": 232.5
|
||||
"simulator": true,
|
||||
"total_height": 300
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "deck",
|
||||
"name": "deck",
|
||||
|
||||
"sample_id": null,
|
||||
"children": [
|
||||
"tip_rack",
|
||||
"plate_well",
|
||||
@@ -64,7 +64,7 @@
|
||||
{
|
||||
"id": "tip_rack",
|
||||
"name": "tip_rack",
|
||||
|
||||
"sample_id": null,
|
||||
"children": [
|
||||
"tip_rack_A1"
|
||||
],
|
||||
@@ -102,7 +102,7 @@
|
||||
{
|
||||
"id": "tip_rack_A1",
|
||||
"name": "tip_rack_A1",
|
||||
|
||||
"sample_id": null,
|
||||
"children": [],
|
||||
"parent": "tip_rack",
|
||||
"type": "container",
|
||||
@@ -144,7 +144,7 @@
|
||||
{
|
||||
"id": "plate_well",
|
||||
"name": "plate_well",
|
||||
|
||||
"sample_id": null,
|
||||
"children": [
|
||||
"plate_well_A1"
|
||||
],
|
||||
@@ -156,6 +156,18 @@
|
||||
"y": 116,
|
||||
"z": 48.5
|
||||
},
|
||||
"pose": {
|
||||
"position_3d": {
|
||||
"x": 161,
|
||||
"y": 116,
|
||||
"z": 48.5
|
||||
},
|
||||
"rotation": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"type": "Plate",
|
||||
"size_x": 127.76,
|
||||
@@ -183,7 +195,7 @@
|
||||
{
|
||||
"id": "plate_well_A1",
|
||||
"name": "plate_well_A1",
|
||||
|
||||
"sample_id": null,
|
||||
"children": [],
|
||||
"parent": "plate_well",
|
||||
"type": "device",
|
||||
@@ -224,7 +236,7 @@
|
||||
{
|
||||
"id": "tube_rack",
|
||||
"name": "tube_rack",
|
||||
|
||||
"sample_id": null,
|
||||
"children": [
|
||||
"tube_rack_A1"
|
||||
],
|
||||
@@ -259,7 +271,7 @@
|
||||
{
|
||||
"id": "tube_rack_A1",
|
||||
"name": "tube_rack_A1",
|
||||
|
||||
"sample_id": null,
|
||||
"children": [],
|
||||
"parent": "tube_rack",
|
||||
"type": "device",
|
||||
@@ -303,7 +315,7 @@
|
||||
{
|
||||
"id": "bottle_rack",
|
||||
"name": "bottle_rack",
|
||||
|
||||
"sample_id": null,
|
||||
"children": [
|
||||
"bottle_rack_A1"
|
||||
],
|
||||
@@ -339,7 +351,7 @@
|
||||
{
|
||||
"id": "bottle_rack_A1",
|
||||
"name": "bottle_rack_A1",
|
||||
|
||||
"sample_id": null,
|
||||
"children": [],
|
||||
"parent": "bottle_rack",
|
||||
"type": "device",
|
||||
|
||||
@@ -1,383 +0,0 @@
|
||||
{
|
||||
"nodes": [
|
||||
{
|
||||
"id": "liquid_handler",
|
||||
"name": "liquid_handler",
|
||||
"parent": null,
|
||||
"type": "device",
|
||||
"class": "liquid_handler",
|
||||
"position": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
"data": {},
|
||||
"children": [
|
||||
"deck"
|
||||
],
|
||||
"config": {
|
||||
"deck": {
|
||||
"_resource_child_name": "deck",
|
||||
"_resource_type": "unilabos.devices.liquid_handling.laiyu.laiyu:TransformXYZDeck",
|
||||
"name": "deck"
|
||||
},
|
||||
"backend": {
|
||||
"type": "UniLiquidHandlerRvizBackend"
|
||||
},
|
||||
"simulator": true,
|
||||
"total_height": 300,
|
||||
"joint_config": "TransformXYZDeck",
|
||||
"simulate_rviz": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "deck",
|
||||
"name": "deck",
|
||||
|
||||
"children": [
|
||||
"tip_rack",
|
||||
"plate_well",
|
||||
"tube_rack",
|
||||
"bottle_rack"
|
||||
],
|
||||
"parent": "liquid_handler",
|
||||
"type": "deck",
|
||||
"class": "TransformXYZDeck",
|
||||
"position": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 18
|
||||
},
|
||||
"config": {
|
||||
"type": "TransformXYZDeck",
|
||||
"size_x": 624.3,
|
||||
"size_y": 565.2,
|
||||
"size_z": 900,
|
||||
"rotation": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0,
|
||||
"type": "Rotation"
|
||||
}
|
||||
},
|
||||
"data": {}
|
||||
},
|
||||
{
|
||||
"id": "tip_rack",
|
||||
"name": "tip_rack",
|
||||
|
||||
"children": [
|
||||
"tip_rack_A1"
|
||||
],
|
||||
"parent": "deck",
|
||||
"type": "tip_rack",
|
||||
"class": "tiprack_box",
|
||||
"position": {
|
||||
"x": 150,
|
||||
"y": 7,
|
||||
"z": 103
|
||||
},
|
||||
"config": {
|
||||
"type": "TipRack",
|
||||
"size_x": 134,
|
||||
"size_y": 96,
|
||||
"size_z": 7.0,
|
||||
"rotation": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0,
|
||||
"type": "Rotation"
|
||||
},
|
||||
"category": "tip_rack",
|
||||
"model": "tiprack_box",
|
||||
"ordering": [
|
||||
"A1"
|
||||
]
|
||||
},
|
||||
"data": {}
|
||||
},
|
||||
|
||||
|
||||
|
||||
|
||||
{
|
||||
"id": "tip_rack_A1",
|
||||
"name": "tip_rack_A1",
|
||||
|
||||
"children": [],
|
||||
"parent": "tip_rack",
|
||||
"type": "container",
|
||||
"class": "",
|
||||
"position": {
|
||||
"x": 11.12,
|
||||
"y": 75,
|
||||
"z": -91.54
|
||||
},
|
||||
"config": {
|
||||
"type": "TipSpot",
|
||||
"size_x": 9,
|
||||
"size_y": 9,
|
||||
"size_z": 95,
|
||||
"rotation": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0,
|
||||
"type": "Rotation"
|
||||
},
|
||||
"category": "tip_spot",
|
||||
"model": null,
|
||||
"prototype_tip": {
|
||||
"type": "Tip",
|
||||
"total_tip_length": 95,
|
||||
"has_filter": false,
|
||||
"maximal_volume": 1000.0,
|
||||
"fitting_depth": 3.29
|
||||
}
|
||||
},
|
||||
"data": {
|
||||
"tip": null,
|
||||
"tip_state": null,
|
||||
"pending_tip": null
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
{
|
||||
"id": "plate_well",
|
||||
"name": "plate_well",
|
||||
|
||||
"children": [
|
||||
"plate_well_A1"
|
||||
],
|
||||
"parent": "deck",
|
||||
"type": "plate",
|
||||
"class": "plate_96",
|
||||
"position": {
|
||||
"x": 161,
|
||||
"y": 116,
|
||||
"z": 48.5
|
||||
},
|
||||
"config": {
|
||||
"type": "Plate",
|
||||
"size_x": 127.76,
|
||||
"size_y": 85.48,
|
||||
"size_z": 45.5,
|
||||
"rotation": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0,
|
||||
"type": "Rotation"
|
||||
},
|
||||
"category": "plate",
|
||||
"model": "plate_96",
|
||||
"ordering": [
|
||||
"A1"
|
||||
]
|
||||
},
|
||||
"data": {}
|
||||
},
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
{
|
||||
"id": "plate_well_A1",
|
||||
"name": "plate_well_A1",
|
||||
|
||||
"children": [],
|
||||
"parent": "plate_well",
|
||||
"type": "device",
|
||||
"class": "",
|
||||
"position": {
|
||||
"x": 10.1,
|
||||
"y": 70,
|
||||
"z": 6.1
|
||||
},
|
||||
"config": {
|
||||
"type": "Well",
|
||||
"size_x": 8.2,
|
||||
"size_y": 8.2,
|
||||
"size_z": 38,
|
||||
"rotation": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0,
|
||||
"type": "Rotation"
|
||||
},
|
||||
"category": "well",
|
||||
"model": null,
|
||||
"max_volume": 2000,
|
||||
"material_z_thickness": null,
|
||||
"compute_volume_from_height": null,
|
||||
"compute_height_from_volume": null,
|
||||
"bottom_type": "unknown",
|
||||
"cross_section_type": "rectangle"
|
||||
},
|
||||
"data": {
|
||||
"liquids": [["water", 50.0]],
|
||||
"pending_liquids": [["water", 50.0]],
|
||||
"liquid_history": []
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
{
|
||||
"id": "tube_rack",
|
||||
"name": "tube_rack",
|
||||
|
||||
"children": [
|
||||
"tube_rack_A1"
|
||||
],
|
||||
"parent": "deck",
|
||||
"type": "container",
|
||||
"class": "tube_container",
|
||||
"position": {
|
||||
"x": 0,
|
||||
"y": 127,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"type": "Plate",
|
||||
"size_x": 151,
|
||||
"size_y": 75,
|
||||
"size_z": 75,
|
||||
"rotation": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0,
|
||||
"type": "Rotation"
|
||||
},
|
||||
"model": "tube_container",
|
||||
"ordering": [
|
||||
"A1"
|
||||
]
|
||||
},
|
||||
"data": {}
|
||||
},
|
||||
|
||||
|
||||
{
|
||||
"id": "tube_rack_A1",
|
||||
"name": "tube_rack_A1",
|
||||
|
||||
"children": [],
|
||||
"parent": "tube_rack",
|
||||
"type": "device",
|
||||
"class": "",
|
||||
"position": {
|
||||
"x": 6,
|
||||
"y": 38,
|
||||
"z": 10
|
||||
},
|
||||
"config": {
|
||||
"type": "Well",
|
||||
"size_x": 34,
|
||||
"size_y": 34,
|
||||
"size_z": 117,
|
||||
"rotation": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0,
|
||||
"type": "Rotation"
|
||||
},
|
||||
"category": "tube",
|
||||
"model": null,
|
||||
"max_volume": 2000,
|
||||
"material_z_thickness": null,
|
||||
"compute_volume_from_height": null,
|
||||
"compute_height_from_volume": null,
|
||||
"bottom_type": "unknown",
|
||||
"cross_section_type": "rectangle"
|
||||
},
|
||||
"data": {
|
||||
"liquids": [["water", 50.0]],
|
||||
"pending_liquids": [["water", 50.0]],
|
||||
"liquid_history": []
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
,
|
||||
|
||||
|
||||
{
|
||||
"id": "bottle_rack",
|
||||
"name": "bottle_rack",
|
||||
|
||||
"children": [
|
||||
"bottle_rack_A1"
|
||||
],
|
||||
"parent": "deck",
|
||||
"type": "container",
|
||||
"class": "bottle_container",
|
||||
"position": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"type": "Plate",
|
||||
"size_x": 130,
|
||||
"size_y": 117,
|
||||
"size_z": 8,
|
||||
"rotation": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0,
|
||||
"type": "Rotation"
|
||||
},
|
||||
"category": "tube_rack",
|
||||
"model": "bottle_container",
|
||||
"ordering": [
|
||||
"A1"
|
||||
]
|
||||
},
|
||||
"data": {}
|
||||
},
|
||||
|
||||
|
||||
{
|
||||
"id": "bottle_rack_A1",
|
||||
"name": "bottle_rack_A1",
|
||||
|
||||
"children": [],
|
||||
"parent": "bottle_rack",
|
||||
"type": "device",
|
||||
"class": "",
|
||||
"position": {
|
||||
"x": 25,
|
||||
"y": 18.5,
|
||||
"z": 8
|
||||
},
|
||||
"config": {
|
||||
"type": "Well",
|
||||
"size_x": 80,
|
||||
"size_y": 80,
|
||||
"size_z": 117,
|
||||
"rotation": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0,
|
||||
"type": "Rotation"
|
||||
},
|
||||
"category": "tube",
|
||||
"model": null,
|
||||
"max_volume": 2000,
|
||||
"material_z_thickness": null,
|
||||
"compute_volume_from_height": null,
|
||||
"compute_height_from_volume": null,
|
||||
"bottom_type": "unknown",
|
||||
"cross_section_type": "rectangle"
|
||||
},
|
||||
"data": {
|
||||
"liquids": [["water", 50.0]],
|
||||
"pending_liquids": [["water", 50.0]],
|
||||
"liquid_history": []
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
],
|
||||
"links": []
|
||||
}
|
||||
@@ -3,8 +3,7 @@
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
from typing import Dict, Any, List
|
||||
from unilabos.ros.nodes.base_device_node import BaseROS2DeviceNode
|
||||
from typing import Dict, Any, Optional, List
|
||||
|
||||
|
||||
class SmartPumpController:
|
||||
@@ -15,8 +14,6 @@ class SmartPumpController:
|
||||
适用于实验室自动化系统中的液体处理任务。
|
||||
"""
|
||||
|
||||
_ros_node: BaseROS2DeviceNode
|
||||
|
||||
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.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:
|
||||
"""
|
||||
连接到泵设备
|
||||
@@ -96,7 +90,7 @@ class SmartPumpController:
|
||||
pump_time = (volume / flow_rate) * 60 # 转换为秒
|
||||
|
||||
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.current_flow_rate = 0.0
|
||||
@@ -176,8 +170,6 @@ class AdvancedTemperatureController:
|
||||
适用于需要精确温度控制的化学反应和材料处理过程。
|
||||
"""
|
||||
|
||||
_ros_node: BaseROS2DeviceNode
|
||||
|
||||
def __init__(self, controller_id: str = "temp_controller_01"):
|
||||
"""
|
||||
初始化温度控制器
|
||||
@@ -193,9 +185,6 @@ class AdvancedTemperatureController:
|
||||
self.pid_enabled = True
|
||||
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:
|
||||
"""
|
||||
设置目标温度
|
||||
@@ -249,7 +238,7 @@ class AdvancedTemperatureController:
|
||||
}
|
||||
)
|
||||
|
||||
await self._ros_node.sleep(step_time)
|
||||
await asyncio.sleep(step_time)
|
||||
|
||||
# 保持历史记录不超过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):
|
||||
"""
|
||||
初始化多通道分析仪
|
||||
@@ -357,9 +344,6 @@ class MultiChannelAnalyzer:
|
||||
self.is_measuring = False
|
||||
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:
|
||||
"""
|
||||
配置通道
|
||||
@@ -392,7 +376,7 @@ class MultiChannelAnalyzer:
|
||||
|
||||
# 模拟数据采集
|
||||
measurements = []
|
||||
for _ in range(duration):
|
||||
for second in range(duration):
|
||||
timestamp = asyncio.get_event_loop().time()
|
||||
frame_data = {}
|
||||
|
||||
@@ -407,7 +391,7 @@ class MultiChannelAnalyzer:
|
||||
|
||||
measurements.append({"timestamp": timestamp, "data": frame_data})
|
||||
|
||||
await self._ros_node.sleep(1.0) # 每秒采集一次
|
||||
await asyncio.sleep(1.0) # 每秒采集一次
|
||||
|
||||
self.is_measuring = False
|
||||
|
||||
@@ -481,8 +465,6 @@ class AutomatedDispenser:
|
||||
集成称重功能,确保分配精度和重现性。
|
||||
"""
|
||||
|
||||
_ros_node: BaseROS2DeviceNode
|
||||
|
||||
def __init__(self, dispenser_id: str = "dispenser_01"):
|
||||
"""
|
||||
初始化自动分配器
|
||||
@@ -497,9 +479,6 @@ class AutomatedDispenser:
|
||||
self.container_capacity = 1000.0 # mL
|
||||
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:
|
||||
"""
|
||||
移动到指定位置
|
||||
@@ -538,7 +517,7 @@ class AutomatedDispenser:
|
||||
if viscosity == "high":
|
||||
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
|
||||
|
||||
|
||||
@@ -12,13 +12,23 @@ lab_registry.setup()
|
||||
|
||||
|
||||
type_mapping = {
|
||||
"烧杯": ("BIOYOND_PolymerStation_1FlaskCarrier", "3a14196b-24f2-ca49-9081-0cab8021bf1a"),
|
||||
"试剂瓶": ("BIOYOND_PolymerStation_1BottleCarrier", ""),
|
||||
"样品板": ("BIOYOND_PolymerStation_6StockCarrier", "3a14196e-b7a0-a5da-1931-35f3000281e9"),
|
||||
"分装板": ("BIOYOND_PolymerStation_6VialCarrier", "3a14196e-5dfe-6e21-0c79-fe2036d052c4"),
|
||||
"样品瓶": ("BIOYOND_PolymerStation_Solid_Stock", "3a14196a-cf7d-8aea-48d8-b9662c7dba94"),
|
||||
"90%分装小瓶": ("BIOYOND_PolymerStation_Solid_Vial", "3a14196c-cdcf-088d-dc7d-5cf38f0ad9ea"),
|
||||
"10%分装小瓶": ("BIOYOND_PolymerStation_Liquid_Vial", "3a14196c-76be-2279-4e22-7310d69aed68"),
|
||||
"烧杯": "BIOYOND_PolymerStation_1FlaskCarrier",
|
||||
"试剂瓶": "BIOYOND_PolymerStation_1BottleCarrier",
|
||||
"样品板": "BIOYOND_PolymerStation_6StockCarrier",
|
||||
"分装板": "BIOYOND_PolymerStation_6VialCarrier",
|
||||
"样品瓶": "BIOYOND_PolymerStation_Solid_Stock",
|
||||
"90%分装小瓶": "BIOYOND_PolymerStation_Solid_Vial",
|
||||
"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,68 +0,0 @@
|
||||
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
|
||||
|
||||
lab_registry.setup()
|
||||
|
||||
|
||||
type_mapping = {
|
||||
"烧杯": ("BIOYOND_PolymerStation_1FlaskCarrier", "3a14196b-24f2-ca49-9081-0cab8021bf1a"),
|
||||
"试剂瓶": ("BIOYOND_PolymerStation_1BottleCarrier", ""),
|
||||
"样品板": ("BIOYOND_PolymerStation_6StockCarrier", "3a14196e-b7a0-a5da-1931-35f3000281e9"),
|
||||
"分装板": ("BIOYOND_PolymerStation_6VialCarrier", "3a14196e-5dfe-6e21-0c79-fe2036d052c4"),
|
||||
"样品瓶": ("BIOYOND_PolymerStation_Solid_Stock", "3a14196a-cf7d-8aea-48d8-b9662c7dba94"),
|
||||
"90%分装小瓶": ("BIOYOND_PolymerStation_Solid_Vial", "3a14196c-cdcf-088d-dc7d-5cf38f0ad9ea"),
|
||||
"10%分装小瓶": ("BIOYOND_PolymerStation_Liquid_Vial", "3a14196c-76be-2279-4e22-7310d69aed68"),
|
||||
}
|
||||
|
||||
|
||||
@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(materials_fixture, request) -> list[dict]:
|
||||
materials = request.getfixturevalue(materials_fixture)
|
||||
deck = BIOYOND_PolymerReactionStation_Deck("test_deck")
|
||||
output = resource_bioyond_to_plr(materials, type_mapping=type_mapping, deck=deck)
|
||||
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)
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
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}
|
||||
]
|
||||
}
|
||||
|
Before Width: | Height: | Size: 140 KiB |
|
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)
|
||||
@@ -1 +1 @@
|
||||
__version__ = "0.10.10"
|
||||
__version__ = "0.10.7"
|
||||
|
||||
@@ -13,7 +13,7 @@ def start_backend(
|
||||
graph=None,
|
||||
controllers_config: dict = {},
|
||||
bridges=[],
|
||||
is_slave: bool = False,
|
||||
without_host: bool = False,
|
||||
visual: str = "None",
|
||||
resources_mesh_config: dict = {},
|
||||
**kwargs,
|
||||
@@ -32,7 +32,7 @@ def start_backend(
|
||||
raise ValueError(f"Unsupported backend: {backend}")
|
||||
|
||||
backend_thread = threading.Thread(
|
||||
target=main if not is_slave else slave,
|
||||
target=main if not without_host else slave,
|
||||
args=(
|
||||
devices_config,
|
||||
resources_config,
|
||||
|
||||
@@ -11,14 +11,18 @@ from typing import Dict, Any, List
|
||||
import networkx as nx
|
||||
import yaml
|
||||
|
||||
from unilabos.ros.nodes.resource_tracker import ResourceTreeSet, ResourceDict
|
||||
|
||||
# 首先添加项目根目录到路径
|
||||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
unilabos_dir = os.path.dirname(os.path.dirname(current_dir))
|
||||
if unilabos_dir not in sys.path:
|
||||
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.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):
|
||||
if config_path is None:
|
||||
@@ -180,7 +184,6 @@ def main():
|
||||
working_dir = os.path.abspath(os.getcwd())
|
||||
else:
|
||||
working_dir = os.path.abspath(os.path.join(os.getcwd(), "unilabos_data"))
|
||||
|
||||
if args_dict.get("working_dir"):
|
||||
working_dir = args_dict.get("working_dir", "")
|
||||
if config_path and not os.path.exists(config_path):
|
||||
@@ -212,14 +215,6 @@ def main():
|
||||
# 加载配置文件
|
||||
print_status(f"当前工作目录为 {working_dir}", "info")
|
||||
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":
|
||||
print_status("使用测试环境地址", "info")
|
||||
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 start_server
|
||||
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)
|
||||
@@ -356,7 +349,7 @@ def main():
|
||||
|
||||
if BasicConfig.upload_registry:
|
||||
# 设备注册到服务端 - 需要 ak 和 sk
|
||||
if BasicConfig.ak and BasicConfig.sk:
|
||||
if args_dict.get("ak") and args_dict.get("sk"):
|
||||
print_status("开始注册设备到服务端...", "info")
|
||||
try:
|
||||
register_devices_and_resources(lab_registry)
|
||||
@@ -375,23 +368,22 @@ def main():
|
||||
|
||||
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"]:
|
||||
args_dict["bridges"].append(http_client)
|
||||
# 获取通信客户端(仅支持WebSocket)
|
||||
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)
|
||||
if "websocket" in args_dict["app_bridges"]:
|
||||
|
||||
signal.signal(signal.SIGINT, _exit)
|
||||
signal.signal(signal.SIGTERM, _exit)
|
||||
comm_client.start()
|
||||
else:
|
||||
print_status("SlaveMode跳过Websocket连接")
|
||||
def _exit(signum, frame):
|
||||
comm_client.stop()
|
||||
sys.exit(0)
|
||||
|
||||
signal.signal(signal.SIGINT, _exit)
|
||||
signal.signal(signal.SIGTERM, _exit)
|
||||
comm_client.start()
|
||||
args_dict["resources_mesh_config"] = {}
|
||||
args_dict["resources_edge_config"] = resource_edge_info
|
||||
# web visiualize 2D
|
||||
@@ -419,23 +411,7 @@ def main():
|
||||
)
|
||||
server_thread.start()
|
||||
asyncio.set_event_loop(asyncio.new_event_loop())
|
||||
try:
|
||||
resource_visualization.start()
|
||||
except OSError as e:
|
||||
if "AMENT_PREFIX_PATH" in str(e):
|
||||
print_status(
|
||||
f"ROS 2环境未正确设置,跳过3D可视化启动。错误详情: {e}",
|
||||
"warning"
|
||||
)
|
||||
print_status(
|
||||
"建议解决方案:\n"
|
||||
"1. 激活Conda环境: conda activate unilab\n"
|
||||
"2. 或使用 --backend simple 参数\n"
|
||||
"3. 或使用 --visual disable 参数禁用可视化",
|
||||
"info"
|
||||
)
|
||||
else:
|
||||
raise
|
||||
resource_visualization.start()
|
||||
while True:
|
||||
time.sleep(1)
|
||||
else:
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import json
|
||||
import time
|
||||
from typing import Optional, Tuple, Dict, Any
|
||||
|
||||
from unilabos.utils.log import logger
|
||||
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)
|
||||
"""
|
||||
@@ -29,8 +28,6 @@ def register_devices_and_resources(lab_registry, gather_only=False) -> Optional[
|
||||
resources_to_register[resource_info["id"]] = resource_info
|
||||
logger.debug(f"[UniLab Register] 收集资源: {resource_info['id']}")
|
||||
|
||||
if gather_only:
|
||||
return devices_to_register, resources_to_register
|
||||
# 注册设备
|
||||
if devices_to_register:
|
||||
try:
|
||||
|
||||
@@ -6,8 +6,6 @@ HTTP客户端模块
|
||||
|
||||
import json
|
||||
import os
|
||||
import time
|
||||
from threading import Thread
|
||||
from typing import List, Dict, Any, Optional
|
||||
|
||||
import requests
|
||||
@@ -75,8 +73,6 @@ class HTTPClient:
|
||||
Returns:
|
||||
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)
|
||||
old_uuids = {n.res_content.uuid: n for n in resources.all_nodes}
|
||||
if not self.initialized or first_add:
|
||||
@@ -86,18 +82,16 @@ class HTTPClient:
|
||||
f"{self.remote_addr}/edge/material",
|
||||
json={"nodes": [x for xs in resources.dump() for x in xs], "mount_uuid": mount_uuid},
|
||||
headers={"Authorization": f"Lab {self.auth}"},
|
||||
timeout=60,
|
||||
timeout=100,
|
||||
)
|
||||
else:
|
||||
response = requests.put(
|
||||
f"{self.remote_addr}/edge/material",
|
||||
json={"nodes": [x for xs in resources.dump() for x in xs], "mount_uuid": mount_uuid},
|
||||
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_mapping = {}
|
||||
if response.status_code == 200:
|
||||
@@ -128,16 +122,12 @@ class HTTPClient:
|
||||
Returns:
|
||||
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(
|
||||
f"{self.remote_addr}/edge/material/query",
|
||||
json={"uuids": uuid_list, "with_children": with_children},
|
||||
headers={"Authorization": f"Lab {self.auth}"},
|
||||
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:
|
||||
res = response.json()
|
||||
if "code" in res and res["code"] != 0:
|
||||
@@ -193,16 +183,12 @@ class HTTPClient:
|
||||
Returns:
|
||||
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(
|
||||
f"{self.remote_addr}/lab/material",
|
||||
params={"id": id, "with_children": with_children},
|
||||
headers={"Authorization": f"Lab {self.auth}"},
|
||||
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()
|
||||
|
||||
def resource_del(self, id: str) -> requests.Response:
|
||||
|
||||
@@ -261,28 +261,29 @@ class DeviceActionManager:
|
||||
device_key = job_info.device_action_key
|
||||
|
||||
# 如果是正在执行的任务
|
||||
if device_key in self.active_jobs and self.active_jobs[device_key].job_id == job_id:
|
||||
# 清理active job状态
|
||||
del self.active_jobs[device_key]
|
||||
job_info.status = JobStatus.ENDED
|
||||
# 从all_jobs中移除
|
||||
del self.all_jobs[job_id]
|
||||
job_log = format_job_log(job_info.job_id, job_info.task_id, job_info.device_id, job_info.action_name)
|
||||
logger.info(f"[DeviceActionManager] Active job {job_log} cancelled for {device_key}")
|
||||
if (
|
||||
device_key in self.active_jobs and self.active_jobs[device_key].job_id == job_id
|
||||
): # 后面需要和cancel_goal进行联动,而不是在这里进行处理,现在默认等待这个job结束
|
||||
# del self.active_jobs[device_key]
|
||||
# job_info.status = JobStatus.ENDED
|
||||
# # 从all_jobs中移除
|
||||
# del self.all_jobs[job_id]
|
||||
# job_log = format_job_log(job_info.job_id, job_info.task_id, job_info.device_id, job_info.action_name)
|
||||
# logger.info(f"[DeviceActionManager] Active job {job_log} cancelled for {device_key}")
|
||||
|
||||
# 启动下一个任务
|
||||
if device_key in self.device_queues and self.device_queues[device_key]:
|
||||
next_job = self.device_queues[device_key].pop(0)
|
||||
# 将下一个job设置为READY状态并放入active_jobs
|
||||
next_job.status = JobStatus.READY
|
||||
next_job.update_timestamp()
|
||||
next_job.set_ready_timeout(10)
|
||||
self.active_jobs[device_key] = next_job
|
||||
next_job_log = format_job_log(
|
||||
next_job.job_id, next_job.task_id, next_job.device_id, next_job.action_name
|
||||
)
|
||||
logger.info(f"[DeviceActionManager] Next job {next_job_log} can start after cancel")
|
||||
return True
|
||||
# # 启动下一个任务
|
||||
# if device_key in self.device_queues and self.device_queues[device_key]:
|
||||
# next_job = self.device_queues[device_key].pop(0)
|
||||
# # 将下一个job设置为READY状态并放入active_jobs
|
||||
# next_job.status = JobStatus.READY
|
||||
# next_job.update_timestamp()
|
||||
# next_job.set_ready_timeout(10)
|
||||
# self.active_jobs[device_key] = next_job
|
||||
# next_job_log = format_job_log(next_job.job_id, next_job.task_id,
|
||||
# next_job.device_id, next_job.action_name)
|
||||
# logger.info(f"[DeviceActionManager] Next job {next_job_log} can start after cancel")
|
||||
# return True
|
||||
pass
|
||||
|
||||
# 如果是排队中的任务
|
||||
elif device_key in self.device_queues:
|
||||
@@ -740,51 +741,31 @@ class MessageProcessor:
|
||||
job_info.action_name if job_info else "",
|
||||
)
|
||||
|
||||
# 先通知HostNode取消ROS2 action(如果存在)
|
||||
host_node = HostNode.get_instance(0)
|
||||
ros_cancel_success = False
|
||||
if host_node:
|
||||
ros_cancel_success = host_node.cancel_goal(job_id)
|
||||
if ros_cancel_success:
|
||||
logger.info(f"[MessageProcessor] ROS2 cancel request sent for job {job_log}")
|
||||
else:
|
||||
logger.debug(
|
||||
f"[MessageProcessor] Job {job_log} not in ROS2 goals " "(may be queued or already finished)"
|
||||
)
|
||||
|
||||
# 按job_id取消单个job(清理状态机)
|
||||
# 按job_id取消单个job
|
||||
success = self.device_manager.cancel_job(job_id)
|
||||
if success:
|
||||
logger.info(f"[MessageProcessor] Job {job_log} cancelled from queue/active list")
|
||||
# 通知HostNode取消
|
||||
host_node = HostNode.get_instance(0)
|
||||
if host_node:
|
||||
host_node.cancel_goal(job_id)
|
||||
logger.info(f"[MessageProcessor] Job {job_log} cancelled")
|
||||
|
||||
# 通知QueueProcessor有队列更新
|
||||
if self.queue_processor:
|
||||
self.queue_processor.notify_queue_update()
|
||||
else:
|
||||
logger.warning(f"[MessageProcessor] Failed to cancel job {job_log} from queue")
|
||||
logger.warning(f"[MessageProcessor] Failed to cancel job {job_log}")
|
||||
|
||||
elif task_id:
|
||||
# 先通知HostNode取消所有ROS2 actions
|
||||
# 需要先获取所有相关job_ids
|
||||
jobs_to_cancel = []
|
||||
with self.device_manager.lock:
|
||||
jobs_to_cancel = [
|
||||
job_info for job_info in self.device_manager.all_jobs.values() if job_info.task_id == task_id
|
||||
]
|
||||
|
||||
host_node = HostNode.get_instance(0)
|
||||
if host_node and jobs_to_cancel:
|
||||
ros_cancelled_count = 0
|
||||
for job_info in jobs_to_cancel:
|
||||
if host_node.cancel_goal(job_info.job_id):
|
||||
ros_cancelled_count += 1
|
||||
logger.info(
|
||||
f"[MessageProcessor] Sent ROS2 cancel for " f"{ros_cancelled_count}/{len(jobs_to_cancel)} jobs"
|
||||
)
|
||||
|
||||
# 按task_id取消所有相关job(清理状态机)
|
||||
# 按task_id取消所有相关job
|
||||
cancelled_job_ids = self.device_manager.cancel_jobs_by_task_id(task_id)
|
||||
if cancelled_job_ids:
|
||||
# 通知HostNode取消所有job
|
||||
host_node = HostNode.get_instance(0)
|
||||
if host_node:
|
||||
for cancelled_job_id in cancelled_job_ids:
|
||||
host_node.cancel_goal(cancelled_job_id)
|
||||
|
||||
logger.info(f"[MessageProcessor] Cancelled {len(cancelled_job_ids)} jobs for task_id: {task_id}")
|
||||
|
||||
# 通知QueueProcessor有队列更新
|
||||
@@ -1075,19 +1056,11 @@ class QueueProcessor:
|
||||
"""处理任务完成"""
|
||||
# 获取job信息用于日志
|
||||
job_info = self.device_manager.get_job_info(job_id)
|
||||
|
||||
# 如果job不存在,说明可能已被手动取消
|
||||
if not job_info:
|
||||
logger.debug(
|
||||
f"[QueueProcessor] Job {job_id[:8]} not found in manager " "(may have been cancelled manually)"
|
||||
)
|
||||
return
|
||||
|
||||
job_log = format_job_log(
|
||||
job_id,
|
||||
job_info.task_id,
|
||||
job_info.device_id,
|
||||
job_info.action_name,
|
||||
job_info.task_id if job_info else "",
|
||||
job_info.device_id if job_info else "",
|
||||
job_info.action_name if job_info else "",
|
||||
)
|
||||
|
||||
logger.info(f"[QueueProcessor] Job {job_log} completed with status: {status}")
|
||||
|
||||
@@ -2,7 +2,7 @@ import base64
|
||||
import traceback
|
||||
import os
|
||||
import importlib.util
|
||||
from typing import Optional, Literal
|
||||
from typing import Optional
|
||||
from unilabos.utils import logger
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@ class BasicConfig:
|
||||
vis_2d_enable = False
|
||||
enable_resource_load = True
|
||||
communication_protocol = "websocket"
|
||||
log_level: Literal['TRACE', 'DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'] = "DEBUG" # 'TRACE', 'DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'
|
||||
|
||||
@classmethod
|
||||
def auth_secret(cls):
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
# OPC UA 通用客户端
|
||||
|
||||
本模块提供了一个通用的 OPC UA 客户端实现,可以通过外部配置(CSV文件)来定义节点,并通过JSON配置来执行工作流。
|
||||
|
||||
## 特点
|
||||
|
||||
- 支持通过 CSV 文件配置 OPC UA 节点(只需提供名称、类型和数据类型,支持节点为中文名,需指定NodeLanguage)
|
||||
- 自动查找服务器中的节点,无需知道确切的节点ID
|
||||
- 提供工作流机制
|
||||
- 支持通过 JSON 配置创建工作流
|
||||
|
||||
## 使用方法
|
||||
|
||||
step1: 准备opcua_nodes.csv文件
|
||||
step2: 编写opcua_workflow_example.json,以定义工作流。指定opcua_nodes.csv
|
||||
step3: 编写工作流对应action
|
||||
step4: 编写opcua_example.yaml注册表
|
||||
step5: 编写opcua_example.json组态图。指定opcua_workflow_example.json定义工作流文件
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
from unilabos.device_comms.opcua_client.node.uniopcua import Variable, Method, Object, NodeType, DataType
|
||||
|
||||
__all__ = [
|
||||
'Variable',
|
||||
'Method',
|
||||
'Object',
|
||||
'NodeType',
|
||||
'DataType',
|
||||
]
|
||||
@@ -1,10 +0,0 @@
|
||||
from unilabos.device_comms.opcua_client.node.uniopcua import Variable, Method, Object, NodeType, DataType, Base
|
||||
|
||||
__all__ = [
|
||||
'Variable',
|
||||
'Method',
|
||||
'Object',
|
||||
'NodeType',
|
||||
'DataType',
|
||||
'Base',
|
||||
]
|
||||
@@ -1,180 +0,0 @@
|
||||
# coding=utf-8
|
||||
from enum import Enum
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Tuple, Union, Optional, Any, List
|
||||
|
||||
from opcua import Client, Node
|
||||
from opcua.ua import NodeId, NodeClass, VariantType
|
||||
|
||||
|
||||
class DataType(Enum):
|
||||
BOOLEAN = VariantType.Boolean
|
||||
SBYTE = VariantType.SByte
|
||||
BYTE = VariantType.Byte
|
||||
INT16 = VariantType.Int16
|
||||
UINT16 = VariantType.UInt16
|
||||
INT32 = VariantType.Int32
|
||||
UINT32 = VariantType.UInt32
|
||||
INT64 = VariantType.Int64
|
||||
UINT64 = VariantType.UInt64
|
||||
FLOAT = VariantType.Float
|
||||
DOUBLE = VariantType.Double
|
||||
STRING = VariantType.String
|
||||
DATETIME = VariantType.DateTime
|
||||
BYTESTRING = VariantType.ByteString
|
||||
|
||||
|
||||
class NodeType(Enum):
|
||||
VARIABLE = NodeClass.Variable
|
||||
OBJECT = NodeClass.Object
|
||||
METHOD = NodeClass.Method
|
||||
OBJECTTYPE = NodeClass.ObjectType
|
||||
VARIABLETYPE = NodeClass.VariableType
|
||||
REFERENCETYPE = NodeClass.ReferenceType
|
||||
DATATYPE = NodeClass.DataType
|
||||
VIEW = NodeClass.View
|
||||
|
||||
|
||||
class Base(ABC):
|
||||
def __init__(self, client: Client, name: str, node_id: str, typ: NodeType, data_type: DataType):
|
||||
self._node_id: str = node_id
|
||||
self._client = client
|
||||
self._name = name
|
||||
self._type = typ
|
||||
self._data_type = data_type
|
||||
self._node: Optional[Node] = None
|
||||
|
||||
def _get_node(self) -> Node:
|
||||
if self._node is None:
|
||||
try:
|
||||
# 检查是否是NumericNodeId(ns=X;i=Y)格式
|
||||
if "NumericNodeId" in self._node_id:
|
||||
# 从字符串中提取命名空间和标识符
|
||||
import re
|
||||
match = re.search(r'ns=(\d+);i=(\d+)', self._node_id)
|
||||
if match:
|
||||
ns = int(match.group(1))
|
||||
identifier = int(match.group(2))
|
||||
node_id = NodeId(identifier, ns)
|
||||
self._node = self._client.get_node(node_id)
|
||||
else:
|
||||
raise ValueError(f"无法解析节点ID: {self._node_id}")
|
||||
else:
|
||||
# 直接使用节点ID字符串
|
||||
self._node = self._client.get_node(self._node_id)
|
||||
except Exception as e:
|
||||
print(f"获取节点失败: {self._node_id}, 错误: {e}")
|
||||
raise
|
||||
return self._node
|
||||
|
||||
@abstractmethod
|
||||
def read(self) -> Tuple[Any, bool]:
|
||||
"""读取节点值,返回(值, 是否出错)"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def write(self, value: Any) -> bool:
|
||||
"""写入节点值,返回是否出错"""
|
||||
pass
|
||||
|
||||
@property
|
||||
def type(self) -> NodeType:
|
||||
return self._type
|
||||
|
||||
@property
|
||||
def node_id(self) -> str:
|
||||
return self._node_id
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return self._name
|
||||
|
||||
|
||||
class Variable(Base):
|
||||
def __init__(self, client: Client, name: str, node_id: str, data_type: DataType):
|
||||
super().__init__(client, name, node_id, NodeType.VARIABLE, data_type)
|
||||
|
||||
def read(self) -> Tuple[Any, bool]:
|
||||
try:
|
||||
value = self._get_node().get_value()
|
||||
return value, False
|
||||
except Exception as e:
|
||||
print(f"读取变量 {self._name} 失败: {e}")
|
||||
return None, True
|
||||
|
||||
def write(self, value: Any) -> bool:
|
||||
try:
|
||||
self._get_node().set_value(value)
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"写入变量 {self._name} 失败: {e}")
|
||||
return True
|
||||
|
||||
|
||||
class Method(Base):
|
||||
def __init__(self, client: Client, name: str, node_id: str, parent_node_id: str, data_type: DataType):
|
||||
super().__init__(client, name, node_id, NodeType.METHOD, data_type)
|
||||
self._parent_node_id = parent_node_id
|
||||
self._parent_node = None
|
||||
|
||||
def _get_parent_node(self) -> Node:
|
||||
if self._parent_node is None:
|
||||
try:
|
||||
# 检查是否是NumericNodeId(ns=X;i=Y)格式
|
||||
if "NumericNodeId" in self._parent_node_id:
|
||||
# 从字符串中提取命名空间和标识符
|
||||
import re
|
||||
match = re.search(r'ns=(\d+);i=(\d+)', self._parent_node_id)
|
||||
if match:
|
||||
ns = int(match.group(1))
|
||||
identifier = int(match.group(2))
|
||||
node_id = NodeId(identifier, ns)
|
||||
self._parent_node = self._client.get_node(node_id)
|
||||
else:
|
||||
raise ValueError(f"无法解析父节点ID: {self._parent_node_id}")
|
||||
else:
|
||||
# 直接使用节点ID字符串
|
||||
self._parent_node = self._client.get_node(self._parent_node_id)
|
||||
except Exception as e:
|
||||
print(f"获取父节点失败: {self._parent_node_id}, 错误: {e}")
|
||||
raise
|
||||
return self._parent_node
|
||||
|
||||
def read(self) -> Tuple[Any, bool]:
|
||||
"""方法节点不支持读取操作"""
|
||||
return None, True
|
||||
|
||||
def write(self, value: Any) -> bool:
|
||||
"""方法节点不支持写入操作"""
|
||||
return True
|
||||
|
||||
def call(self, *args) -> Tuple[Any, bool]:
|
||||
"""调用方法,返回(返回值, 是否出错)"""
|
||||
try:
|
||||
result = self._get_parent_node().call_method(self._get_node(), *args)
|
||||
return result, False
|
||||
except Exception as e:
|
||||
print(f"调用方法 {self._name} 失败: {e}")
|
||||
return None, True
|
||||
|
||||
|
||||
class Object(Base):
|
||||
def __init__(self, client: Client, name: str, node_id: str):
|
||||
super().__init__(client, name, node_id, NodeType.OBJECT, None)
|
||||
|
||||
def read(self) -> Tuple[Any, bool]:
|
||||
"""对象节点不支持直接读取操作"""
|
||||
return None, True
|
||||
|
||||
def write(self, value: Any) -> bool:
|
||||
"""对象节点不支持直接写入操作"""
|
||||
return True
|
||||
|
||||
def get_children(self) -> Tuple[List[Node], bool]:
|
||||
"""获取子节点列表,返回(子节点列表, 是否出错)"""
|
||||
try:
|
||||
children = self._get_node().get_children()
|
||||
return children, False
|
||||
except Exception as e:
|
||||
print(f"获取对象 {self._name} 的子节点失败: {e}")
|
||||
return [], True
|
||||
@@ -1,98 +0,0 @@
|
||||
{
|
||||
"register_node_list_from_csv_path": {
|
||||
"path": "simple_opcua_nodes.csv"
|
||||
},
|
||||
"create_flow": [
|
||||
{
|
||||
"name": "温度控制流程",
|
||||
"action": [
|
||||
{
|
||||
"name": "温度控制动作",
|
||||
"node_function_to_create": [
|
||||
{
|
||||
"func_name": "read_temperature",
|
||||
"node_name": "Temperature",
|
||||
"mode": "read"
|
||||
},
|
||||
{
|
||||
"func_name": "read_heating_status",
|
||||
"node_name": "HeatingStatus",
|
||||
"mode": "read"
|
||||
},
|
||||
{
|
||||
"func_name": "set_heating",
|
||||
"node_name": "HeatingEnabled",
|
||||
"mode": "write",
|
||||
"value": true
|
||||
}
|
||||
],
|
||||
"create_init_function": {
|
||||
"func_name": "init_setpoint",
|
||||
"node_name": "Setpoint",
|
||||
"mode": "write",
|
||||
"value": 25.0
|
||||
},
|
||||
"create_start_function": {
|
||||
"func_name": "start_heating_control",
|
||||
"node_name": "HeatingEnabled",
|
||||
"mode": "write",
|
||||
"write_functions": [
|
||||
"set_heating"
|
||||
],
|
||||
"condition_functions": [
|
||||
"read_temperature",
|
||||
"read_heating_status"
|
||||
],
|
||||
"stop_condition_expression": "read_temperature >= 25.0 and read_heating_status"
|
||||
},
|
||||
"create_stop_function": {
|
||||
"func_name": "stop_heating",
|
||||
"node_name": "HeatingEnabled",
|
||||
"mode": "write",
|
||||
"value": false
|
||||
},
|
||||
"create_cleanup_function": null
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "报警重置流程",
|
||||
"action": [
|
||||
{
|
||||
"name": "报警重置动作",
|
||||
"node_function_to_create": [
|
||||
{
|
||||
"func_name": "reset_alarm",
|
||||
"node_name": "ResetAlarm",
|
||||
"mode": "call",
|
||||
"value": []
|
||||
}
|
||||
],
|
||||
"create_init_function": null,
|
||||
"create_start_function": {
|
||||
"func_name": "start_reset_alarm",
|
||||
"node_name": "ResetAlarm",
|
||||
"mode": "call",
|
||||
"write_functions": [],
|
||||
"condition_functions": [
|
||||
"reset_alarm"
|
||||
],
|
||||
"stop_condition_expression": "True"
|
||||
},
|
||||
"create_stop_function": null,
|
||||
"create_cleanup_function": null
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "完整控制流程",
|
||||
"action": [
|
||||
"温度控制流程",
|
||||
"报警重置流程"
|
||||
]
|
||||
}
|
||||
],
|
||||
"execute_flow": [
|
||||
"完整控制流程"
|
||||
]
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
Name,EnglishName,NodeType,DataType,NodeLanguage
|
||||
中文名,EnglishName,VARIABLE,INT32,Chinese
|
||||
|
@@ -1,30 +0,0 @@
|
||||
{
|
||||
"register_node_list_from_csv_path": {
|
||||
"path": "opcua_nodes_example.csv"
|
||||
},
|
||||
"create_flow": [
|
||||
{
|
||||
"name": "name",
|
||||
"description": "description",
|
||||
"parameters": ["parameter1", "parameter2"],
|
||||
"action": [
|
||||
{
|
||||
"init_function": {
|
||||
"func_name": "init_grab_params",
|
||||
"write_nodes": ["parameter1", "parameter2"]
|
||||
},
|
||||
"start_function": {
|
||||
"func_name": "start_grab",
|
||||
"write_nodes": {"parameter_start": true},
|
||||
"condition_nodes": ["parameter_condition"],
|
||||
"stop_condition_expression": "parameter_condition == True"
|
||||
},
|
||||
"stop_function": {
|
||||
"func_name": "stop_grab",
|
||||
"write_nodes": {"parameter_start": false}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,311 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
OPC UA测试服务器
|
||||
用于测试OPC UA客户端功能,特别是temperature_control和valve_control工作流
|
||||
"""
|
||||
|
||||
import sys
|
||||
import time
|
||||
import logging
|
||||
from opcua import Server, ua
|
||||
import threading
|
||||
|
||||
# 设置日志
|
||||
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class OpcUaTestServer:
|
||||
"""OPC UA测试服务器类"""
|
||||
|
||||
def __init__(self, endpoint="opc.tcp://localhost:4840/freeopcua/server/"):
|
||||
"""
|
||||
初始化OPC UA服务器
|
||||
|
||||
Args:
|
||||
endpoint: 服务器端点URL
|
||||
"""
|
||||
self.server = Server()
|
||||
self.server.set_endpoint(endpoint)
|
||||
|
||||
# 设置服务器名称
|
||||
self.server.set_server_name("UniLabOS OPC UA Test Server")
|
||||
|
||||
# 设置服务器命名空间
|
||||
self.idx = self.server.register_namespace("http://unilabos.com/opcua/test")
|
||||
|
||||
# 获取Objects节点
|
||||
self.objects = self.server.get_objects_node()
|
||||
|
||||
# 创建设备对象
|
||||
self.device = self.objects.add_object(self.idx, "TestDevice")
|
||||
|
||||
# 存储所有节点的字典
|
||||
self.nodes = {}
|
||||
|
||||
# 初始化标志
|
||||
self.running = False
|
||||
|
||||
# 控制标志
|
||||
self.simulation_active = True
|
||||
|
||||
def add_variable(self, name, value, data_type=None):
|
||||
"""
|
||||
添加变量节点
|
||||
|
||||
Args:
|
||||
name: 变量名称
|
||||
value: 初始值
|
||||
data_type: 数据类型 (可选)
|
||||
"""
|
||||
if data_type is None:
|
||||
var = self.device.add_variable(self.idx, name, value)
|
||||
else:
|
||||
var = self.device.add_variable(self.idx, name, value, data_type)
|
||||
|
||||
# 设置变量可写
|
||||
var.set_writable()
|
||||
|
||||
# 存储节点
|
||||
self.nodes[name] = var
|
||||
logger.info(f"添加变量节点: {name}, 初始值: {value}")
|
||||
return var
|
||||
|
||||
def add_method(self, name, callback, inputs=None, outputs=None):
|
||||
"""
|
||||
添加方法节点
|
||||
|
||||
Args:
|
||||
name: 方法名称
|
||||
callback: 回调函数
|
||||
inputs: 输入参数列表 [(name, type), ...]
|
||||
outputs: 输出参数列表 [(name, type), ...]
|
||||
"""
|
||||
if inputs is None:
|
||||
inputs = []
|
||||
if outputs is None:
|
||||
outputs = []
|
||||
|
||||
# 创建输入参数
|
||||
input_args = []
|
||||
for arg_name, arg_type in inputs:
|
||||
input_args.append(ua.Argument())
|
||||
input_args[-1].Name = arg_name
|
||||
input_args[-1].DataType = arg_type
|
||||
input_args[-1].ValueRank = -1
|
||||
|
||||
# 创建输出参数
|
||||
output_args = []
|
||||
for arg_name, arg_type in outputs:
|
||||
output_args.append(ua.Argument())
|
||||
output_args[-1].Name = arg_name
|
||||
output_args[-1].DataType = arg_type
|
||||
output_args[-1].ValueRank = -1
|
||||
|
||||
# 添加方法
|
||||
method = self.device.add_method(
|
||||
self.idx,
|
||||
name,
|
||||
callback,
|
||||
input_args,
|
||||
output_args
|
||||
)
|
||||
|
||||
# 存储节点
|
||||
self.nodes[name] = method
|
||||
logger.info(f"添加方法节点: {name}")
|
||||
return method
|
||||
|
||||
def start(self):
|
||||
"""启动服务器"""
|
||||
if not self.running:
|
||||
self.server.start()
|
||||
self.running = True
|
||||
logger.info("OPC UA服务器已启动")
|
||||
|
||||
# 启动模拟线程
|
||||
self.simulation_thread = threading.Thread(target=self.run_simulation)
|
||||
self.simulation_thread.daemon = True
|
||||
self.simulation_thread.start()
|
||||
|
||||
def stop(self):
|
||||
"""停止服务器"""
|
||||
if self.running:
|
||||
self.simulation_active = False
|
||||
if hasattr(self, 'simulation_thread'):
|
||||
self.simulation_thread.join(timeout=2)
|
||||
self.server.stop()
|
||||
self.running = False
|
||||
logger.info("OPC UA服务器已停止")
|
||||
|
||||
def get_node(self, name):
|
||||
"""获取节点"""
|
||||
if name in self.nodes:
|
||||
return self.nodes[name]
|
||||
return None
|
||||
|
||||
def update_variable(self, name, value):
|
||||
"""更新变量值"""
|
||||
if name in self.nodes:
|
||||
self.nodes[name].set_value(value)
|
||||
logger.debug(f"更新变量 {name} = {value}")
|
||||
return True
|
||||
logger.warning(f"变量 {name} 不存在")
|
||||
return False
|
||||
|
||||
def run_simulation(self):
|
||||
"""运行模拟线程"""
|
||||
logger.info("启动模拟线程")
|
||||
|
||||
temp = 20.0
|
||||
valve_position = 0.0
|
||||
flow_rate = 0.0
|
||||
|
||||
while self.simulation_active and self.running:
|
||||
try:
|
||||
# 温度控制模拟
|
||||
heating_enabled = self.get_node("HeatingEnabled").get_value()
|
||||
setpoint = self.get_node("Setpoint").get_value()
|
||||
|
||||
if heating_enabled:
|
||||
self.update_variable("HeatingStatus", True)
|
||||
if temp < setpoint:
|
||||
temp += 0.5 # 加快温度上升速度
|
||||
else:
|
||||
temp -= 0.1
|
||||
else:
|
||||
self.update_variable("HeatingStatus", False)
|
||||
if temp > 20.0:
|
||||
temp -= 0.2
|
||||
|
||||
# 更新温度
|
||||
self.update_variable("Temperature", round(temp, 2))
|
||||
|
||||
# 阀门控制模拟
|
||||
valve_control = self.get_node("ValveControl").get_value()
|
||||
valve_setpoint = self.get_node("ValveSetpoint").get_value()
|
||||
|
||||
if valve_control:
|
||||
if valve_position < valve_setpoint:
|
||||
valve_position += 5.0 # 加快阀门开启速度
|
||||
if valve_position > valve_setpoint:
|
||||
valve_position = valve_setpoint
|
||||
else:
|
||||
valve_position -= 1.0
|
||||
if valve_position < 0:
|
||||
valve_position = 0
|
||||
else:
|
||||
if valve_position > 0:
|
||||
valve_position -= 5.0
|
||||
if valve_position < 0:
|
||||
valve_position = 0
|
||||
|
||||
# 更新阀门位置
|
||||
self.update_variable("ValvePosition", round(valve_position, 2))
|
||||
|
||||
# 流量模拟 - 与阀门位置成正比
|
||||
flow_rate = valve_position * 0.2 # 简单线性关系
|
||||
self.update_variable("FlowRate", round(flow_rate, 2))
|
||||
|
||||
# 更新系统状态
|
||||
status = []
|
||||
if heating_enabled:
|
||||
status.append("Heating")
|
||||
if valve_control:
|
||||
status.append("Valve_Open")
|
||||
|
||||
if status:
|
||||
self.update_variable("SystemStatus", "_".join(status))
|
||||
else:
|
||||
self.update_variable("SystemStatus", "Idle")
|
||||
|
||||
# 每200毫秒更新一次
|
||||
time.sleep(0.2)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"模拟线程错误: {e}")
|
||||
time.sleep(1) # 出错时稍等一会再继续
|
||||
|
||||
logger.info("模拟线程已停止")
|
||||
|
||||
def reset_alarm_callback(parent, *args):
|
||||
"""重置报警的回调函数"""
|
||||
logger.info("调用了重置报警方法")
|
||||
return True
|
||||
|
||||
def start_process_callback(parent, *args):
|
||||
"""启动流程的回调函数"""
|
||||
process_id = args[0] if args else 0
|
||||
logger.info(f"启动流程 ID: {process_id}")
|
||||
return process_id
|
||||
|
||||
def stop_process_callback(parent, *args):
|
||||
"""停止流程的回调函数"""
|
||||
process_id = args[0] if args else 0
|
||||
logger.info(f"停止流程 ID: {process_id}")
|
||||
return True
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
try:
|
||||
# 创建服务器
|
||||
server = OpcUaTestServer()
|
||||
|
||||
# 添加变量节点 - 温度控制相关
|
||||
server.add_variable("Temperature", 20.0, ua.VariantType.Float)
|
||||
server.add_variable("Setpoint", 22.0, ua.VariantType.Float)
|
||||
server.add_variable("HeatingEnabled", False, ua.VariantType.Boolean)
|
||||
server.add_variable("HeatingStatus", False, ua.VariantType.Boolean)
|
||||
|
||||
# 添加变量节点 - 阀门控制相关
|
||||
server.add_variable("ValvePosition", 0.0, ua.VariantType.Float)
|
||||
server.add_variable("ValveSetpoint", 0.0, ua.VariantType.Float)
|
||||
server.add_variable("ValveControl", False, ua.VariantType.Boolean)
|
||||
server.add_variable("FlowRate", 0.0, ua.VariantType.Float)
|
||||
|
||||
# 其他状态变量
|
||||
server.add_variable("SystemStatus", "Idle", ua.VariantType.String)
|
||||
|
||||
# 添加方法节点
|
||||
server.add_method(
|
||||
"ResetAlarm",
|
||||
reset_alarm_callback,
|
||||
[],
|
||||
[("Result", ua.VariantType.Boolean)]
|
||||
)
|
||||
|
||||
server.add_method(
|
||||
"StartProcess",
|
||||
start_process_callback,
|
||||
[("ProcessId", ua.VariantType.Int32)],
|
||||
[("Result", ua.VariantType.Int32)]
|
||||
)
|
||||
|
||||
server.add_method(
|
||||
"StopProcess",
|
||||
stop_process_callback,
|
||||
[("ProcessId", ua.VariantType.Int32)],
|
||||
[("Result", ua.VariantType.Boolean)]
|
||||
)
|
||||
|
||||
# 启动服务器
|
||||
server.start()
|
||||
logger.info("服务器已启动,按Ctrl+C停止")
|
||||
|
||||
# 保持服务器运行
|
||||
try:
|
||||
while True:
|
||||
time.sleep(1)
|
||||
except KeyboardInterrupt:
|
||||
logger.info("收到键盘中断,正在停止服务器...")
|
||||
|
||||
# 停止服务器
|
||||
server.stop()
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"服务器错误: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,25 +0,0 @@
|
||||
dummy2_robot:
|
||||
kinematics:
|
||||
# DH parameters for Dummy2 6-DOF robot arm
|
||||
# [theta, d, a, alpha] for each joint
|
||||
joint_1: [0.0, 0.1, 0.0, 1.5708] # Base rotation
|
||||
joint_2: [0.0, 0.0, 0.2, 0.0] # Shoulder
|
||||
joint_3: [0.0, 0.0, 0.15, 0.0] # Elbow
|
||||
joint_4: [0.0, 0.1, 0.0, 1.5708] # Wrist roll
|
||||
joint_5: [0.0, 0.0, 0.0, -1.5708] # Wrist pitch
|
||||
joint_6: [0.0, 0.06, 0.0, 0.0] # Wrist yaw
|
||||
|
||||
# Tool center point offset from last joint
|
||||
tcp_offset:
|
||||
x: 0.0
|
||||
y: 0.0
|
||||
z: 0.04
|
||||
|
||||
# Workspace limits
|
||||
workspace:
|
||||
x_min: -0.5
|
||||
x_max: 0.5
|
||||
y_min: -0.5
|
||||
y_max: 0.5
|
||||
z_min: 0.0
|
||||
z_max: 0.6
|
||||
@@ -1,45 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--This does not replace URDF, and is not an extension of URDF.
|
||||
This is a format for representing semantic information about the robot structure.
|
||||
A URDF file must exist for this robot as well, where the joints and the links that are referenced are defined
|
||||
-->
|
||||
<robot name="dummy2">
|
||||
<!--GROUPS: Representation of a set of joints and links. This can be useful for specifying DOF to plan for, defining arms, end effectors, etc-->
|
||||
<!--LINKS: When a link is specified, the parent joint of that link (if it exists) is automatically included-->
|
||||
<!--JOINTS: When a joint is specified, the child link of that joint (which will always exist) is automatically included-->
|
||||
<!--CHAINS: When a chain is specified, all the links along the chain (including endpoints) are included in the group. Additionally, all the joints that are parents to included links are also included. This means that joints along the chain and the parent joint of the base link are included in the group-->
|
||||
<!--SUBGROUPS: Groups can also be formed by referencing to already defined group names-->
|
||||
<group name="dummy2_arm">
|
||||
<joint name="virtual_joint"/>
|
||||
<joint name="Joint1"/>
|
||||
<joint name="Joint2"/>
|
||||
<joint name="Joint3"/>
|
||||
<joint name="Joint4"/>
|
||||
<joint name="Joint5"/>
|
||||
<joint name="Joint6"/>
|
||||
</group>
|
||||
<!--GROUP STATES: Purpose: Define a named state for a particular group, in terms of joint values. This is useful to define states like 'folded arms'-->
|
||||
<group_state name="home" group="dummy2_arm">
|
||||
<joint name="Joint1" value="0"/>
|
||||
<joint name="Joint2" value="0"/>
|
||||
<joint name="Joint3" value="0"/>
|
||||
<joint name="Joint4" value="0"/>
|
||||
<joint name="Joint5" value="0"/>
|
||||
<joint name="Joint6" value="0"/>
|
||||
</group_state>
|
||||
<!--VIRTUAL JOINT: Purpose: this element defines a virtual joint between a robot link and an external frame of reference (considered fixed with respect to the robot)-->
|
||||
<virtual_joint name="virtual_joint" type="fixed" parent_frame="world" child_link="base_link"/>
|
||||
<!--DISABLE COLLISIONS: By default it is assumed that any link of the robot could potentially come into collision with any other link in the robot. This tag disables collision checking between a specified pair of links. -->
|
||||
<disable_collisions link1="J1_1" link2="J2_1" reason="Adjacent"/>
|
||||
<disable_collisions link1="J1_1" link2="J3_1" reason="Never"/>
|
||||
<disable_collisions link1="J1_1" link2="J4_1" reason="Never"/>
|
||||
<disable_collisions link1="J1_1" link2="base_link" reason="Adjacent"/>
|
||||
<disable_collisions link1="J2_1" link2="J3_1" reason="Adjacent"/>
|
||||
<disable_collisions link1="J3_1" link2="J4_1" reason="Adjacent"/>
|
||||
<disable_collisions link1="J3_1" link2="J5_1" reason="Never"/>
|
||||
<disable_collisions link1="J3_1" link2="J6_1" reason="Never"/>
|
||||
<disable_collisions link1="J3_1" link2="base_link" reason="Never"/>
|
||||
<disable_collisions link1="J4_1" link2="J5_1" reason="Adjacent"/>
|
||||
<disable_collisions link1="J4_1" link2="J6_1" reason="Never"/>
|
||||
<disable_collisions link1="J5_1" link2="J6_1" reason="Adjacent"/>
|
||||
</robot>
|
||||
@@ -1,70 +0,0 @@
|
||||
<?xml version="1.0" ?>
|
||||
<robot name="dummy2" xmlns:xacro="http://www.ros.org/wiki/xacro" >
|
||||
|
||||
<transmission name="Joint1_tran">
|
||||
<type>transmission_interface/SimpleTransmission</type>
|
||||
<joint name="Joint1">
|
||||
<hardwareInterface>hardware_interface/EffortJointInterface</hardwareInterface>
|
||||
</joint>
|
||||
<actuator name="Joint1_actr">
|
||||
<hardwareInterface>hardware_interface/EffortJointInterface</hardwareInterface>
|
||||
<mechanicalReduction>1</mechanicalReduction>
|
||||
</actuator>
|
||||
</transmission>
|
||||
|
||||
<transmission name="Joint2_tran">
|
||||
<type>transmission_interface/SimpleTransmission</type>
|
||||
<joint name="Joint2">
|
||||
<hardwareInterface>hardware_interface/EffortJointInterface</hardwareInterface>
|
||||
</joint>
|
||||
<actuator name="Joint2_actr">
|
||||
<hardwareInterface>hardware_interface/EffortJointInterface</hardwareInterface>
|
||||
<mechanicalReduction>1</mechanicalReduction>
|
||||
</actuator>
|
||||
</transmission>
|
||||
|
||||
<transmission name="Joint3_tran">
|
||||
<type>transmission_interface/SimpleTransmission</type>
|
||||
<joint name="Joint3">
|
||||
<hardwareInterface>hardware_interface/EffortJointInterface</hardwareInterface>
|
||||
</joint>
|
||||
<actuator name="Joint3_actr">
|
||||
<hardwareInterface>hardware_interface/EffortJointInterface</hardwareInterface>
|
||||
<mechanicalReduction>1</mechanicalReduction>
|
||||
</actuator>
|
||||
</transmission>
|
||||
|
||||
<transmission name="Joint4_tran">
|
||||
<type>transmission_interface/SimpleTransmission</type>
|
||||
<joint name="Joint4">
|
||||
<hardwareInterface>hardware_interface/EffortJointInterface</hardwareInterface>
|
||||
</joint>
|
||||
<actuator name="Joint4_actr">
|
||||
<hardwareInterface>hardware_interface/EffortJointInterface</hardwareInterface>
|
||||
<mechanicalReduction>1</mechanicalReduction>
|
||||
</actuator>
|
||||
</transmission>
|
||||
|
||||
<transmission name="Joint5_tran">
|
||||
<type>transmission_interface/SimpleTransmission</type>
|
||||
<joint name="Joint5">
|
||||
<hardwareInterface>hardware_interface/EffortJointInterface</hardwareInterface>
|
||||
</joint>
|
||||
<actuator name="Joint5_actr">
|
||||
<hardwareInterface>hardware_interface/EffortJointInterface</hardwareInterface>
|
||||
<mechanicalReduction>1</mechanicalReduction>
|
||||
</actuator>
|
||||
</transmission>
|
||||
|
||||
<transmission name="Joint6_tran">
|
||||
<type>transmission_interface/SimpleTransmission</type>
|
||||
<joint name="Joint6">
|
||||
<hardwareInterface>hardware_interface/EffortJointInterface</hardwareInterface>
|
||||
</joint>
|
||||
<actuator name="Joint6_actr">
|
||||
<hardwareInterface>hardware_interface/EffortJointInterface</hardwareInterface>
|
||||
<mechanicalReduction>1</mechanicalReduction>
|
||||
</actuator>
|
||||
</transmission>
|
||||
|
||||
</robot>
|
||||
@@ -1,14 +0,0 @@
|
||||
<?xml version="1.0"?>
|
||||
<robot xmlns:xacro="http://www.ros.org/wiki/xacro" name="dummy2">
|
||||
<xacro:arg name="initial_positions_file" default="initial_positions.yaml" />
|
||||
|
||||
<!-- Import dummy2 urdf file -->
|
||||
<xacro:include filename="$(find dummy2_description)/urdf/dummy2.xacro" />
|
||||
|
||||
<!-- Import control_xacro -->
|
||||
<xacro:include filename="dummy2.ros2_control.xacro" />
|
||||
|
||||
|
||||
<xacro:dummy2_ros2_control name="FakeSystem" initial_positions_file="$(arg initial_positions_file)"/>
|
||||
|
||||
</robot>
|
||||
@@ -1,73 +0,0 @@
|
||||
###############################################
|
||||
# Modify all parameters related to servoing here
|
||||
###############################################
|
||||
# adapt to dummy2 by Muzhxiaowen, check out the details on bilibili.com
|
||||
|
||||
use_gazebo: false # Whether the robot is started in a Gazebo simulation environment
|
||||
|
||||
## Properties of incoming commands
|
||||
command_in_type: "unitless" # "unitless"> in the range [-1:1], as if from joystick. "speed_units"> cmds are in m/s and rad/s
|
||||
scale:
|
||||
# Scale parameters are only used if command_in_type=="unitless"
|
||||
linear: 0.4 # Max linear velocity. Unit is [m/s]. Only used for Cartesian commands.
|
||||
rotational: 0.8 # Max angular velocity. Unit is [rad/s]. Only used for Cartesian commands.
|
||||
# Max joint angular/linear velocity. Only used for joint commands on joint_command_in_topic.
|
||||
joint: 0.5
|
||||
|
||||
# Optionally override Servo's internal velocity scaling when near singularity or collision (0.0 = use internal velocity scaling)
|
||||
# override_velocity_scaling_factor = 0.0 # valid range [0.0:1.0]
|
||||
|
||||
## Properties of outgoing commands
|
||||
publish_period: 0.034 # 1/Nominal publish rate [seconds]
|
||||
low_latency_mode: false # Set this to true to publish as soon as an incoming Twist command is received (publish_period is ignored)
|
||||
|
||||
# What type of topic does your robot driver expect?
|
||||
# Currently supported are std_msgs/Float64MultiArray or trajectory_msgs/JointTrajectory
|
||||
command_out_type: trajectory_msgs/JointTrajectory
|
||||
|
||||
# What to publish? Can save some bandwidth as most robots only require positions or velocities
|
||||
publish_joint_positions: true
|
||||
publish_joint_velocities: true
|
||||
publish_joint_accelerations: false
|
||||
|
||||
## Plugins for smoothing outgoing commands
|
||||
smoothing_filter_plugin_name: "online_signal_smoothing::ButterworthFilterPlugin"
|
||||
|
||||
# If is_primary_planning_scene_monitor is set to true, the Servo server's PlanningScene advertises the /get_planning_scene service,
|
||||
# which other nodes can use as a source for information about the planning environment.
|
||||
# NOTE: If a different node in your system is responsible for the "primary" planning scene instance (e.g. the MoveGroup node),
|
||||
# then is_primary_planning_scene_monitor needs to be set to false.
|
||||
is_primary_planning_scene_monitor: true
|
||||
|
||||
## MoveIt properties
|
||||
move_group_name: dummy2_arm # Often 'manipulator' or 'arm'
|
||||
planning_frame: base_link # The MoveIt planning frame. Often 'base_link' or 'world'
|
||||
|
||||
## Other frames
|
||||
ee_frame_name: J6_1 # The name of the end effector link, used to return the EE pose
|
||||
robot_link_command_frame: base_link # commands must be given in the frame of a robot link. Usually either the base or end effector
|
||||
|
||||
## Stopping behaviour
|
||||
incoming_command_timeout: 0.1 # Stop servoing if X seconds elapse without a new command
|
||||
# If 0, republish commands forever even if the robot is stationary. Otherwise, specify num. to publish.
|
||||
# Important because ROS may drop some messages and we need the robot to halt reliably.
|
||||
num_outgoing_halt_msgs_to_publish: 4
|
||||
|
||||
## Configure handling of singularities and joint limits
|
||||
lower_singularity_threshold: 170.0 # Start decelerating when the condition number hits this (close to singularity)
|
||||
hard_stop_singularity_threshold: 3000.0 # Stop when the condition number hits this
|
||||
joint_limit_margin: 0.1 # added as a buffer to joint limits [radians]. If moving quickly, make this larger.
|
||||
leaving_singularity_threshold_multiplier: 2.0 # Multiply the hard stop limit by this when leaving singularity (see https://github.com/ros-planning/moveit2/pull/620)
|
||||
|
||||
## Topic names
|
||||
cartesian_command_in_topic: ~/delta_twist_cmds # Topic for incoming Cartesian twist commands
|
||||
joint_command_in_topic: ~/delta_joint_cmds # Topic for incoming joint angle commands
|
||||
joint_topic: /joint_states
|
||||
status_topic: ~/status # Publish status to this topic
|
||||
command_out_topic: /dummy2_arm_controller/joint_trajectory # Publish outgoing commands here
|
||||
|
||||
## Collision checking for the entire robot body
|
||||
check_collisions: true # Check collisions?
|
||||
collision_check_rate: 10.0 # [Hz] Collision-checking can easily bog down a CPU if done too often.
|
||||
self_collision_proximity_threshold: 0.001 # Start decelerating when a self-collision is this far [m]
|
||||
scene_collision_proximity_threshold: 0.002 # Start decelerating when a scene collision is this far [m]
|
||||
@@ -1,9 +0,0 @@
|
||||
# Default initial positions for dummy2's ros2_control fake system
|
||||
|
||||
initial_positions:
|
||||
Joint1: 0
|
||||
Joint2: 0
|
||||
Joint3: 0
|
||||
Joint4: 0
|
||||
Joint5: 0
|
||||
Joint6: 0
|
||||
@@ -1,40 +0,0 @@
|
||||
# joint_limits.yaml allows the dynamics properties specified in the URDF to be overwritten or augmented as needed
|
||||
|
||||
# For beginners, we downscale velocity and acceleration limits.
|
||||
# You can always specify higher scaling factors (<= 1.0) in your motion requests. # Increase the values below to 1.0 to always move at maximum speed.
|
||||
default_velocity_scaling_factor: 0.1
|
||||
default_acceleration_scaling_factor: 0.1
|
||||
|
||||
# Specific joint properties can be changed with the keys [max_position, min_position, max_velocity, max_acceleration]
|
||||
# Joint limits can be turned off with [has_velocity_limits, has_acceleration_limits]
|
||||
joint_limits:
|
||||
joint_1:
|
||||
has_velocity_limits: true
|
||||
max_velocity: 2.0
|
||||
has_acceleration_limits: false
|
||||
max_acceleration: 0
|
||||
joint_2:
|
||||
has_velocity_limits: true
|
||||
max_velocity: 2.0
|
||||
has_acceleration_limits: false
|
||||
max_acceleration: 0
|
||||
joint_3:
|
||||
has_velocity_limits: true
|
||||
max_velocity: 2.0
|
||||
has_acceleration_limits: false
|
||||
max_acceleration: 0
|
||||
joint_4:
|
||||
has_velocity_limits: true
|
||||
max_velocity: 2.0
|
||||
has_acceleration_limits: false
|
||||
max_acceleration: 0
|
||||
joint_5:
|
||||
has_velocity_limits: true
|
||||
max_velocity: 2.0
|
||||
has_acceleration_limits: false
|
||||
max_acceleration: 0
|
||||
joint_6:
|
||||
has_velocity_limits: true
|
||||
max_velocity: 2.0
|
||||
has_acceleration_limits: false
|
||||
max_acceleration: 0
|
||||
@@ -1,4 +0,0 @@
|
||||
dummy2_arm:
|
||||
kinematics_solver: kdl_kinematics_plugin/KDLKinematicsPlugin
|
||||
kinematics_solver_search_resolution: 0.0050000000000000001
|
||||
kinematics_solver_timeout: 0.5
|
||||
@@ -1,60 +0,0 @@
|
||||
<?xml version="1.0"?>
|
||||
<robot xmlns:xacro="http://www.ros.org/wiki/xacro">
|
||||
<xacro:macro name="dummy2_robot_ros2_control" params="device_name mesh_path">
|
||||
<xacro:property name="initial_positions" value="${load_yaml(mesh_path + '/devices/dummy2_robot/config/initial_positions.yaml')['initial_positions']}"/>
|
||||
|
||||
<ros2_control name="${device_name}dummy2" type="system">
|
||||
<hardware>
|
||||
<!-- By default, set up controllers for simulation. This won't work on real hardware -->
|
||||
<plugin>mock_components/GenericSystem</plugin>
|
||||
</hardware>
|
||||
|
||||
<!-- <plugin>mock_components/GenericSystem</plugin> -->
|
||||
<joint name="${device_name}Joint1">
|
||||
<command_interface name="position"/>
|
||||
<state_interface name="position">
|
||||
<param name="initial_value">${initial_positions['Joint1']}</param>
|
||||
</state_interface>
|
||||
<state_interface name="velocity"/>
|
||||
</joint>
|
||||
<joint name="${device_name}Joint2">
|
||||
<command_interface name="position"/>
|
||||
<state_interface name="position">
|
||||
<param name="initial_value">${initial_positions['Joint2']}</param>
|
||||
</state_interface>
|
||||
<state_interface name="velocity"/>
|
||||
</joint>
|
||||
<joint name="${device_name}Joint3">
|
||||
<command_interface name="position"/>
|
||||
<state_interface name="position">
|
||||
<param name="initial_value">${initial_positions['Joint3']}</param>
|
||||
</state_interface>
|
||||
<state_interface name="velocity"/>
|
||||
</joint>
|
||||
<joint name="${device_name}Joint4">
|
||||
<command_interface name="position"/>
|
||||
<state_interface name="position">
|
||||
<param name="initial_value">${initial_positions['Joint4']}</param>
|
||||
</state_interface>
|
||||
<state_interface name="velocity"/>
|
||||
</joint>
|
||||
<joint name="${device_name}Joint5">
|
||||
<command_interface name="position"/>
|
||||
<state_interface name="position">
|
||||
<param name="initial_value">${initial_positions['Joint5']}</param>
|
||||
</state_interface>
|
||||
<state_interface name="velocity"/>
|
||||
</joint>
|
||||
<joint name="${device_name}Joint6">
|
||||
<command_interface name="position"/>
|
||||
<state_interface name="position">
|
||||
<param name="initial_value">${initial_positions['Joint6']}</param>
|
||||
</state_interface>
|
||||
<state_interface name="velocity"/>
|
||||
</joint>
|
||||
|
||||
|
||||
</ros2_control>
|
||||
</xacro:macro>
|
||||
|
||||
</robot>
|
||||
@@ -1,49 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<robot xmlns:xacro="http://www.ros.org/wiki/xacro">
|
||||
<xacro:macro name="dummy2_robot_srdf" params="device_name">
|
||||
|
||||
<!--GROUPS: Representation of a set of joints and links. This can be useful for specifying DOF to plan for, defining arms, end effectors, etc-->
|
||||
<!--LINKS: When a link is specified, the parent joint of that link (if it exists) is automatically included-->
|
||||
<!--JOINTS: When a joint is specified, the child link of that joint (which will always exist) is automatically included-->
|
||||
<!--CHAINS: When a chain is specified, all the links along the chain (including endpoints) are included in the group. Additionally, all the joints that are parents to included links are also included. This means that joints along the chain and the parent joint of the base link are included in the group-->
|
||||
<!--SUBGROUPS: Groups can also be formed by referencing to already defined group names
|
||||
This is a format for representing semantic information about the robot structure.
|
||||
A URDF file must exist for this robot as well, where the joints and the links that are referenced are defined
|
||||
-->
|
||||
<group name="${device_name}dummy2_arm">
|
||||
<joint name="${device_name}virtual_joint"/>
|
||||
<joint name="${device_name}Joint1"/>
|
||||
<joint name="${device_name}Joint2"/>
|
||||
<joint name="${device_name}Joint3"/>
|
||||
<joint name="${device_name}Joint4"/>
|
||||
<joint name="${device_name}Joint5"/>
|
||||
<joint name="${device_name}Joint6"/>
|
||||
</group>
|
||||
<!--GROUP STATES: Purpose: Define a named state for a particular group, in terms of joint values. This is useful to define states like 'folded arms'-->
|
||||
<group_state name="home" group="${device_name}dummy2_arm">
|
||||
<joint name="${device_name}Joint1" value="0"/>
|
||||
<joint name="${device_name}Joint2" value="0"/>
|
||||
<joint name="${device_name}Joint3" value="0"/>
|
||||
<joint name="${device_name}Joint4" value="0"/>
|
||||
<joint name="${device_name}Joint5" value="0"/>
|
||||
<joint name="${device_name}Joint6" value="0"/>
|
||||
</group_state>
|
||||
<!--VIRTUAL JOINT: Purpose: this element defines a virtual joint between a robot link and an external frame of reference (considered fixed with respect to the robot)-->
|
||||
<virtual_joint name="${device_name}virtual_joint" type="fixed" parent_frame="world" child_link="${device_name}base_link"/>
|
||||
<!--DISABLE COLLISIONS: By default it is assumed that any link of the robot could potentially come into collision with any other link in the robot. This tag disables collision checking between a specified pair of links. -->
|
||||
<disable_collisions link1="${device_name}J1_1" link2="${device_name}J2_1" reason="Adjacent"/>
|
||||
<disable_collisions link1="${device_name}J1_1" link2="${device_name}J3_1" reason="Never"/>
|
||||
<disable_collisions link1="${device_name}J1_1" link2="${device_name}J4_1" reason="Never"/>
|
||||
<disable_collisions link1="${device_name}J1_1" link2="${device_name}base_link" reason="Adjacent"/>
|
||||
<disable_collisions link1="${device_name}J2_1" link2="${device_name}J3_1" reason="Adjacent"/>
|
||||
<disable_collisions link1="${device_name}J3_1" link2="${device_name}J4_1" reason="Adjacent"/>
|
||||
<disable_collisions link1="${device_name}J3_1" link2="${device_name}J5_1" reason="Never"/>
|
||||
<disable_collisions link1="${device_name}J3_1" link2="${device_name}J6_1" reason="Never"/>
|
||||
<disable_collisions link1="${device_name}J3_1" link2="${device_name}base_link" reason="Never"/>
|
||||
<disable_collisions link1="${device_name}J4_1" link2="${device_name}J5_1" reason="Adjacent"/>
|
||||
<disable_collisions link1="${device_name}J4_1" link2="${device_name}J6_1" reason="Never"/>
|
||||
<disable_collisions link1="${device_name}J5_1" link2="${device_name}J6_1" reason="Adjacent"/>
|
||||
|
||||
</xacro:macro>
|
||||
|
||||
</robot>
|
||||
@@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" ?>
|
||||
<robot name="dummy2" xmlns:xacro="http://www.ros.org/wiki/xacro" >
|
||||
|
||||
<material name="silver">
|
||||
<color rgba="0.700 0.700 0.700 1.000"/>
|
||||
</material>
|
||||
|
||||
</robot>
|
||||
@@ -1,14 +0,0 @@
|
||||
{
|
||||
"arm": {
|
||||
"joint_names": [
|
||||
"joint_1",
|
||||
"joint_2",
|
||||
"joint_3",
|
||||
"joint_4",
|
||||
"joint_5",
|
||||
"joint_6"
|
||||
],
|
||||
"base_link_name": "base_link",
|
||||
"end_effector_name": "J6_1"
|
||||
}
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
Panels:
|
||||
- Class: rviz_common/Displays
|
||||
Name: Displays
|
||||
Property Tree Widget:
|
||||
Expanded:
|
||||
- /MotionPlanning1
|
||||
- Class: rviz_common/Help
|
||||
Name: Help
|
||||
- Class: rviz_common/Views
|
||||
Name: Views
|
||||
Visualization Manager:
|
||||
Displays:
|
||||
- Class: rviz_default_plugins/Grid
|
||||
Name: Grid
|
||||
Value: true
|
||||
- Class: moveit_rviz_plugin/MotionPlanning
|
||||
Name: MotionPlanning
|
||||
Planned Path:
|
||||
Loop Animation: true
|
||||
State Display Time: 0.05 s
|
||||
Trajectory Topic: display_planned_path
|
||||
Planning Scene Topic: monitored_planning_scene
|
||||
Robot Description: robot_description
|
||||
Scene Geometry:
|
||||
Scene Alpha: 1
|
||||
Scene Robot:
|
||||
Robot Alpha: 0.5
|
||||
Value: true
|
||||
Global Options:
|
||||
Fixed Frame: base_link
|
||||
Tools:
|
||||
- Class: rviz_default_plugins/Interact
|
||||
- Class: rviz_default_plugins/MoveCamera
|
||||
- Class: rviz_default_plugins/Select
|
||||
Value: true
|
||||
Views:
|
||||
Current:
|
||||
Class: rviz_default_plugins/Orbit
|
||||
Distance: 2.0
|
||||
Focal Point:
|
||||
X: -0.1
|
||||
Y: 0.25
|
||||
Z: 0.30
|
||||
Name: Current View
|
||||
Pitch: 0.5
|
||||
Target Frame: base_link
|
||||
Yaw: -0.623
|
||||
Window Geometry:
|
||||
Height: 975
|
||||
QMainWindow State: 000000ff00000000fd0000000100000000000002b400000375fc0200000005fb00000044004d006f00740069006f006e0050006c0061006e006e0069006e00670020002d0020005400720061006a006500630074006f0072007900200053006c00690064006500720000000000ffffffff0000004100fffffffb000000100044006900730070006c006100790073010000003d00000123000000c900fffffffb0000001c004d006f00740069006f006e0050006c0061006e006e0069006e00670100000166000001910000018800fffffffb0000000800480065006c0070000000029a0000006e0000006e00fffffffb0000000a0056006900650077007301000002fd000000b5000000a400ffffff000001f60000037500000004000000040000000800000008fc0000000100000002000000010000000a0054006f006f006c00730100000000ffffffff0000000000000000
|
||||
Width: 1200
|
||||
@@ -1,21 +0,0 @@
|
||||
# MoveIt uses this configuration for controller management
|
||||
|
||||
moveit_controller_manager: moveit_simple_controller_manager/MoveItSimpleControllerManager
|
||||
|
||||
moveit_simple_controller_manager:
|
||||
controller_names:
|
||||
- dummy2_arm_controller
|
||||
|
||||
dummy2_arm_controller:
|
||||
type: FollowJointTrajectory
|
||||
action_ns: follow_joint_trajectory
|
||||
default: true
|
||||
joints:
|
||||
- Joint1
|
||||
- Joint2
|
||||
- Joint3
|
||||
- Joint4
|
||||
- Joint5
|
||||
- Joint6
|
||||
action_ns: follow_joint_trajectory
|
||||
default: true
|
||||
@@ -1,39 +0,0 @@
|
||||
dummy2_robot:
|
||||
# Physical properties for each link
|
||||
link_masses:
|
||||
base_link: 5.0
|
||||
link_1: 3.0
|
||||
link_2: 2.5
|
||||
link_3: 2.0
|
||||
link_4: 1.5
|
||||
link_5: 1.0
|
||||
link_6: 0.5
|
||||
|
||||
# Center of mass for each link (relative to joint frame)
|
||||
link_com:
|
||||
base_link: [0.0, 0.0, 0.05]
|
||||
link_1: [0.0, 0.0, 0.05]
|
||||
link_2: [0.1, 0.0, 0.0]
|
||||
link_3: [0.08, 0.0, 0.0]
|
||||
link_4: [0.0, 0.0, 0.05]
|
||||
link_5: [0.0, 0.0, 0.03]
|
||||
link_6: [0.0, 0.0, 0.02]
|
||||
|
||||
# Moment of inertia matrices
|
||||
link_inertias:
|
||||
base_link: [0.02, 0.0, 0.0, 0.02, 0.0, 0.02]
|
||||
link_1: [0.01, 0.0, 0.0, 0.01, 0.0, 0.01]
|
||||
link_2: [0.008, 0.0, 0.0, 0.008, 0.0, 0.008]
|
||||
link_3: [0.006, 0.0, 0.0, 0.006, 0.0, 0.006]
|
||||
link_4: [0.004, 0.0, 0.0, 0.004, 0.0, 0.004]
|
||||
link_5: [0.002, 0.0, 0.0, 0.002, 0.0, 0.002]
|
||||
link_6: [0.001, 0.0, 0.0, 0.001, 0.0, 0.001]
|
||||
|
||||
# Motor specifications
|
||||
motor_specs:
|
||||
joint_1: { max_torque: 150.0, max_speed: 2.0, gear_ratio: 100 }
|
||||
joint_2: { max_torque: 150.0, max_speed: 2.0, gear_ratio: 100 }
|
||||
joint_3: { max_torque: 150.0, max_speed: 2.0, gear_ratio: 100 }
|
||||
joint_4: { max_torque: 50.0, max_speed: 2.0, gear_ratio: 50 }
|
||||
joint_5: { max_torque: 50.0, max_speed: 2.0, gear_ratio: 50 }
|
||||
joint_6: { max_torque: 25.0, max_speed: 2.0, gear_ratio: 25 }
|
||||
@@ -1,6 +0,0 @@
|
||||
# Limits for the Pilz planner
|
||||
cartesian_limits:
|
||||
max_trans_vel: 1.0
|
||||
max_trans_acc: 2.25
|
||||
max_trans_dec: -5.0
|
||||
max_rot_vel: 1.57
|
||||
@@ -1,26 +0,0 @@
|
||||
# This config file is used by ros2_control
|
||||
controller_manager:
|
||||
ros__parameters:
|
||||
update_rate: 100 # Hz
|
||||
|
||||
dummy2_arm_controller:
|
||||
type: joint_trajectory_controller/JointTrajectoryController
|
||||
|
||||
|
||||
joint_state_broadcaster:
|
||||
type: joint_state_broadcaster/JointStateBroadcaster
|
||||
|
||||
dummy2_arm_controller:
|
||||
ros__parameters:
|
||||
joints:
|
||||
- Joint1
|
||||
- Joint2
|
||||
- Joint3
|
||||
- Joint4
|
||||
- Joint5
|
||||
- Joint6
|
||||
command_interfaces:
|
||||
- position
|
||||
state_interfaces:
|
||||
- position
|
||||
- velocity
|
||||
@@ -1,35 +0,0 @@
|
||||
dummy2_robot:
|
||||
# Visual appearance settings
|
||||
materials:
|
||||
base_material:
|
||||
color: [0.8, 0.8, 0.8, 1.0] # Light gray
|
||||
metallic: 0.1
|
||||
roughness: 0.3
|
||||
|
||||
link_material:
|
||||
color: [0.2, 0.2, 0.8, 1.0] # Blue
|
||||
metallic: 0.3
|
||||
roughness: 0.2
|
||||
|
||||
joint_material:
|
||||
color: [0.6, 0.6, 0.6, 1.0] # Dark gray
|
||||
metallic: 0.5
|
||||
roughness: 0.1
|
||||
|
||||
camera_material:
|
||||
color: [0.1, 0.1, 0.1, 1.0] # Black
|
||||
metallic: 0.0
|
||||
roughness: 0.8
|
||||
|
||||
# Mesh scaling factors
|
||||
mesh_scale: [0.001, 0.001, 0.001] # Convert mm to m
|
||||
|
||||
# Collision geometry simplification
|
||||
collision_geometries:
|
||||
base_link: "cylinder" # radius: 0.08, height: 0.1
|
||||
link_1: "cylinder" # radius: 0.05, height: 0.15
|
||||
link_2: "box" # size: [0.2, 0.08, 0.08]
|
||||
link_3: "box" # size: [0.15, 0.06, 0.06]
|
||||
link_4: "cylinder" # radius: 0.03, height: 0.1
|
||||
link_5: "cylinder" # radius: 0.025, height: 0.06
|
||||
link_6: "cylinder" # radius: 0.02, height: 0.04
|
||||
@@ -1,237 +0,0 @@
|
||||
<?xml version="1.0" ?>
|
||||
<robot name="dummy2" xmlns:xacro="http://www.ros.org/wiki/xacro">
|
||||
|
||||
<xacro:include filename="$(find dummy2_description)/urdf/materials.xacro" />
|
||||
<xacro:include filename="$(find dummy2_description)/urdf/dummy2.trans" />
|
||||
<xacro:include filename="$(find dummy2_description)/urdf/dummy2.gazebo" />
|
||||
<link name="world" />
|
||||
<joint name="world_joint" type="fixed">
|
||||
<parent link="world" />
|
||||
<child link = "base_link" />
|
||||
<origin xyz="0.0 0.0 0.0" rpy="0.0 0.0 0.0" />
|
||||
</joint>
|
||||
|
||||
<link name="base_link">
|
||||
<inertial>
|
||||
<origin xyz="0.00010022425916431473 -6.186605493937309e-05 0.05493640543484716" rpy="0 0 0"/>
|
||||
<mass value="1.2152141810431654"/>
|
||||
<inertia ixx="0.002105" iyy="0.002245" izz="0.002436" ixy="-0.0" iyz="-1.1e-05" ixz="0.0"/>
|
||||
</inertial>
|
||||
<visual>
|
||||
<origin xyz="0 0 0" rpy="0 0 0"/>
|
||||
<geometry>
|
||||
<mesh filename="file://$(find dummy2_description)/meshes/base_link.stl" scale="0.001 0.001 0.001"/>
|
||||
</geometry>
|
||||
<material name="silver"/>
|
||||
</visual>
|
||||
<collision>
|
||||
<origin xyz="0 0 0" rpy="0 0 0"/>
|
||||
<geometry>
|
||||
<mesh filename="file://$(find dummy2_description)/meshes/base_link.stl" scale="0.001 0.001 0.001"/>
|
||||
</geometry>
|
||||
</collision>
|
||||
</link>
|
||||
|
||||
<link name="J1_1">
|
||||
<inertial>
|
||||
<origin xyz="-0.00617659688932347 0.007029599744830012 0.012866826083045027" rpy="0 0 0"/>
|
||||
<mass value="0.1332774369186824"/>
|
||||
<inertia ixx="6e-05" iyy="5e-05" izz="8.8e-05" ixy="2.1e-05" iyz="-1.4e-05" ixz="8e-06"/>
|
||||
</inertial>
|
||||
<visual>
|
||||
<origin xyz="-0.0001 0.000289 -0.097579" rpy="0 0 0"/>
|
||||
<geometry>
|
||||
<mesh filename="file://$(find dummy2_description)/meshes/J1_1.stl" scale="0.001 0.001 0.001"/>
|
||||
</geometry>
|
||||
<material name="silver"/>
|
||||
</visual>
|
||||
<collision>
|
||||
<origin xyz="-0.0001 0.000289 -0.097579" rpy="0 0 0"/>
|
||||
<geometry>
|
||||
<mesh filename="file://$(find dummy2_description)/meshes/J1_1.stl" scale="0.001 0.001 0.001"/>
|
||||
</geometry>
|
||||
</collision>
|
||||
</link>
|
||||
|
||||
<link name="J2_1">
|
||||
<inertial>
|
||||
<origin xyz="0.019335709221765855 0.0019392793940843159 0.07795928103332703" rpy="0 0 0"/>
|
||||
<mass value="1.9268013917303417"/>
|
||||
<inertia ixx="0.006165" iyy="0.006538" izz="0.00118" ixy="-3e-06" iyz="4.7e-05" ixz="0.0007"/>
|
||||
</inertial>
|
||||
<visual>
|
||||
<origin xyz="0.011539 -0.034188 -0.12478" rpy="0 0 0"/>
|
||||
<geometry>
|
||||
<mesh filename="file://$(find dummy2_description)/meshes/J2_1.stl" scale="0.001 0.001 0.001"/>
|
||||
</geometry>
|
||||
<material name="silver"/>
|
||||
</visual>
|
||||
<collision>
|
||||
<origin xyz="0.011539 -0.034188 -0.12478" rpy="0 0 0"/>
|
||||
<geometry>
|
||||
<mesh filename="file://$(find dummy2_description)/meshes/J2_1.stl" scale="0.001 0.001 0.001"/>
|
||||
</geometry>
|
||||
</collision>
|
||||
</link>
|
||||
|
||||
<link name="J3_1">
|
||||
<inertial>
|
||||
<origin xyz="-0.010672101243726572 -0.02723871972304964 0.04876701375652198" rpy="0 0 0"/>
|
||||
<mass value="0.30531962155452225"/>
|
||||
<inertia ixx="0.00029" iyy="0.000238" izz="0.000191" ixy="-1.3e-05" iyz="4.1e-05" ixz="3e-05"/>
|
||||
</inertial>
|
||||
<visual>
|
||||
<origin xyz="-0.023811 -0.034188 -0.28278" rpy="0 0 0"/>
|
||||
<geometry>
|
||||
<mesh filename="file://$(find dummy2_description)/meshes/J3_1.stl" scale="0.001 0.001 0.001"/>
|
||||
</geometry>
|
||||
<material name="silver"/>
|
||||
</visual>
|
||||
<collision>
|
||||
<origin xyz="-0.023811 -0.034188 -0.28278" rpy="0 0 0"/>
|
||||
<geometry>
|
||||
<mesh filename="file://$(find dummy2_description)/meshes/J3_1.stl" scale="0.001 0.001 0.001"/>
|
||||
</geometry>
|
||||
</collision>
|
||||
</link>
|
||||
|
||||
<link name="J4_1">
|
||||
<inertial>
|
||||
<origin xyz="-0.005237398377441591 0.06002028183461833 0.0005891767740203724" rpy="0 0 0"/>
|
||||
<mass value="0.14051172121899885"/>
|
||||
<inertia ixx="0.000245" iyy="7.9e-05" izz="0.00027" ixy="1.6e-05" iyz="-2e-06" ixz="1e-06"/>
|
||||
</inertial>
|
||||
<visual>
|
||||
<origin xyz="-0.010649 -0.038288 -0.345246" rpy="0 0 0"/>
|
||||
<geometry>
|
||||
<mesh filename="file://$(find dummy2_description)/meshes/J4_1.stl" scale="0.001 0.001 0.001"/>
|
||||
</geometry>
|
||||
<material name="silver"/>
|
||||
</visual>
|
||||
<collision>
|
||||
<origin xyz="-0.010649 -0.038288 -0.345246" rpy="0 0 0"/>
|
||||
<geometry>
|
||||
<mesh filename="file://$(find dummy2_description)/meshes/J4_1.stl" scale="0.001 0.001 0.001"/>
|
||||
</geometry>
|
||||
</collision>
|
||||
</link>
|
||||
|
||||
<link name="J5_1">
|
||||
<inertial>
|
||||
<origin xyz="-0.014389813882964664 0.07305218143664277 -0.0009243405950149497" rpy="0 0 0"/>
|
||||
<mass value="0.7783315754227634"/>
|
||||
<inertia ixx="0.000879" iyy="0.000339" izz="0.000964" ixy="0.000146" iyz="1e-06" ixz="-5e-06"/>
|
||||
</inertial>
|
||||
<visual>
|
||||
<origin xyz="-0.031949 -0.148289 -0.345246" rpy="0 0 0"/>
|
||||
<geometry>
|
||||
<mesh filename="file://$(find dummy2_description)/meshes/J5_1.stl" scale="0.001 0.001 0.001"/>
|
||||
</geometry>
|
||||
<material name="silver"/>
|
||||
</visual>
|
||||
<collision>
|
||||
<origin xyz="-0.031949 -0.148289 -0.345246" rpy="0 0 0"/>
|
||||
<geometry>
|
||||
<mesh filename="file://$(find dummy2_description)/meshes/J5_1.stl" scale="0.001 0.001 0.001"/>
|
||||
</geometry>
|
||||
</collision>
|
||||
</link>
|
||||
|
||||
<link name="J6_1">
|
||||
<inertial>
|
||||
<origin xyz="3.967160300787087e-07 0.0004995066702210837 1.4402781733924286e-07" rpy="0 0 0"/>
|
||||
<mass value="0.0020561527568204153"/>
|
||||
<inertia ixx="0.0" iyy="0.0" izz="0.0" ixy="0.0" iyz="0.0" ixz="0.0"/>
|
||||
</inertial>
|
||||
<visual>
|
||||
<origin xyz="-0.012127 -0.267789 -0.344021" rpy="0 0 0"/>
|
||||
<geometry>
|
||||
<mesh filename="file://$(find dummy2_description)/meshes/J6_1.stl" scale="0.001 0.001 0.001"/>
|
||||
</geometry>
|
||||
<material name="silver"/>
|
||||
</visual>
|
||||
<collision>
|
||||
<origin xyz="-0.012127 -0.267789 -0.344021" rpy="0 0 0"/>
|
||||
<geometry>
|
||||
<mesh filename="file://$(find dummy2_description)/meshes/J6_1.stl" scale="0.001 0.001 0.001"/>
|
||||
</geometry>
|
||||
</collision>
|
||||
</link>
|
||||
|
||||
<link name="camera">
|
||||
<inertial>
|
||||
<origin xyz="-0.0006059984983273845 0.0005864706438700462 0.04601775357664567" rpy="0 0 0"/>
|
||||
<mass value="0.21961029019655884"/>
|
||||
<inertia ixx="2.9e-05" iyy="0.000206" izz="0.000198" ixy="-0.0" iyz="2e-06" ixz="-0.0"/>
|
||||
</inertial>
|
||||
<visual>
|
||||
<origin xyz="-0.012661 -0.239774 -0.37985" rpy="0 0 0"/>
|
||||
<geometry>
|
||||
<mesh filename="file://$(find dummy2_description)/meshes/camera_1.stl" scale="0.001 0.001 0.001"/>
|
||||
</geometry>
|
||||
<material name="silver"/>
|
||||
</visual>
|
||||
<collision>
|
||||
<origin xyz="-0.012661 -0.239774 -0.37985" rpy="0 0 0"/>
|
||||
<geometry>
|
||||
<mesh filename="file://$(find dummy2_description)/meshes/camera_1.stl" scale="0.001 0.001 0.001"/>
|
||||
</geometry>
|
||||
</collision>
|
||||
</link>
|
||||
|
||||
<joint name="Joint1" type="revolute">
|
||||
<origin xyz="0.0001 -0.000289 0.097579" rpy="0 0 0"/>
|
||||
<parent link="base_link"/>
|
||||
<child link="J1_1"/>
|
||||
<axis xyz="-0.0 -0.0 1.0"/>
|
||||
<limit upper="3.054326" lower="-3.054326" effort="100" velocity="100"/>
|
||||
</joint>
|
||||
|
||||
<joint name="Joint2" type="revolute">
|
||||
<origin xyz="-0.011639 0.034477 0.027201" rpy="0 0 0"/>
|
||||
<parent link="J1_1"/>
|
||||
<child link="J2_1"/>
|
||||
<axis xyz="1.0 0.0 -0.0"/>
|
||||
<limit upper="1.308997" lower="-2.007129" effort="100" velocity="100"/>
|
||||
</joint>
|
||||
|
||||
<joint name="Joint3" type="revolute">
|
||||
<origin xyz="0.03535 0.0 0.158" rpy="0 0 0"/>
|
||||
<parent link="J2_1"/>
|
||||
<child link="J3_1"/>
|
||||
<axis xyz="-1.0 0.0 -0.0"/>
|
||||
<limit upper="1.570796" lower="-1.047198" effort="100" velocity="100"/>
|
||||
</joint>
|
||||
|
||||
<joint name="Joint4" type="revolute">
|
||||
<origin xyz="-0.013162 0.0041 0.062466" rpy="0 0 0"/>
|
||||
<parent link="J3_1"/>
|
||||
<child link="J4_1"/>
|
||||
<axis xyz="0.0 1.0 -0.0"/>
|
||||
<limit upper="3.141593" lower="-3.141593" effort="100" velocity="100"/>
|
||||
</joint>
|
||||
|
||||
<joint name="Joint5" type="revolute">
|
||||
<origin xyz="0.0213 0.110001 0.0" rpy="0 0 0"/>
|
||||
<parent link="J4_1"/>
|
||||
<child link="J5_1"/>
|
||||
<axis xyz="-1.0 -0.0 -0.0"/>
|
||||
<limit upper="2.094395" lower="-1.919862" effort="100" velocity="100"/>
|
||||
</joint>
|
||||
|
||||
<joint name="Joint6" type="continuous">
|
||||
<origin xyz="-0.019822 0.1195 -0.001225" rpy="0 0 0"/>
|
||||
<parent link="J5_1"/>
|
||||
<child link="J6_1"/>
|
||||
<axis xyz="0.0 -1.0 0.0"/>
|
||||
</joint>
|
||||
|
||||
<joint name="camera" type="fixed">
|
||||
<origin xyz="-0.019988 0.091197 0.024883" rpy="0 0 0"/>
|
||||
<parent link="J5_1"/>
|
||||
<child link="camera"/>
|
||||
<axis xyz="1.0 -0.0 0.0"/>
|
||||
<limit upper="0.0" lower="0.0" effort="100" velocity="100"/>
|
||||
</joint>
|
||||
|
||||
</robot>
|
||||
@@ -1,37 +0,0 @@
|
||||
joint_limits:
|
||||
|
||||
joint_1:
|
||||
effort: 150
|
||||
velocity: 2.0
|
||||
lower: !degrees -180
|
||||
upper: !degrees 180
|
||||
|
||||
joint_2:
|
||||
effort: 150
|
||||
velocity: 2.0
|
||||
lower: !degrees -90
|
||||
upper: !degrees 90
|
||||
|
||||
joint_3:
|
||||
effort: 150
|
||||
velocity: 2.0
|
||||
lower: !degrees -90
|
||||
upper: !degrees 90
|
||||
|
||||
joint_4:
|
||||
effort: 50
|
||||
velocity: 2.0
|
||||
lower: !degrees -180
|
||||
upper: !degrees 180
|
||||
|
||||
joint_5:
|
||||
effort: 50
|
||||
velocity: 2.0
|
||||
lower: !degrees -90
|
||||
upper: !degrees 90
|
||||
|
||||
joint_6:
|
||||
effort: 25
|
||||
velocity: 2.0
|
||||
lower: !degrees -180
|
||||
upper: !degrees 180
|
||||
@@ -1,249 +0,0 @@
|
||||
<?xml version="1.0" ?>
|
||||
<robot name="dummy2" xmlns:xacro="http://www.ros.org/wiki/xacro">
|
||||
|
||||
|
||||
<xacro:macro name="dummy2_robot" params="mesh_path:='' parent_link:='' station_name:='' device_name:='' x:=0 y:=0 z:=0 rx:=0 ry:=0 r:=0">
|
||||
|
||||
<xacro:include filename="${mesh_path}/devices/dummy2_robot/config/materials.xacro" />
|
||||
<xacro:include filename="${mesh_path}/devices/dummy2_robot/config/dummy2.trans" />
|
||||
|
||||
<joint name="${station_name}${device_name}base_link_joint" type="fixed">
|
||||
<origin xyz="${x} ${y} ${z}" rpy="${rx} ${ry} ${r}" />
|
||||
<parent link="${parent_link}"/>
|
||||
<child link="${station_name}${device_name}device_link"/>
|
||||
<axis xyz="0 0 0"/>
|
||||
</joint>
|
||||
|
||||
<link name="${station_name}${device_name}device_link"/>
|
||||
<joint name="${station_name}${device_name}device_link_joint" type="fixed">
|
||||
<origin xyz="0 0 0" rpy="0 0 0" />
|
||||
<parent link="${station_name}${device_name}device_link"/>
|
||||
<child link="${station_name}${device_name}base_link"/>
|
||||
<axis xyz="0 0 0"/>
|
||||
</joint>
|
||||
|
||||
<link name="${station_name}${device_name}base_link">
|
||||
<inertial>
|
||||
<origin xyz="0.00010022425916431473 -6.186605493937309e-05 0.05493640543484716" rpy="0 0 0"/>
|
||||
<mass value="1.2152141810431654"/>
|
||||
<inertia ixx="0.002105" iyy="0.002245" izz="0.002436" ixy="-0.0" iyz="-1.1e-05" ixz="0.0"/>
|
||||
</inertial>
|
||||
<visual>
|
||||
<origin xyz="0 0 0" rpy="0 0 0"/>
|
||||
<geometry>
|
||||
<mesh filename="file://${mesh_path}/devices/dummy2_robot/meshes/base_link.stl" scale="0.001 0.001 0.001"/>
|
||||
</geometry>
|
||||
<material name="silver"/>
|
||||
</visual>
|
||||
<collision>
|
||||
<origin xyz="0 0 0" rpy="0 0 0"/>
|
||||
<geometry>
|
||||
<mesh filename="file://${mesh_path}/devices/dummy2_robot/meshes/base_link.stl" scale="0.001 0.001 0.001"/>
|
||||
</geometry>
|
||||
</collision>
|
||||
</link>
|
||||
|
||||
<link name="${station_name}${device_name}J1_1">
|
||||
<inertial>
|
||||
<origin xyz="-0.00617659688932347 0.007029599744830012 0.012866826083045027" rpy="0 0 0"/>
|
||||
<mass value="0.1332774369186824"/>
|
||||
<inertia ixx="6e-05" iyy="5e-05" izz="8.8e-05" ixy="2.1e-05" iyz="-1.4e-05" ixz="8e-06"/>
|
||||
</inertial>
|
||||
<visual>
|
||||
<origin xyz="-0.0001 0.000289 -0.097579" rpy="0 0 0"/>
|
||||
<geometry>
|
||||
<mesh filename="file://${mesh_path}/devices/dummy2_robot/meshes/J1_1.stl" scale="0.001 0.001 0.001"/>
|
||||
</geometry>
|
||||
<material name="silver"/>
|
||||
</visual>
|
||||
<collision>
|
||||
<origin xyz="-0.0001 0.000289 -0.097579" rpy="0 0 0"/>
|
||||
<geometry>
|
||||
<mesh filename="file://${mesh_path}/devices/dummy2_robot/meshes/J1_1.stl" scale="0.001 0.001 0.001"/>
|
||||
</geometry>
|
||||
</collision>
|
||||
</link>
|
||||
|
||||
<link name="${station_name}${device_name}J2_1">
|
||||
<inertial>
|
||||
<origin xyz="0.019335709221765855 0.0019392793940843159 0.07795928103332703" rpy="0 0 0"/>
|
||||
<mass value="1.9268013917303417"/>
|
||||
<inertia ixx="0.006165" iyy="0.006538" izz="0.00118" ixy="-3e-06" iyz="4.7e-05" ixz="0.0007"/>
|
||||
</inertial>
|
||||
<visual>
|
||||
<origin xyz="0.011539 -0.034188 -0.12478" rpy="0 0 0"/>
|
||||
<geometry>
|
||||
<mesh filename="file://${mesh_path}/devices/dummy2_robot/meshes/J2_1.stl" scale="0.001 0.001 0.001"/>
|
||||
</geometry>
|
||||
<material name="silver"/>
|
||||
</visual>
|
||||
<collision>
|
||||
<origin xyz="0.011539 -0.034188 -0.12478" rpy="0 0 0"/>
|
||||
<geometry>
|
||||
<mesh filename="file://${mesh_path}/devices/dummy2_robot/meshes/J2_1.stl" scale="0.001 0.001 0.001"/>
|
||||
</geometry>
|
||||
</collision>
|
||||
</link>
|
||||
|
||||
<link name="${station_name}${device_name}J3_1">
|
||||
<inertial>
|
||||
<origin xyz="-0.010672101243726572 -0.02723871972304964 0.04876701375652198" rpy="0 0 0"/>
|
||||
<mass value="0.30531962155452225"/>
|
||||
<inertia ixx="0.00029" iyy="0.000238" izz="0.000191" ixy="-1.3e-05" iyz="4.1e-05" ixz="3e-05"/>
|
||||
</inertial>
|
||||
<visual>
|
||||
<origin xyz="-0.023811 -0.034188 -0.28278" rpy="0 0 0"/>
|
||||
<geometry>
|
||||
<mesh filename="file://${mesh_path}/devices/dummy2_robot/meshes/J3_1.stl" scale="0.001 0.001 0.001"/>
|
||||
</geometry>
|
||||
<material name="silver"/>
|
||||
</visual>
|
||||
<collision>
|
||||
<origin xyz="-0.023811 -0.034188 -0.28278" rpy="0 0 0"/>
|
||||
<geometry>
|
||||
<mesh filename="file://${mesh_path}/devices/dummy2_robot/meshes/J3_1.stl" scale="0.001 0.001 0.001"/>
|
||||
</geometry>
|
||||
</collision>
|
||||
</link>
|
||||
|
||||
<link name="${station_name}${device_name}J4_1">
|
||||
<inertial>
|
||||
<origin xyz="-0.005237398377441591 0.06002028183461833 0.0005891767740203724" rpy="0 0 0"/>
|
||||
<mass value="0.14051172121899885"/>
|
||||
<inertia ixx="0.000245" iyy="7.9e-05" izz="0.00027" ixy="1.6e-05" iyz="-2e-06" ixz="1e-06"/>
|
||||
</inertial>
|
||||
<visual>
|
||||
<origin xyz="-0.010649 -0.038288 -0.345246" rpy="0 0 0"/>
|
||||
<geometry>
|
||||
<mesh filename="file://${mesh_path}/devices/dummy2_robot/meshes/J4_1.stl" scale="0.001 0.001 0.001"/>
|
||||
</geometry>
|
||||
<material name="silver"/>
|
||||
</visual>
|
||||
<collision>
|
||||
<origin xyz="-0.010649 -0.038288 -0.345246" rpy="0 0 0"/>
|
||||
<geometry>
|
||||
<mesh filename="file://${mesh_path}/devices/dummy2_robot/meshes/J4_1.stl" scale="0.001 0.001 0.001"/>
|
||||
</geometry>
|
||||
</collision>
|
||||
</link>
|
||||
|
||||
<link name="${station_name}${device_name}J5_1">
|
||||
<inertial>
|
||||
<origin xyz="-0.014389813882964664 0.07305218143664277 -0.0009243405950149497" rpy="0 0 0"/>
|
||||
<mass value="0.7783315754227634"/>
|
||||
<inertia ixx="0.000879" iyy="0.000339" izz="0.000964" ixy="0.000146" iyz="1e-06" ixz="-5e-06"/>
|
||||
</inertial>
|
||||
<visual>
|
||||
<origin xyz="-0.031949 -0.148289 -0.345246" rpy="0 0 0"/>
|
||||
<geometry>
|
||||
<mesh filename="file://${mesh_path}/devices/dummy2_robot/meshes/J5_1.stl" scale="0.001 0.001 0.001"/>
|
||||
</geometry>
|
||||
<material name="silver"/>
|
||||
</visual>
|
||||
<collision>
|
||||
<origin xyz="-0.031949 -0.148289 -0.345246" rpy="0 0 0"/>
|
||||
<geometry>
|
||||
<mesh filename="file://${mesh_path}/devices/dummy2_robot/meshes/J5_1.stl" scale="0.001 0.001 0.001"/>
|
||||
</geometry>
|
||||
</collision>
|
||||
</link>
|
||||
|
||||
<link name="${station_name}${device_name}J6_1">
|
||||
<inertial>
|
||||
<origin xyz="3.967160300787087e-07 0.0004995066702210837 1.4402781733924286e-07" rpy="0 0 0"/>
|
||||
<mass value="0.0020561527568204153"/>
|
||||
<inertia ixx="0.0" iyy="0.0" izz="0.0" ixy="0.0" iyz="0.0" ixz="0.0"/>
|
||||
</inertial>
|
||||
<visual>
|
||||
<origin xyz="-0.012127 -0.267789 -0.344021" rpy="0 0 0"/>
|
||||
<geometry>
|
||||
<mesh filename="file://${mesh_path}/devices/dummy2_robot/meshes/J6_1.stl" scale="0.001 0.001 0.001"/>
|
||||
</geometry>
|
||||
<material name="silver"/>
|
||||
</visual>
|
||||
<collision>
|
||||
<origin xyz="-0.012127 -0.267789 -0.344021" rpy="0 0 0"/>
|
||||
<geometry>
|
||||
<mesh filename="file://${mesh_path}/devices/dummy2_robot/meshes/J6_1.stl" scale="0.001 0.001 0.001"/>
|
||||
</geometry>
|
||||
</collision>
|
||||
</link>
|
||||
|
||||
<link name="${station_name}${device_name}camera">
|
||||
<inertial>
|
||||
<origin xyz="-0.0006059984983273845 0.0005864706438700462 0.04601775357664567" rpy="0 0 0"/>
|
||||
<mass value="0.21961029019655884"/>
|
||||
<inertia ixx="2.9e-05" iyy="0.000206" izz="0.000198" ixy="-0.0" iyz="2e-06" ixz="-0.0"/>
|
||||
</inertial>
|
||||
<visual>
|
||||
<origin xyz="-0.012661 -0.239774 -0.37985" rpy="0 0 0"/>
|
||||
<geometry>
|
||||
<mesh filename="file://${mesh_path}/devices/dummy2_robot/meshes/camera_1.stl" scale="0.001 0.001 0.001"/>
|
||||
</geometry>
|
||||
<material name="silver"/>
|
||||
</visual>
|
||||
<collision>
|
||||
<origin xyz="-0.012661 -0.239774 -0.37985" rpy="0 0 0"/>
|
||||
<geometry>
|
||||
<mesh filename="file://${mesh_path}/devices/dummy2_robot/meshes/camera_1.stl" scale="0.001 0.001 0.001"/>
|
||||
</geometry>
|
||||
</collision>
|
||||
</link>
|
||||
|
||||
<joint name="${station_name}${device_name}Joint1" type="revolute">
|
||||
<origin xyz="0.0001 -0.000289 0.097579" rpy="0 0 0"/>
|
||||
<parent link="${station_name}${device_name}base_link"/>
|
||||
<child link="${station_name}${device_name}J1_1"/>
|
||||
<axis xyz="-0.0 -0.0 1.0"/>
|
||||
<limit upper="3.054326" lower="-3.054326" effort="100" velocity="100"/>
|
||||
</joint>
|
||||
|
||||
<joint name="${station_name}${device_name}Joint2" type="revolute">
|
||||
<origin xyz="-0.011639 0.034477 0.027201" rpy="0 0 0"/>
|
||||
<parent link="${station_name}${device_name}J1_1"/>
|
||||
<child link="${station_name}${device_name}J2_1"/>
|
||||
<axis xyz="1.0 0.0 -0.0"/>
|
||||
<limit upper="1.308997" lower="-2.007129" effort="100" velocity="100"/>
|
||||
</joint>
|
||||
|
||||
<joint name="${station_name}${device_name}Joint3" type="revolute">
|
||||
<origin xyz="0.03535 0.0 0.158" rpy="0 0 0"/>
|
||||
<parent link="${station_name}${device_name}J2_1"/>
|
||||
<child link="${station_name}${device_name}J3_1"/>
|
||||
<axis xyz="-1.0 0.0 -0.0"/>
|
||||
<limit upper="1.570796" lower="-1.047198" effort="100" velocity="100"/>
|
||||
</joint>
|
||||
|
||||
<joint name="${station_name}${device_name}Joint4" type="revolute">
|
||||
<origin xyz="-0.013162 0.0041 0.062466" rpy="0 0 0"/>
|
||||
<parent link="${station_name}${device_name}J3_1"/>
|
||||
<child link="${station_name}${device_name}J4_1"/>
|
||||
<axis xyz="0.0 1.0 -0.0"/>
|
||||
<limit upper="3.141593" lower="-3.141593" effort="100" velocity="100"/>
|
||||
</joint>
|
||||
|
||||
<joint name="${station_name}${device_name}Joint5" type="revolute">
|
||||
<origin xyz="0.0213 0.110001 0.0" rpy="0 0 0"/>
|
||||
<parent link="${station_name}${device_name}J4_1"/>
|
||||
<child link="${station_name}${device_name}J5_1"/>
|
||||
<axis xyz="-1.0 -0.0 -0.0"/>
|
||||
<limit upper="2.094395" lower="-1.919862" effort="100" velocity="100"/>
|
||||
</joint>
|
||||
|
||||
<joint name="${station_name}${device_name}Joint6" type="continuous">
|
||||
<origin xyz="-0.019822 0.1195 -0.001225" rpy="0 0 0"/>
|
||||
<parent link="${station_name}${device_name}J5_1"/>
|
||||
<child link="${station_name}${device_name}J6_1"/>
|
||||
<axis xyz="0.0 -1.0 0.0"/>
|
||||
</joint>
|
||||
|
||||
<joint name="${station_name}${device_name}camera" type="fixed">
|
||||
<origin xyz="-0.019988 0.091197 0.024883" rpy="0 0 0"/>
|
||||
<parent link="${station_name}${device_name}J5_1"/>
|
||||
<child link="${station_name}${device_name}camera"/>
|
||||
<axis xyz="1.0 -0.0 0.0"/>
|
||||
<limit upper="0.0" lower="0.0" effort="100" velocity="100"/>
|
||||
</joint>
|
||||
</xacro:macro>
|
||||
|
||||
</robot>
|
||||
@@ -1,237 +0,0 @@
|
||||
<?xml version="1.0" ?>
|
||||
<robot name="dummy2" xmlns:xacro="http://www.ros.org/wiki/xacro">
|
||||
|
||||
<xacro:include filename="$(find dummy2_description)/urdf/materials.xacro" />
|
||||
<xacro:include filename="$(find dummy2_description)/urdf/dummy2.trans" />
|
||||
<xacro:include filename="$(find dummy2_description)/urdf/dummy2.gazebo" />
|
||||
<link name="world" />
|
||||
<joint name="world_joint" type="fixed">
|
||||
<parent link="world" />
|
||||
<child link = "base_link" />
|
||||
<origin xyz="0.0 0.0 0.0" rpy="0.0 0.0 0.0" />
|
||||
</joint>
|
||||
|
||||
<link name="base_link">
|
||||
<inertial>
|
||||
<origin xyz="0.00010022425916431473 -6.186605493937309e-05 0.05493640543484716" rpy="0 0 0"/>
|
||||
<mass value="1.2152141810431654"/>
|
||||
<inertia ixx="0.002105" iyy="0.002245" izz="0.002436" ixy="-0.0" iyz="-1.1e-05" ixz="0.0"/>
|
||||
</inertial>
|
||||
<visual>
|
||||
<origin xyz="0 0 0" rpy="0 0 0"/>
|
||||
<geometry>
|
||||
<mesh filename="file://$(find dummy2_description)/meshes/base_link.stl" scale="0.001 0.001 0.001"/>
|
||||
</geometry>
|
||||
<material name="silver"/>
|
||||
</visual>
|
||||
<collision>
|
||||
<origin xyz="0 0 0" rpy="0 0 0"/>
|
||||
<geometry>
|
||||
<mesh filename="file://$(find dummy2_description)/meshes/base_link.stl" scale="0.001 0.001 0.001"/>
|
||||
</geometry>
|
||||
</collision>
|
||||
</link>
|
||||
|
||||
<link name="J1_1">
|
||||
<inertial>
|
||||
<origin xyz="-0.00617659688932347 0.007029599744830012 0.012866826083045027" rpy="0 0 0"/>
|
||||
<mass value="0.1332774369186824"/>
|
||||
<inertia ixx="6e-05" iyy="5e-05" izz="8.8e-05" ixy="2.1e-05" iyz="-1.4e-05" ixz="8e-06"/>
|
||||
</inertial>
|
||||
<visual>
|
||||
<origin xyz="-0.0001 0.000289 -0.097579" rpy="0 0 0"/>
|
||||
<geometry>
|
||||
<mesh filename="file://$(find dummy2_description)/meshes/J1_1.stl" scale="0.001 0.001 0.001"/>
|
||||
</geometry>
|
||||
<material name="silver"/>
|
||||
</visual>
|
||||
<collision>
|
||||
<origin xyz="-0.0001 0.000289 -0.097579" rpy="0 0 0"/>
|
||||
<geometry>
|
||||
<mesh filename="file://$(find dummy2_description)/meshes/J1_1.stl" scale="0.001 0.001 0.001"/>
|
||||
</geometry>
|
||||
</collision>
|
||||
</link>
|
||||
|
||||
<link name="J2_1">
|
||||
<inertial>
|
||||
<origin xyz="0.019335709221765855 0.0019392793940843159 0.07795928103332703" rpy="0 0 0"/>
|
||||
<mass value="1.9268013917303417"/>
|
||||
<inertia ixx="0.006165" iyy="0.006538" izz="0.00118" ixy="-3e-06" iyz="4.7e-05" ixz="0.0007"/>
|
||||
</inertial>
|
||||
<visual>
|
||||
<origin xyz="0.011539 -0.034188 -0.12478" rpy="0 0 0"/>
|
||||
<geometry>
|
||||
<mesh filename="file://$(find dummy2_description)/meshes/J2_1.stl" scale="0.001 0.001 0.001"/>
|
||||
</geometry>
|
||||
<material name="silver"/>
|
||||
</visual>
|
||||
<collision>
|
||||
<origin xyz="0.011539 -0.034188 -0.12478" rpy="0 0 0"/>
|
||||
<geometry>
|
||||
<mesh filename="file://$(find dummy2_description)/meshes/J2_1.stl" scale="0.001 0.001 0.001"/>
|
||||
</geometry>
|
||||
</collision>
|
||||
</link>
|
||||
|
||||
<link name="J3_1">
|
||||
<inertial>
|
||||
<origin xyz="-0.010672101243726572 -0.02723871972304964 0.04876701375652198" rpy="0 0 0"/>
|
||||
<mass value="0.30531962155452225"/>
|
||||
<inertia ixx="0.00029" iyy="0.000238" izz="0.000191" ixy="-1.3e-05" iyz="4.1e-05" ixz="3e-05"/>
|
||||
</inertial>
|
||||
<visual>
|
||||
<origin xyz="-0.023811 -0.034188 -0.28278" rpy="0 0 0"/>
|
||||
<geometry>
|
||||
<mesh filename="file://$(find dummy2_description)/meshes/J3_1.stl" scale="0.001 0.001 0.001"/>
|
||||
</geometry>
|
||||
<material name="silver"/>
|
||||
</visual>
|
||||
<collision>
|
||||
<origin xyz="-0.023811 -0.034188 -0.28278" rpy="0 0 0"/>
|
||||
<geometry>
|
||||
<mesh filename="file://$(find dummy2_description)/meshes/J3_1.stl" scale="0.001 0.001 0.001"/>
|
||||
</geometry>
|
||||
</collision>
|
||||
</link>
|
||||
|
||||
<link name="J4_1">
|
||||
<inertial>
|
||||
<origin xyz="-0.005237398377441591 0.06002028183461833 0.0005891767740203724" rpy="0 0 0"/>
|
||||
<mass value="0.14051172121899885"/>
|
||||
<inertia ixx="0.000245" iyy="7.9e-05" izz="0.00027" ixy="1.6e-05" iyz="-2e-06" ixz="1e-06"/>
|
||||
</inertial>
|
||||
<visual>
|
||||
<origin xyz="-0.010649 -0.038288 -0.345246" rpy="0 0 0"/>
|
||||
<geometry>
|
||||
<mesh filename="file://$(find dummy2_description)/meshes/J4_1.stl" scale="0.001 0.001 0.001"/>
|
||||
</geometry>
|
||||
<material name="silver"/>
|
||||
</visual>
|
||||
<collision>
|
||||
<origin xyz="-0.010649 -0.038288 -0.345246" rpy="0 0 0"/>
|
||||
<geometry>
|
||||
<mesh filename="file://$(find dummy2_description)/meshes/J4_1.stl" scale="0.001 0.001 0.001"/>
|
||||
</geometry>
|
||||
</collision>
|
||||
</link>
|
||||
|
||||
<link name="J5_1">
|
||||
<inertial>
|
||||
<origin xyz="-0.014389813882964664 0.07305218143664277 -0.0009243405950149497" rpy="0 0 0"/>
|
||||
<mass value="0.7783315754227634"/>
|
||||
<inertia ixx="0.000879" iyy="0.000339" izz="0.000964" ixy="0.000146" iyz="1e-06" ixz="-5e-06"/>
|
||||
</inertial>
|
||||
<visual>
|
||||
<origin xyz="-0.031949 -0.148289 -0.345246" rpy="0 0 0"/>
|
||||
<geometry>
|
||||
<mesh filename="file://$(find dummy2_description)/meshes/J5_1.stl" scale="0.001 0.001 0.001"/>
|
||||
</geometry>
|
||||
<material name="silver"/>
|
||||
</visual>
|
||||
<collision>
|
||||
<origin xyz="-0.031949 -0.148289 -0.345246" rpy="0 0 0"/>
|
||||
<geometry>
|
||||
<mesh filename="file://$(find dummy2_description)/meshes/J5_1.stl" scale="0.001 0.001 0.001"/>
|
||||
</geometry>
|
||||
</collision>
|
||||
</link>
|
||||
|
||||
<link name="J6_1">
|
||||
<inertial>
|
||||
<origin xyz="3.967160300787087e-07 0.0004995066702210837 1.4402781733924286e-07" rpy="0 0 0"/>
|
||||
<mass value="0.0020561527568204153"/>
|
||||
<inertia ixx="0.0" iyy="0.0" izz="0.0" ixy="0.0" iyz="0.0" ixz="0.0"/>
|
||||
</inertial>
|
||||
<visual>
|
||||
<origin xyz="-0.012127 -0.267789 -0.344021" rpy="0 0 0"/>
|
||||
<geometry>
|
||||
<mesh filename="file://$(find dummy2_description)/meshes/J6_1.stl" scale="0.001 0.001 0.001"/>
|
||||
</geometry>
|
||||
<material name="silver"/>
|
||||
</visual>
|
||||
<collision>
|
||||
<origin xyz="-0.012127 -0.267789 -0.344021" rpy="0 0 0"/>
|
||||
<geometry>
|
||||
<mesh filename="file://$(find dummy2_description)/meshes/J6_1.stl" scale="0.001 0.001 0.001"/>
|
||||
</geometry>
|
||||
</collision>
|
||||
</link>
|
||||
|
||||
<link name="camera">
|
||||
<inertial>
|
||||
<origin xyz="-0.0006059984983273845 0.0005864706438700462 0.04601775357664567" rpy="0 0 0"/>
|
||||
<mass value="0.21961029019655884"/>
|
||||
<inertia ixx="2.9e-05" iyy="0.000206" izz="0.000198" ixy="-0.0" iyz="2e-06" ixz="-0.0"/>
|
||||
</inertial>
|
||||
<visual>
|
||||
<origin xyz="-0.012661 -0.239774 -0.37985" rpy="0 0 0"/>
|
||||
<geometry>
|
||||
<mesh filename="file://$(find dummy2_description)/meshes/camera_1.stl" scale="0.001 0.001 0.001"/>
|
||||
</geometry>
|
||||
<material name="silver"/>
|
||||
</visual>
|
||||
<collision>
|
||||
<origin xyz="-0.012661 -0.239774 -0.37985" rpy="0 0 0"/>
|
||||
<geometry>
|
||||
<mesh filename="file://$(find dummy2_description)/meshes/camera_1.stl" scale="0.001 0.001 0.001"/>
|
||||
</geometry>
|
||||
</collision>
|
||||
</link>
|
||||
|
||||
<joint name="Joint1" type="revolute">
|
||||
<origin xyz="0.0001 -0.000289 0.097579" rpy="0 0 0"/>
|
||||
<parent link="base_link"/>
|
||||
<child link="J1_1"/>
|
||||
<axis xyz="-0.0 -0.0 1.0"/>
|
||||
<limit upper="3.054326" lower="-3.054326" effort="100" velocity="100"/>
|
||||
</joint>
|
||||
|
||||
<joint name="Joint2" type="revolute">
|
||||
<origin xyz="-0.011639 0.034477 0.027201" rpy="0 0 0"/>
|
||||
<parent link="J1_1"/>
|
||||
<child link="J2_1"/>
|
||||
<axis xyz="1.0 0.0 -0.0"/>
|
||||
<limit upper="1.308997" lower="-2.007129" effort="100" velocity="100"/>
|
||||
</joint>
|
||||
|
||||
<joint name="Joint3" type="revolute">
|
||||
<origin xyz="0.03535 0.0 0.158" rpy="0 0 0"/>
|
||||
<parent link="J2_1"/>
|
||||
<child link="J3_1"/>
|
||||
<axis xyz="-1.0 0.0 -0.0"/>
|
||||
<limit upper="1.570796" lower="-1.047198" effort="100" velocity="100"/>
|
||||
</joint>
|
||||
|
||||
<joint name="Joint4" type="revolute">
|
||||
<origin xyz="-0.013162 0.0041 0.062466" rpy="0 0 0"/>
|
||||
<parent link="J3_1"/>
|
||||
<child link="J4_1"/>
|
||||
<axis xyz="0.0 1.0 -0.0"/>
|
||||
<limit upper="3.141593" lower="-3.141593" effort="100" velocity="100"/>
|
||||
</joint>
|
||||
|
||||
<joint name="Joint5" type="revolute">
|
||||
<origin xyz="0.0213 0.110001 0.0" rpy="0 0 0"/>
|
||||
<parent link="J4_1"/>
|
||||
<child link="J5_1"/>
|
||||
<axis xyz="-1.0 -0.0 -0.0"/>
|
||||
<limit upper="2.094395" lower="-1.919862" effort="100" velocity="100"/>
|
||||
</joint>
|
||||
|
||||
<joint name="Joint6" type="continuous">
|
||||
<origin xyz="-0.019822 0.1195 -0.001225" rpy="0 0 0"/>
|
||||
<parent link="J5_1"/>
|
||||
<child link="J6_1"/>
|
||||
<axis xyz="0.0 -1.0 0.0"/>
|
||||
</joint>
|
||||
|
||||
<joint name="camera" type="fixed">
|
||||
<origin xyz="-0.019988 0.091197 0.024883" rpy="0 0 0"/>
|
||||
<parent link="J5_1"/>
|
||||
<child link="camera"/>
|
||||
<axis xyz="1.0 -0.0 0.0"/>
|
||||
<limit upper="0.0" lower="0.0" effort="100" velocity="100"/>
|
||||
</joint>
|
||||
|
||||
</robot>
|
||||
0
unilabos/device_mesh/devices/liquid_transform_xyz/meshes/base_link.STL
Executable file → Normal file
0
unilabos/device_mesh/devices/liquid_transform_xyz/meshes/x_link.STL
Executable file → Normal file
0
unilabos/device_mesh/devices/liquid_transform_xyz/meshes/y_link.STL
Executable file → Normal file
0
unilabos/device_mesh/devices/liquid_transform_xyz/meshes/z_link.STL
Executable file → Normal file
@@ -14,7 +14,6 @@ from launch_ros.parameter_descriptions import ParameterFile
|
||||
from unilabos.registry.registry import lab_registry
|
||||
from ament_index_python.packages import get_package_share_directory
|
||||
|
||||
|
||||
def get_pattern_matches(folder, pattern):
|
||||
"""Given all the files in the folder, find those that match the pattern.
|
||||
|
||||
@@ -52,7 +51,7 @@ class ResourceVisualization:
|
||||
self.launch_description = LaunchDescription()
|
||||
self.resource_dict = resource
|
||||
self.resource_model = {}
|
||||
self.resource_type = ['deck', 'plate', 'container', 'tip_rack']
|
||||
self.resource_type = ['deck', 'plate', 'container']
|
||||
self.mesh_path = Path(__file__).parent.absolute()
|
||||
self.enable_rviz = enable_rviz
|
||||
registry = lab_registry
|
||||
@@ -129,9 +128,9 @@ class ResourceVisualization:
|
||||
# if node["parent"] is not None:
|
||||
# new_dev.set("station_name", node["parent"]+'_')
|
||||
|
||||
new_dev.set("x",str(float(node["position"]["position"]["x"])/1000))
|
||||
new_dev.set("y",str(float(node["position"]["position"]["y"])/1000))
|
||||
new_dev.set("z",str(float(node["position"]["position"]["z"])/1000))
|
||||
new_dev.set("x",str(float(node["position"]["x"])/1000))
|
||||
new_dev.set("y",str(float(node["position"]["y"])/1000))
|
||||
new_dev.set("z",str(float(node["position"]["z"])/1000))
|
||||
if "rotation" in node["config"]:
|
||||
new_dev.set("rx",str(float(node["config"]["rotation"]["x"])))
|
||||
new_dev.set("ry",str(float(node["config"]["rotation"]["y"])))
|
||||
@@ -141,7 +140,7 @@ class ResourceVisualization:
|
||||
new_dev.set(key, str(value))
|
||||
|
||||
# 添加ros2_controller
|
||||
if node['class'].find('moveit.')!= -1:
|
||||
if node['class'].startswith('moveit.'):
|
||||
new_include_controller = etree.SubElement(self.root, f"{{{xacro_uri}}}include")
|
||||
new_include_controller.set("filename", f"{str(self.mesh_path)}/devices/{model_config['mesh']}/config/macro.ros2_control.xacro")
|
||||
new_controller = etree.SubElement(self.root, f"{{{xacro_uri}}}{model_config['mesh']}_ros2_control")
|
||||
@@ -204,24 +203,7 @@ class ResourceVisualization:
|
||||
Returns:
|
||||
LaunchDescription: launch描述对象
|
||||
"""
|
||||
# 检查ROS 2环境变量
|
||||
if "AMENT_PREFIX_PATH" not in os.environ:
|
||||
raise OSError(
|
||||
"ROS 2环境未正确设置。需要设置 AMENT_PREFIX_PATH 环境变量。\n"
|
||||
"请确保:\n"
|
||||
"1. 已安装ROS 2 (推荐使用 ros-humble-desktop-full)\n"
|
||||
"2. 已激活Conda环境: conda activate unilab\n"
|
||||
"3. 或手动source ROS 2 setup文件: source /opt/ros/humble/setup.bash\n"
|
||||
"4. 或者使用 --backend simple 参数跳过ROS依赖"
|
||||
)
|
||||
|
||||
try:
|
||||
moveit_configs_utils_path = Path(get_package_share_directory("moveit_configs_utils"))
|
||||
except Exception as e:
|
||||
raise OSError(
|
||||
f"无法找到moveit_configs_utils包。请确保ROS 2和MoveIt 2已正确安装。\n"
|
||||
f"原始错误: {e}"
|
||||
)
|
||||
moveit_configs_utils_path = Path(get_package_share_directory("moveit_configs_utils"))
|
||||
default_folder = moveit_configs_utils_path / "default_configs"
|
||||
planning_pattern = re.compile("^(.*)_planning.yaml$")
|
||||
pipelines = []
|
||||
@@ -282,8 +264,7 @@ class ResourceVisualization:
|
||||
parameters=[
|
||||
{"robot_description": robot_description},
|
||||
ros2_controllers,
|
||||
],
|
||||
env=dict(os.environ)
|
||||
]
|
||||
)
|
||||
)
|
||||
for controller in self.moveit_controllers_yaml['moveit_simple_controller_manager']['controller_names']:
|
||||
@@ -293,7 +274,6 @@ class ResourceVisualization:
|
||||
executable="spawner",
|
||||
arguments=[f"{controller}", "--controller-manager", f"controller_manager"],
|
||||
output="screen",
|
||||
env=dict(os.environ)
|
||||
)
|
||||
)
|
||||
controllers.append(
|
||||
@@ -302,7 +282,6 @@ class ResourceVisualization:
|
||||
executable="spawner",
|
||||
arguments=["joint_state_broadcaster", "--controller-manager", f"controller_manager"],
|
||||
output="screen",
|
||||
env=dict(os.environ)
|
||||
)
|
||||
)
|
||||
for i in controllers:
|
||||
@@ -321,8 +300,7 @@ class ResourceVisualization:
|
||||
'use_sim_time': False
|
||||
},
|
||||
# kinematics_dict
|
||||
],
|
||||
env=dict(os.environ)
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
@@ -353,8 +331,7 @@ class ResourceVisualization:
|
||||
package='moveit_ros_move_group',
|
||||
executable='move_group',
|
||||
output='screen',
|
||||
parameters=moveit_params,
|
||||
env=dict(os.environ)
|
||||
parameters=moveit_params
|
||||
)
|
||||
|
||||
|
||||
@@ -377,8 +354,7 @@ class ResourceVisualization:
|
||||
robot_description_planning,
|
||||
planning_pipelines,
|
||||
|
||||
],
|
||||
env=dict(os.environ)
|
||||
]
|
||||
)
|
||||
self.launch_description.add_action(rviz_node)
|
||||
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
<?xml version="1.0" ?>
|
||||
<robot xmlns:xacro="http://www.ros.org/wiki/xacro" name="bottle">
|
||||
<link name='bottle'>
|
||||
<visual name='visual'>
|
||||
<geometry>
|
||||
<mesh filename="meshes/bottle.stl"/>
|
||||
</geometry>
|
||||
<material name="clay" />
|
||||
</visual>
|
||||
</link>
|
||||
</robot>
|
||||
@@ -1,11 +0,0 @@
|
||||
<?xml version="1.0" ?>
|
||||
<robot xmlns:xacro="http://www.ros.org/wiki/xacro" name="bottle_container">
|
||||
<link name='bottle_container'>
|
||||
<visual name='visual'>
|
||||
<geometry>
|
||||
<mesh filename="meshes/bottle_container.stl"/>
|
||||
</geometry>
|
||||
<material name="clay" />
|
||||
</visual>
|
||||
</link>
|
||||
</robot>
|
||||