Compare commits

..

1 Commits

Author SHA1 Message Date
Xuwznln
9aeffebde1 0.10.7 Update (#101)
* Cleanup registry to be easy-understanding (#76)

* delete deprecated mock devices

* rename categories

* combine chromatographic devices

* rename rviz simulation nodes

* organic virtual devices

* parse vessel_id

* run registry completion before merge

---------

Co-authored-by: Xuwznln <18435084+Xuwznln@users.noreply.github.com>

* fix: workstation handlers and vessel_id parsing

* fix: working dir error when input config path
feat: report publish topic when error

* modify default discovery_interval to 15s

* feat: add trace log level

* feat: 添加ChinWe设备控制类,支持串口通信和电机控制功能 (#79)

* fix: drop_tips not using auto resource select

* fix: discard_tips error

* fix: discard_tips

* fix: prcxi_res

* add: prcxi res
fix: startup slow

* feat: workstation example

* fix pumps and liquid_handler handle

* feat: 优化protocol node节点运行日志

* fix all protocol_compilers and remove deprecated devices

* feat: 新增use_remote_resource参数

* fix and remove redundant info

* bugfixes on organic protocols

* fix filter protocol

* fix protocol node

* 临时兼容错误的driver写法

* fix: prcxi import error

* use call_async in all service to avoid deadlock

* fix: figure_resource

* Update recipe.yaml

* add workstation template and battery example

* feat: add sk & ak

* update workstation base

* Create workstation_architecture.md

* refactor: workstation_base 重构为仅含业务逻辑,通信和子设备管理交给 ProtocolNode

* refactor: ProtocolNode→WorkstationNode

* Add:msgs.action (#83)

* update: Workstation dev 将版本号从 0.10.3 更新为 0.10.4 (#84)

* Add:msgs.action

* update: 将版本号从 0.10.3 更新为 0.10.4

* simplify resource system

* uncompleted refactor

* example for use WorkstationBase

* feat: websocket

* feat: websocket test

* feat: workstation example

* feat: action status

* fix: station自己的方法注册错误

* fix: 还原protocol node处理方法

* fix: build

* fix: missing job_id key

* ws test version 1

* ws test version 2

* ws protocol

* 增加物料关系上传日志

* 增加物料关系上传日志

* 修正物料关系上传

* 修复工站的tracker实例追踪失效问题

* 增加handle检测,增加material edge关系上传

* 修复event loop错误

* 修复edge上报错误

* 修复async错误

* 更新schema的title字段

* 主机节点信息等支持自动刷新

* 注册表编辑器

* 修复status密集发送时,消息出错

* 增加addr参数

* fix: addr param

* fix: addr param

* 取消labid 和 强制config输入

* Add action definitions for LiquidHandlerSetGroup and LiquidHandlerTransferGroup

- Created LiquidHandlerSetGroup.action with fields for group name, wells, and volumes.
- Created LiquidHandlerTransferGroup.action with fields for source and target group names and unit volume.
- Both actions include response fields for return information and success status.

* Add LiquidHandlerSetGroup and LiquidHandlerTransferGroup actions to CMakeLists

* Add set_group and transfer_group methods to PRCXI9300Handler and update liquid_handler.yaml

* result_info改为字典类型

* 新增uat的地址替换

* runze multiple pump support

(cherry picked from commit 49354fcf39)

* remove runze multiple software obtainer

(cherry picked from commit 8bcc92a394)

* support multiple backbone

(cherry picked from commit 4771ff2347)

* Update runze pump format

* Correct runze multiple backbone

* Update runze_multiple_backbone

* Correct runze pump multiple receive method.

* Correct runze pump multiple receive method.

* 对于PRCXI9320的transfer_group,一对多和多对多

* 移除MQTT,更新launch文档,提供注册表示例文件,更新到0.10.5

* fix import error

* fix dupe upload registry

* refactor ws client

* add server timeout

* Fix: run-column with correct vessel id (#86)

* fix run_column

* Update run_column_protocol.py

(cherry picked from commit e5aa4d940a)

* resource_update use resource_add

* 新增版位推荐功能

* 重新规定了版位推荐的入参

* update registry with nested obj

* fix protocol node log_message, added create_resource return value

* fix protocol node log_message, added create_resource return value

* try fix add protocol

* fix resource_add

* 修复移液站错误的aspirate注册表

* Feature/xprbalance-zhida (#80)

* feat(devices): add Zhida GC/MS pretreatment automation workstation

* feat(devices): add mettler_toledo xpr balance

* balance

* 重新补全zhida注册表

* PRCXI9320 json

* PRCXI9320 json

* PRCXI9320 json

* fix resource download

* remove class for resource

* bump version to 0.10.6

* 更新所有注册表

* 修复protocolnode的兼容性

* 修复protocolnode的兼容性

* Update install md

* Add Defaultlayout

* 更新物料接口

* fix dict to tree/nested-dict converter

* coin_cell_station draft

* refactor: rename "station_resource" to "deck"

* add standardized BIOYOND resources: bottle_carrier, bottle

* refactor and add BIOYOND resources tests

* add BIOYOND deck assignment and pass all tests

* fix: update resource with correct structure; remove deprecated liquid_handler set_group action

* feat: 将新威电池测试系统驱动与配置文件并入 workstation_dev_YB2 (#92)

* feat: 新威电池测试系统驱动与注册文件

* feat: bring neware driver & battery.json into workstation_dev_YB2

* add bioyond studio draft

* bioyond station with communication init and resource sync

* fix bioyond station and registry

* fix: update resource with correct structure; remove deprecated liquid_handler set_group action

* frontend_docs

* create/update resources with POST/PUT for big amount/ small amount data

* create/update resources with POST/PUT for big amount/ small amount data

* refactor: add itemized_carrier instead of carrier consists of ResourceHolder

* create warehouse by factory func

* update bioyond launch json

* add child_size for itemized_carrier

* fix bioyond resource io

* Workstation templates: Resources and its CRUD, and workstation tasks (#95)

* coin_cell_station draft

* refactor: rename "station_resource" to "deck"

* add standardized BIOYOND resources: bottle_carrier, bottle

* refactor and add BIOYOND resources tests

* add BIOYOND deck assignment and pass all tests

* fix: update resource with correct structure; remove deprecated liquid_handler set_group action

* feat: 将新威电池测试系统驱动与配置文件并入 workstation_dev_YB2 (#92)

* feat: 新威电池测试系统驱动与注册文件

* feat: bring neware driver & battery.json into workstation_dev_YB2

* add bioyond studio draft

* bioyond station with communication init and resource sync

* fix bioyond station and registry

* create/update resources with POST/PUT for big amount/ small amount data

* refactor: add itemized_carrier instead of carrier consists of ResourceHolder

* create warehouse by factory func

* update bioyond launch json

* add child_size for itemized_carrier

* fix bioyond resource io

---------

Co-authored-by: h840473807 <47357934+h840473807@users.noreply.github.com>
Co-authored-by: Xie Qiming <97236197+Andy6M@users.noreply.github.com>

* 更新物料接口

* Workstation dev yb2 (#100)

* Refactor and extend reaction station action messages

* Refactor dispensing station tasks to enhance parameter clarity and add batch processing capabilities

- Updated `create_90_10_vial_feeding_task` to include detailed parameters for 90%/10% vial feeding, improving clarity and usability.
- Introduced `create_batch_90_10_vial_feeding_task` for batch processing of 90%/10% vial feeding tasks with JSON formatted input.
- Added `create_batch_diamine_solution_task` for batch preparation of diamine solution, also utilizing JSON formatted input.
- Refined `create_diamine_solution_task` to include additional parameters for better task configuration.
- Enhanced schema descriptions and default values for improved user guidance.

* 修复to_plr_resources

* add update remove

* 支持选择器注册表自动生成
支持转运物料

* 修复资源添加

* 修复transfer_resource_to_another生成

* 更新transfer_resource_to_another参数,支持spot入参

* 新增test_resource动作

* fix host_node error

* fix host_node test_resource error

* fix host_node test_resource error

* 过滤本地动作

* 移动内部action以兼容host node

* 修复同步任务报错不显示的bug

* feat: 允许返回非本节点物料,后面可以通过decoration进行区分,就不进行warning了

* update todo

* modify bioyond/plr converter, bioyond resource registry, and tests

* pass the tests

* update todo

* add conda-pack-build.yml

* add auto install script for conda-pack-build.yml

(cherry picked from commit 172599adcf)

* update conda-pack-build.yml

* update conda-pack-build.yml

* update conda-pack-build.yml

* update conda-pack-build.yml

* update conda-pack-build.yml

* Add version in __init__.py
Update conda-pack-build.yml
Add create_zip_archive.py

* Update conda-pack-build.yml

* Update conda-pack-build.yml (with mamba)

* Update conda-pack-build.yml

* Fix FileNotFoundError

* Try fix 'charmap' codec can't encode characters in position 16-23: character maps to <undefined>

* Fix unilabos msgs search error

* Fix environment_check.py

* Update recipe.yaml

* Update registry. Update uuid loop figure method. Update install docs.

* Fix nested conda pack

* Fix one-key installation path error

* Bump version to 0.10.7

* Workshop bj (#99)

* Add LaiYu Liquid device integration and tests

Introduce LaiYu Liquid device implementation, including backend, controllers, drivers, configuration, and resource files. Add hardware connection, tip pickup, and simplified test scripts, as well as experiment and registry configuration for LaiYu Liquid. Documentation and .gitignore for the device are also included.

* feat(LaiYu_Liquid): 重构设备模块结构并添加硬件文档

refactor: 重新组织LaiYu_Liquid模块目录结构
docs: 添加SOPA移液器和步进电机控制指令文档
fix: 修正设备配置中的最大体积默认值
test: 新增工作台配置测试用例
chore: 删除过时的测试脚本和配置文件

* add

* 重构: 将 LaiYu_Liquid.py 重命名为 laiyu_liquid_main.py 并更新所有导入引用

- 使用 git mv 将 LaiYu_Liquid.py 重命名为 laiyu_liquid_main.py
- 更新所有相关文件中的导入引用
- 保持代码功能不变,仅改善命名一致性
- 测试确认所有导入正常工作

* 修复: 在 core/__init__.py 中添加 LaiYuLiquidBackend 导出

- 添加 LaiYuLiquidBackend 到导入列表
- 添加 LaiYuLiquidBackend 到 __all__ 导出列表
- 确保所有主要类都可以正确导入

* 修复大小写文件夹名字

* 电池装配工站二次开发教程(带目录)上传至dev (#94)

* 电池装配工站二次开发教程

* Update intro.md

* 物料教程

* 更新物料教程,json格式注释

* Update prcxi driver & fix transfer_liquid mix_times (#90)

* Update prcxi driver & fix transfer_liquid mix_times

* fix: correct mix_times type

* Update liquid_handler registry

* test: prcxi.py

* Update registry from pr

* fix ony-key script not exist

* clean files

---------

Co-authored-by: Junhan Chang <changjh@dp.tech>
Co-authored-by: ZiWei <131428629+ZiWei09@users.noreply.github.com>
Co-authored-by: Guangxin Zhang <guangxin.zhang.bio@gmail.com>
Co-authored-by: Xie Qiming <97236197+Andy6M@users.noreply.github.com>
Co-authored-by: h840473807 <47357934+h840473807@users.noreply.github.com>
Co-authored-by: LccLink <1951855008@qq.com>
Co-authored-by: lixinyu1011 <61094742+lixinyu1011@users.noreply.github.com>
Co-authored-by: shiyubo0410 <shiyubo@dp.tech>
2025-10-12 23:34:26 +08:00
82 changed files with 9032 additions and 61483 deletions

View File

@@ -42,7 +42,7 @@ jobs:
defaults: defaults:
run: run:
# Windows uses cmd for better conda/mamba compatibility, Unix uses bash # Windows uses cmd for better conda/mamba compatibility, Unix uses bash
shell: ${{ matrix.platform == 'win-64' && 'cmd' || 'bash' }} shell: ${{ matrix.platform == 'win-64' && 'cmd /C CALL {0}' || 'bash -el {0}' }}
steps: steps:
- name: Check if platform should be built - name: Check if platform should be built
@@ -73,6 +73,7 @@ jobs:
channels: conda-forge,robostack-staging,uni-lab,defaults channels: conda-forge,robostack-staging,uni-lab,defaults
channel-priority: flexible channel-priority: flexible
activate-environment: unilab activate-environment: unilab
auto-activate-base: true
auto-update-conda: false auto-update-conda: false
show-channel-urls: true show-channel-urls: true
@@ -81,7 +82,7 @@ jobs:
run: | run: |
echo Installing unilabos and dependencies to unilab environment... echo Installing unilabos and dependencies to unilab environment...
echo Using mamba for faster and more reliable dependency resolution... echo Using mamba for faster and more reliable dependency resolution...
mamba install -n unilab uni-lab::unilabos conda-pack -c uni-lab -c robostack-staging -c conda-forge -y mamba install uni-lab::unilabos conda-pack -c uni-lab -c robostack-staging -c conda-forge -y
- name: Install conda-pack, unilabos and dependencies (Unix) - name: Install conda-pack, unilabos and dependencies (Unix)
if: steps.should_build.outputs.should_build == 'true' && matrix.platform != 'win-64' if: steps.should_build.outputs.should_build == 'true' && matrix.platform != 'win-64'
@@ -89,15 +90,15 @@ jobs:
run: | run: |
echo "Installing unilabos and dependencies to unilab environment..." echo "Installing unilabos and dependencies to unilab environment..."
echo "Using mamba for faster and more reliable dependency resolution..." echo "Using mamba for faster and more reliable dependency resolution..."
mamba install -n unilab uni-lab::unilabos conda-pack -c uni-lab -c robostack-staging -c conda-forge -y mamba install uni-lab::unilabos conda-pack -c uni-lab -c robostack-staging -c conda-forge -y
- name: Get latest ros-humble-unilabos-msgs version (Windows) - name: Get latest ros-humble-unilabos-msgs version (Windows)
if: steps.should_build.outputs.should_build == 'true' && matrix.platform == 'win-64' if: steps.should_build.outputs.should_build == 'true' && matrix.platform == 'win-64'
id: msgs_version_win id: msgs_version_win
run: | run: |
echo Checking installed ros-humble-unilabos-msgs version... echo Checking installed ros-humble-unilabos-msgs version...
conda list -n unilab ros-humble-unilabos-msgs conda list ros-humble-unilabos-msgs
for /f "tokens=2" %%i in ('conda list -n unilab ros-humble-unilabos-msgs --json ^| python -c "import sys, json; pkgs=json.load(sys.stdin); print(pkgs[0]['version'] if pkgs else 'not-found')"') do set VERSION=%%i for /f "tokens=2" %%i in ('conda list ros-humble-unilabos-msgs --json ^| python -c "import sys, json; pkgs=json.load(sys.stdin); print(pkgs[0]['version'] if pkgs else 'not-found')"') do set VERSION=%%i
echo installed_version=%VERSION% >> %GITHUB_OUTPUT% echo installed_version=%VERSION% >> %GITHUB_OUTPUT%
echo Installed ros-humble-unilabos-msgs version: %VERSION% echo Installed ros-humble-unilabos-msgs version: %VERSION%
@@ -107,7 +108,7 @@ jobs:
shell: bash shell: bash
run: | run: |
echo "Checking installed ros-humble-unilabos-msgs version..." echo "Checking installed ros-humble-unilabos-msgs version..."
VERSION=$(conda list -n unilab ros-humble-unilabos-msgs --json | python -c "import sys, json; pkgs=json.load(sys.stdin); print(pkgs[0]['version'] if pkgs else 'not-found')") VERSION=$(conda list ros-humble-unilabos-msgs --json | python -c "import sys, json; pkgs=json.load(sys.stdin); print(pkgs[0]['version'] if pkgs else 'not-found')")
echo "installed_version=$VERSION" >> $GITHUB_OUTPUT echo "installed_version=$VERSION" >> $GITHUB_OUTPUT
echo "Installed ros-humble-unilabos-msgs version: $VERSION" echo "Installed ros-humble-unilabos-msgs version: $VERSION"
@@ -118,7 +119,7 @@ jobs:
mamba search ros-humble-unilabos-msgs -c uni-lab -c robostack-staging -c conda-forge || echo Search completed mamba search ros-humble-unilabos-msgs -c uni-lab -c robostack-staging -c conda-forge || echo Search completed
echo. echo.
echo Updating ros-humble-unilabos-msgs to latest version... echo Updating ros-humble-unilabos-msgs to latest version...
mamba update -n unilab ros-humble-unilabos-msgs -c uni-lab -c robostack-staging -c conda-forge -y || echo Already at latest version mamba update ros-humble-unilabos-msgs -c uni-lab -c robostack-staging -c conda-forge -y || echo Already at latest version
- name: Check for newer ros-humble-unilabos-msgs (Unix) - name: Check for newer ros-humble-unilabos-msgs (Unix)
if: steps.should_build.outputs.should_build == 'true' && matrix.platform != 'win-64' if: steps.should_build.outputs.should_build == 'true' && matrix.platform != 'win-64'
@@ -128,65 +129,65 @@ jobs:
mamba search ros-humble-unilabos-msgs -c uni-lab -c robostack-staging -c conda-forge || echo "Search completed" mamba search ros-humble-unilabos-msgs -c uni-lab -c robostack-staging -c conda-forge || echo "Search completed"
echo "" echo ""
echo "Updating ros-humble-unilabos-msgs to latest version..." echo "Updating ros-humble-unilabos-msgs to latest version..."
mamba update -n unilab ros-humble-unilabos-msgs -c uni-lab -c robostack-staging -c conda-forge -y || echo "Already at latest version" mamba update ros-humble-unilabos-msgs -c uni-lab -c robostack-staging -c conda-forge -y || echo "Already at latest version"
- name: Install latest unilabos from source (Windows) - name: Install latest unilabos from source (Windows)
if: steps.should_build.outputs.should_build == 'true' && matrix.platform == 'win-64' if: steps.should_build.outputs.should_build == 'true' && matrix.platform == 'win-64'
run: | run: |
echo Uninstalling existing unilabos... echo Uninstalling existing unilabos...
mamba run -n unilab pip uninstall unilabos -y || echo unilabos not installed via pip pip uninstall unilabos -y || echo unilabos not installed via pip
echo Installing unilabos from source (branch: ${{ github.event.inputs.branch }})... echo Installing unilabos from source (branch: ${{ github.event.inputs.branch }})...
mamba run -n unilab pip install . pip install .
echo Verifying installation... echo Verifying installation...
mamba run -n unilab pip show unilabos pip show unilabos
- name: Install latest unilabos from source (Unix) - name: Install latest unilabos from source (Unix)
if: steps.should_build.outputs.should_build == 'true' && matrix.platform != 'win-64' if: steps.should_build.outputs.should_build == 'true' && matrix.platform != 'win-64'
shell: bash shell: bash
run: | run: |
echo "Uninstalling existing unilabos..." echo "Uninstalling existing unilabos..."
mamba run -n unilab pip uninstall unilabos -y || echo "unilabos not installed via pip" pip uninstall unilabos -y || echo "unilabos not installed via pip"
echo "Installing unilabos from source (branch: ${{ github.event.inputs.branch }})..." echo "Installing unilabos from source (branch: ${{ github.event.inputs.branch }})..."
mamba run -n unilab pip install . pip install .
echo "Verifying installation..." echo "Verifying installation..."
mamba run -n unilab pip show unilabos pip show unilabos
- name: Display environment info (Windows) - name: Display environment info (Windows)
if: steps.should_build.outputs.should_build == 'true' && matrix.platform == 'win-64' if: steps.should_build.outputs.should_build == 'true' && matrix.platform == 'win-64'
run: | run: |
echo === Environment Information === echo === Environment Information ===
mamba env list conda env list
echo. echo.
echo === Installed Packages === echo === Installed Packages ===
mamba list -n unilab | findstr /C:"unilabos" /C:"ros-humble-unilabos-msgs" || mamba list -n unilab conda list | findstr /C:"unilabos" /C:"ros-humble-unilabos-msgs" || conda list
echo. echo.
echo === Python Packages === echo === Python Packages ===
mamba run -n unilab pip list | findstr unilabos || mamba run -n unilab pip list pip list | findstr unilabos || pip list
- name: Display environment info (Unix) - name: Display environment info (Unix)
if: steps.should_build.outputs.should_build == 'true' && matrix.platform != 'win-64' if: steps.should_build.outputs.should_build == 'true' && matrix.platform != 'win-64'
shell: bash shell: bash
run: | run: |
echo "=== Environment Information ===" echo "=== Environment Information ==="
mamba env list conda env list
echo "" echo ""
echo "=== Installed Packages ===" echo "=== Installed Packages ==="
mamba list -n unilab | grep -E "(unilabos|ros-humble-unilabos-msgs)" || mamba list -n unilab conda list | grep -E "(unilabos|ros-humble-unilabos-msgs)" || conda list
echo "" echo ""
echo "=== Python Packages ===" echo "=== Python Packages ==="
mamba run -n unilab pip list | grep unilabos || mamba run -n unilab pip list pip list | grep unilabos || pip list
- name: Verify environment integrity (Windows) - name: Verify environment integrity (Windows)
if: steps.should_build.outputs.should_build == 'true' && matrix.platform == 'win-64' if: steps.should_build.outputs.should_build == 'true' && matrix.platform == 'win-64'
run: | run: |
echo Verifying Python version... echo Verifying Python version...
mamba run -n unilab python -c "import sys; print(f'Python version: {sys.version}')" python -c "import sys; print(f'Python version: {sys.version}')"
echo Verifying unilabos import... echo Verifying unilabos import...
mamba run -n unilab python -c "import unilabos; print(f'UniLabOS version: {unilabos.__version__}')" || echo Warning: Could not import unilabos python -c "import unilabos; print(f'UniLabOS version: {unilabos.__version__}')" || echo Warning: Could not import unilabos
echo Checking critical packages... echo Checking critical packages...
mamba run -n unilab python -c "import rclpy; print('ROS2 rclpy: OK')" python -c "import rclpy; print('ROS2 rclpy: OK')"
echo Running comprehensive verification script... echo Running comprehensive verification script...
mamba run -n unilab python scripts\verify_installation.py --auto-install || echo Warning: Verification script reported issues python scripts\verify_installation.py || echo Warning: Verification script reported issues
echo Environment verification complete! echo Environment verification complete!
- name: Verify environment integrity (Unix) - name: Verify environment integrity (Unix)
@@ -194,20 +195,20 @@ jobs:
shell: bash shell: bash
run: | run: |
echo "Verifying Python version..." echo "Verifying Python version..."
mamba run -n unilab python -c "import sys; print(f'Python version: {sys.version}')" python -c "import sys; print(f'Python version: {sys.version}')"
echo "Verifying unilabos import..." echo "Verifying unilabos import..."
mamba run -n unilab python -c "import unilabos; print(f'UniLabOS version: {unilabos.__version__}')" || echo "Warning: Could not import unilabos" python -c "import unilabos; print(f'UniLabOS version: {unilabos.__version__}')" || echo "Warning: Could not import unilabos"
echo "Checking critical packages..." echo "Checking critical packages..."
mamba run -n unilab python -c "import rclpy; print('ROS2 rclpy: OK')" python -c "import rclpy; print('ROS2 rclpy: OK')"
echo "Running comprehensive verification script..." echo "Running comprehensive verification script..."
mamba run -n unilab python scripts/verify_installation.py --auto-install || echo "Warning: Verification script reported issues" python scripts/verify_installation.py || echo "Warning: Verification script reported issues"
echo "Environment verification complete!" echo "Environment verification complete!"
- name: Pack conda environment (Windows) - name: Pack conda environment (Windows)
if: steps.should_build.outputs.should_build == 'true' && matrix.platform == 'win-64' if: steps.should_build.outputs.should_build == 'true' && matrix.platform == 'win-64'
run: | run: |
echo Packing unilab environment with conda-pack... echo Packing unilab environment with conda-pack...
mamba activate unilab && conda pack -n unilab -o unilab-env-${{ matrix.platform }}.tar.gz --ignore-missing-files conda pack -n unilab -o unilab-env-${{ matrix.platform }}.tar.gz --ignore-missing-files
echo Pack file created: echo Pack file created:
dir unilab-env-${{ matrix.platform }}.tar.gz dir unilab-env-${{ matrix.platform }}.tar.gz
@@ -216,7 +217,6 @@ jobs:
shell: bash shell: bash
run: | run: |
echo "Packing unilab environment with conda-pack..." echo "Packing unilab environment with conda-pack..."
mamba install conda-pack -c conda-forge -y
conda pack -n unilab -o unilab-env-${{ matrix.platform }}.tar.gz --ignore-missing-files conda pack -n unilab -o unilab-env-${{ matrix.platform }}.tar.gz --ignore-missing-files
echo "Pack file created:" echo "Pack file created:"
ls -lh unilab-env-${{ matrix.platform }}.tar.gz ls -lh unilab-env-${{ matrix.platform }}.tar.gz
@@ -242,10 +242,6 @@ jobs:
echo Adding: verify_installation.py echo Adding: verify_installation.py
copy scripts\verify_installation.py dist-package\ copy scripts\verify_installation.py dist-package\
rem Copy source code repository (including .git)
echo Adding: Uni-Lab-OS source repository
robocopy . dist-package\Uni-Lab-OS /E /XD dist-package /NFL /NDL /NJH /NJS /NC /NS || if %ERRORLEVEL% LSS 8 exit /b 0
rem Create README using Python script rem Create README using Python script
echo Creating: README.txt echo Creating: README.txt
python scripts\create_readme.py ${{ matrix.platform }} ${{ github.event.inputs.branch }} dist-package\README.txt python scripts\create_readme.py ${{ matrix.platform }} ${{ github.event.inputs.branch }} dist-package\README.txt
@@ -278,10 +274,6 @@ jobs:
echo "Adding: verify_installation.py" echo "Adding: verify_installation.py"
cp scripts/verify_installation.py dist-package/ cp scripts/verify_installation.py dist-package/
# Copy source code repository (including .git)
echo "Adding: Uni-Lab-OS source repository"
rsync -a --exclude='dist-package' . dist-package/Uni-Lab-OS
# Create README using Python script # Create README using Python script
echo "Creating: README.txt" echo "Creating: README.txt"
python scripts/create_readme.py ${{ matrix.platform }} ${{ github.event.inputs.branch }} dist-package/README.txt python scripts/create_readme.py ${{ matrix.platform }} ${{ github.event.inputs.branch }} dist-package/README.txt
@@ -291,6 +283,46 @@ jobs:
ls -lh dist-package/ ls -lh dist-package/
echo "" echo ""
- name: Finalize Windows distribution package
if: steps.should_build.outputs.should_build == 'true' && matrix.platform == 'win-64'
run: |
echo ==========================================
echo Windows distribution package ready
echo.
echo Package will be uploaded as artifact
echo GitHub Actions will automatically create ZIP
echo.
echo Contents:
dir /b dist-package
echo.
echo Users will download a ZIP containing:
echo - install_unilab.bat
echo - unilab-env-${{ matrix.platform }}.tar.gz
echo - verify_installation.py
echo - README.txt
echo ==========================================
- name: Create Unix/Linux TAR.GZ archive
if: steps.should_build.outputs.should_build == 'true' && matrix.platform != 'win-64'
shell: bash
run: |
echo "=========================================="
echo "Creating Unix/Linux TAR.GZ archive..."
echo "Archive: unilab-pack-${{ matrix.platform }}.tar.gz"
echo "Contents: install_unilab.sh + unilab-env-${{ matrix.platform }}.tar.gz + extras"
tar -czf unilab-pack-${{ matrix.platform }}.tar.gz -C dist-package .
echo "=========================================="
echo ""
echo "Final package created:"
ls -lh unilab-pack-*
echo ""
echo "Users can now:"
echo " 1. Download unilab-pack-${{ matrix.platform }}.tar.gz"
echo " 2. Extract it: tar -xzf unilab-pack-${{ matrix.platform }}.tar.gz"
echo " 3. Run: bash install_unilab.sh"
echo ""
- name: Upload distribution package - name: Upload distribution package
if: steps.should_build.outputs.should_build == 'true' if: steps.should_build.outputs.should_build == 'true'
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
@@ -333,8 +365,12 @@ jobs:
echo "Distribution package contents:" echo "Distribution package contents:"
ls -lh dist-package/ ls -lh dist-package/
echo "" echo ""
echo "Package size (tar.gz):"
ls -lh unilab-pack-*.tar.gz
echo ""
echo "Artifact name: unilab-pack-${{ matrix.platform }}-${{ github.event.inputs.branch }}" echo "Artifact name: unilab-pack-${{ matrix.platform }}-${{ github.event.inputs.branch }}"
echo "" echo ""
echo "After download:" echo "After download:"
echo " install_unilab.sh" echo " - Windows/macOS: Extract ZIP, then: tar -xzf unilab-pack-${{ matrix.platform }}.tar.gz"
echo " - Linux: Extract ZIP (or download tar.gz directly), run install_unilab.sh"
echo "==========================================" echo "=========================================="

View File

@@ -39,39 +39,24 @@ jobs:
uses: actions/checkout@v4 uses: actions/checkout@v4
with: with:
ref: ${{ github.event.inputs.branch || github.ref }} ref: ${{ github.event.inputs.branch || github.ref }}
fetch-depth: 0
- name: Setup Miniforge (with mamba) - name: Setup Python environment
uses: conda-incubator/setup-miniconda@v3 uses: actions/setup-python@v5
with: with:
miniforge-version: latest python-version: '3.10'
use-mamba: true
python-version: '3.11.11'
channels: conda-forge,robostack-staging,uni-lab,defaults
channel-priority: flexible
activate-environment: unilab
auto-update-conda: false
show-channel-urls: true
- name: Install unilabos and dependencies - name: Install system dependencies
run: | run: |
echo "Installing unilabos and dependencies to unilab environment..." sudo apt-get update
echo "Using mamba for faster and more reliable dependency resolution..." sudo apt-get install -y pandoc
mamba install -n unilab uni-lab::unilabos -c uni-lab -c robostack-staging -c conda-forge -y
- name: Install latest unilabos from source - name: Install Python dependencies
run: | run: |
echo "Uninstalling existing unilabos..." python -m pip install --upgrade pip
mamba run -n unilab pip uninstall unilabos -y || echo "unilabos not installed via pip" # Install package in development mode to get version info
echo "Installing unilabos from source..." pip install -e .
mamba run -n unilab pip install . # Install documentation dependencies
echo "Verifying installation..." pip install -r docs/requirements.txt
mamba run -n unilab pip show unilabos
- name: Install documentation dependencies
run: |
echo "Installing documentation build dependencies..."
mamba run -n unilab pip install -r docs/requirements.txt
- name: Setup Pages - name: Setup Pages
id: pages id: pages
@@ -83,8 +68,8 @@ jobs:
cd docs cd docs
# Clean previous builds # Clean previous builds
rm -rf _build rm -rf _build
# Build HTML documentation in conda environment # Build HTML documentation
mamba run -n unilab python -m sphinx -b html . _build/html -v python -m sphinx -b html . _build/html -v
- name: Check build results - name: Check build results
run: | run: |

View File

@@ -31,7 +31,7 @@ Join the [Intelligent Organic Chemistry Synthesis Competition](https://bohrium.d
Detailed documentation can be found at: Detailed documentation can be found at:
- [Online Documentation](https://xuwznln.github.io/Uni-Lab-OS-Doc/) - [Online Documentation](https://dptech-corp.github.io/Uni-Lab-OS/)
## Quick Start ## Quick Start
@@ -55,7 +55,7 @@ pip install .
3. Start Uni-Lab System: 3. Start Uni-Lab System:
Please refer to [Documentation - Boot Examples](https://xuwznln.github.io/Uni-Lab-OS-Doc/boot_examples/index.html) Please refer to [Documentation - Boot Examples](https://dptech-corp.github.io/Uni-Lab-OS/boot_examples/index.html)
## Message Format ## Message Format

View File

@@ -31,7 +31,7 @@ Uni-Lab-OS 是一个用于实验室自动化的综合平台,旨在连接和控
详细文档可在以下位置找到: 详细文档可在以下位置找到:
- [在线文档](https://xuwznln.github.io/Uni-Lab-OS-Doc/) - [在线文档](https://dptech-corp.github.io/Uni-Lab-OS/)
## 快速开始 ## 快速开始
@@ -57,7 +57,7 @@ pip install .
3. 启动 Uni-Lab 系统: 3. 启动 Uni-Lab 系统:
请见[文档-启动样例](https://xuwznln.github.io/Uni-Lab-OS-Doc/boot_examples/index.html) 请见[文档-启动样例](https://dptech-corp.github.io/Uni-Lab-OS/boot_examples/index.html)
## 消息格式 ## 消息格式

File diff suppressed because it is too large Load Diff

View File

@@ -91,7 +91,7 @@
使用以下命令启动模拟反应器: 使用以下命令启动模拟反应器:
```bash ```bash
unilab -g test/experiments/mock_reactor.json unilab -g test/experiments/mock_reactor.json --app_bridges ""
``` ```
### 2. 执行抽真空和充气操作 ### 2. 执行抽真空和充气操作

View File

@@ -23,7 +23,7 @@ extensions = [
"myst_parser", "myst_parser",
"sphinx.ext.autodoc", "sphinx.ext.autodoc",
"sphinx.ext.napoleon", # 如果您使用 Google 或 NumPy 风格的 docstrings "sphinx.ext.napoleon", # 如果您使用 Google 或 NumPy 风格的 docstrings
"sphinx_rtd_theme", "sphinx_rtd_theme"
] ]
source_suffix = { source_suffix = {

View File

@@ -172,7 +172,7 @@ Examples:
with open(output_path, "w", encoding="utf-8") as f: with open(output_path, "w", encoding="utf-8") as f:
f.write(readme_content) f.write(readme_content)
print(f" README.txt created: {output_path}") print(f" README.txt created: {output_path}")
print(f" Platform: {args.platform}") print(f" Platform: {args.platform}")
print(f" Branch: {args.branch}") print(f" Branch: {args.branch}")

View File

@@ -8,10 +8,7 @@ This script verifies that UniLabOS and its dependencies are correctly installed.
Run this script after installing the conda-pack environment to ensure everything works. Run this script after installing the conda-pack environment to ensure everything works.
Usage: Usage:
python verify_installation.py [--auto-install] python verify_installation.py
Options:
--auto-install Automatically install missing packages
Or in the conda environment: Or in the conda environment:
conda activate unilab conda activate unilab
@@ -20,15 +17,14 @@ Usage:
import sys import sys
import os import os
import argparse
# IMPORTANT: Set UTF-8 encoding BEFORE any other imports # IMPORTANT: Set UTF-8 encoding BEFORE any other imports
# This ensures all subsequent imports (including unilabos) can output UTF-8 characters # This ensures all subsequent imports (including unilabos) can output UTF-8 characters
if sys.platform == "win32": if sys.platform == "win32":
# Method 1: Reconfigure stdout/stderr to use UTF-8 with error handling # Method 1: Reconfigure stdout/stderr to use UTF-8 with error handling
try: try:
sys.stdout.reconfigure(encoding="utf-8", errors="replace") # type: ignore sys.stdout.reconfigure(encoding="utf-8", errors="replace")
sys.stderr.reconfigure(encoding="utf-8", errors="replace") # type: ignore sys.stderr.reconfigure(encoding="utf-8", errors="replace")
except (AttributeError, OSError): except (AttributeError, OSError):
pass pass
@@ -53,7 +49,7 @@ CHECK_MARK = "[OK]"
CROSS_MARK = "[FAIL]" CROSS_MARK = "[FAIL]"
def check_package(package_name: str, display_name: str | None = None) -> bool: def check_package(package_name: str, display_name: str = None) -> bool:
""" """
Check if a package can be imported. Check if a package can be imported.
@@ -91,25 +87,9 @@ def check_python_version() -> bool:
def main(): def main():
"""Run all verification checks.""" """Run all verification checks."""
# Parse command line arguments
parser = argparse.ArgumentParser(
description="Verify UniLabOS installation",
formatter_class=argparse.RawDescriptionHelpFormatter,
)
parser.add_argument(
"--auto-install",
action="store_true",
help="Automatically install missing packages",
)
args = parser.parse_args()
print("=" * 60) print("=" * 60)
print("UniLabOS Installation Verification") print("UniLabOS Installation Verification")
print("=" * 60) print("=" * 60)
if args.auto_install:
print("Mode: Auto-install missing packages")
else:
print("Mode: Verification only")
print() print()
all_passed = True all_passed = True
@@ -133,16 +113,14 @@ def main():
print(f" {CHECK_MARK} UniLabOS installed") print(f" {CHECK_MARK} UniLabOS installed")
# Check environment with optional auto-install # Check environment without auto-install (verification only)
# Set show_details=False to suppress detailed Chinese output that may cause encoding issues # Set show_details=False to suppress detailed Chinese output that may cause encoding issues
env_check_passed = check_environment(auto_install=args.auto_install, show_details=False) env_check_passed = check_environment(auto_install=False, show_details=False)
if env_check_passed: if env_check_passed:
print(f" {CHECK_MARK} All required packages available") print(f" {CHECK_MARK} All required packages available")
else: else:
print(f" {CROSS_MARK} Some optional packages are missing") print(f" {CROSS_MARK} Some optional packages are missing")
if not args.auto_install:
print(" Hint: Run with --auto-install to automatically install missing packages")
except ImportError: except ImportError:
print(f" {CROSS_MARK} UniLabOS not installed") print(f" {CROSS_MARK} UniLabOS not installed")
all_passed = False all_passed = False

View File

@@ -170,16 +170,15 @@
"z": 0 "z": 0
}, },
"config": { "config": {
"max_volume": 1000.0, "max_volume": 1000.0
"type": "RegularContainer",
"category": "container",
"size_x": 200,
"size_y": 150,
"size_z": 0
}, },
"data": { "data": {
"liquids": [["DMF", 500.0]], "liquids": [
"pending_liquids": [["DMF", 500.0]] {
"liquid_type": "DMF",
"liquid_volume": 1000.0
}
]
} }
}, },
{ {
@@ -195,16 +194,15 @@
"z": 0 "z": 0
}, },
"config": { "config": {
"max_volume": 1000.0, "max_volume": 1000.0
"type": "RegularContainer",
"category": "container",
"size_x": 200,
"size_y": 150,
"size_z": 0
}, },
"data": { "data": {
"liquids": [["ethyl_acetate", 1000.0]], "liquids": [
"pending_liquids": [["ethyl_acetate", 1000.0]] {
"liquid_type": "ethyl_acetate",
"liquid_volume": 1000.0
}
]
} }
}, },
{ {
@@ -220,16 +218,15 @@
"z": 0 "z": 0
}, },
"config": { "config": {
"max_volume": 1000.0, "max_volume": 1000.0
"type": "RegularContainer",
"category": "container",
"size_x": 300,
"size_y": 150,
"size_z": 0
}, },
"data": { "data": {
"liquids": [["hexane", 1000.0]], "liquids": [
"pending_liquids": [["hexane", 1000.0]] {
"liquid_type": "hexane",
"liquid_volume": 1000.0
}
]
} }
}, },
{ {
@@ -245,16 +242,15 @@
"z": 0 "z": 0
}, },
"config": { "config": {
"max_volume": 1000.0, "max_volume": 1000.0
"type": "RegularContainer",
"category": "container",
"size_x": 900,
"size_y": 150,
"size_z": 0
}, },
"data": { "data": {
"liquids": [["methanol", 1000.0]], "liquids": [
"pending_liquids": [["methanol", 1000.0]] {
"liquid_type": "methanol",
"liquid_volume": 1000.0
}
]
} }
}, },
{ {
@@ -270,16 +266,15 @@
"z": 0 "z": 0
}, },
"config": { "config": {
"max_volume": 1000.0, "max_volume": 1000.0
"type": "RegularContainer",
"category": "container",
"size_x": 950,
"size_y": 150,
"size_z": 0
}, },
"data": { "data": {
"liquids": [["water", 1000.0]], "liquids": [
"pending_liquids": [["water", 1000.0]] {
"liquid_type": "water",
"liquid_volume": 1000.0
}
]
} }
}, },
{ {
@@ -340,16 +335,14 @@
}, },
"config": { "config": {
"max_volume": 500.0, "max_volume": 500.0,
"type": "RegularContainer",
"category": "container",
"max_temp": 200.0, "max_temp": 200.0,
"min_temp": -20.0, "min_temp": -20.0,
"has_stirrer": true, "has_stirrer": true,
"has_heater": true "has_heater": true
}, },
"data": { "data": {
"liquids": [], "liquids": [
"pending_liquids": [] ]
} }
}, },
{ {
@@ -426,16 +419,11 @@
"z": 0 "z": 0
}, },
"config": { "config": {
"max_volume": 2000.0, "max_volume": 2000.0
"type": "RegularContainer",
"category": "container",
"size_x": 500,
"size_y": 400,
"size_z": 0
}, },
"data": { "data": {
"liquids": [], "liquids": [
"pending_liquids": [] ]
} }
}, },
{ {
@@ -451,16 +439,11 @@
"z": 0 "z": 0
}, },
"config": { "config": {
"max_volume": 2000.0, "max_volume": 2000.0
"type": "RegularContainer",
"category": "container",
"size_x": 1100,
"size_y": 500,
"size_z": 0
}, },
"data": { "data": {
"liquids": [], "liquids": [
"pending_liquids": [] ]
} }
}, },
{ {
@@ -666,16 +649,11 @@
"z": 0 "z": 0
}, },
"config": { "config": {
"max_volume": 250.0, "max_volume": 250.0
"type": "RegularContainer",
"category": "container",
"size_x": 900,
"size_y": 500,
"size_z": 0
}, },
"data": { "data": {
"liquids": [], "liquids": [
"pending_liquids": [] ]
} }
}, },
{ {
@@ -691,16 +669,11 @@
"z": 0 "z": 0
}, },
"config": { "config": {
"max_volume": 250.0, "max_volume": 250.0
"type": "RegularContainer",
"category": "container",
"size_x": 950,
"size_y": 500,
"size_z": 0
}, },
"data": { "data": {
"liquids": [], "liquids": [
"pending_liquids": [] ]
} }
}, },
{ {
@@ -716,16 +689,11 @@
"z": 0 "z": 0
}, },
"config": { "config": {
"max_volume": 250.0, "max_volume": 250.0
"type": "RegularContainer",
"category": "container",
"size_x": 1050,
"size_y": 500,
"size_z": 0
}, },
"data": { "data": {
"liquids": [], "liquids": [
"pending_liquids": [] ]
} }
}, },
{ {
@@ -765,11 +733,6 @@
}, },
"config": { "config": {
"max_volume": 500.0, "max_volume": 500.0,
"size_x": 550,
"size_y": 250,
"size_z": 0,
"type": "RegularContainer",
"category": "container",
"reagent": "sodium_chloride", "reagent": "sodium_chloride",
"physical_state": "solid" "physical_state": "solid"
}, },
@@ -793,11 +756,6 @@
}, },
"config": { "config": {
"volume": 500.0, "volume": 500.0,
"size_x": 600,
"size_y": 250,
"size_z": 0,
"type": "RegularContainer",
"category": "container",
"reagent": "sodium_carbonate", "reagent": "sodium_carbonate",
"physical_state": "solid" "physical_state": "solid"
}, },
@@ -821,11 +779,6 @@
}, },
"config": { "config": {
"volume": 500.0, "volume": 500.0,
"size_x": 650,
"size_y": 250,
"size_z": 0,
"type": "RegularContainer",
"category": "container",
"reagent": "magnesium_chloride", "reagent": "magnesium_chloride",
"physical_state": "solid" "physical_state": "solid"
}, },

View File

@@ -8,7 +8,7 @@
], ],
"parent": null, "parent": null,
"type": "device", "type": "device",
"class": "workstation.bioyond_dispensing_station", "class": "dispensing_station.bioyond",
"config": { "config": {
"config": { "config": {
"api_key": "DE9BDDA0", "api_key": "DE9BDDA0",
@@ -20,6 +20,13 @@
"_resource_type": "unilabos.resources.bioyond.decks:BIOYOND_PolymerPreparationStation_Deck" "_resource_type": "unilabos.resources.bioyond.decks:BIOYOND_PolymerPreparationStation_Deck"
} }
}, },
"station_config": {
"station_type": "dispensing_station",
"enable_dispensing_station": true,
"enable_reaction_station": false,
"station_name": "DispensingStation_001",
"description": "Bioyond配液工作站"
},
"protocol_type": [] "protocol_type": []
}, },
"data": {} "data": {}

View File

@@ -10,7 +10,7 @@
"type": "device", "type": "device",
"class": "reaction_station.bioyond", "class": "reaction_station.bioyond",
"config": { "config": {
"config": { "bioyond_config": {
"api_key": "DE9BDDA0", "api_key": "DE9BDDA0",
"api_host": "http://192.168.1.200:44402", "api_host": "http://192.168.1.200:44402",
"workflow_mappings": { "workflow_mappings": {
@@ -19,18 +19,14 @@
"Solid_feeding_vials": "3a160877-87e7-7699-7bc6-ec72b05eb5e6", "Solid_feeding_vials": "3a160877-87e7-7699-7bc6-ec72b05eb5e6",
"Liquid_feeding_vials(non-titration)": "3a167d99-6158-c6f0-15b5-eb030f7d8e47", "Liquid_feeding_vials(non-titration)": "3a167d99-6158-c6f0-15b5-eb030f7d8e47",
"Liquid_feeding_solvents": "3a160824-0665-01ed-285a-51ef817a9046", "Liquid_feeding_solvents": "3a160824-0665-01ed-285a-51ef817a9046",
"Liquid_feeding(titration)": "3a16082a-96ac-0449-446a-4ed39f3365b6", "Liquid_feeding(titration)": "3a160824-0665-01ed-285a-51ef817a9046",
"liquid_feeding_beaker": "3a16087e-124f-8ddb-8ec1-c2dff09ca784", "Liquid_feeding_beaker": "3a16087e-124f-8ddb-8ec1-c2dff09ca784",
"Drip_back": "3a162cf9-6aac-565a-ddd7-682ba1796a4a" "Drip_back": "3a162cf9-6aac-565a-ddd7-682ba1796a4a"
}, },
"material_type_mappings": { "material_type_mappings": {
"烧杯": ["YB_1FlaskCarrier", "3a14196b-24f2-ca49-9081-0cab8021bf1a"], "烧杯": "BIOYOND_PolymerStation_1FlaskCarrier",
"试剂瓶": ["YB_1BottleCarrier", ""], "试剂瓶": "BIOYOND_PolymerStation_1BottleCarrier",
"样品板": ["YB_6StockCarrier", "3a14196e-b7a0-a5da-1931-35f3000281e9"], "样品板": "BIOYOND_PolymerStation_6VialCarrier"
"分装板": ["YB_6VialCarrier", "3a14196e-5dfe-6e21-0c79-fe2036d052c4"],
"样品瓶": ["YB_Solid_Stock", "3a14196a-cf7d-8aea-48d8-b9662c7dba94"],
"90%分装小瓶": ["YB_Solid_Vial", "3a14196c-cdcf-088d-dc7d-5cf38f0ad9ea"],
"10%分装小瓶": ["YB_Liquid_Vial", "3a14196c-76be-2279-4e22-7310d69aed68"]
} }
}, },
"deck": { "deck": {
@@ -46,6 +42,7 @@
{ {
"id": "Bioyond_Deck", "id": "Bioyond_Deck",
"name": "Bioyond_Deck", "name": "Bioyond_Deck",
"sample_id": null,
"children": [ "children": [
], ],
"parent": "reaction_station_bioyond", "parent": "reaction_station_bioyond",

View File

@@ -24,9 +24,9 @@
"Drip_back": "3a162cf9-6aac-565a-ddd7-682ba1796a4a" "Drip_back": "3a162cf9-6aac-565a-ddd7-682ba1796a4a"
}, },
"material_type_mappings": { "material_type_mappings": {
"烧杯": "YB_1FlaskCarrier", "烧杯": "BIOYOND_PolymerStation_1FlaskCarrier",
"试剂瓶": "YB_1BottleCarrier", "试剂瓶": "BIOYOND_PolymerStation_1BottleCarrier",
"样品板": "YB_6VialCarrier" "样品板": "BIOYOND_PolymerStation_6VialCarrier"
} }
}, },
"deck": { "deck": {

View File

@@ -1,7 +1,7 @@
import pytest import pytest
from unilabos.resources.bioyond.bottle_carriers import BIOYOND_Electrolyte_6VialCarrier, BIOYOND_Electrolyte_1BottleCarrier from unilabos.resources.bioyond.bottle_carriers import BIOYOND_Electrolyte_6VialCarrier, BIOYOND_Electrolyte_1BottleCarrier
from unilabos.resources.bioyond.bottles import YB_Solid_Vial, YB_Solution_Beaker, YB_Reagent_Bottle from unilabos.resources.bioyond.bottles import BIOYOND_PolymerStation_Solid_Vial, BIOYOND_PolymerStation_Solution_Beaker, BIOYOND_PolymerStation_Reagent_Bottle
def test_bottle_carrier() -> "BottleCarrier": def test_bottle_carrier() -> "BottleCarrier":
@@ -16,9 +16,9 @@ def test_bottle_carrier() -> "BottleCarrier":
print(f"1烧杯载架: {beaker_carrier.name}, 位置数: {len(beaker_carrier.sites)}") print(f"1烧杯载架: {beaker_carrier.name}, 位置数: {len(beaker_carrier.sites)}")
# 创建瓶子和烧杯 # 创建瓶子和烧杯
powder_bottle = YB_Solid_Vial("powder_bottle_01") powder_bottle = BIOYOND_PolymerStation_Solid_Vial("powder_bottle_01")
solution_beaker = YB_Solution_Beaker("solution_beaker_01") solution_beaker = BIOYOND_PolymerStation_Solution_Beaker("solution_beaker_01")
reagent_bottle = YB_Reagent_Bottle("reagent_bottle_01") reagent_bottle = BIOYOND_PolymerStation_Reagent_Bottle("reagent_bottle_01")
print(f"\n创建的物料:") print(f"\n创建的物料:")
print(f"粉末瓶: {powder_bottle.name} - {powder_bottle.diameter}mm x {powder_bottle.height}mm, {powder_bottle.max_volume}μL") print(f"粉末瓶: {powder_bottle.name} - {powder_bottle.diameter}mm x {powder_bottle.height}mm, {powder_bottle.max_volume}μL")

View File

@@ -12,13 +12,23 @@ lab_registry.setup()
type_mapping = { type_mapping = {
"烧杯": ("YB_1FlaskCarrier", "3a14196b-24f2-ca49-9081-0cab8021bf1a"), "烧杯": "BIOYOND_PolymerStation_1FlaskCarrier",
"试剂瓶": ("YB_1BottleCarrier", ""), "试剂瓶": "BIOYOND_PolymerStation_1BottleCarrier",
"样品板": ("YB_6StockCarrier", "3a14196e-b7a0-a5da-1931-35f3000281e9"), "样品板": "BIOYOND_PolymerStation_6StockCarrier",
"分装板": ("YB_6VialCarrier", "3a14196e-5dfe-6e21-0c79-fe2036d052c4"), "分装板": "BIOYOND_PolymerStation_6VialCarrier",
"样品瓶": ("YB_Solid_Stock", "3a14196a-cf7d-8aea-48d8-b9662c7dba94"), "样品瓶": "BIOYOND_PolymerStation_Solid_Stock",
"90%分装小瓶": ("YB_Solid_Vial", "3a14196c-cdcf-088d-dc7d-5cf38f0ad9ea"), "90%分装小瓶": "BIOYOND_PolymerStation_Solid_Vial",
"10%分装小瓶": ("YB_Liquid_Vial", "3a14196c-76be-2279-4e22-7310d69aed68"), "10%分装小瓶": "BIOYOND_PolymerStation_Liquid_Vial",
}
type_uuid_mapping = {
"烧杯": "",
"试剂瓶": "",
"样品板": "",
"分装板": "3a14196e-5dfe-6e21-0c79-fe2036d052c4",
"样品瓶": "3a14196a-cf7d-8aea-48d8-b9662c7dba94",
"90%分装小瓶": "3a14196c-cdcf-088d-dc7d-5cf38f0ad9ea",
"10%分装小瓶": "3a14196c-76be-2279-4e22-7310d69aed68",
} }

View File

@@ -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()

View File

@@ -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 = {
"烧杯": ("YB_1FlaskCarrier", "3a14196b-24f2-ca49-9081-0cab8021bf1a"),
"试剂瓶": ("YB_1BottleCarrier", ""),
"样品板": ("YB_6StockCarrier", "3a14196e-b7a0-a5da-1931-35f3000281e9"),
"分装板": ("YB_6VialCarrier", "3a14196e-5dfe-6e21-0c79-fe2036d052c4"),
"样品瓶": ("YB_Solid_Stock", "3a14196a-cf7d-8aea-48d8-b9662c7dba94"),
"90%分装小瓶": ("YB_Solid_Vial", "3a14196c-cdcf-088d-dc7d-5cf38f0ad9ea"),
"10%分装小瓶": ("YB_Liquid_Vial", "3a14196c-76be-2279-4e22-7310d69aed68"),
}
@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)

View File

@@ -11,14 +11,18 @@ from typing import Dict, Any, List
import networkx as nx import networkx as nx
import yaml import yaml
from unilabos.ros.nodes.resource_tracker import ResourceTreeSet, ResourceDict
# 首先添加项目根目录到路径 # 首先添加项目根目录到路径
current_dir = os.path.dirname(os.path.abspath(__file__)) current_dir = os.path.dirname(os.path.abspath(__file__))
unilabos_dir = os.path.dirname(os.path.dirname(current_dir)) unilabos_dir = os.path.dirname(os.path.dirname(current_dir))
if unilabos_dir not in sys.path: if unilabos_dir not in sys.path:
sys.path.append(unilabos_dir) sys.path.append(unilabos_dir)
from unilabos.utils.banner_print import print_status, print_unilab_banner
from unilabos.config.config import load_config, BasicConfig, HTTPConfig from unilabos.config.config import load_config, BasicConfig, HTTPConfig
from unilabos.utils.banner_print import print_status, print_unilab_banner
from unilabos.resources.graphio import modify_to_backend_format
def load_config_from_file(config_path): def load_config_from_file(config_path):
if config_path is None: if config_path is None:
@@ -180,7 +184,6 @@ def main():
working_dir = os.path.abspath(os.getcwd()) working_dir = os.path.abspath(os.getcwd())
else: else:
working_dir = os.path.abspath(os.path.join(os.getcwd(), "unilabos_data")) working_dir = os.path.abspath(os.path.join(os.getcwd(), "unilabos_data"))
if args_dict.get("working_dir"): if args_dict.get("working_dir"):
working_dir = args_dict.get("working_dir", "") working_dir = args_dict.get("working_dir", "")
if config_path and not os.path.exists(config_path): if config_path and not os.path.exists(config_path):
@@ -212,14 +215,6 @@ def main():
# 加载配置文件 # 加载配置文件
print_status(f"当前工作目录为 {working_dir}", "info") print_status(f"当前工作目录为 {working_dir}", "info")
load_config_from_file(config_path) load_config_from_file(config_path)
# 根据配置重新设置日志级别
from unilabos.utils.log import configure_logger, logger
if hasattr(BasicConfig, "log_level"):
logger.info(f"Log level set to '{BasicConfig.log_level}' from config file.")
configure_logger(loglevel=BasicConfig.log_level)
if args_dict["addr"] == "test": if args_dict["addr"] == "test":
print_status("使用测试环境地址", "info") print_status("使用测试环境地址", "info")
HTTPConfig.remote_addr = "https://uni-lab.test.bohrium.com/api/v1" HTTPConfig.remote_addr = "https://uni-lab.test.bohrium.com/api/v1"
@@ -273,8 +268,6 @@ def main():
from unilabos.app.web import http_client from unilabos.app.web import http_client
from unilabos.app.web import start_server from unilabos.app.web import start_server
from unilabos.app.register import register_devices_and_resources from unilabos.app.register import register_devices_and_resources
from unilabos.resources.graphio import modify_to_backend_format
from unilabos.ros.nodes.resource_tracker import ResourceTreeSet, ResourceDict
# 显示启动横幅 # 显示启动横幅
print_unilab_banner(args_dict) print_unilab_banner(args_dict)
@@ -356,7 +349,7 @@ def main():
if BasicConfig.upload_registry: if BasicConfig.upload_registry:
# 设备注册到服务端 - 需要 ak 和 sk # 设备注册到服务端 - 需要 ak 和 sk
if BasicConfig.ak and BasicConfig.sk: if args_dict.get("ak") and args_dict.get("sk"):
print_status("开始注册设备到服务端...", "info") print_status("开始注册设备到服务端...", "info")
try: try:
register_devices_and_resources(lab_registry) register_devices_and_resources(lab_registry)

View File

@@ -73,8 +73,6 @@ class HTTPClient:
Returns: Returns:
Dict[str, str]: 旧UUID到新UUID的映射关系 {old_uuid: new_uuid} Dict[str, str]: 旧UUID到新UUID的映射关系 {old_uuid: new_uuid}
""" """
with open(os.path.join(BasicConfig.working_dir, "req_resource_tree_add.json"), "w", encoding="utf-8") as f:
f.write(json.dumps({"nodes": [x for xs in resources.dump() for x in xs], "mount_uuid": mount_uuid}, indent=4))
# 从序列化数据中提取所有节点的UUID保存旧UUID # 从序列化数据中提取所有节点的UUID保存旧UUID
old_uuids = {n.res_content.uuid: n for n in resources.all_nodes} old_uuids = {n.res_content.uuid: n for n in resources.all_nodes}
if not self.initialized or first_add: if not self.initialized or first_add:
@@ -94,8 +92,6 @@ class HTTPClient:
timeout=100, timeout=100,
) )
with open(os.path.join(BasicConfig.working_dir, "res_resource_tree_add.json"), "w", encoding="utf-8") as f:
f.write(f"{response.status_code}" + "\n" + response.text)
# 处理响应构建UUID映射 # 处理响应构建UUID映射
uuid_mapping = {} uuid_mapping = {}
if response.status_code == 200: if response.status_code == 200:

View File

@@ -2,7 +2,7 @@ import base64
import traceback import traceback
import os import os
import importlib.util import importlib.util
from typing import Optional, Literal from typing import Optional
from unilabos.utils import logger from unilabos.utils import logger
@@ -18,7 +18,6 @@ class BasicConfig:
vis_2d_enable = False vis_2d_enable = False
enable_resource_load = True enable_resource_load = True
communication_protocol = "websocket" communication_protocol = "websocket"
log_level: Literal['TRACE', 'DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'] = "DEBUG" # 'TRACE', 'DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'
@classmethod @classmethod
def auth_secret(cls): def auth_secret(cls):

View File

@@ -37,7 +37,7 @@ def _initialize_material_system(self, deck_config: Dict[str, Any], children_conf
**定义在**: `workstation_base.py` **定义在**: `workstation_base.py`
**设计目的** **设计目的**
- 提供外部物料系统如Bioyond、LIMS等集成的标准接口 - 提供外部物料系统如Bioyong、LIMS等集成的标准接口
- 双向同步从外部系统同步到本地deck以及将本地变更同步到外部系统 - 双向同步从外部系统同步到本地deck以及将本地变更同步到外部系统
- 处理外部系统的变更通知 - 处理外部系统的变更通知
@@ -59,7 +59,7 @@ async def handle_external_change(self, change_info: Dict[str, Any]) -> bool:
**扩展功能** **扩展功能**
- HTTP报送接收服务集成 - HTTP报送接收服务集成
- 具体工作流实现(液体转移、板洗等) - 具体工作流实现(液体转移、板洗等)
- Bioyond物料系统同步器示例 - Bioyong物料系统同步器示例
- 外部报送处理方法 - 外部报送处理方法
## 技术栈 ## 技术栈
@@ -142,11 +142,11 @@ success = workstation.execute_workflow("liquid_transfer", {
### 3. 外部系统集成 ### 3. 外部系统集成
```python ```python
class BioyondResourceSynchronizer(ResourceSynchronizer): class BioyongResourceSynchronizer(ResourceSynchronizer):
"""Bioyond系统同步器""" """Bioyong系统同步器"""
async def sync_from_external(self) -> bool: async def sync_from_external(self) -> bool:
# 从Bioyond API获取物料 # 从Bioyong API获取物料
external_materials = await self._fetch_bioyong_materials() external_materials = await self._fetch_bioyong_materials()
# 转换并添加到本地deck # 转换并添加到本地deck

View File

@@ -1,715 +0,0 @@
# -*- coding: utf-8 -*-
from typing import Dict, Any, List, Optional
from datetime import datetime, timezone
import requests
from pathlib import Path
import pandas as pd
import time
from datetime import datetime, timezone, timedelta
import re
import threading
from unilabos.devices.workstation.workstation_base import WorkstationBase
from unilabos.devices.workstation.workstation_http_service import WorkstationHTTPService
from unilabos.utils.log import logger
from pylabrobot.resources.deck import Deck
def _iso_utc_now_ms() -> str:
# 文档要求:到毫秒 + Z例如 2025-08-15T05:43:22.814Z
dt = datetime.now(timezone.utc)
return dt.strftime("%Y-%m-%dT%H:%M:%S.") + f"{int(dt.microsecond/1000):03d}Z"
class BioyondWorkstation(WorkstationBase):
"""
集成 Bioyond LIMS 的工作站示例,
覆盖:入库(2.17/2.18) → 新建实验(2.14) → 启动调度(2.7) →
运行中推送:物料变更(2.24)、步骤完成(2.21)、订单完成(2.23) →
查询实验(2.5/2.6) → 3-2-1 转运(2.32) → 样品/废料取出(2.28)
"""
def __init__(
self,
bioyond_config: Optional[Dict[str, Any]] = None,
station_resource: Optional[Dict[str, Any]] = None,
debug_mode: bool = False, # 增加调试模式开关
*args, **kwargs,
):
self.bioyond_config = bioyond_config or {
"base_url": "http://192.168.1.200:44386",
"api_key": "8A819E5C",
"timeout": 30,
"report_token": "CHANGE_ME_TOKEN"
}
self.http_service_started = False
self.debug_mode = debug_mode
super().__init__(deck=Deck, station_resource=station_resource, *args, **kwargs)
logger.info(f"Bioyond工作站初始化完成 (debug_mode={self.debug_mode})")
# 实例化并在后台线程启动 HTTP 报送服务
self.order_status = {}
try:
t = threading.Thread(target=self._start_http_service_bg, daemon=True, name="unilab_http")
t.start()
except Exception as e:
logger.error(f"unilab-server后台启动报送服务失败: {e}")
@property
def device_id(self) -> str:
try:
return getattr(self, "_ros_node").device_id # 兼容 ROS 场景
except Exception:
return "bioyond_workstation"
def _start_http_service_bg(self, host: str = "192.168.1.104", port: int = 8080) -> None:
logger.info("进入 _start_http_service_bg 函数")
try:
self.service = WorkstationHTTPService(self, host=host, port=port)
logger.info("WorkstationHTTPService 实例化完成")
self.service.start()
self.http_service_started = True
logger.info(f"unilab_HTTP 服务成功启动: {host}:{port}")
#一直挂着,直到进程退出
while True:
time.sleep(1)
except Exception as e:
self.http_service_started = False
logger.error(f"启动unilab_HTTP服务失败: {e}", exc_info=True)
# -------------------- 基础HTTP封装 --------------------
def _url(self, path: str) -> str:
return f"{self.bioyond_config['base_url'].rstrip('/')}/{path.lstrip('/')}"
def _post_lims(self, path: str, data: Optional[Any] = None) -> Dict[str, Any]:
"""LIMS API大多数接口用 {apiKey/requestTime,data} 包装"""
payload = {
"apiKey": self.bioyond_config["api_key"],
"requestTime": _iso_utc_now_ms()
}
if data is not None:
payload["data"] = data
if self.debug_mode:
# 模拟返回,不发真实请求
logger.info(f"[DEBUG] POST {path} with payload={payload}")
return {"debug": True, "url": self._url(path), "payload": payload, "status": "ok"}
try:
r = requests.post(
self._url(path),
json=payload,
timeout=self.bioyond_config.get("timeout", 30),
headers={"Content-Type": "application/json"}
)
r.raise_for_status()
return r.json()
except Exception as e:
logger.error(f"POST {path} 失败: {e}")
return {"error": str(e)}
# --- 修正_post_report / _post_report_raw 同样走 debug_mode ---
def _post_report(self, path: str, data: Dict[str, Any]) -> Dict[str, Any]:
payload = {
"token": self.bioyond_config.get("report_token", ""),
"request_time": _iso_utc_now_ms(),
"data": data
}
if self.debug_mode:
logger.info(f"[DEBUG] POST {path} with payload={payload}")
return {"debug": True, "url": self._url(path), "payload": payload, "status": "ok"}
try:
r = requests.post(self._url(path), json=payload,
timeout=self.bioyond_config.get("timeout", 30),
headers={"Content-Type": "application/json"})
r.raise_for_status()
return r.json()
except Exception as e:
logger.error(f"POST {path} 失败: {e}")
return {"error": str(e)}
def _post_report_raw(self, path: str, body: Dict[str, Any]) -> Dict[str, Any]:
if self.debug_mode:
logger.info(f"[DEBUG] POST {path} with body={body}")
return {"debug": True, "url": self._url(path), "payload": body, "status": "ok"}
try:
r = requests.post(self._url(path), json=body,
timeout=self.bioyond_config.get("timeout", 30),
headers={"Content-Type": "application/json"})
r.raise_for_status()
return r.json()
except Exception as e:
logger.error(f"POST {path} 失败: {e}")
return {"error": str(e)}
# -------------------- 单点接口封装 --------------------
# 2.17 入库物料(单个)
def storage_inbound(self, material_id: str, location_id: str) -> Dict[str, Any]:
return self._post_lims("/api/lims/storage/inbound", {
"materialId": material_id,
"locationId": location_id
})
# 2.18 批量入库(多个)
def storage_batch_inbound(self, items: List[Dict[str, str]]) -> Dict[str, Any]:
"""
items = [{"materialId": "...", "locationId": "..."}, ...]
"""
return self._post_lims("/api/lims/storage/batch-inbound", items)
# 3.30 自动化上料Excel -> JSON -> POST /api/lims/order/auto-feeding4to3
def auto_feeding4to3_from_xlsx(self, xlsx_path: str) -> Dict[str, Any]:
"""
根据固定模板解析 Excel
- 四号手套箱加样头面 (2-13行, 3-7列)
- 四号手套箱原液瓶面 (15-23行, 3-9列)
- 三号手套箱人工堆栈 (26-40行, 3-7列)
"""
path = Path(xlsx_path)
if not path.exists():
raise FileNotFoundError(f"未找到 Excel 文件:{path}")
try:
df = pd.read_excel(path, sheet_name=0, header=None, engine="openpyxl")
except Exception as e:
raise RuntimeError(f"读取 Excel 失败:{e}")
items: List[Dict[str, Any]] = []
# 四号手套箱 - 加样头面2-13行, 3-7列
for _, row in df.iloc[1:13, 2:7].iterrows():
item = {
"sourceWHName": "四号手套箱堆栈",
"posX": int(row[2]),
"posY": int(row[3]),
"posZ": int(row[4]),
"materialName": str(row[5]).strip() if pd.notna(row[5]) else "",
"quantity": float(row[6]) if pd.notna(row[6]) else 0.0,
}
if item["materialName"]:
items.append(item)
# 四号手套箱 - 原液瓶面15-23行, 3-9列
for _, row in df.iloc[14:23, 2:9].iterrows():
item = {
"sourceWHName": "四号手套箱堆栈",
"posX": int(row[2]),
"posY": int(row[3]),
"posZ": int(row[4]),
"materialName": str(row[5]).strip() if pd.notna(row[5]) else "",
"quantity": float(row[6]) if pd.notna(row[6]) else 0.0,
"materialType": str(row[7]).strip() if pd.notna(row[7]) else "",
"targetWH": str(row[8]).strip() if pd.notna(row[8]) else "",
}
if item["materialName"]:
items.append(item)
# 三号手套箱人工堆栈26-40行, 3-7列
for _, row in df.iloc[25:40, 2:7].iterrows():
item = {
"sourceWHName": "三号手套箱人工堆栈",
"posX": int(row[2]),
"posY": int(row[3]),
"posZ": int(row[4]),
"materialType": str(row[5]).strip() if pd.notna(row[5]) else "",
"materialId": str(row[6]).strip() if pd.notna(row[6]) else "",
"quantity": 1 # 默认数量1
}
if item["materialId"] or item["materialType"]:
items.append(item)
return self._post_lims("/api/lims/order/auto-feeding4to3", items)
def auto_batch_outbound_from_xlsx(self, xlsx_path: str) -> Dict[str, Any]:
"""
3.31 自动化下料Excel -> JSON -> POST /api/lims/storage/auto-batch-out-bound
"""
path = Path(xlsx_path)
if not path.exists():
raise FileNotFoundError(f"未找到 Excel 文件:{path}")
try:
df = pd.read_excel(path, sheet_name=0, engine="openpyxl")
except Exception as e:
raise RuntimeError(f"读取 Excel 失败:{e}")
def pick(names: List[str]) -> Optional[str]:
for n in names:
if n in df.columns:
return n
return None
c_loc = pick(["locationId", "库位ID", "库位Id", "库位id"])
c_wh = pick(["warehouseId", "仓库ID", "仓库Id", "仓库id"])
c_qty = pick(["数量", "quantity"])
c_x = pick(["x", "X", "posX", "坐标X"])
c_y = pick(["y", "Y", "posY", "坐标Y"])
c_z = pick(["z", "Z", "posZ", "坐标Z"])
required = [c_loc, c_wh, c_qty, c_x, c_y, c_z]
if any(c is None for c in required):
raise KeyError("Excel 缺少必要列locationId/warehouseId/数量/x/y/z支持多别名至少要能匹配到")
def as_int(v, d=0):
try:
if pd.isna(v): return d
return int(v)
except Exception:
try:
return int(float(v))
except Exception:
return d
def as_float(v, d=0.0):
try:
if pd.isna(v): return d
return float(v)
except Exception:
return d
def as_str(v, d=""):
if v is None or (isinstance(v, float) and pd.isna(v)): return d
s = str(v).strip()
return s if s else d
items: List[Dict[str, Any]] = []
for _, row in df.iterrows():
items.append({
"locationId": as_str(row[c_loc]),
"warehouseId": as_str(row[c_wh]),
"quantity": as_float(row[c_qty]),
"x": as_int(row[c_x]),
"y": as_int(row[c_y]),
"z": as_int(row[c_z]),
})
return self._post_lims("/api/lims/storage/auto-batch-out-bound", items)
# 2.14 新建实验
def create_orders(self, xlsx_path: str) -> Dict[str, Any]:
"""
从 Excel 解析并创建实验2.14
约定:
- batchId = Excel 文件名(不含扩展名)
- 物料列:所有以 "(g)" 结尾(不再读取“总质量(g)”列)
- totalMass 自动计算为所有物料质量之和
- createTime 缺失或为空时自动填充为当前日期YYYY/M/D
"""
path = Path(xlsx_path)
if not path.exists():
raise FileNotFoundError(f"未找到 Excel 文件:{path}")
try:
df = pd.read_excel(path, sheet_name=0, engine="openpyxl")
except Exception as e:
raise RuntimeError(f"读取 Excel 失败:{e}")
# 列名容错:返回可选列名,找不到则返回 None
def _pick(col_names: List[str]) -> Optional[str]:
for c in col_names:
if c in df.columns:
return c
return None
col_order_name = _pick(["配方ID", "orderName", "订单编号"])
col_create_time = _pick(["创建日期", "createTime"])
col_bottle_type = _pick(["配液瓶类型", "bottleType"])
col_mix_time = _pick(["混匀时间(s)", "mixTime"])
col_load = _pick(["扣电组装分液体积", "loadSheddingInfo"])
col_pouch = _pick(["软包组装分液体积", "pouchCellInfo"])
col_cond = _pick(["电导测试分液体积", "conductivityInfo"])
col_cond_cnt = _pick(["电导测试分液瓶数", "conductivityBottleCount"])
# 物料列:所有以 (g) 结尾
material_cols = [c for c in df.columns if isinstance(c, str) and c.endswith("(g)")]
if not material_cols:
raise KeyError("未发现任何以“(g)”结尾的物料列,请检查表头。")
batch_id = path.stem
def _to_ymd_slash(v) -> str:
# 统一为 "YYYY/M/D";为空或解析失败则用当前日期
if v is None or (isinstance(v, float) and pd.isna(v)) or str(v).strip() == "":
ts = datetime.now()
else:
try:
ts = pd.to_datetime(v)
except Exception:
ts = datetime.now()
return f"{ts.year}/{ts.month}/{ts.day}"
def _as_int(val, default=0) -> int:
try:
if pd.isna(val):
return default
return int(val)
except Exception:
return default
def _as_str(val, default="") -> str:
if val is None or (isinstance(val, float) and pd.isna(val)):
return default
s = str(val).strip()
return s if s else default
orders: List[Dict[str, Any]] = []
for idx, row in df.iterrows():
mats: List[Dict[str, Any]] = []
total_mass = 0.0
for mcol in material_cols:
val = row.get(mcol, None)
if val is None or (isinstance(val, float) and pd.isna(val)):
continue
try:
mass = float(val)
except Exception:
continue
if mass > 0:
mats.append({"name": mcol.replace("(g)", ""), "mass": mass})
total_mass += mass
order_data = {
"batchId": batch_id,
"orderName": _as_str(row[col_order_name], default=f"{batch_id}_order_{idx+1}") if col_order_name else f"{batch_id}_order_{idx+1}",
"createTime": _to_ymd_slash(row[col_create_time]) if col_create_time else _to_ymd_slash(None),
"bottleType": _as_str(row[col_bottle_type], default="配液小瓶") if col_bottle_type else "配液小瓶",
"mixTime": _as_int(row[col_mix_time]) if col_mix_time else 0,
"loadSheddingInfo": _as_int(row[col_load]) if col_load else 0,
"pouchCellInfo": _as_int(row[col_pouch]) if col_pouch else 0,
"conductivityInfo": _as_int(row[col_cond]) if col_cond else 0,
"conductivityBottleCount": _as_int(row[col_cond_cnt]) if col_cond_cnt else 0,
"materialInfos": mats,
"totalMass": round(total_mass, 4) # 自动汇总
}
orders.append(order_data)
# print(orders)
response = self._post_lims("/api/lims/order/orders", orders)
self.order_status[response["data"]["orderCode"]] = "running"
while True:
time.sleep(5)
if self.order_status.get(response["data"]["orderCode"], None) == "finished":
logger.info(f"配液实验已完成 ,即将执行 3-2-1 转运")
break
logger.info(f"等待配液实验完成")
self.transfer_3_to_2_to_1()
r321 = self.wait_for_transfer_task()
logger.info(f"3-2-1 转运完成,返回结果")
return r321
# 2.7 启动调度
def scheduler_start(self) -> Dict[str, Any]:
return self._post_lims("/api/lims/scheduler/start")
# 3.10 停止调度
def scheduler_stop(self) -> Dict[str, Any]:
"""
停止调度 (3.10)
请求体只包含 apiKey 和 requestTime
"""
return self._post_lims("/api/lims/scheduler/stop")
# 2.9 继续调度
def scheduler_continue(self) -> Dict[str, Any]:
"""
继续调度 (2.9)
请求体只包含 apiKey 和 requestTime
"""
return self._post_lims("/api/lims/scheduler/continue")
# 2.24 物料变更推送
def report_material_change(self, material_obj: Dict[str, Any]) -> Dict[str, Any]:
"""
material_obj 按 2.24 的裸对象格式(包含 id/typeName/locations/detail 等)
"""
return self._post_report_raw("/report/material_change", material_obj)
# 2.21 步骤完成推送BS → LIMS
def report_step_finish(self,
order_code: str,
order_name: str,
step_name: str,
step_id: str,
sample_id: str,
start_time: str,
end_time: str,
execution_status: str = "completed") -> Dict[str, Any]:
data = {
"orderCode": order_code,
"orderName": order_name,
"stepName": step_name,
"stepId": step_id,
"sampleId": sample_id,
"startTime": start_time,
"endTime": end_time,
"executionStatus": execution_status
}
return self._post_report("/report/step_finish", data)
# 2.23 订单完成推送BS → LIMS
def report_order_finish(self,
order_code: str,
order_name: str,
start_time: str,
end_time: str,
status: str = "30", # 30 完成 / -11 异常停止 / -12 人工停止
workflow_status: str = "Finished",
completion_time: Optional[str] = None,
used_materials: Optional[List[Dict[str, Any]]] = None) -> Dict[str, Any]:
data = {
"orderCode": order_code,
"orderName": order_name,
"startTime": start_time,
"endTime": end_time,
"status": status,
"workflowStatus": workflow_status,
"completionTime": completion_time or end_time,
"usedMaterials": used_materials or []
}
return self._post_report("/report/order_finish", data)
# 2.5 批量查询实验报告(用于轮询是否完成)
def order_list(self,
status: Optional[str] = None,
begin_time: Optional[str] = None,
end_time: Optional[str] = None,
filter_text: Optional[str] = None,
skip: int = 0, page: int = 10) -> Dict[str, Any]:
data: Dict[str, Any] = {"skipCount": skip, "pageCount": page}
if status is not None: # 80 成功 / 90 失败 / 100 执行中
data["status"] = status
if begin_time:
data["timeType"] = "CreationTime"
data["beginTime"] = begin_time
if end_time:
data["endTime"] = end_time
if filter_text:
data["filter"] = filter_text
return self._post_lims("/api/lims/order/order-list", data)
# 2.6 实验报告查询根据任务ID拿详情
def order_report(self, order_id: str) -> Dict[str, Any]:
return self._post_lims("/api/lims/order/order-report", order_id)
# 2.32 3-2-1 物料转运
def transfer_3_to_2_to_1(self,
# source_wh_id: Optional[str] = None,
source_wh_id: Optional[str] = '3a19debc-84b4-0359-e2d4-b3beea49348b',
source_x: int = 1, source_y: int = 1, source_z: int = 1) -> Dict[str, Any]:
payload: Dict[str, Any] = {
"sourcePosX": source_x, "sourcePosY": source_y, "sourcePosZ": source_z
}
if source_wh_id:
payload["sourceWHID"] = source_wh_id
return self._post_lims("/api/lims/order/transfer-task3To2To1", payload)
# 2.28 样品/废料取出
def take_out(self,
order_id: str,
preintake_ids: Optional[List[str]] = None,
material_ids: Optional[List[str]] = None) -> Dict[str, Any]:
data = {
"orderId": order_id,
"preintakeIds": preintake_ids or [],
"materialIds": material_ids or []
}
return self._post_lims("/api/lims/order/take-out", data)
# --------可选占位方法文档未定义的“1号站内部流程 / 1-2转运”--------
def start_station1_internal_flow(self, **kwargs) -> None:
logger.info("启动1号站内部流程占位按现场系统填充具体指令")
# 3.x 1→2 物料转运
def transfer_1_to_2(self) -> Dict[str, Any]:
"""
1→2 物料转运
URL: /api/lims/order/transfer-task1To2
只需要 apiKey 和 requestTime
"""
return self._post_lims("/api/lims/order/transfer-task1To2")
# -------------------- 整体编排 --------------------
def run_full_workflow(self,
inbound_items: List[Dict[str, str]],
orders: List[Dict[str, Any]],
poll_filter_code: Optional[str] = None,
poll_timeout_s: int = 600,
poll_interval_s: int = 5,
transfer_source: Optional[Dict[str, Any]] = None,
takeout_order_id: Optional[str] = None) -> None:
"""
一键串联:
1) 入库 3-4 个物料 → 2) 新建实验 → 3) 启动调度
运行中如需4) 物料变更推送 5) 步骤完成推送 6) 订单完成推送
完成后查询实验2.5/2.6)→ 7) 3-2-1 转运 → 8) 1号站内部流程
→ 9) 1-2 转运 → 10) 样品/废料取出
"""
# 1. 入库多于1个就用批量接口 2.18
if len(inbound_items) == 1:
r = self.storage_inbound(inbound_items[0]["materialId"], inbound_items[0]["locationId"])
logger.info(f"单个入库结果: {r}")
else:
r = self.storage_batch_inbound(inbound_items)
logger.info(f"批量入库结果: {r}")
# 2. 新建实验2.14
r = self.create_orders(orders)
logger.info(f"新建实验结果: {r}")
# 3. 启动调度2.7
r = self.scheduler_start()
logger.info(f"启动调度结果: {r}")
# —— 运行中各类推送2.24 / 2.21 / 2.23),通常由实际任务驱动,这里提供调用方式 —— #
# self.report_material_change({...})
# self.report_step_finish(order_code="BSO...", order_name="配液分液", step_name="xxx", step_id="...", sample_id="...",
# start_time=_iso_utc_now_ms(), end_time=_iso_utc_now_ms(), execution_status="completed")
# self.report_order_finish(order_code="BSO...", order_name="配液分液", start_time="...", end_time=_iso_utc_now_ms())
# 完成后才能转运:用 2.5 批量查询配合 filter=任务编码 轮询到 status=80成功
if poll_filter_code:
import time
deadline = time.time() + poll_timeout_s
while time.time() < deadline:
res = self.order_list(status="80", filter_text=poll_filter_code, page=5)
if isinstance(res, dict) and res.get("data", {}).get("items"):
logger.info(f"实验 {poll_filter_code} 已完成:{res['data']['items'][0]}")
break
time.sleep(poll_interval_s)
else:
logger.warning(f"等待实验 {poll_filter_code} 完成超时(未到 status=80")
# 7. 启动 3-2-1 转运2.32
if transfer_source:
r = self.transfer_3_to_2_to_1(
source_wh_id=transfer_source.get("sourceWHID"),
source_x=transfer_source.get("sourcePosX", 1),
source_y=transfer_source.get("sourcePosY", 1),
source_z=transfer_source.get("sourcePosZ", 1),
)
logger.info(f"3-2-1 转运结果: {r}")
# 8. 1号站内部流程占位
self.start_station1_internal_flow()
# 9. 1→2 转运(占位)
self.transfer_1_to_2()
# 10. 样品/废料取出2.28
if takeout_order_id:
r = self.take_out(order_id=takeout_order_id)
logger.info(f"样品/废料取出结果: {r}")
# 2.5 批量查询实验报告
def order_list_v2(self,
timeType: str = "string",
beginTime: str = "",
endTime: str = "",
status: str = "",
filter: str = "物料转移任务",
skipCount: int = 0,
pageCount: int = 1,
sorting: str = "") -> Dict[str, Any]:
"""
批量查询实验报告的详细信息 (2.5)
URL: /api/lims/order/order-list
参数默认值和接口文档保持一致
"""
data: Dict[str, Any] = {
"timeType": timeType,
"beginTime": beginTime,
"endTime": endTime,
"status": status,
"filter": filter,
"skipCount": skipCount,
"pageCount": pageCount,
"sorting": sorting
}
return self._post_lims("/api/lims/order/order-list", data)
def wait_for_transfer_task(self, timeout: int = 600, interval: int = 3) -> bool:
"""
轮询查询物料转移任务是否成功完成 (status=80)
- timeout: 最大等待秒数 (默认600秒)
- interval: 轮询间隔秒数 (默认3秒)
返回 True 表示找到并成功完成False 表示超时未找到
"""
now = datetime.now()
beginTime = now.strftime("%Y-%m-%dT%H:%M:%SZ")
endTime = (now + timedelta(minutes=5)).strftime("%Y-%m-%dT%H:%M:%SZ")
print(beginTime, endTime)
deadline = time.time() + timeout
while time.time() < deadline:
result = self.order_list_v2(
timeType="string",
beginTime=beginTime,
endTime=endTime,
status="",
filter="物料转移任务",
skipCount=0,
pageCount=1,
sorting=""
)
print(result)
items = result.get("data", {}).get("items", [])
for item in items:
name = item.get("name", "")
status = item.get("status")
if name.startswith("物料转移任务") and status == 80:
logger.info(f"硬件转移动作完成: {name}")
return True
time.sleep(interval)
logger.warning("超时未找到成功的物料转移任务")
return False
# --------------------------------
if __name__ == "__main__":
ws = BioyondWorkstation()
# ws.scheduler_stop()
ws.scheduler_start()
logger.info("调度启动完成")
# ws.scheduler_continue()
# 3.30 上料:读取模板 Excel 自动解析并 POST
r1 = ws.auto_feeding4to3_from_xlsx(r"C:\ML\GitHub\Uni-Lab-OS\unilabos\devices\workstation\bioyond_cell\样品导入模板 (8).xlsx")
ws.wait_for_transfer_task()
logger.info("4号箱向3号箱转运物料转移任务已完成")
# ws.scheduler_start()
# print(r1["payload"]["data"]) # 调试模式下可直接看到要发的 JSON items
# 新建实验
res = ws.create_orders("C:/ML/GitHub/Uni-Lab-OS/unilabos/devices/workstation/bioyond_cell/2025092501.xlsx")
# ws.scheduler_start()
# print(res)
#1号站启动
ws.transfer_1_to_2()
ws.wait_for_transfer_task()
logger.info("1号站向2号站转移任务完成")
logger.info("全流程结束")
# 3.31 下料:同理
# r2 = ws.auto_batch_outbound_from_xlsx(r"C:/path/样品导入模板 (8).xlsx")
# print(r2["payload"]["data"])

View File

@@ -1,7 +0,0 @@
material_name
LiPF6
LiDFOB
DTD
LiFSI
LiPO2F2
1 material_name
2 LiPF6
3 LiDFOB
4 DTD
5 LiFSI
6 LiPO2F2

File diff suppressed because it is too large Load Diff

View File

@@ -2,242 +2,128 @@
""" """
配置文件 - 包含所有配置信息和映射关系 配置文件 - 包含所有配置信息和映射关系
""" """
import os
# ==================== API 基础配置 ==================== # API配置
# ==================== 完整的 Bioyond 配置 ====================
# BioyondCellWorkstation 默认配置(包含所有必需参数)
API_CONFIG = { API_CONFIG = {
# API 连接配置 "api_key": "",
"api_host": os.getenv("BIOYOND_API_HOST", "http://172.16.11.219:44388"), "api_host": ""
"api_key": os.getenv("BIOYOND_API_KEY", "8A819E5C"), }
"timeout": int(os.getenv("BIOYOND_TIMEOUT", "30")),
# 报送配置 # 站点类型配置
"report_token": os.getenv("BIOYOND_REPORT_TOKEN", "CHANGE_ME_TOKEN"), STATION_TYPES = {
"REACTION": "reaction_station", # 仅反应站
"DISPENSING": "dispensing_station", # 仅配液站
"HYBRID": "hybrid_station" # 混合模式
}
# HTTP 服务配置 # 默认站点配置
"HTTP_host": os.getenv("BIOYOND_HTTP_HOST", "0.0.0.0"), # HTTP服务监听地址0.0.0.0 表示监听所有网络接口) DEFAULT_STATION_CONFIG = {
"HTTP_port": int(os.getenv("BIOYOND_HTTP_PORT", "8080")), "station_type": STATION_TYPES["REACTION"], # 默认反应站模式
"report_ip": os.getenv("BIOYOND_REPORT_IP", "172.21.32.22"), # 报送给 Bioyond 的本机IP地址留空则自动检测 "enable_reaction_station": True, # 是否启用反应站功能
# 调试模式 "enable_dispensing_station": False, # 是否启用配液站功能
"debug_mode": False, "station_name": "BioyondReactionStation", # 站点名称
"description": "Bioyond反应工作站" # 站点描述
}
# 工作流映射配置
WORKFLOW_MAPPINGS = {
"reactor_taken_out": "",
"reactor_taken_in": "",
"Solid_feeding_vials": "",
"Liquid_feeding_vials(non-titration)": "",
"Liquid_feeding_solvents": "",
"Liquid_feeding(titration)": "",
"liquid_feeding_beaker": "",
"Drip_back": "",
}
# 工作流名称到DisplaySectionName的映射
WORKFLOW_TO_SECTION_MAP = {
'reactor_taken_in': '反应器放入',
'liquid_feeding_beaker': '液体投料-烧杯',
'Liquid_feeding_vials(non-titration)': '液体投料-小瓶(非滴定)',
'Liquid_feeding_solvents': '液体投料-溶剂',
'Solid_feeding_vials': '固体投料-小瓶',
'Liquid_feeding(titration)': '液体投料-滴定',
'reactor_taken_out': '反应器取出'
} }
# 库位映射配置 # 库位映射配置
WAREHOUSE_MAPPING = { LOCATION_MAPPING = {
"粉末加样头堆栈": { 'A01': '',
"uuid": "", 'A02': '',
"site_uuids": { 'A03': '',
"A01": "3a19da56-1379-ff7c-1745-07e200b44ce2", 'A04': '',
"B01": "3a19da56-1379-2424-d751-fe6e94cef938", 'A05': '',
"C01": "3a19da56-1379-271c-03e3-6bdb590e395e", 'A06': '',
"D01": "3a19da56-1379-277f-2b1b-0d11f7cf92c6", 'A07': '',
"E01": "3a19da56-1379-2f1c-a15b-e01db90eb39a", 'A08': '',
"F01": "3a19da56-1379-3fa1-846b-088158ac0b3d", 'B01': '',
"G01": "3a19da56-1379-5aeb-d0cd-d3b4609d66e1", 'B02': '',
"H01": "3a19da56-1379-6077-8258-bdc036870b78", 'B03': '',
"I01": "3a19da56-1379-863b-a120-f606baf04617", 'B04': '',
"J01": "3a19da56-1379-8a74-74e5-35a9b41d4fd5", 'B05': '',
"K01": "3a19da56-1379-b270-b7af-f18773918abe", 'B06': '',
"L01": "3a19da56-1379-ba54-6d78-fd770a671ffc", 'B07': '',
"M01": "3a19da56-1379-c22d-c96f-0ceb5eb54a04", 'B08': '',
"N01": "3a19da56-1379-d64e-c6c5-c72ea4829888", 'C01': '',
"O01": "3a19da56-1379-d887-1a3c-6f9cce90f90e", 'C02': '',
"P01": "3a19da56-1379-e77d-0e65-7463b238a3b9", 'C03': '',
"Q01": "3a19da56-1379-edf6-1472-802ddb628774", 'C04': '',
"R01": "3a19da56-1379-f281-0273-e0ef78f0fd97", 'C05': '',
"S01": "3a19da56-1379-f924-7f68-df1fa51489f4", 'C06': '',
"T01": "3a19da56-1379-ff7c-1745-07e200b44ce2" 'C07': '',
} 'C08': '',
}, 'D01': '',
"配液站内试剂仓库": { 'D02': '',
"uuid": "", 'D03': '',
"site_uuids": { 'D04': '',
"A01": "3a19da43-57b5-294f-d663-154a1cc32270", 'D05': '',
"B01": "3a19da43-57b5-7394-5f49-54efe2c9bef2", 'D06': '',
"C01": "3a19da43-57b5-5e75-552f-8dbd0ad1075f", 'D07': '',
"A02": "3a19da43-57b5-8441-db94-b4d3875a4b6c", 'D08': '',
"B02": "3a19da43-57b5-3e41-c181-5119dddaf50c",
"C02": "3a19da43-57b5-269b-282d-fba61fe8ce96",
"A03": "3a19da43-57b5-7c1e-d02e-c40e8c33f8a1",
"B03": "3a19da43-57b5-659f-621f-1dcf3f640363",
"C03": "3a19da43-57b5-855a-6e71-f398e376dee1",
}
},
"试剂替换仓库": {
"uuid": "",
"site_uuids": {
"A01": "3a19da51-8f4e-30f3-ea08-4f8498e9b097",
"B01": "3a19da51-8f4e-1da7-beb0-80a4a01e67a8",
"C01": "3a19da51-8f4e-337d-2675-bfac46880b06",
"D01": "3a19da51-8f4e-e514-b92c-9c44dc5e489d",
"E01": "3a19da51-8f4e-22d1-dd5b-9774ddc80402",
"F01": "3a19da51-8f4e-273a-4871-dff41c29bfd9",
"G01": "3a19da51-8f4e-b32f-454f-74bc1a665653",
"H01": "3a19da51-8f4e-8c93-68c9-0b4382320f59",
"I01": "3a19da51-8f4e-360c-0149-291b47c6089b",
"J01": "3a19da51-8f4e-4152-9bca-8d64df8c1af0"
}
},
"自动堆栈-左": {
"uuid": "",
"site_uuids": {
"A01": "3a19debc-84b5-4c1c-d3a1-26830cf273ff",
"A02": "3a19debc-84b5-033b-b31f-6b87f7c2bf52",
"B01": "3a19debc-84b5-3924-172f-719ab01b125c",
"B02": "3a19debc-84b5-aad8-70c6-b8c6bb2d8750"
}
},
"自动堆栈-右": {
"uuid": "",
"site_uuids": {
"A01": "3a19debe-5200-7df2-1dd9-7d202f158864",
"A02": "3a19debe-5200-573b-6120-8b51f50e1e50",
"B01": "3a19debe-5200-7cd8-7666-851b0a97e309",
"B02": "3a19debe-5200-e6d3-96a3-baa6e3d5e484"
}
},
"手动堆栈": {
"uuid": "",
"site_uuids": {
"A01": "3a19deae-2c7a-36f5-5e41-02c5b66feaea",
"A02": "3a19deae-2c7a-dc6d-c41e-ef285d946cfe",
"A03": "3a19deae-2c7a-5876-c454-6b7e224ca927",
"B01": "3a19deae-2c7a-2426-6d71-e9de3cb158b1",
"B02": "3a19deae-2c7a-79b0-5e44-efaafd1e4cf3",
"B03": "3a19deae-2c7a-b9eb-f4e3-e308e0cf839a",
"C01": "3a19deae-2c7a-32bc-768e-556647e292f3",
"C02": "3a19deae-2c7a-e97a-8484-f5a4599447c4",
"C03": "3a19deae-2c7a-3056-6504-10dc73fbc276",
"D01": "3a19deae-2c7a-ffad-875e-8c4cda61d440",
"D02": "3a19deae-2c7a-61be-601c-b6fb5610499a",
"D03": "3a19deae-2c7a-c0f7-05a7-e3fe2491e560",
"E01": "3a19deae-2c7a-a6f4-edd1-b436a7576363",
"E02": "3a19deae-2c7a-4367-96dd-1ca2186f4910",
"E03": "3a19deae-2c7a-b163-2219-23df15200311",
"F01": "3a19deae-2c7a-d594-fd6a-0d20de3c7c4a",
"F02": "3a19deae-2c7a-a194-ea63-8b342b8d8679",
"F03": "3a19deae-2c7a-f7c4-12bd-425799425698",
"G01": "3a19deae-2c7a-0b56-72f1-8ab86e53b955",
"G02": "3a19deae-2c7a-204e-95ed-1f1950f28343",
"G03": "3a19deae-2c7a-392b-62f1-4907c66343f8",
"H01": "3a19deae-2c7a-5602-e876-d27aca4e3201",
"H02": "3a19deae-2c7a-f15c-70e0-25b58a8c9702",
"H03": "3a19deae-2c7a-780b-8965-2e1345f7e834",
"I01": "3a19deae-2c7a-8849-e172-07de14ede928",
"I02": "3a19deae-2c7a-4772-a37f-ff99270bafc0",
"I03": "3a19deae-2c7a-cce7-6e4a-25ea4a2068c4",
"J01": "3a19deae-2c7a-1848-de92-b5d5ed054cc6",
"J02": "3a19deae-2c7a-1d45-b4f8-6f866530e205",
"J03": "3a19deae-2c7a-f237-89d9-8fe19025dee9"
}
},
} }
# 物料类型配置 # 物料类型配置
MATERIAL_TYPE_IDS = {
"样品板": "",
"样品": "",
"烧杯": ""
}
MATERIAL_TYPE_MAPPINGS = { MATERIAL_TYPE_MAPPINGS = {
"烧杯": ("YB_1FlaskCarrier", "3a14196b-24f2-ca49-9081-0cab8021bf1a"), "烧杯": "BIOYOND_PolymerStation_1FlaskCarrier",
"试剂瓶": ("YB_1BottleCarrier", ""), "试剂瓶": "BIOYOND_PolymerStation_1BottleCarrier",
"样品板": ("YB_6StockCarrier", "3a14196e-b7a0-a5da-1931-35f3000281e9"), "样品板": "BIOYOND_PolymerStation_6VialCarrier",
"分装板": ("YB_6VialCarrier", "3a14196e-5dfe-6e21-0c79-fe2036d052c4"),
"样品瓶": ("YB_Solid_Stock", "3a14196a-cf7d-8aea-48d8-b9662c7dba94"),
"90%分装小瓶": ("YB_Solid_Vial", "3a14196c-cdcf-088d-dc7d-5cf38f0ad9ea"),
"10%分装小瓶": ("YB_Liquid_Vial", "3a14196c-76be-2279-4e22-7310d69aed68"),
"20ml分液瓶": ("YB_20ml_Dispensing_Vial", "3a192c2b-19e8-f0a3-035e-041ca8ca1035"),
"100ml液体": ("YB_100ml_Liquid_Bottle", "d37166b3-ecaa-481e-bd84-3032b795ba07"),
"": ("YB_Liquid_Bottle", "3a190ca1-2add-2b23-f8e1-bbd348b7f790"),
"高粘液": ("YB_High_Viscosity_Liquid_Bottle", "abe8df30-563d-43d2-85e0-cabec59ddc16"),
"加样头(大)": ("YB_Large_Dispense_Head", "3a190ca0-b2f6-9aeb-8067-547e72c11469"),
"5ml分液瓶板": ("YB_6x5ml_DispensingVialCarrier", "3a192fa4-007d-ec7b-456e-2a8be7a13f23"),
"5ml分液瓶": ("YB_5ml_Dispensing_Vial", "3a192c2a-ebb7-58a1-480d-8b3863bf74f4"),
"20ml分液瓶板": ("YB_6x20ml_DispensingVialCarrier", "3a192fa4-47db-3449-162a-eaf8aba57e27"),
"配液瓶(小)板": ("YB_6x_SmallSolutionBottleCarrier", "3a190c8b-3284-af78-d29f-9a69463ad047"),
"配液瓶(小)": ("YB_Small_Solution_Bottle", "3a190c8c-fe8f-bf48-0dc3-97afc7f508eb"),
"配液瓶(大)板": ("YB_4x_LargeSolutionBottleCarrier", "53e50377-32dc-4781-b3c0-5ce45bc7dc27"),
"配液瓶(大)": ("YB_Large_Solution_Bottle", "19c52ad1-51c5-494f-8854-576f4ca9c6ca"),
"加样头(大)板": ("YB_6x_LargeDispenseHeadCarrier", "a8e714ae-2a4e-4eb9-9614-e4c140ec3f16"),
"适配器块": ("YB_AdapterBlock", "efc3bb32-d504-4890-91c0-b64ed3ac80cf"),
"枪头盒": ("YB_TipBox", "3a192c2e-20f3-a44a-0334-c8301839d0b3"),
"枪头": ("YB_Pipette_Tip", "b6196971-1050-46da-9927-333e8dea062d"),
} }
SOLID_LIQUID_MAPPINGS = { # 步骤参数配置各工作流的步骤UUID
# 固体 WORKFLOW_STEP_IDS = {
"LiDFOB": { "reactor_taken_in": {
"typeId": "3a190ca0-b2f6-9aeb-8067-547e72c11469", "config": ""
"code": "",
"barCode": "",
"name": "LiDFOB",
"unit": "g",
"parameters": "",
"quantity": "2",
"warningQuantity": "1",
"details": []
}, },
# "LiPF6": { "liquid_feeding_beaker": {
# "typeId": "3a190ca0-b2f6-9aeb-8067-547e72c11469", "liquid": "",
# "code": "", "observe": ""
# "barCode": "", },
# "name": "LiPF6", "liquid_feeding_vials_non_titration": {
# "unit": "g", "liquid": "",
# "parameters": "", "observe": ""
# "quantity": 2, },
# "warningQuantity": 1, "liquid_feeding_solvents": {
# "details": [] "liquid": "",
# }, "observe": ""
# "LiFSI": { },
# "typeId": "3a190ca0-b2f6-9aeb-8067-547e72c11469", "solid_feeding_vials": {
# "code": "", "feeding": "",
# "barCode": "", "observe": ""
# "name": "LiFSI", },
# "unit": "g", "liquid_feeding_titration": {
# "parameters": "", "liquid": "",
# "quantity": 2, "observe": ""
# "warningQuantity": 1, },
# "details": [] "drip_back": {
# }, "liquid": "",
# "DTC": { "observe": ""
# "typeId": "3a190ca0-b2f6-9aeb-8067-547e72c11469", }
# "code": "",
# "barCode": "",
# "name": "DTC",
# "unit": "g",
# "parameters": "",
# "quantity": 2,
# "warningQuantity": 1,
# "details": []
# },
# "LiPO2F2": {
# "typeId": "3a190ca0-b2f6-9aeb-8067-547e72c11469",
# "code": "",
# "barCode": "",
# "name": "LiPO2F2",
# "unit": "g",
# "parameters": "",
# "quantity": 2,
# "warningQuantity": 1,
# "details": []
# },
# 液体
# "SA": ("BIOYOND_PolymerStation_Solid_Stock", "3a190ca0-b2f6-9aeb-8067-547e72c11469"),
# "EC": ("BIOYOND_PolymerStation_Solid_Stock", "3a190ca0-b2f6-9aeb-8067-547e72c11469"),
# "VC": ("BIOYOND_PolymerStation_Solid_Stock", "3a190ca0-b2f6-9aeb-8067-547e72c11469"),
# "AND": ("BIOYOND_PolymerStation_Solid_Stock", "3a190ca0-b2f6-9aeb-8067-547e72c11469"),
# "HTCN": ("BIOYOND_PolymerStation_Solid_Stock", "3a190ca0-b2f6-9aeb-8067-547e72c11469"),
# "DENE": ("BIOYOND_PolymerStation_Solid_Stock", "3a190ca0-b2f6-9aeb-8067-547e72c11469"),
# "TMSP": ("BIOYOND_PolymerStation_Solid_Stock", "3a190ca0-b2f6-9aeb-8067-547e72c11469"),
# "TMSB": ("BIOYOND_PolymerStation_Solid_Stock", "3a190ca0-b2f6-9aeb-8067-547e72c11469"),
# "EP": ("BIOYOND_PolymerStation_Solid_Stock", "3a190ca0-b2f6-9aeb-8067-547e72c11469"),
# "DEC": ("BIOYOND_PolymerStation_Solid_Stock", "3a190ca0-b2f6-9aeb-8067-547e72c11469"),
# "EMC": ("BIOYOND_PolymerStation_Solid_Stock", "3a190ca0-b2f6-9aeb-8067-547e72c11469"),
# "SN": ("BIOYOND_PolymerStation_Solid_Stock", "3a190ca0-b2f6-9aeb-8067-547e72c11469"),
# "DMC": ("BIOYOND_PolymerStation_Solid_Stock", "3a190ca0-b2f6-9aeb-8067-547e72c11469"),
# "FEC": ("BIOYOND_PolymerStation_Solid_Stock", "3a190ca0-b2f6-9aeb-8067-547e72c11469"),
} }
WORKFLOW_MAPPINGS = {}
LOCATION_MAPPING = {}

View File

@@ -1,831 +0,0 @@
from datetime import datetime
import json
from unilabos.devices.workstation.bioyond_studio.bioyond_rpc import BioyondException
from unilabos.devices.workstation.bioyond_studio.station import BioyondWorkstation
class BioyondDispensingStation(BioyondWorkstation):
def __init__(
self,
config,
# 桌子
deck,
*args,
**kwargs,
):
super().__init__(config, deck, *args, **kwargs)
# self.config = config
# self.api_key = config["api_key"]
# self.host = config["api_host"]
#
# # 使用简单的Logger替代原来的logger
# self._logger = SimpleLogger()
# self.is_running = False
# 90%10%小瓶投料任务创建方法
def create_90_10_vial_feeding_task(self,
order_name: str = None,
speed: str = None,
temperature: str = None,
delay_time: str = None,
percent_90_1_assign_material_name: str = None,
percent_90_1_target_weigh: str = None,
percent_90_2_assign_material_name: str = None,
percent_90_2_target_weigh: str = None,
percent_90_3_assign_material_name: str = None,
percent_90_3_target_weigh: str = None,
percent_10_1_assign_material_name: str = None,
percent_10_1_target_weigh: str = None,
percent_10_1_volume: str = None,
percent_10_1_liquid_material_name: str = None,
percent_10_2_assign_material_name: str = None,
percent_10_2_target_weigh: str = None,
percent_10_2_volume: str = None,
percent_10_2_liquid_material_name: str = None,
percent_10_3_assign_material_name: str = None,
percent_10_3_target_weigh: str = None,
percent_10_3_volume: str = None,
percent_10_3_liquid_material_name: str = None,
hold_m_name: str = None) -> dict:
"""
创建90%10%小瓶投料任务
参数说明:
- order_name: 任务名称如果为None则使用默认名称
- speed: 搅拌速度如果为None则使用默认值400
- temperature: 温度如果为None则使用默认值40
- delay_time: 延迟时间如果为None则使用默认值600
- percent_90_1_assign_material_name: 90%_1物料名称
- percent_90_1_target_weigh: 90%_1目标重量
- percent_90_2_assign_material_name: 90%_2物料名称
- percent_90_2_target_weigh: 90%_2目标重量
- percent_90_3_assign_material_name: 90%_3物料名称
- percent_90_3_target_weigh: 90%_3目标重量
- percent_10_1_assign_material_name: 10%_1固体物料名称
- percent_10_1_target_weigh: 10%_1固体目标重量
- percent_10_1_volume: 10%_1液体体积
- percent_10_1_liquid_material_name: 10%_1液体物料名称
- percent_10_2_assign_material_name: 10%_2固体物料名称
- percent_10_2_target_weigh: 10%_2固体目标重量
- percent_10_2_volume: 10%_2液体体积
- percent_10_2_liquid_material_name: 10%_2液体物料名称
- percent_10_3_assign_material_name: 10%_3固体物料名称
- percent_10_3_target_weigh: 10%_3固体目标重量
- percent_10_3_volume: 10%_3液体体积
- percent_10_3_liquid_material_name: 10%_3液体物料名称
- hold_m_name: 库位名称,如"C01"用于查找对应的holdMId
返回: 任务创建结果
异常:
- BioyondException: 各种错误情况下的统一异常
"""
try:
# 1. 参数验证
if not hold_m_name:
raise BioyondException("hold_m_name 是必填参数")
# 检查90%物料参数的完整性
# 90%_1物料如果有物料名称或目标重量就必须有全部参数
if percent_90_1_assign_material_name or percent_90_1_target_weigh:
if not percent_90_1_assign_material_name:
raise BioyondException("90%_1物料如果提供了目标重量必须同时提供物料名称")
if not percent_90_1_target_weigh:
raise BioyondException("90%_1物料如果提供了物料名称必须同时提供目标重量")
# 90%_2物料如果有物料名称或目标重量就必须有全部参数
if percent_90_2_assign_material_name or percent_90_2_target_weigh:
if not percent_90_2_assign_material_name:
raise BioyondException("90%_2物料如果提供了目标重量必须同时提供物料名称")
if not percent_90_2_target_weigh:
raise BioyondException("90%_2物料如果提供了物料名称必须同时提供目标重量")
# 90%_3物料如果有物料名称或目标重量就必须有全部参数
if percent_90_3_assign_material_name or percent_90_3_target_weigh:
if not percent_90_3_assign_material_name:
raise BioyondException("90%_3物料如果提供了目标重量必须同时提供物料名称")
if not percent_90_3_target_weigh:
raise BioyondException("90%_3物料如果提供了物料名称必须同时提供目标重量")
# 检查10%物料参数的完整性
# 10%_1物料如果有物料名称、目标重量、体积或液体物料名称中的任何一个就必须有全部参数
if any([percent_10_1_assign_material_name, percent_10_1_target_weigh, percent_10_1_volume, percent_10_1_liquid_material_name]):
if not percent_10_1_assign_material_name:
raise BioyondException("10%_1物料如果提供了其他参数必须同时提供固体物料名称")
if not percent_10_1_target_weigh:
raise BioyondException("10%_1物料如果提供了其他参数必须同时提供固体目标重量")
if not percent_10_1_volume:
raise BioyondException("10%_1物料如果提供了其他参数必须同时提供液体体积")
if not percent_10_1_liquid_material_name:
raise BioyondException("10%_1物料如果提供了其他参数必须同时提供液体物料名称")
# 10%_2物料如果有物料名称、目标重量、体积或液体物料名称中的任何一个就必须有全部参数
if any([percent_10_2_assign_material_name, percent_10_2_target_weigh, percent_10_2_volume, percent_10_2_liquid_material_name]):
if not percent_10_2_assign_material_name:
raise BioyondException("10%_2物料如果提供了其他参数必须同时提供固体物料名称")
if not percent_10_2_target_weigh:
raise BioyondException("10%_2物料如果提供了其他参数必须同时提供固体目标重量")
if not percent_10_2_volume:
raise BioyondException("10%_2物料如果提供了其他参数必须同时提供液体体积")
if not percent_10_2_liquid_material_name:
raise BioyondException("10%_2物料如果提供了其他参数必须同时提供液体物料名称")
# 10%_3物料如果有物料名称、目标重量、体积或液体物料名称中的任何一个就必须有全部参数
if any([percent_10_3_assign_material_name, percent_10_3_target_weigh, percent_10_3_volume, percent_10_3_liquid_material_name]):
if not percent_10_3_assign_material_name:
raise BioyondException("10%_3物料如果提供了其他参数必须同时提供固体物料名称")
if not percent_10_3_target_weigh:
raise BioyondException("10%_3物料如果提供了其他参数必须同时提供固体目标重量")
if not percent_10_3_volume:
raise BioyondException("10%_3物料如果提供了其他参数必须同时提供液体体积")
if not percent_10_3_liquid_material_name:
raise BioyondException("10%_3物料如果提供了其他参数必须同时提供液体物料名称")
# 2. 生成任务编码和设置默认值
order_code = "task_vial_" + str(int(datetime.now().timestamp()))
if order_name is None:
order_name = "90%10%小瓶投料任务"
if speed is None:
speed = "400"
if temperature is None:
temperature = "40"
if delay_time is None:
delay_time = "600"
# 3. 工作流ID
workflow_id = "3a19310d-16b9-9d81-b109-0748e953694b"
# 4. 查询工作流对应的holdMID
material_info = self.hardware_interface.material_id_query(workflow_id)
if not material_info:
raise BioyondException(f"无法查询工作流 {workflow_id} 的物料信息")
# 获取locations列表
locations = material_info.get("locations", []) if isinstance(material_info, dict) else []
if not locations:
raise BioyondException(f"工作流 {workflow_id} 没有找到库位信息")
# 查找指定名称的库位
hold_mid = None
for location in locations:
if location.get("holdMName") == hold_m_name:
hold_mid = location.get("holdMId")
break
if not hold_mid:
raise BioyondException(f"未找到库位名称为 {hold_m_name} 的库位,请检查名称是否正确")
extend_properties = f"{{\"{ hold_mid }\": {{}}}}"
self.hardware_interface._logger.info(f"找到库位 {hold_m_name} 对应的holdMId: {hold_mid}")
# 5. 构建任务参数
order_data = {
"orderCode": order_code,
"orderName": order_name,
"workflowId": workflow_id,
"borderNumber": 1,
"paramValues": {},
"ExtendProperties": extend_properties
}
# 添加搅拌参数
order_data["paramValues"]["e8264e47-c319-d9d9-8676-4dd5cb382b11"] = [
{"m": 0, "n": 3, "Key": "speed", "Value": speed},
{"m": 0, "n": 3, "Key": "temperature", "Value": temperature}
]
# 添加延迟时间参数
order_data["paramValues"]["dc5dba79-5e4b-8eae-cbc5-e93482e43b1f"] = [
{"m": 0, "n": 4, "Key": "DelayTime", "Value": delay_time}
]
# 添加90%_1参数
if percent_90_1_assign_material_name is not None and percent_90_1_target_weigh is not None:
order_data["paramValues"]["e7d3c0a3-25c2-c42d-c84b-860c4a5ef844"] = [
{"m": 15, "n": 1, "Key": "targetWeigh", "Value": percent_90_1_target_weigh},
{"m": 15, "n": 1, "Key": "assignMaterialName", "Value": percent_90_1_assign_material_name}
]
# 添加90%_2参数
if percent_90_2_assign_material_name is not None and percent_90_2_target_weigh is not None:
order_data["paramValues"]["50b912c4-6c81-0734-1c8b-532428b2a4a5"] = [
{"m": 18, "n": 1, "Key": "targetWeigh", "Value": percent_90_2_target_weigh},
{"m": 18, "n": 1, "Key": "assignMaterialName", "Value": percent_90_2_assign_material_name}
]
# 添加90%_3参数
if percent_90_3_assign_material_name is not None and percent_90_3_target_weigh is not None:
order_data["paramValues"]["9c3674b3-c7cb-946e-fa03-fa2861d8aec4"] = [
{"m": 21, "n": 1, "Key": "targetWeigh", "Value": percent_90_3_target_weigh},
{"m": 21, "n": 1, "Key": "assignMaterialName", "Value": percent_90_3_assign_material_name}
]
# 添加10%_1固体参数
if percent_10_1_assign_material_name is not None and percent_10_1_target_weigh is not None:
order_data["paramValues"]["73a0bfd8-1967-45e9-4bab-c07ccd1a2727"] = [
{"m": 3, "n": 1, "Key": "targetWeigh", "Value": percent_10_1_target_weigh},
{"m": 3, "n": 1, "Key": "assignMaterialName", "Value": percent_10_1_assign_material_name}
]
# 添加10%_1液体参数
if percent_10_1_liquid_material_name is not None and percent_10_1_volume is not None:
order_data["paramValues"]["39634d40-c623-473a-8e5f-bc301aca2522"] = [
{"m": 3, "n": 3, "Key": "volume", "Value": percent_10_1_volume},
{"m": 3, "n": 3, "Key": "assignMaterialName", "Value": percent_10_1_liquid_material_name}
]
# 添加10%_2固体参数
if percent_10_2_assign_material_name is not None and percent_10_2_target_weigh is not None:
order_data["paramValues"]["2d9c16fa-2a19-cd47-a67b-3cadff9e3e3d"] = [
{"m": 7, "n": 1, "Key": "targetWeigh", "Value": percent_10_2_target_weigh},
{"m": 7, "n": 1, "Key": "assignMaterialName", "Value": percent_10_2_assign_material_name}
]
# 添加10%_2液体参数
if percent_10_2_liquid_material_name is not None and percent_10_2_volume is not None:
order_data["paramValues"]["e60541bb-ed68-e839-7305-2b4abe38a13d"] = [
{"m": 7, "n": 3, "Key": "volume", "Value": percent_10_2_volume},
{"m": 7, "n": 3, "Key": "assignMaterialName", "Value": percent_10_2_liquid_material_name}
]
# 添加10%_3固体参数
if percent_10_3_assign_material_name is not None and percent_10_3_target_weigh is not None:
order_data["paramValues"]["27494733-0f71-a916-7cd2-1929a0125f17"] = [
{"m": 11, "n": 1, "Key": "targetWeigh", "Value": percent_10_3_target_weigh},
{"m": 11, "n": 1, "Key": "assignMaterialName", "Value": percent_10_3_assign_material_name}
]
# 添加10%_3液体参数
if percent_10_3_liquid_material_name is not None and percent_10_3_volume is not None:
order_data["paramValues"]["c8798c29-786f-6858-7d7f-5330b890f2a6"] = [
{"m": 11, "n": 3, "Key": "volume", "Value": percent_10_3_volume},
{"m": 11, "n": 3, "Key": "assignMaterialName", "Value": percent_10_3_liquid_material_name}
]
# 6. 转换为JSON字符串并创建任务
json_str = json.dumps([order_data], ensure_ascii=False)
self.hardware_interface._logger.info(f"创建90%10%小瓶投料任务参数: {json_str}")
# 7. 调用create_order方法创建任务
result = self.hardware_interface.create_order(json_str)
self.hardware_interface._logger.info(f"创建90%10%小瓶投料任务结果: {result}")
return json.dumps({"suc": True})
except BioyondException:
# 重新抛出BioyondException
raise
except Exception as e:
# 捕获其他未预期的异常转换为BioyondException
error_msg = f"创建90%10%小瓶投料任务时发生未预期的错误: {str(e)}"
self.hardware_interface._logger.error(error_msg)
raise BioyondException(error_msg)
# 二胺溶液配置任务创建方法
def create_diamine_solution_task(self,
order_name: str = None,
material_name: str = None,
target_weigh: str = None,
volume: str = None,
liquid_material_name: str = "NMP",
speed: str = None,
temperature: str = None,
delay_time: str = None,
hold_m_name: str = None) -> dict:
"""
创建二胺溶液配置任务
参数说明:
- order_name: 任务名称如果为None则使用默认名称
- material_name: 固体物料名称,必填
- target_weigh: 固体目标重量,必填
- volume: 液体体积,必填
- liquid_material_name: 液体物料名称默认为NMP
- speed: 搅拌速度如果为None则使用默认值400
- temperature: 温度如果为None则使用默认值20
- delay_time: 延迟时间如果为None则使用默认值600
- hold_m_name: 库位名称,如"ODA-1"用于查找对应的holdMId
返回: 任务创建结果
异常:
- BioyondException: 各种错误情况下的统一异常
"""
try:
# 1. 参数验证
if not material_name:
raise BioyondException("material_name 是必填参数")
if not target_weigh:
raise BioyondException("target_weigh 是必填参数")
if not volume:
raise BioyondException("volume 是必填参数")
if not hold_m_name:
raise BioyondException("hold_m_name 是必填参数")
# 2. 生成任务编码和设置默认值
order_code = "task_oda_" + str(int(datetime.now().timestamp()))
if order_name is None:
order_name = f"二胺溶液配置-{material_name}"
if speed is None:
speed = "400"
if temperature is None:
temperature = "20"
if delay_time is None:
delay_time = "600"
# 3. 工作流ID - 二胺溶液配置工作流
workflow_id = "3a15d4a1-3bbe-76f9-a458-292896a338f5"
# 4. 查询工作流对应的holdMID
material_info = self.material_id_query(workflow_id)
if not material_info:
raise BioyondException(f"无法查询工作流 {workflow_id} 的物料信息")
# 获取locations列表
locations = material_info.get("locations", []) if isinstance(material_info, dict) else []
if not locations:
raise BioyondException(f"工作流 {workflow_id} 没有找到库位信息")
# 查找指定名称的库位
hold_mid = None
for location in locations:
if location.get("holdMName") == hold_m_name:
hold_mid = location.get("holdMId")
break
if not hold_mid:
raise BioyondException(f"未找到库位名称为 {hold_m_name} 的库位,请检查名称是否正确")
extend_properties = f"{{\"{ hold_mid }\": {{}}}}"
self.hardware_interface._logger.info(f"找到库位 {hold_m_name} 对应的holdMId: {hold_mid}")
# 5. 构建任务参数
order_data = {
"orderCode": order_code,
"orderName": order_name,
"workflowId": workflow_id,
"borderNumber": 1,
"paramValues": {
# 固体物料参数
"3a15d4a1-3bde-f5bc-053f-1ae0bf1f357e": [
{"m": 3, "n": 2, "Key": "targetWeigh", "Value": target_weigh},
{"m": 3, "n": 2, "Key": "assignMaterialName", "Value": material_name}
],
# 液体物料参数
"3a15d4a1-3bde-d584-b309-e661ae8f1c01": [
{"m": 3, "n": 3, "Key": "volume", "Value": volume},
{"m": 3, "n": 3, "Key": "assignMaterialName", "Value": liquid_material_name}
],
# 搅拌参数
"3a15d4a1-3bde-8ec4-1ced-92efc97ed73d": [
{"m": 3, "n": 6, "Key": "speed", "Value": speed},
{"m": 3, "n": 6, "Key": "temperature", "Value": temperature}
],
# 延迟时间参数
"3a15d4a1-3bde-3b92-83ff-8923a0addbbc": [
{"m": 3, "n": 7, "Key": "DelayTime", "Value": delay_time}
]
},
"ExtendProperties": extend_properties
}
# 6. 转换为JSON字符串并创建任务
json_str = json.dumps([order_data], ensure_ascii=False)
self.hardware_interface._logger.info(f"创建二胺溶液配置任务参数: {json_str}")
# 7. 调用create_order方法创建任务
result = self.hardware_interface.create_order(json_str)
self.hardware_interface._logger.info(f"创建二胺溶液配置任务结果: {result}")
return json.dumps({"suc": True})
except BioyondException:
# 重新抛出BioyondException
raise
except Exception as e:
# 捕获其他未预期的异常转换为BioyondException
error_msg = f"创建二胺溶液配置任务时发生未预期的错误: {str(e)}"
self.hardware_interface._logger.error(error_msg)
raise BioyondException(error_msg)
if __name__ == "__main__":
bioyond = BioyondDispensingStation(config={
"api_key": "DE9BDDA0",
"api_host": "http://192.168.1.200:44388"
})
# 示例1使用material_id_query查询工作流对应的holdMID
workflow_id_1 = "3a15d4a1-3bbe-76f9-a458-292896a338f5" # 二胺溶液配置工作流ID
workflow_id_2 = "3a19310d-16b9-9d81-b109-0748e953694b" # 90%10%小瓶投料工作流ID
#示例2创建二胺溶液配置任务 - ODA指定库位名称
# bioyond.create_diamine_solution_task(
# order_code="task_oda_" + str(int(datetime.now().timestamp())),
# order_name="二胺溶液配置-ODA",
# material_name="ODA-1",
# target_weigh="12.000",
# volume="60",
# liquid_material_name= "NMP",
# speed="400",
# temperature="20",
# delay_time="600",
# hold_m_name="烧杯ODA"
# )
# bioyond.create_diamine_solution_task(
# order_code="task_pda_" + str(int(datetime.now().timestamp())),
# order_name="二胺溶液配置-PDA",
# material_name="PDA-1",
# target_weigh="4.178",
# volume="60",
# liquid_material_name= "NMP",
# speed="400",
# temperature="20",
# delay_time="600",
# hold_m_name="烧杯PDA-2"
# )
# bioyond.create_diamine_solution_task(
# order_code="task_mpda_" + str(int(datetime.now().timestamp())),
# order_name="二胺溶液配置-MPDA",
# material_name="MPDA-1",
# target_weigh="3.298",
# volume="50",
# liquid_material_name= "NMP",
# speed="400",
# temperature="20",
# delay_time="600",
# hold_m_name="烧杯MPDA"
# )
bioyond.material_id_query("3a19310d-16b9-9d81-b109-0748e953694b")
bioyond.material_id_query("3a15d4a1-3bbe-76f9-a458-292896a338f5")
#示例4创建90%10%小瓶投料任务
# vial_result = bioyond.create_90_10_vial_feeding_task(
# order_code="task_vial_" + str(int(datetime.now().timestamp())),
# order_name="90%10%小瓶投料-1",
# percent_90_1_assign_material_name="BTDA-1",
# percent_90_1_target_weigh="7.392",
# percent_90_2_assign_material_name="BTDA-1",
# percent_90_2_target_weigh="7.392",
# percent_90_3_assign_material_name="BTDA-2",
# percent_90_3_target_weigh="7.392",
# percent_10_1_assign_material_name="BTDA-2",
# percent_10_1_target_weigh="1.500",
# percent_10_1_volume="20",
# percent_10_1_liquid_material_name="NMP",
# # percent_10_2_assign_material_name="BTDA-c",
# # percent_10_2_target_weigh="1.2",
# # percent_10_2_volume="20",
# # percent_10_2_liquid_material_name="NMP",
# speed="400",
# temperature="60",
# delay_time="1200",
# hold_m_name="8.4分装板-1"
# )
# vial_result = bioyond.create_90_10_vial_feeding_task(
# order_code="task_vial_" + str(int(datetime.now().timestamp())),
# order_name="90%10%小瓶投料-2",
# percent_90_1_assign_material_name="BPDA-1",
# percent_90_1_target_weigh="5.006",
# percent_90_2_assign_material_name="PMDA-1",
# percent_90_2_target_weigh="3.810",
# percent_90_3_assign_material_name="BPDA-1",
# percent_90_3_target_weigh="8.399",
# percent_10_1_assign_material_name="BPDA-1",
# percent_10_1_target_weigh="1.200",
# percent_10_1_volume="20",
# percent_10_1_liquid_material_name="NMP",
# percent_10_2_assign_material_name="BPDA-1",
# percent_10_2_target_weigh="1.200",
# percent_10_2_volume="20",
# percent_10_2_liquid_material_name="NMP",
# speed="400",
# temperature="60",
# delay_time="1200",
# hold_m_name="8.4分装板-2"
# )
#启动调度器
#bioyond.scheduler_start()
#继续调度器
#bioyond.scheduler_continue()
result0 = bioyond.stock_material('{"typeMode": 0, "includeDetail": true}')
result1 = bioyond.stock_material('{"typeMode": 1, "includeDetail": true}')
result2 = bioyond.stock_material('{"typeMode": 2, "includeDetail": true}')
matpos1 = bioyond.query_warehouse_by_material_type("3a14196e-b7a0-a5da-1931-35f3000281e9")
matpos2 = bioyond.query_warehouse_by_material_type("3a14196e-5dfe-6e21-0c79-fe2036d052c4")
matpos3 = bioyond.query_warehouse_by_material_type("3a14196b-24f2-ca49-9081-0cab8021bf1a")
#样品板(里面有样品瓶)
material_data_yp = {
"typeId": "3a14196e-b7a0-a5da-1931-35f3000281e9",
#"code": "物料编码001",
#"barCode": "物料条码001",
"name": "8.4样品板",
"unit": "",
"quantity": 1,
"details": [
{
"typeId": "3a14196a-cf7d-8aea-48d8-b9662c7dba94",
#"code": "物料编码001",
"name": "BTDA-1",
"quantity": 20,
"x": 1,
"y": 1,
#"unit": "单位"
"molecular": 1,
"Parameters":"{\"molecular\": 1}"
},
{
"typeId": "3a14196a-cf7d-8aea-48d8-b9662c7dba94",
#"code": "物料编码001",
"name": "BPDA-1",
"quantity": 20,
"x": 2,
"y": 1, #x1y2是A02
#"unit": "单位"
"molecular": 1,
"Parameters":"{\"molecular\": 1}"
},
{
"typeId": "3a14196a-cf7d-8aea-48d8-b9662c7dba94",
#"code": "物料编码001",
"name": "BTDA-2",
"quantity": 20,
"x": 1,
"y": 2, #x1y2是A02
#"unit": "单位"
"molecular": 1,
"Parameters":"{\"molecular\": 1}"
},
{
"typeId": "3a14196a-cf7d-8aea-48d8-b9662c7dba94",
#"code": "物料编码001",
"name": "PMDA-1",
"quantity": 20,
"x": 2,
"y": 2, #x1y2是A02
#"unit": "单位"
"molecular": 1,
"Parameters":"{\"molecular\": 1}"
}
],
"Parameters":"{}"
}
material_data_yp = {
"typeId": "3a14196e-b7a0-a5da-1931-35f3000281e9",
#"code": "物料编码001",
#"barCode": "物料条码001",
"name": "8.7样品板",
"unit": "",
"quantity": 1,
"details": [
{
"typeId": "3a14196a-cf7d-8aea-48d8-b9662c7dba94",
#"code": "物料编码001",
"name": "mianfen",
"quantity": 13,
"x": 1,
"y": 1,
#"unit": "单位"
"molecular": 1,
"Parameters":"{\"molecular\": 1}"
},
{
"typeId": "3a14196a-cf7d-8aea-48d8-b9662c7dba94",
#"code": "物料编码001",
"name": "mianfen2",
"quantity": 13,
"x": 1,
"y": 2, #x1y2是A02
#"unit": "单位"
"molecular": 1,
"Parameters":"{\"molecular\": 1}"
}
],
"Parameters":"{}"
}
#分装板
material_data_fzb_1 = {
"typeId": "3a14196e-5dfe-6e21-0c79-fe2036d052c4",
#"code": "物料编码001",
#"barCode": "物料条码001",
"name": "8.7分装板",
"unit": "",
"quantity": 1,
"details": [
{
"typeId": "3a14196c-76be-2279-4e22-7310d69aed68",
#"code": "物料编码001",
"name": "10%小瓶1",
"quantity": 1,
"x": 1,
"y": 1,
#"unit": "单位"
"molecular": 1,
"Parameters":"{\"molecular\": 1}"
},
{
"typeId": "3a14196c-76be-2279-4e22-7310d69aed68",
#"code": "物料编码001",
"name": "10%小瓶2",
"quantity": 1,
"x": 1,
"y": 2,
#"unit": "单位"
"molecular": 1,
"Parameters":"{\"molecular\": 1}"
},
{
"typeId": "3a14196c-76be-2279-4e22-7310d69aed68",
#"code": "物料编码001",
"name": "10%小瓶3",
"quantity": 1,
"x": 1,
"y": 3,
#"unit": "单位"
"molecular": 1,
"Parameters":"{\"molecular\": 1}"
},
{
"typeId": "3a14196c-cdcf-088d-dc7d-5cf38f0ad9ea",
#"code": "物料编码001",
"name": "90%小瓶1",
"quantity": 1,
"x": 2,
"y": 1, #x1y2是A02
#"unit": "单位"
"molecular": 1,
"Parameters":"{\"molecular\": 1}"
},
{
"typeId": "3a14196c-cdcf-088d-dc7d-5cf38f0ad9ea",
#"code": "物料编码001",
"name": "90%小瓶2",
"quantity": 1,
"x": 2,
"y": 2,
#"unit": "单位"
"molecular": 1,
"Parameters":"{\"molecular\": 1}"
},
{
"typeId": "3a14196c-cdcf-088d-dc7d-5cf38f0ad9ea",
#"code": "物料编码001",
"name": "90%小瓶3",
"quantity": 1,
"x": 2,
"y": 3,
"molecular": 1,
"Parameters":"{\"molecular\": 1}"
}
],
"Parameters":"{}"
}
material_data_fzb_2 = {
"typeId": "3a14196e-5dfe-6e21-0c79-fe2036d052c4",
#"code": "物料编码001",
#"barCode": "物料条码001",
"name": "8.4分装板-2",
"unit": "",
"quantity": 1,
"details": [
{
"typeId": "3a14196c-76be-2279-4e22-7310d69aed68",
#"code": "物料编码001",
"name": "10%小瓶1",
"quantity": 1,
"x": 1,
"y": 1,
#"unit": "单位"
"molecular": 1,
"Parameters":"{\"molecular\": 1}"
},
{
"typeId": "3a14196c-76be-2279-4e22-7310d69aed68",
#"code": "物料编码001",
"name": "10%小瓶2",
"quantity": 1,
"x": 1,
"y": 2,
#"unit": "单位"
"molecular": 1,
"Parameters":"{\"molecular\": 1}"
},
{
"typeId": "3a14196c-76be-2279-4e22-7310d69aed68",
#"code": "物料编码001",
"name": "10%小瓶3",
"quantity": 1,
"x": 1,
"y": 3,
#"unit": "单位"
"molecular": 1,
"Parameters":"{\"molecular\": 1}"
},
{
"typeId": "3a14196c-cdcf-088d-dc7d-5cf38f0ad9ea",
#"code": "物料编码001",
"name": "90%小瓶1",
"quantity": 1,
"x": 2,
"y": 1, #x1y2是A02
#"unit": "单位"
"molecular": 1,
"Parameters":"{\"molecular\": 1}"
},
{
"typeId": "3a14196c-cdcf-088d-dc7d-5cf38f0ad9ea",
#"code": "物料编码001",
"name": "90%小瓶2",
"quantity": 1,
"x": 2,
"y": 2,
#"unit": "单位"
"molecular": 1,
"Parameters":"{\"molecular\": 1}"
},
{
"typeId": "3a14196c-cdcf-088d-dc7d-5cf38f0ad9ea",
#"code": "物料编码001",
"name": "90%小瓶3",
"quantity": 1,
"x": 2,
"y": 3,
"molecular": 1,
"Parameters":"{\"molecular\": 1}"
}
],
"Parameters":"{}"
}
#烧杯
material_data_sb_oda = {
"typeId": "3a14196b-24f2-ca49-9081-0cab8021bf1a",
#"code": "物料编码001",
#"barCode": "物料条码001",
"name": "mianfen1",
"unit": "",
"quantity": 1,
"Parameters":"{}"
}
material_data_sb_pda_2 = {
"typeId": "3a14196b-24f2-ca49-9081-0cab8021bf1a",
#"code": "物料编码001",
#"barCode": "物料条码001",
"name": "mianfen2",
"unit": "",
"quantity": 1,
"Parameters":"{}"
}
# material_data_sb_mpda = {
# "typeId": "3a14196b-24f2-ca49-9081-0cab8021bf1a",
# #"code": "物料编码001",
# #"barCode": "物料条码001",
# "name": "烧杯MPDA",
# "unit": "个",
# "quantity": 1,
# "Parameters":"{}"
# }
#result_1 = bioyond.add_material(json.dumps(material_data_yp, ensure_ascii=False))
#result_2 = bioyond.add_material(json.dumps(material_data_fzb_1, ensure_ascii=False))
# result_3 = bioyond.add_material(json.dumps(material_data_fzb_2, ensure_ascii=False))
# result_4 = bioyond.add_material(json.dumps(material_data_sb_oda, ensure_ascii=False))
# result_5 = bioyond.add_material(json.dumps(material_data_sb_pda_2, ensure_ascii=False))
# #result会返回id
# #样品板1id3a1b3e7d-339d-0291-dfd3-13e2a78fe521
# #将指定物料入库到指定库位
#bioyond.material_inbound(result_1, "3a14198e-6929-31f0-8a22-0f98f72260df")
#bioyond.material_inbound(result_2, "3a14198e-6929-46fe-841e-03dd753f1e4a")
# bioyond.material_inbound(result_3, "3a14198e-6929-72ac-32ce-9b50245682b8")
# bioyond.material_inbound(result_4, "3a14198e-d724-e036-afdc-2ae39a7f3383")
# bioyond.material_inbound(result_5, "3a14198e-d724-d818-6d4f-5725191a24b5")
#bioyond.material_outbound(result_1, "3a14198e-6929-31f0-8a22-0f98f72260df")
# bioyond.stock_material('{"typeMode": 2, "includeDetail": true}')
query_order = {"status":"100", "pageCount": "10"}
bioyond.order_query(json.dumps(query_order, ensure_ascii=False))
# id = "3a1bce3c-4f31-c8f3-5525-f3b273bc34dc"
# bioyond.sample_waste_removal(id)

View File

@@ -1,10 +1,11 @@
# experiment_workflow.py
""" """
实验流程主程序 实验流程主程序
""" """
import json import json
from unilabos.devices.workstation.bioyond_studio.reaction_station import BioyondReactionStation from bioyond_rpc import BioyondV1RPC
from unilabos.devices.workstation.bioyond_studio.config import API_CONFIG, WORKFLOW_MAPPINGS, DECK_CONFIG, MATERIAL_TYPE_MAPPINGS from config import API_CONFIG, WORKFLOW_MAPPINGS
def run_experiment(): def run_experiment():
@@ -13,20 +14,15 @@ def run_experiment():
# 初始化Bioyond客户端 # 初始化Bioyond客户端
config = { config = {
**API_CONFIG, **API_CONFIG,
"workflow_mappings": WORKFLOW_MAPPINGS, "workflow_mappings": WORKFLOW_MAPPINGS
"material_type_mappings": MATERIAL_TYPE_MAPPINGS
} }
# 创建BioyondReactionStation实例传入deck配置 Bioyond = BioyondV1RPC(config)
Bioyond = BioyondReactionStation(
config=config,
deck=DECK_CONFIG
)
print("\n============= 多工作流参数测试(简化接口+材料缓存)=============") print("\n============= 多工作流参数测试(简化接口+材料缓存)=============")
# 显示可用的材料名称前20个 # 显示可用的材料名称前20个
available_materials = Bioyond.hardware_interface.get_available_materials() available_materials = Bioyond.get_available_materials()
print(f"可用材料名称前20个: {available_materials[:20]}") print(f"可用材料名称前20个: {available_materials[:20]}")
print(f"总共有 {len(available_materials)} 个材料可用\n") print(f"总共有 {len(available_materials)} 个材料可用\n")
@@ -45,7 +41,7 @@ def run_experiment():
assign_material_name="ODA", assign_material_name="ODA",
time="0", time="0",
torque_variation="1", torque_variation="1",
titration_type="1", titrationType="1",
temperature=-10 temperature=-10
) )
@@ -56,14 +52,14 @@ def run_experiment():
assign_material_name="MPDA", assign_material_name="MPDA",
time="5", time="5",
torque_variation="2", torque_variation="2",
titration_type="1", titrationType="1",
temperature=0 temperature=0
) )
# 4. 液体投料-小瓶非滴定 # 4. 液体投料-小瓶非滴定
print("4. 添加液体投料-小瓶非滴定,带参数...") print("4. 添加液体投料-小瓶非滴定,带参数...")
Bioyond.liquid_feeding_vials_non_titration( Bioyond.liquid_feeding_vials_non_titration(
volume_formula="639.5", volumeFormula="639.5",
assign_material_name="SIDA", assign_material_name="SIDA",
titration_type="1", titration_type="1",
time="0", time="0",
@@ -88,7 +84,7 @@ def run_experiment():
material_id="3", material_id="3",
time="180", time="180",
torque_variation="2", torque_variation="2",
assign_material_name="BTDA1", assign_material_name="BTDA-1",
temperature=-10.00 temperature=-10.00
) )
@@ -97,7 +93,7 @@ def run_experiment():
material_id="3", material_id="3",
time="180", time="180",
torque_variation="2", torque_variation="2",
assign_material_name="BTDA2", assign_material_name="BTDA-2",
temperature=25.00 temperature=25.00
) )
@@ -106,14 +102,14 @@ def run_experiment():
material_id="3", material_id="3",
time="480", time="480",
torque_variation="2", torque_variation="2",
assign_material_name="BTDA3", assign_material_name="BTDA-3",
temperature=25.00 temperature=25.00
) )
# 液体投料滴定(第一个) # 液体投料滴定(第一个)
print("9. 添加液体投料滴定,带参数...") # ODPA print("9. 添加液体投料滴定,带参数...") # ODPA
Bioyond.liquid_feeding_titration( Bioyond.liquid_feeding_titration(
volume_formula="{{6-0-5}}+{{7-0-5}}+{{8-0-5}}", volume_formula="1000",
assign_material_name="BTDA-DD", assign_material_name="BTDA-DD",
titration_type="1", titration_type="1",
time="360", time="360",
@@ -173,6 +169,8 @@ def run_experiment():
temperature="25.00" temperature="25.00"
) )
print("15. 添加液体投料溶剂,带参数...") print("15. 添加液体投料溶剂,带参数...")
Bioyond.liquid_feeding_solvents( Bioyond.liquid_feeding_solvents(
assign_material_name="PGME", assign_material_name="PGME",
@@ -196,8 +194,8 @@ def run_experiment():
print("\n4. 执行process_and_execute_workflow...") print("\n4. 执行process_and_execute_workflow...")
result = Bioyond.process_and_execute_workflow( result = Bioyond.process_and_execute_workflow(
workflow_name="test3", workflow_name="test3_86",
task_name="实验3" task_name="实验3_86"
) )
# 显示执行结果 # 显示执行结果
@@ -207,9 +205,9 @@ def run_experiment():
result_dict = json.loads(result) result_dict = json.loads(result)
if result_dict.get("success"): if result_dict.get("success"):
print("任务创建成功!") print("任务创建成功!")
# print(f"- 工作流: {result_dict.get('workflow', {}).get('name')}") print(f"- 工作流: {result_dict.get('workflow', {}).get('name')}")
# print(f"- 工作流ID: {result_dict.get('workflow', {}).get('id')}") print(f"- 工作流ID: {result_dict.get('workflow', {}).get('id')}")
# print(f"- 任务结果: {result_dict.get('task')}") print(f"- 任务结果: {result_dict.get('task')}")
else: else:
print(f"任务创建失败: {result_dict.get('error')}") print(f"任务创建失败: {result_dict.get('error')}")
except: except:
@@ -229,166 +227,166 @@ def run_experiment():
return Bioyond return Bioyond
# def prepare_materials(bioyond): def prepare_materials(bioyond):
# """准备实验材料(可选)""" """准备实验材料(可选)"""
# # 样品板材料数据定义 # 样品板材料数据定义
# material_data_yp_1 = { material_data_yp_1 = {
# "typeId": "3a142339-80de-8f25-6093-1b1b1b6c322e", "typeId": "3a142339-80de-8f25-6093-1b1b1b6c322e",
# "name": "样品板-1", "name": "样品板-1",
# "unit": "个", "unit": "",
# "quantity": 1, "quantity": 1,
# "details": [ "details": [
# { {
# "typeId": "3a14233a-84a3-088d-6676-7cb4acd57c64", "typeId": "3a14233a-84a3-088d-6676-7cb4acd57c64",
# "name": "BPDA-DD-1", "name": "BPDA-DD-1",
# "quantity": 1, "quantity": 1,
# "x": 1, "x": 1,
# "y": 1, "y": 1,
# "Parameters": "{\"molecular\": 1}" "Parameters": "{\"molecular\": 1}"
# }, },
# { {
# "typeId": "3a14233a-84a3-088d-6676-7cb4acd57c64", "typeId": "3a14233a-84a3-088d-6676-7cb4acd57c64",
# "name": "PEPA", "name": "PEPA",
# "quantity": 1, "quantity": 1,
# "x": 1, "x": 1,
# "y": 2, "y": 2,
# "Parameters": "{\"molecular\": 1}" "Parameters": "{\"molecular\": 1}"
# }, },
# { {
# "typeId": "3a14233a-84a3-088d-6676-7cb4acd57c64", "typeId": "3a14233a-84a3-088d-6676-7cb4acd57c64",
# "name": "BPDA-DD-2", "name": "BPDA-DD-2",
# "quantity": 1, "quantity": 1,
# "x": 1, "x": 1,
# "y": 3, "y": 3,
# "Parameters": "{\"molecular\": 1}" "Parameters": "{\"molecular\": 1}"
# }, },
# { {
# "typeId": "3a14233a-84a3-088d-6676-7cb4acd57c64", "typeId": "3a14233a-84a3-088d-6676-7cb4acd57c64",
# "name": "BPDA-1", "name": "BPDA-1",
# "quantity": 1, "quantity": 1,
# "x": 2, "x": 2,
# "y": 1, "y": 1,
# "Parameters": "{\"molecular\": 1}" "Parameters": "{\"molecular\": 1}"
# }, },
# { {
# "typeId": "3a14233a-84a3-088d-6676-7cb4acd57c64", "typeId": "3a14233a-84a3-088d-6676-7cb4acd57c64",
# "name": "PMDA", "name": "PMDA",
# "quantity": 1, "quantity": 1,
# "x": 2, "x": 2,
# "y": 2, "y": 2,
# "Parameters": "{\"molecular\": 1}" "Parameters": "{\"molecular\": 1}"
# }, },
# { {
# "typeId": "3a14233a-84a3-088d-6676-7cb4acd57c64", "typeId": "3a14233a-84a3-088d-6676-7cb4acd57c64",
# "name": "BPDA-2", "name": "BPDA-2",
# "quantity": 1, "quantity": 1,
# "x": 2, "x": 2,
# "y": 3, "y": 3,
# "Parameters": "{\"molecular\": 1}" "Parameters": "{\"molecular\": 1}"
# } }
# ], ],
# "Parameters": "{}" "Parameters": "{}"
# } }
# material_data_yp_2 = { material_data_yp_2 = {
# "typeId": "3a142339-80de-8f25-6093-1b1b1b6c322e", "typeId": "3a142339-80de-8f25-6093-1b1b1b6c322e",
# "name": "样品板-2", "name": "样品板-2",
# "unit": "个", "unit": "",
# "quantity": 1, "quantity": 1,
# "details": [ "details": [
# { {
# "typeId": "3a14233a-84a3-088d-6676-7cb4acd57c64", "typeId": "3a14233a-84a3-088d-6676-7cb4acd57c64",
# "name": "BPDA-DD", "name": "BPDA-DD",
# "quantity": 1, "quantity": 1,
# "x": 1, "x": 1,
# "y": 1, "y": 1,
# "Parameters": "{\"molecular\": 1}" "Parameters": "{\"molecular\": 1}"
# }, },
# { {
# "typeId": "3a14233a-84a3-088d-6676-7cb4acd57c64", "typeId": "3a14233a-84a3-088d-6676-7cb4acd57c64",
# "name": "SIDA", "name": "SIDA",
# "quantity": 1, "quantity": 1,
# "x": 1, "x": 1,
# "y": 2, "y": 2,
# "Parameters": "{\"molecular\": 1}" "Parameters": "{\"molecular\": 1}"
# }, },
# { {
# "typeId": "3a14233a-84a3-088d-6676-7cb4acd57c64", "typeId": "3a14233a-84a3-088d-6676-7cb4acd57c64",
# "name": "BTDA-1", "name": "BTDA-1",
# "quantity": 1, "quantity": 1,
# "x": 2, "x": 2,
# "y": 1, "y": 1,
# "Parameters": "{\"molecular\": 1}" "Parameters": "{\"molecular\": 1}"
# }, },
# { {
# "typeId": "3a14233a-84a3-088d-6676-7cb4acd57c64", "typeId": "3a14233a-84a3-088d-6676-7cb4acd57c64",
# "name": "BTDA-2", "name": "BTDA-2",
# "quantity": 1, "quantity": 1,
# "x": 2, "x": 2,
# "y": 2, "y": 2,
# "Parameters": "{\"molecular\": 1}" "Parameters": "{\"molecular\": 1}"
# }, },
# { {
# "typeId": "3a14233a-84a3-088d-6676-7cb4acd57c64", "typeId": "3a14233a-84a3-088d-6676-7cb4acd57c64",
# "name": "BTDA-3", "name": "BTDA-3",
# "quantity": 1, "quantity": 1,
# "x": 2, "x": 2,
# "y": 3, "y": 3,
# "Parameters": "{\"molecular\": 1}" "Parameters": "{\"molecular\": 1}"
# } }
# ], ],
# "Parameters": "{}" "Parameters": "{}"
# } }
# # 烧杯材料数据定义 # 烧杯材料数据定义
# beaker_materials = [ beaker_materials = [
# { {
# "typeId": "3a14233b-f0a9-ba84-eaa9-0d4718b361b6", "typeId": "3a14233b-f0a9-ba84-eaa9-0d4718b361b6",
# "name": "PDA-1", "name": "PDA-1",
# "unit": "微升", "unit": "微升",
# "quantity": 1, "quantity": 1,
# "parameters": "{\"DeviceMaterialType\":\"NMP\"}" "parameters": "{\"DeviceMaterialType\":\"NMP\"}"
# }, },
# { {
# "typeId": "3a14233b-f0a9-ba84-eaa9-0d4718b361b6", "typeId": "3a14233b-f0a9-ba84-eaa9-0d4718b361b6",
# "name": "TFDB", "name": "TFDB",
# "unit": "微升", "unit": "微升",
# "quantity": 1, "quantity": 1,
# "parameters": "{\"DeviceMaterialType\":\"NMP\"}" "parameters": "{\"DeviceMaterialType\":\"NMP\"}"
# }, },
# { {
# "typeId": "3a14233b-f0a9-ba84-eaa9-0d4718b361b6", "typeId": "3a14233b-f0a9-ba84-eaa9-0d4718b361b6",
# "name": "ODA", "name": "ODA",
# "unit": "微升", "unit": "微升",
# "quantity": 1, "quantity": 1,
# "parameters": "{\"DeviceMaterialType\":\"NMP\"}" "parameters": "{\"DeviceMaterialType\":\"NMP\"}"
# }, },
# { {
# "typeId": "3a14233b-f0a9-ba84-eaa9-0d4718b361b6", "typeId": "3a14233b-f0a9-ba84-eaa9-0d4718b361b6",
# "name": "MPDA", "name": "MPDA",
# "unit": "微升", "unit": "微升",
# "quantity": 1, "quantity": 1,
# "parameters": "{\"DeviceMaterialType\":\"NMP\"}" "parameters": "{\"DeviceMaterialType\":\"NMP\"}"
# }, },
# { {
# "typeId": "3a14233b-f0a9-ba84-eaa9-0d4718b361b6", "typeId": "3a14233b-f0a9-ba84-eaa9-0d4718b361b6",
# "name": "PDA-2", "name": "PDA-2",
# "unit": "微升", "unit": "微升",
# "quantity": 1, "quantity": 1,
# "parameters": "{\"DeviceMaterialType\":\"NMP\"}" "parameters": "{\"DeviceMaterialType\":\"NMP\"}"
# } }
# ] ]
# # 如果需要可以在这里调用add_material方法添加材料 # 如果需要可以在这里调用add_material方法添加材料
# # 例如: # 例如:
# # result = bioyond.add_material(json.dumps(material_data_yp_1)) # result = bioyond.add_material(json.dumps(material_data_yp_1))
# # print(f"添加材料结果: {result}") # print(f"添加材料结果: {result}")
# return { return {
# "sample_plates": [material_data_yp_1, material_data_yp_2], "sample_plates": [material_data_yp_1, material_data_yp_2],
# "beakers": beaker_materials "beakers": beaker_materials
# } }
if __name__ == "__main__": if __name__ == "__main__":

View File

@@ -1,752 +0,0 @@
import json
import requests
from typing import List, Dict, Any
from unilabos.devices.workstation.bioyond_studio.station import BioyondWorkstation
from unilabos.devices.workstation.bioyond_studio.config import (
WORKFLOW_STEP_IDS,
WORKFLOW_TO_SECTION_MAP,
ACTION_NAMES
)
from unilabos.devices.workstation.bioyond_studio.config import API_CONFIG
class BioyondReactionStation(BioyondWorkstation):
"""Bioyond反应站类
继承自BioyondWorkstation提供反应站特定的业务方法
"""
def __init__(self, config: dict = None, deck=None, protocol_type=None, **kwargs):
"""初始化反应站
Args:
config: 配置字典应包含workflow_mappings等配置
deck: Deck对象
protocol_type: 协议类型由ROS系统传递此处忽略
**kwargs: 其他可能的参数
"""
if deck is None and config:
deck = config.get('deck')
print(f"BioyondReactionStation初始化 - config包含workflow_mappings: {'workflow_mappings' in (config or {})}")
if config and 'workflow_mappings' in config:
print(f"workflow_mappings内容: {config['workflow_mappings']}")
super().__init__(bioyond_config=config, deck=deck)
print(f"BioyondReactionStation初始化完成 - workflow_mappings: {self.workflow_mappings}")
print(f"workflow_mappings长度: {len(self.workflow_mappings)}")
# ==================== 工作流方法 ====================
def reactor_taken_out(self):
"""反应器取出"""
self.append_to_workflow_sequence('{"web_workflow_name": "reactor_taken_out"}')
reactor_taken_out_params = {"param_values": {}}
self.pending_task_params.append(reactor_taken_out_params)
print(f"成功添加反应器取出工作流")
print(f"当前队列长度: {len(self.pending_task_params)}")
return json.dumps({"suc": True})
def reactor_taken_in(
self,
assign_material_name: str,
cutoff: str = "900000",
temperature: float = -10.00
):
"""反应器放入
Args:
assign_material_name: 物料名称(不能为空)
cutoff: 截止值/通量配置(需为有效数字字符串,默认 "900000"
temperature: 温度上限°C范围-50.00 至 100.00
Returns:
str: JSON 字符串,格式为 {"suc": True}
Raises:
ValueError: 若物料名称无效或 cutoff 格式错误
"""
if not assign_material_name:
raise ValueError("物料名称不能为空")
try:
float(cutoff)
except ValueError:
raise ValueError("cutoff 必须是有效的数字字符串")
self.append_to_workflow_sequence('{"web_workflow_name": "reactor_taken_in"}')
material_id = self.hardware_interface._get_material_id_by_name(assign_material_name)
if material_id is None:
raise ValueError(f"无法找到物料 {assign_material_name} 的 ID")
if isinstance(temperature, str):
temperature = float(temperature)
step_id = WORKFLOW_STEP_IDS["reactor_taken_in"]["config"]
reactor_taken_in_params = {
"param_values": {
step_id: {
ACTION_NAMES["reactor_taken_in"]["config"]: [
{"m": 0, "n": 3, "Key": "cutoff", "Value": cutoff},
{"m": 0, "n": 3, "Key": "assignMaterialName", "Value": material_id}
],
ACTION_NAMES["reactor_taken_in"]["stirring"]: [
{"m": 0, "n": 3, "Key": "temperature", "Value": f"{temperature:.2f}"}
]
}
}
}
self.pending_task_params.append(reactor_taken_in_params)
print(f"成功添加反应器放入参数: material={assign_material_name}->ID:{material_id}, cutoff={cutoff}, temp={temperature:.2f}")
print(f"当前队列长度: {len(self.pending_task_params)}")
return json.dumps({"suc": True})
def solid_feeding_vials(
self,
material_id: str,
time: str = "0",
torque_variation: int = 1,
assign_material_name: str = None,
temperature: float = 25.00
):
"""固体进料小瓶
Args:
material_id: 粉末类型ID
time: 观察时间(分钟)
torque_variation: 是否观察扭矩变化(int类型, 1=否, 2=是)
assign_material_name: 物料名称(用于获取试剂瓶位ID)
temperature: 温度上限(°C)
"""
self.append_to_workflow_sequence('{"web_workflow_name": "Solid_feeding_vials"}')
material_id_m = self.hardware_interface._get_material_id_by_name(assign_material_name) if assign_material_name else None
if isinstance(temperature, str):
temperature = float(temperature)
feeding_step_id = WORKFLOW_STEP_IDS["solid_feeding_vials"]["feeding"]
observe_step_id = WORKFLOW_STEP_IDS["solid_feeding_vials"]["observe"]
solid_feeding_vials_params = {
"param_values": {
feeding_step_id: {
ACTION_NAMES["solid_feeding_vials"]["feeding"]: [
{"m": 0, "n": 3, "Key": "materialId", "Value": material_id},
{"m": 0, "n": 3, "Key": "assignMaterialName", "Value": material_id_m} if material_id_m else {}
]
},
observe_step_id: {
ACTION_NAMES["solid_feeding_vials"]["observe"]: [
{"m": 1, "n": 0, "Key": "time", "Value": time},
{"m": 1, "n": 0, "Key": "torqueVariation", "Value": str(torque_variation)},
{"m": 1, "n": 0, "Key": "temperature", "Value": f"{temperature:.2f}"}
]
}
}
}
self.pending_task_params.append(solid_feeding_vials_params)
print(f"成功添加固体进料小瓶参数: material_id={material_id}, time={time}min, torque={torque_variation}, temp={temperature:.2f}°C")
print(f"当前队列长度: {len(self.pending_task_params)}")
return json.dumps({"suc": True})
def liquid_feeding_vials_non_titration(
self,
volume_formula: str,
assign_material_name: str,
titration_type: str = "1",
time: str = "0",
torque_variation: int = 1,
temperature: float = 25.00
):
"""液体进料小瓶(非滴定)
Args:
volume_formula: 分液公式(μL)
assign_material_name: 物料名称
titration_type: 是否滴定(1=滴定, 其他=非滴定)
time: 观察时间(分钟)
torque_variation: 是否观察扭矩变化(int类型, 1=否, 2=是)
temperature: 温度(°C)
"""
self.append_to_workflow_sequence('{"web_workflow_name": "Liquid_feeding_vials(non-titration)"}')
material_id = self.hardware_interface._get_material_id_by_name(assign_material_name)
if material_id is None:
raise ValueError(f"无法找到物料 {assign_material_name} 的 ID")
if isinstance(temperature, str):
temperature = float(temperature)
liquid_step_id = WORKFLOW_STEP_IDS["liquid_feeding_vials_non_titration"]["liquid"]
observe_step_id = WORKFLOW_STEP_IDS["liquid_feeding_vials_non_titration"]["observe"]
params = {
"param_values": {
liquid_step_id: {
ACTION_NAMES["liquid_feeding_vials_non_titration"]["liquid"]: [
{"m": 0, "n": 3, "Key": "volumeFormula", "Value": volume_formula},
{"m": 0, "n": 3, "Key": "assignMaterialName", "Value": material_id},
{"m": 0, "n": 3, "Key": "titrationType", "Value": titration_type}
]
},
observe_step_id: {
ACTION_NAMES["liquid_feeding_vials_non_titration"]["observe"]: [
{"m": 1, "n": 0, "Key": "time", "Value": time},
{"m": 1, "n": 0, "Key": "torqueVariation", "Value": str(torque_variation)},
{"m": 1, "n": 0, "Key": "temperature", "Value": f"{temperature:.2f}"}
]
}
}
}
self.pending_task_params.append(params)
print(f"成功添加液体进料小瓶(非滴定)参数: volume={volume_formula}μL, material={assign_material_name}->ID:{material_id}")
print(f"当前队列长度: {len(self.pending_task_params)}")
return json.dumps({"suc": True})
def liquid_feeding_solvents(
self,
assign_material_name: str,
volume: str,
titration_type: str = "1",
time: str = "360",
torque_variation: int = 2,
temperature: float = 25.00
):
"""液体进料-溶剂
Args:
assign_material_name: 物料名称
volume: 分液量(μL)
titration_type: 是否滴定
time: 观察时间(分钟)
torque_variation: 是否观察扭矩变化(int类型, 1=否, 2=是)
temperature: 温度上限(°C)
"""
self.append_to_workflow_sequence('{"web_workflow_name": "Liquid_feeding_solvents"}')
material_id = self.hardware_interface._get_material_id_by_name(assign_material_name)
if material_id is None:
raise ValueError(f"无法找到物料 {assign_material_name} 的 ID")
if isinstance(temperature, str):
temperature = float(temperature)
liquid_step_id = WORKFLOW_STEP_IDS["liquid_feeding_solvents"]["liquid"]
observe_step_id = WORKFLOW_STEP_IDS["liquid_feeding_solvents"]["observe"]
params = {
"param_values": {
liquid_step_id: {
ACTION_NAMES["liquid_feeding_solvents"]["liquid"]: [
{"m": 0, "n": 1, "Key": "titrationType", "Value": titration_type},
{"m": 0, "n": 1, "Key": "volume", "Value": volume},
{"m": 0, "n": 1, "Key": "assignMaterialName", "Value": material_id}
]
},
observe_step_id: {
ACTION_NAMES["liquid_feeding_solvents"]["observe"]: [
{"m": 1, "n": 0, "Key": "time", "Value": time},
{"m": 1, "n": 0, "Key": "torqueVariation", "Value": str(torque_variation)},
{"m": 1, "n": 0, "Key": "temperature", "Value": f"{temperature:.2f}"}
]
}
}
}
self.pending_task_params.append(params)
print(f"成功添加液体进料溶剂参数: material={assign_material_name}->ID:{material_id}, volume={volume}μL")
print(f"当前队列长度: {len(self.pending_task_params)}")
return json.dumps({"suc": True})
def liquid_feeding_titration(
self,
volume_formula: str,
assign_material_name: str,
titration_type: str = "1",
time: str = "90",
torque_variation: int = 2,
temperature: float = 25.00
):
"""液体进料(滴定)
Args:
volume_formula: 分液公式(μL)
assign_material_name: 物料名称
titration_type: 是否滴定
time: 观察时间(分钟)
torque_variation: 是否观察扭矩变化(int类型, 1=否, 2=是)
temperature: 温度(°C)
"""
self.append_to_workflow_sequence('{"web_workflow_name": "Liquid_feeding(titration)"}')
material_id = self.hardware_interface._get_material_id_by_name(assign_material_name)
if material_id is None:
raise ValueError(f"无法找到物料 {assign_material_name} 的 ID")
if isinstance(temperature, str):
temperature = float(temperature)
liquid_step_id = WORKFLOW_STEP_IDS["liquid_feeding_titration"]["liquid"]
observe_step_id = WORKFLOW_STEP_IDS["liquid_feeding_titration"]["observe"]
params = {
"param_values": {
liquid_step_id: {
ACTION_NAMES["liquid_feeding_titration"]["liquid"]: [
{"m": 0, "n": 3, "Key": "volumeFormula", "Value": volume_formula},
{"m": 0, "n": 3, "Key": "titrationType", "Value": titration_type},
{"m": 0, "n": 3, "Key": "assignMaterialName", "Value": material_id}
]
},
observe_step_id: {
ACTION_NAMES["liquid_feeding_titration"]["observe"]: [
{"m": 1, "n": 0, "Key": "time", "Value": time},
{"m": 1, "n": 0, "Key": "torqueVariation", "Value": str(torque_variation)},
{"m": 1, "n": 0, "Key": "temperature", "Value": f"{temperature:.2f}"}
]
}
}
}
self.pending_task_params.append(params)
print(f"成功添加液体进料滴定参数: volume={volume_formula}μL, material={assign_material_name}->ID:{material_id}")
print(f"当前队列长度: {len(self.pending_task_params)}")
return json.dumps({"suc": True})
def liquid_feeding_beaker(
self,
volume: str = "35000",
assign_material_name: str = "BAPP",
time: str = "0",
torque_variation: int = 1,
titration_type: str = "1",
temperature: float = 25.00
):
"""液体进料烧杯
Args:
volume: 分液量(μL)
assign_material_name: 物料名称(试剂瓶位)
time: 观察时间(分钟)
torque_variation: 是否观察扭矩变化(int类型, 1=否, 2=是)
titration_type: 是否滴定
temperature: 温度上限(°C)
"""
self.append_to_workflow_sequence('{"web_workflow_name": "liquid_feeding_beaker"}')
material_id = self.hardware_interface._get_material_id_by_name(assign_material_name)
if material_id is None:
raise ValueError(f"无法找到物料 {assign_material_name} 的 ID")
if isinstance(temperature, str):
temperature = float(temperature)
liquid_step_id = WORKFLOW_STEP_IDS["liquid_feeding_beaker"]["liquid"]
observe_step_id = WORKFLOW_STEP_IDS["liquid_feeding_beaker"]["observe"]
params = {
"param_values": {
liquid_step_id: {
ACTION_NAMES["liquid_feeding_beaker"]["liquid"]: [
{"m": 0, "n": 2, "Key": "volume", "Value": volume},
{"m": 0, "n": 2, "Key": "assignMaterialName", "Value": material_id},
{"m": 0, "n": 2, "Key": "titrationType", "Value": titration_type}
]
},
observe_step_id: {
ACTION_NAMES["liquid_feeding_beaker"]["observe"]: [
{"m": 1, "n": 0, "Key": "time", "Value": time},
{"m": 1, "n": 0, "Key": "torqueVariation", "Value": str(torque_variation)},
{"m": 1, "n": 0, "Key": "temperature", "Value": f"{temperature:.2f}"}
]
}
}
}
self.pending_task_params.append(params)
print(f"成功添加液体进料烧杯参数: volume={volume}μL, material={assign_material_name}->ID:{material_id}")
print(f"当前队列长度: {len(self.pending_task_params)}")
return json.dumps({"suc": True})
def drip_back(
self,
assign_material_name: str,
volume: str,
titration_type: str = "1",
time: str = "90",
torque_variation: int = 2,
temperature: float = 25.00
):
"""滴回去
Args:
assign_material_name: 物料名称(液体种类)
volume: 分液量(μL)
titration_type: 是否滴定
time: 观察时间(分钟)
torque_variation: 是否观察扭矩变化(int类型, 1=否, 2=是)
temperature: 温度(°C)
"""
self.append_to_workflow_sequence('{"web_workflow_name": "drip_back"}')
material_id = self.hardware_interface._get_material_id_by_name(assign_material_name)
if material_id is None:
raise ValueError(f"无法找到物料 {assign_material_name} 的 ID")
if isinstance(temperature, str):
temperature = float(temperature)
liquid_step_id = WORKFLOW_STEP_IDS["drip_back"]["liquid"]
observe_step_id = WORKFLOW_STEP_IDS["drip_back"]["observe"]
params = {
"param_values": {
liquid_step_id: {
ACTION_NAMES["drip_back"]["liquid"]: [
{"m": 0, "n": 1, "Key": "titrationType", "Value": titration_type},
{"m": 0, "n": 1, "Key": "assignMaterialName", "Value": material_id},
{"m": 0, "n": 1, "Key": "volume", "Value": volume}
]
},
observe_step_id: {
ACTION_NAMES["drip_back"]["observe"]: [
{"m": 1, "n": 0, "Key": "time", "Value": time},
{"m": 1, "n": 0, "Key": "torqueVariation", "Value": str(torque_variation)},
{"m": 1, "n": 0, "Key": "temperature", "Value": f"{temperature:.2f}"}
]
}
}
}
self.pending_task_params.append(params)
print(f"成功添加滴回去参数: material={assign_material_name}->ID:{material_id}, volume={volume}μL")
print(f"当前队列长度: {len(self.pending_task_params)}")
return json.dumps({"suc": True})
# ==================== 工作流管理方法 ====================
def get_workflow_sequence(self) -> List[str]:
"""获取当前工作流执行顺序
Returns:
工作流名称列表
"""
id_to_name = {workflow_id: name for name, workflow_id in self.workflow_mappings.items()}
workflow_names = []
for workflow_id in self.workflow_sequence:
workflow_name = id_to_name.get(workflow_id, workflow_id)
workflow_names.append(workflow_name)
print(f"工作流序列: {workflow_names}")
return workflow_names
def workflow_step_query(self, workflow_id: str) -> dict:
"""查询工作流步骤参数
Args:
workflow_id: 工作流ID
Returns:
工作流步骤参数字典
"""
return self.hardware_interface.workflow_step_query(workflow_id)
def create_order(self, json_str: str) -> dict:
"""创建订单
Args:
json_str: 订单参数的JSON字符串
Returns:
创建结果
"""
return self.hardware_interface.create_order(json_str)
# ==================== 工作流执行核心方法 ====================
def process_web_workflows(self, web_workflow_json: str) -> List[Dict[str, str]]:
"""处理网页工作流列表
Args:
web_workflow_json: JSON 格式的网页工作流列表
Returns:
List[Dict[str, str]]: 包含工作流 ID 和名称的字典列表
"""
try:
web_workflow_data = json.loads(web_workflow_json)
web_workflow_list = web_workflow_data.get("web_workflow_list", [])
workflows_result = []
for name in web_workflow_list:
workflow_id = self.workflow_mappings.get(name, "")
if not workflow_id:
print(f"警告:未找到工作流名称 {name} 对应的 ID")
continue
workflows_result.append({"id": workflow_id, "name": name})
print(f"process_web_workflows 输出: {workflows_result}")
return workflows_result
except json.JSONDecodeError as e:
print(f"错误:无法解析 web_workflow_json: {e}")
return []
except Exception as e:
print(f"错误:处理工作流失败: {e}")
return []
def process_and_execute_workflow(self, workflow_name: str, task_name: str) -> dict:
"""
一站式处理工作流程:解析网页工作流列表,合并工作流(带参数),然后发布任务
Args:
workflow_name: 合并后的工作流名称
task_name: 任务名称
Returns:
任务创建结果
"""
web_workflow_list = self.get_workflow_sequence()
print(f"\n{'='*60}")
print(f"📋 处理网页工作流列表: {web_workflow_list}")
print(f"{'='*60}")
web_workflow_json = json.dumps({"web_workflow_list": web_workflow_list})
workflows_result = self.process_web_workflows(web_workflow_json)
if not workflows_result:
return self._create_error_result("处理网页工作流列表失败", "process_web_workflows")
print(f"workflows_result 类型: {type(workflows_result)}")
print(f"workflows_result 内容: {workflows_result}")
workflows_with_params = self._build_workflows_with_parameters(workflows_result)
merge_data = {
"name": workflow_name,
"workflows": workflows_with_params
}
# print(f"\n🔄 合并工作流(带参数),名称: {workflow_name}")
merged_workflow = self.merge_workflow_with_parameters(json.dumps(merge_data))
if not merged_workflow:
return self._create_error_result("合并工作流失败", "merge_workflow_with_parameters")
workflow_id = merged_workflow.get("subWorkflows", [{}])[0].get("id", "")
# print(f"\n📤 使用工作流创建任务: {workflow_name} (ID: {workflow_id})")
order_params = [{
"orderCode": f"task_{self.hardware_interface.get_current_time_iso8601()}",
"orderName": task_name,
"workFlowId": workflow_id,
"borderNumber": 1,
"paramValues": {}
}]
result = self.create_order(json.dumps(order_params))
if not result:
return self._create_error_result("创建任务失败", "create_order")
# 清空工作流序列和参数,防止下次执行时累积重复
self.pending_task_params = []
self.clear_workflows() # 清空工作流序列,避免重复累积
# print(f"\n✅ 任务创建成功: {result}")
# print(f"\n✅ 任务创建成功")
print(f"{'='*60}\n")
return json.dumps({"success": True, "result": result})
def _build_workflows_with_parameters(self, workflows_result: list) -> list:
"""
构建带参数的工作流列表
Args:
workflows_result: 处理后的工作流列表(应为包含 id 和 name 的字典列表)
Returns:
符合新接口格式的工作流参数结构
"""
workflows_with_params = []
total_params = 0
successful_params = 0
failed_params = []
for idx, workflow_info in enumerate(workflows_result):
if not isinstance(workflow_info, dict):
print(f"错误workflows_result[{idx}] 不是字典,而是 {type(workflow_info)}: {workflow_info}")
continue
workflow_id = workflow_info.get("id")
if not workflow_id:
print(f"警告workflows_result[{idx}] 缺少 'id'")
continue
workflow_name = workflow_info.get("name", "")
# print(f"\n🔧 处理工作流 [{idx}]: {workflow_name} (ID: {workflow_id})")
if idx >= len(self.pending_task_params):
# print(f" ⚠️ 无对应参数,跳过")
workflows_with_params.append({"id": workflow_id})
continue
param_data = self.pending_task_params[idx]
param_values = param_data.get("param_values", {})
if not param_values:
# print(f" ⚠️ 参数为空,跳过")
workflows_with_params.append({"id": workflow_id})
continue
step_parameters = {}
for step_id, actions_dict in param_values.items():
# print(f" 📍 步骤ID: {step_id}")
for action_name, param_list in actions_dict.items():
# print(f" 🔹 模块: {action_name}, 参数数量: {len(param_list)}")
if step_id not in step_parameters:
step_parameters[step_id] = {}
if action_name not in step_parameters[step_id]:
step_parameters[step_id][action_name] = []
for param_item in param_list:
param_key = param_item.get("Key", "")
param_value = param_item.get("Value", "")
total_params += 1
step_parameters[step_id][action_name].append({
"Key": param_key,
"DisplayValue": param_value
})
successful_params += 1
# print(f" ✓ {param_key} = {param_value}")
workflows_with_params.append({
"id": workflow_id,
"stepParameters": step_parameters
})
self._print_mapping_stats(total_params, successful_params, failed_params)
return workflows_with_params
def _print_mapping_stats(self, total: int, success: int, failed: list):
"""打印参数映射统计"""
print(f"\n{'='*20} 参数映射统计 {'='*20}")
print(f"📊 总参数数量: {total}")
print(f"✅ 成功映射: {success}")
print(f"❌ 映射失败: {len(failed)}")
if not failed:
print("🎉 成功映射所有参数!")
else:
print(f"⚠️ 失败的参数: {', '.join(failed)}")
success_rate = (success/total*100) if total > 0 else 0
print(f"📈 映射成功率: {success_rate:.1f}%")
print("="*60)
def _create_error_result(self, error_msg: str, step: str) -> str:
"""创建统一的错误返回格式"""
print(f"{error_msg}")
return json.dumps({
"success": False,
"error": f"process_and_execute_workflow: {error_msg}",
"method": "process_and_execute_workflow",
"step": step
})
def merge_workflow_with_parameters(self, json_str: str) -> dict:
"""
调用新接口:合并工作流并传递参数
Args:
json_str: JSON格式的字符串包含:
- name: 工作流名称
- workflows: [{"id": "工作流ID", "stepParameters": {...}}]
Returns:
合并后的工作流信息
"""
try:
data = json.loads(json_str)
# 在工作流名称后面添加时间戳,避免重复
if "name" in data and data["name"]:
timestamp = self.hardware_interface.get_current_time_iso8601().replace(":", "-").replace(".", "-")
original_name = data["name"]
data["name"] = f"{original_name}_{timestamp}"
print(f"🕒 工作流名称已添加时间戳: {original_name} -> {data['name']}")
request_data = {
"apiKey": API_CONFIG["api_key"],
"requestTime": self.hardware_interface.get_current_time_iso8601(),
"data": data
}
print(f"\n📤 发送合并请求:")
print(f" 工作流名称: {data.get('name')}")
print(f" 子工作流数量: {len(data.get('workflows', []))}")
# 打印完整的POST请求内容
print(f"\n🔍 POST请求详细内容:")
print(f" URL: {self.hardware_interface.host}/api/lims/workflow/merge-workflow-with-parameters")
print(f" Headers: {{'Content-Type': 'application/json'}}")
print(f" Request Data:")
print(f" {json.dumps(request_data, indent=4, ensure_ascii=False)}")
#
response = requests.post(
f"{self.hardware_interface.host}/api/lims/workflow/merge-workflow-with-parameters",
json=request_data,
headers={"Content-Type": "application/json"},
timeout=30
)
# # 打印响应详细内容
# print(f"\n📥 POST响应详细内容:")
# print(f" 状态码: {response.status_code}")
# print(f" 响应头: {dict(response.headers)}")
# print(f" 响应体: {response.text}")
# #
try:
result = response.json()
# #
# print(f"\n📋 解析后的响应JSON:")
# print(f" {json.dumps(result, indent=4, ensure_ascii=False)}")
# #
except json.JSONDecodeError:
print(f"❌ 服务器返回非 JSON 格式响应: {response.text}")
return None
if result.get("code") == 1:
print(f"✅ 工作流合并成功(带参数)")
return result.get("data", {})
else:
error_msg = result.get('message', '未知错误')
print(f"❌ 工作流合并失败: {error_msg}")
return None
except requests.exceptions.Timeout:
print(f"❌ 合并工作流请求超时")
return None
except requests.exceptions.RequestException as e:
print(f"❌ 合并工作流网络异常: {str(e)}")
return None
except json.JSONDecodeError as e:
print(f"❌ 合并工作流响应解析失败: {str(e)}")
return None
except Exception as e:
print(f"❌ 合并工作流异常: {str(e)}")
return None
def _validate_and_refresh_workflow_if_needed(self, workflow_name: str) -> bool:
"""验证工作流ID是否有效如果无效则重新合并
Args:
workflow_name: 工作流名称
Returns:
bool: 验证或刷新是否成功
"""
print(f"\n🔍 验证工作流ID有效性...")
if not self.workflow_sequence:
print(f" ⚠️ 工作流序列为空,需要重新合并")
return False
first_workflow_id = self.workflow_sequence[0]
try:
structure = self.workflow_step_query(first_workflow_id)
if structure:
print(f" ✅ 工作流ID有效")
return True
else:
print(f" ⚠️ 工作流ID已过期需要重新合并")
return False
except Exception as e:
print(f" ❌ 工作流ID验证失败: {e}")
print(f" 💡 将重新合并工作流")
return False

File diff suppressed because it is too large Load Diff

View File

@@ -24,13 +24,6 @@ class ElectrodeSheetState(TypedDict):
thickness: float # 厚度 (mm) thickness: float # 厚度 (mm)
mass: float # 质量 (g) mass: float # 质量 (g)
material_type: str # 材料类型(正极、负极、隔膜、弹片、垫片、铝箔等) material_type: str # 材料类型(正极、负极、隔膜、弹片、垫片、铝箔等)
height: float
electrolyte_name: str
data_electrolyte_code: str
open_circuit_voltage: float
assembly_pressure: float
electrolyte_volume: float
info: Optional[str] # 附加信息 info: Optional[str] # 附加信息
class ElectrodeSheet(Resource): class ElectrodeSheet(Resource):
@@ -68,7 +61,6 @@ class ElectrodeSheet(Resource):
info=None info=None
) )
# TODO: 这个还要不要给self._unilabos_state赋值的
def load_state(self, state: Dict[str, Any]) -> None: def load_state(self, state: Dict[str, Any]) -> None:
"""格式不变""" """格式不变"""
super().load_state(state) super().load_state(state)
@@ -154,10 +146,10 @@ class MaterialHole(Resource):
): ):
"""放置极片""" """放置极片"""
# TODO: 这里要改diameter找不到加入._unilabos_state后应该没问题 # TODO: 这里要改diameter找不到加入._unilabos_state后应该没问题
#if resource._unilabos_state["diameter"] > self._unilabos_state["diameter"]: if resource._unilabos_state["diameter"] > self._unilabos_state["diameter"]:
# raise ValueError(f"极片直径 {resource._unilabos_state['diameter']} 超过洞位直径 {self._unilabos_state['diameter']}") raise ValueError(f"极片直径 {resource._unilabos_state['diameter']} 超过洞位直径 {self._unilabos_state['diameter']}")
#if len(self.children) >= self._unilabos_state["max_sheets"]: if len(self.children) >= self._unilabos_state["max_sheets"]:
# raise ValueError(f"洞位已满,无法放置更多极片") raise ValueError(f"洞位已满,无法放置更多极片")
super().assign_child_resource(resource, location, reassign) super().assign_child_resource(resource, location, reassign)
# 根据children的编号取物料对象。 # 根据children的编号取物料对象。
@@ -172,6 +164,8 @@ class MaterialPlateState(TypedDict):
hole_diameter: float hole_diameter: float
info: Optional[str] # 附加信息 info: Optional[str] # 附加信息
class MaterialPlate(ItemizedResource[MaterialHole]): class MaterialPlate(ItemizedResource[MaterialHole]):
"""料板类 - 4x4个洞位每个洞位放1个极片""" """料板类 - 4x4个洞位每个洞位放1个极片"""
@@ -329,13 +323,12 @@ class PlateSlot(ResourceStack):
class ClipMagazineHole(Container): class ClipMagazineHole(Container):
"""子弹夹洞位类""" """子弹夹洞位类"""
children: List[ElectrodeSheet] = []
def __init__( def __init__(
self, self,
name: str, name: str,
diameter: float, diameter: float,
depth: float, depth: float,
max_sheets: int = 100,
category: str = "clip_magazine_hole", category: str = "clip_magazine_hole",
): ):
"""初始化子弹夹洞位 """初始化子弹夹洞位
@@ -344,7 +337,6 @@ class ClipMagazineHole(Container):
name: 洞位名称 name: 洞位名称
diameter: 洞直径 (mm) diameter: 洞直径 (mm)
depth: 洞深度 (mm) depth: 洞深度 (mm)
max_sheets: 最大极片数量
category: 类别 category: 类别
""" """
super().__init__( super().__init__(
@@ -356,46 +348,143 @@ class ClipMagazineHole(Container):
) )
self.diameter = diameter self.diameter = diameter
self.depth = depth self.depth = depth
self.max_sheets = max_sheets
self._sheets: List[ElectrodeSheet] = []
def can_add_sheet(self, sheet: ElectrodeSheet) -> bool: def can_add_sheet(self, sheet: ElectrodeSheet) -> bool:
"""检查是否可以添加极片""" """检查是否可以添加极片
return (len(self._sheets) < self.max_sheets and
sheet.diameter <= self.diameter)
def add_sheet(self, sheet: ElectrodeSheet) -> None: 根据洞的深度和极片的厚度来判断是否可以添加极片
"""添加极片""" """
if not self.can_add_sheet(sheet): # 检查极片直径是否适合洞的直径
raise ValueError(f"无法向洞位 {self.name} 添加极片") if sheet._unilabos_state["diameter"] > self.diameter:
self._sheets.append(sheet) return False
# 计算当前已添加极片的总厚度
current_thickness = sum(s._unilabos_state["thickness"] for s in self.children)
# 检查添加新极片后总厚度是否超过洞的深度
if current_thickness + sheet._unilabos_state["thickness"] > self.depth:
return False
return True
def assign_child_resource(
self,
resource: ElectrodeSheet,
location: Optional[Coordinate] = None,
reassign: bool = True,
):
"""放置极片到洞位中
Args:
resource: 要放置的极片
location: 极片在洞位中的位置对于洞位通常为None
reassign: 是否允许重新分配
"""
# 检查是否可以添加极片
if not self.can_add_sheet(resource):
raise ValueError(f"无法向洞位 {self.name} 添加极片:直径或厚度不匹配")
# 调用父类方法实际执行分配
super().assign_child_resource(resource, location, reassign)
def unassign_child_resource(self, resource: ElectrodeSheet):
"""从洞位中移除极片
Args:
resource: 要移除的极片
"""
if resource not in self.children:
raise ValueError(f"极片 {resource.name} 不在洞位 {self.name}")
# 调用父类方法实际执行移除
super().unassign_child_resource(resource)
def take_sheet(self) -> ElectrodeSheet:
"""取出极片"""
if len(self._sheets) == 0:
raise ValueError(f"洞位 {self.name} 没有极片")
return self._sheets.pop()
def get_sheet_count(self) -> int:
"""获取极片数量"""
return len(self._sheets)
def serialize_state(self) -> Dict[str, Any]: def serialize_state(self) -> Dict[str, Any]:
return { return {
"sheet_count": len(self._sheets), "sheet_count": len(self.children),
"sheets": [sheet.serialize() for sheet in self._sheets], "sheets": [sheet.serialize() for sheet in self.children],
} }
class ClipMagazine_four(ItemizedResource[ClipMagazineHole]):
# TODO: 这个要改 """子弹夹类 - 有4个洞位每个洞位放多个极片"""
class ClipMagazine(Resource): children: List[ClipMagazineHole]
"""子弹夹类 - 有6个洞位每个洞位放多个极片"""
def __init__( def __init__(
self, self,
name: str, name: str,
size_x: float, size_x: float,
size_y: float, size_y: float,
size_z: float, size_z: float,
hole_diameter: float = 14.0,
hole_depth: float = 10.0,
hole_spacing: float = 25.0,
max_sheets_per_hole: int = 100,
category: str = "clip_magazine_four",
model: Optional[str] = None,
):
"""初始化子弹夹
Args:
name: 子弹夹名称
size_x: 长度 (mm)
size_y: 宽度 (mm)
size_z: 高度 (mm)
hole_diameter: 洞直径 (mm)
hole_depth: 洞深度 (mm)
hole_spacing: 洞位间距 (mm)
max_sheets_per_hole: 每个洞位最大极片数量
category: 类别
model: 型号
"""
# 创建4个洞位排成2x2布局
holes = create_ordered_items_2d(
klass=ClipMagazineHole,
num_items_x=2,
num_items_y=2,
dx=(size_x - 2 * hole_spacing) / 2, # 居中
dy=(size_y - hole_spacing) / 2, # 居中
dz=size_z - 0,
item_dx=hole_spacing,
item_dy=hole_spacing,
diameter=hole_diameter,
depth=hole_depth,
)
super().__init__(
name=name,
size_x=size_x,
size_y=size_y,
size_z=size_z,
ordered_items=holes,
category=category,
model=model,
)
# 保存洞位的直径和深度
self.hole_diameter = hole_diameter
self.hole_depth = hole_depth
self.max_sheets_per_hole = max_sheets_per_hole
def serialize(self) -> dict:
return {
**super().serialize(),
"hole_diameter": self.hole_diameter,
"hole_depth": self.hole_depth,
"max_sheets_per_hole": self.max_sheets_per_hole,
}
# TODO: 这个要改
class ClipMagazine(ItemizedResource[ClipMagazineHole]):
"""子弹夹类 - 有6个洞位每个洞位放多个极片"""
children: List[ClipMagazineHole]
def __init__(
self,
name: str,
size_x: float,
size_y: float,
size_z: float,
hole_diameter: float = 14.0,
hole_depth: float = 10.0,
hole_spacing: float = 25.0, hole_spacing: float = 25.0,
max_sheets_per_hole: int = 100, max_sheets_per_hole: int = 100,
category: str = "clip_magazine", category: str = "clip_magazine",
@@ -425,8 +514,8 @@ class ClipMagazine(Resource):
dz=size_z - 0, dz=size_z - 0,
item_dx=hole_spacing, item_dx=hole_spacing,
item_dy=hole_spacing, item_dy=hole_spacing,
diameter=0, diameter=hole_diameter,
depth=0, depth=hole_depth,
) )
super().__init__( super().__init__(
@@ -439,6 +528,7 @@ class ClipMagazine(Resource):
model=model, model=model,
) )
# 保存洞位的直径和深度
self.hole_diameter = hole_diameter self.hole_diameter = hole_diameter
self.hole_depth = hole_depth self.hole_depth = hole_depth
self.max_sheets_per_hole = max_sheets_per_hole self.max_sheets_per_hole = max_sheets_per_hole
@@ -455,9 +545,9 @@ class BatteryState(TypedDict):
"""电池状态字典""" """电池状态字典"""
diameter: float diameter: float
height: float height: float
assembly_pressure: float
electrolyte_volume: float
electrolyte_name: str electrolyte_name: str
electrolyte_volume: float
class Battery(Resource): class Battery(Resource):
"""电池类 - 可容纳极片""" """电池类 - 可容纳极片"""
@@ -466,9 +556,6 @@ class Battery(Resource):
def __init__( def __init__(
self, self,
name: str, name: str,
size_x=1,
size_y=1,
size_z=1,
category: str = "battery", category: str = "battery",
): ):
"""初始化电池 """初始化电池
@@ -489,13 +576,7 @@ class Battery(Resource):
size_z=1, size_z=1,
category=category, category=category,
) )
self._unilabos_state: BatteryState = BatteryState( self._unilabos_state: BatteryState = BatteryState()
diameter = 1.0,
height = 1.0,
assembly_pressure = 1.0,
electrolyte_volume = 1.0,
electrolyte_name = "DP001"
)
def add_electrolyte_with_bottle(self, bottle: Bottle) -> bool: def add_electrolyte_with_bottle(self, bottle: Bottle) -> bool:
to_add_name = bottle._unilabos_state["electrolyte_name"] to_add_name = bottle._unilabos_state["electrolyte_name"]
@@ -583,7 +664,6 @@ class BatteryPressSlot(Resource):
reassign: bool = True, reassign: bool = True,
): ):
"""放置极片""" """放置极片"""
# TODO: 让高京看下槽位只有一个电池时是否这么写。
if self.has_battery(): if self.has_battery():
raise ValueError(f"槽位已含有一个电池,无法再放置其他电池") raise ValueError(f"槽位已含有一个电池,无法再放置其他电池")
super().assign_child_resource(resource, location, reassign) super().assign_child_resource(resource, location, reassign)
@@ -592,7 +672,6 @@ class BatteryPressSlot(Resource):
def get_battery_info(self, index: int) -> Battery: def get_battery_info(self, index: int) -> Battery:
return self.children[0] return self.children[0]
# TODO:这个移液枪架子看一下从哪继承
class TipBox64State(TypedDict): class TipBox64State(TypedDict):
"""电池状态字典""" """电池状态字典"""
tip_diameter: float = 5.0 tip_diameter: float = 5.0
@@ -651,6 +730,15 @@ class TipBox64(TipRack):
make_tip=make_tip, make_tip=make_tip,
) )
self._unilabos_state: WasteTipBoxstate = WasteTipBoxstate() self._unilabos_state: WasteTipBoxstate = WasteTipBoxstate()
# 记录网格参数用于前端渲染
self._grid_params = {
"num_items_x": 8,
"num_items_y": 8,
"dx": 8.0,
"dy": 8.0,
"item_dx": 9.0,
"item_dy": 9.0,
}
super().__init__( super().__init__(
name=name, name=name,
size_x=size_x, size_x=size_x,
@@ -662,6 +750,12 @@ class TipBox64(TipRack):
with_tips=True, with_tips=True,
) )
def serialize(self) -> dict:
return {
**super().serialize(),
**self._grid_params,
}
class WasteTipBoxstate(TypedDict): class WasteTipBoxstate(TypedDict):
@@ -739,10 +833,19 @@ class BottleRackState(TypedDict):
name_to_index: dict name_to_index: dict
class BottleRackState(TypedDict):
""" bottle_diameter: 瓶子直径 (mm)
bottle_height: 瓶子高度 (mm)
position_spacing: 位置间距 (mm)"""
bottle_diameter: float
bottle_height: float
position_spacing: float
name_to_index: dict
class BottleRack(Resource): class BottleRack(Resource):
"""瓶架类 - 12个待配位置+12个已配位置""" """瓶架类 - 12个待配位置+12个已配位置"""
children: List[Bottle] = [] children: List[Resource] = []
def __init__( def __init__(
self, self,
@@ -752,6 +855,12 @@ class BottleRack(Resource):
size_z: float, size_z: float,
category: str = "bottle_rack", category: str = "bottle_rack",
model: Optional[str] = None, model: Optional[str] = None,
num_items_x: int = 3,
num_items_y: int = 4,
position_spacing: float = 35.0,
orientation: str = "horizontal",
padding_x: float = 20.0,
padding_y: float = 20.0,
): ):
"""初始化瓶架 """初始化瓶架
@@ -771,13 +880,42 @@ class BottleRack(Resource):
category=category, category=category,
model=model, model=model,
) )
# TODO: 添加瓶位坐标映射 # 初始化状态
self.index_to_pos = { self._unilabos_state: BottleRackState = BottleRackState(
0: Coordinate.zero(), bottle_diameter=30.0,
1: Coordinate(x=1, y=2, z=3) # 添加 bottle_height=100.0,
} position_spacing=position_spacing,
name_to_index={},
)
# 基于网格生成瓶位坐标映射(居中摆放)
# 使用内边距避免点跑到容器外前端渲染不按mm等比缩放时更稳妥
origin_x = padding_x
origin_y = padding_y
self.index_to_pos = {}
for j in range(num_items_y):
for i in range(num_items_x):
idx = j * num_items_x + i
if orientation == "vertical":
# 纵向:沿 y 方向优先排列
self.index_to_pos[idx] = Coordinate(
x=origin_x + j * position_spacing,
y=origin_y + i * position_spacing,
z=0,
)
else:
# 横向(默认):沿 x 方向优先排列
self.index_to_pos[idx] = Coordinate(
x=origin_x + i * position_spacing,
y=origin_y + j * position_spacing,
z=0,
)
self.name_to_index = {} self.name_to_index = {}
self.name_to_pos = {} self.name_to_pos = {}
self.num_items_x = num_items_x
self.num_items_y = num_items_y
self.orientation = orientation
self.padding_x = padding_x
self.padding_y = padding_y
def load_state(self, state: Dict[str, Any]) -> None: def load_state(self, state: Dict[str, Any]) -> None:
"""格式不变""" """格式不变"""
@@ -787,20 +925,23 @@ class BottleRack(Resource):
def serialize_state(self) -> Dict[str, Dict[str, Any]]: def serialize_state(self) -> Dict[str, Dict[str, Any]]:
"""格式不变""" """格式不变"""
data = super().serialize_state() data = super().serialize_state()
data.update(self._unilabos_state) # Container自身的信息云端物料将保存这一data本地也通过这里的data进行读写当前类用来表示这个物料的长宽高大小的属性而datastate用来表示物料的内容细节等 data.update(
self._unilabos_state) # Container自身的信息云端物料将保存这一data本地也通过这里的data进行读写当前类用来表示这个物料的长宽高大小的属性而datastate用来表示物料的内容细节等
return data return data
# TODO: 这里有些问题要重新写一下 # TODO: 这里有些问题要重新写一下
def assign_child_resource(self, resource: Bottle, location=Coordinate.zero(), reassign = True): def assign_child_resource_old(self, resource: Resource, location=Coordinate.zero(), reassign=True):
assert len(self.children) <= 12, "瓶架已满,无法添加更多瓶子" capacity = self.num_items_x * self.num_items_y
assert len(self.children) < capacity, "瓶架已满,无法添加更多瓶子"
index = len(self.children) index = len(self.children)
location = Coordinate(x=20 + (index % 4) * 15, y=20 + (index // 4) * 15, z=0) location = self.index_to_pos.get(index, Coordinate.zero())
self.name_to_pos[resource.name] = location self.name_to_pos[resource.name] = location
self.name_to_index[resource.name] = index self.name_to_index[resource.name] = index
return super().assign_child_resource(resource, location, reassign) return super().assign_child_resource(resource, location, reassign)
def assign_child_resource_by_index(self, resource: Bottle, index: int): def assign_child_resource(self, resource: Resource, index: int):
assert 0 <= index < 12, "无效的瓶子索引" capacity = self.num_items_x * self.num_items_y
assert 0 <= index < capacity, "无效的瓶子索引"
self.name_to_index[resource.name] = index self.name_to_index[resource.name] = index
location = self.index_to_pos[index] location = self.index_to_pos[index]
return super().assign_child_resource(resource, location) return super().assign_child_resource(resource, location)
@@ -809,9 +950,16 @@ class BottleRack(Resource):
super().unassign_child_resource(resource) super().unassign_child_resource(resource)
self.index_to_pos.pop(self.name_to_index.pop(resource.name, None), None) self.index_to_pos.pop(self.name_to_index.pop(resource.name, None), None)
# def serialize(self): def serialize(self) -> dict:
# self.children.sort(key=lambda x: self.name_to_index.get(x.name, 0)) return {
# return super().serialize() **super().serialize(),
"num_items_x": self.num_items_x,
"num_items_y": self.num_items_y,
"position_spacing": self._unilabos_state.get("position_spacing", 35.0),
"orientation": self.orientation,
"padding_x": self.padding_x,
"padding_y": self.padding_y,
}
class BottleState(TypedDict): class BottleState(TypedDict):
@@ -947,20 +1095,16 @@ class CoincellDeck(Deck):
# plate.assign_child_resource(hole) # plate.assign_child_resource(hole)
# return plate # return plate
import json def create_a_liaopan():
liaopan = MaterialPlate(name="liaopan", size_x=120.8, size_y=120.5, size_z=10.0, fill=True)
for i in range(16):
jipian = ElectrodeSheet(name=f"jipian_{i}", size_x= 12, size_y=12, size_z=0.1)
liaopan1.children[i].assign_child_resource(jipian, location=None)
return liaopan
if __name__ == "__main__": def create_a_coin_cell_deck():
#electrode1 = BatteryPressSlot() deck = Deck(size_x=1200,
#print(electrode1.get_size_x()) size_y=800,
#print(electrode1.get_size_y())
#print(electrode1.get_size_z())
#jipian = ElectrodeSheet()
#jipian._unilabos_state["diameter"] = 18
#print(jipian.serialize())
#print(jipian.serialize_state())
deck = CoincellDeck(size_x=1000,
size_y=1000,
size_z=900) size_z=900)
#liaopan = TipBox64(name="liaopan") #liaopan = TipBox64(name="liaopan")
@@ -971,33 +1115,172 @@ if __name__ == "__main__":
deck.assign_child_resource(liaopan1, Coordinate(x=0, y=0, z=0)) deck.assign_child_resource(liaopan1, Coordinate(x=0, y=0, z=0))
#创建一个极片 #创建一个极片
for i in range(16): for i in range(16):
jipian = ElectrodeSheet(name=f"jipian1_{i}", size_x= 12, size_y=12, size_z=0.1) jipian = ElectrodeSheet(name=f"jipian_{i}", size_x= 12, size_y=12, size_z=0.1)
liaopan1.children[i].assign_child_resource(jipian, location=None) liaopan1.children[i].assign_child_resource(jipian, location=None)
#
#创建一个4*4的物料板 #创建一个4*4的物料板
liaopan2 = MaterialPlate(name="liaopan2", size_x=120.8, size_y=120.5, size_z=10.0, fill=True) liaopan2 = MaterialPlate(name="liaopan2", size_x=120.8, size_y=120.5, size_z=10.0, fill=True)
#把物料板放到桌子上 #把物料板放到桌子上
deck.assign_child_resource(liaopan2, Coordinate(x=500, y=0, z=0)) deck.assign_child_resource(liaopan2, Coordinate(x=500, y=0, z=0))
#创建一个4*4的物料板 #创建一个4*4的物料板
liaopan3 = MaterialPlate(name="电池料盘", size_x=120.8, size_y=160.5, size_z=10.0, fill=True) liaopan3 = MaterialPlate(name="liaopan3", size_x=120.8, size_y=120.5, size_z=10.0, fill=True)
#把物料板放到桌子上 #把物料板放到桌子上
deck.assign_child_resource(liaopan3, Coordinate(x=100, y=100, z=0)) deck.assign_child_resource(liaopan3, Coordinate(x=1000, y=0, z=0))
print(deck)
return deck
import json
if __name__ == "__main__":
electrode1 = BatteryPressSlot()
#print(electrode1.get_size_x())
#print(electrode1.get_size_y())
#print(electrode1.get_size_z())
#jipian = ElectrodeSheet()
#jipian._unilabos_state["diameter"] = 18
#print(jipian.serialize())
#print(jipian.serialize_state())
deck = CoincellDeck()
"""======================================子弹夹============================================"""
zip_dan_jia = ClipMagazine_four("zi_dan_jia", 80, 80, 10)
deck.assign_child_resource(zip_dan_jia, Coordinate(x=1400, y=50, z=0))
zip_dan_jia2 = ClipMagazine_four("zi_dan_jia2", 80, 80, 10)
deck.assign_child_resource(zip_dan_jia2, Coordinate(x=1600, y=200, z=0))
zip_dan_jia3 = ClipMagazine("zi_dan_jia3", 80, 80, 10)
deck.assign_child_resource(zip_dan_jia3, Coordinate(x=1500, y=200, z=0))
zip_dan_jia4 = ClipMagazine("zi_dan_jia4", 80, 80, 10)
deck.assign_child_resource(zip_dan_jia4, Coordinate(x=1500, y=300, z=0))
zip_dan_jia5 = ClipMagazine("zi_dan_jia5", 80, 80, 10)
deck.assign_child_resource(zip_dan_jia5, Coordinate(x=1600, y=300, z=0))
zip_dan_jia6 = ClipMagazine("zi_dan_jia6", 80, 80, 10)
deck.assign_child_resource(zip_dan_jia6, Coordinate(x=1530, y=500, z=0))
zip_dan_jia7 = ClipMagazine("zi_dan_jia7", 80, 80, 10)
deck.assign_child_resource(zip_dan_jia7, Coordinate(x=1180, y=400, z=0))
zip_dan_jia8 = ClipMagazine("zi_dan_jia8", 80, 80, 10)
deck.assign_child_resource(zip_dan_jia8, Coordinate(x=1280, y=400, z=0))
for i in range(4):
jipian = ElectrodeSheet(name=f"zi_dan_jia_jipian_{i}", size_x=12, size_y=12, size_z=0.1)
zip_dan_jia2.children[i].assign_child_resource(jipian, location=None)
for i in range(4):
jipian2 = ElectrodeSheet(name=f"zi_dan_jia2_jipian_{i}", size_x=12, size_y=12, size_z=0.1)
zip_dan_jia.children[i].assign_child_resource(jipian2, location=None)
for i in range(6):
jipian3 = ElectrodeSheet(name=f"zi_dan_jia3_jipian_{i}", size_x=12, size_y=12, size_z=0.1)
zip_dan_jia3.children[i].assign_child_resource(jipian3, location=None)
for i in range(6):
jipian4 = ElectrodeSheet(name=f"zi_dan_jia4_jipian_{i}", size_x=12, size_y=12, size_z=0.1)
zip_dan_jia4.children[i].assign_child_resource(jipian4, location=None)
for i in range(6):
jipian5 = ElectrodeSheet(name=f"zi_dan_jia5_jipian_{i}", size_x=12, size_y=12, size_z=0.1)
zip_dan_jia5.children[i].assign_child_resource(jipian5, location=None)
for i in range(6):
jipian6 = ElectrodeSheet(name=f"zi_dan_jia6_jipian_{i}", size_x=12, size_y=12, size_z=0.1)
zip_dan_jia6.children[i].assign_child_resource(jipian6, location=None)
for i in range(6):
jipian7 = ElectrodeSheet(name=f"zi_dan_jia7_jipian_{i}", size_x=12, size_y=12, size_z=0.1)
zip_dan_jia7.children[i].assign_child_resource(jipian7, location=None)
for i in range(6):
jipian8 = ElectrodeSheet(name=f"zi_dan_jia8_jipian_{i}", size_x=12, size_y=12, size_z=0.1)
zip_dan_jia8.children[i].assign_child_resource(jipian8, location=None)
"""======================================子弹夹============================================"""
#liaopan = TipBox64(name="liaopan")
"""======================================物料板============================================"""
#创建一个4*4的物料板
liaopan1 = MaterialPlate(name="liaopan1", size_x=120, size_y=100, size_z=10.0, fill=True)
deck.assign_child_resource(liaopan1, Coordinate(x=1010, y=50, z=0))
for i in range(16):
jipian_1 = ElectrodeSheet(name=f"{liaopan1.name}_jipian_{i}", size_x=12, size_y=12, size_z=0.1)
liaopan1.children[i].assign_child_resource(jipian_1, location=None)
liaopan2 = MaterialPlate(name="liaopan2", size_x=120, size_y=100, size_z=10.0, fill=True)
deck.assign_child_resource(liaopan2, Coordinate(x=1130, y=50, z=0))
liaopan3 = MaterialPlate(name="liaopan3", size_x=120, size_y=100, size_z=10.0, fill=True)
deck.assign_child_resource(liaopan3, Coordinate(x=1250, y=50, z=0))
liaopan4 = MaterialPlate(name="liaopan4", size_x=120, size_y=100, size_z=10.0, fill=True)
deck.assign_child_resource(liaopan4, Coordinate(x=1010, y=150, z=0))
for i in range(16):
jipian_4 = ElectrodeSheet(name=f"{liaopan4.name}_jipian_{i}", size_x=12, size_y=12, size_z=0.1)
liaopan4.children[i].assign_child_resource(jipian_4, location=None)
liaopan5 = MaterialPlate(name="liaopan5", size_x=120, size_y=100, size_z=10.0, fill=True)
deck.assign_child_resource(liaopan5, Coordinate(x=1130, y=150, z=0))
liaopan6 = MaterialPlate(name="liaopan6", size_x=120, size_y=100, size_z=10.0, fill=True)
deck.assign_child_resource(liaopan6, Coordinate(x=1250, y=150, z=0))
#liaopan.children[3].assign_child_resource(jipian, location=None) #liaopan.children[3].assign_child_resource(jipian, location=None)
"""======================================物料板============================================"""
"""======================================瓶架,移液枪============================================"""
# 在台面上放置 3x4 瓶架、6x2 瓶架 与 64孔移液枪头盒
bottle_rack_3x4 = BottleRack(
name="bottle_rack_3x4",
size_x=210.0,
size_y=140.0,
size_z=100.0,
num_items_x=3,
num_items_y=4,
position_spacing=35.0,
orientation="vertical",
)
deck.assign_child_resource(bottle_rack_3x4, Coordinate(x=100, y=200, z=0))
bottle_rack_6x2 = BottleRack(
name="bottle_rack_6x2",
size_x=120.0,
size_y=250.0,
size_z=100.0,
num_items_x=6,
num_items_y=2,
position_spacing=35.0,
orientation="vertical",
)
deck.assign_child_resource(bottle_rack_6x2, Coordinate(x=300, y=300, z=0))
bottle_rack_6x2_2 = BottleRack(
name="bottle_rack_6x2_2",
size_x=120.0,
size_y=250.0,
size_z=100.0,
num_items_x=6,
num_items_y=2,
position_spacing=35.0,
orientation="vertical",
)
deck.assign_child_resource(bottle_rack_6x2_2, Coordinate(x=430, y=300, z=0))
# 将 ElectrodeSheet 放满 3x4 与 6x2 的所有孔位
for idx in range(bottle_rack_3x4.num_items_x * bottle_rack_3x4.num_items_y):
sheet = ElectrodeSheet(name=f"sheet_3x4_{idx}", size_x=12, size_y=12, size_z=0.1)
bottle_rack_3x4.assign_child_resource(sheet, index=idx)
for idx in range(bottle_rack_6x2.num_items_x * bottle_rack_6x2.num_items_y):
sheet = ElectrodeSheet(name=f"sheet_6x2_{idx}", size_x=12, size_y=12, size_z=0.1)
bottle_rack_6x2.assign_child_resource(sheet, index=idx)
tip_box = TipBox64(name="tip_box_64")
deck.assign_child_resource(tip_box, Coordinate(x=300, y=100, z=0))
waste_tip_box = WasteTipBox(name="waste_tip_box")
deck.assign_child_resource(waste_tip_box, Coordinate(x=300, y=200, z=0))
"""======================================瓶架,移液枪============================================"""
print(deck) print(deck)
from unilabos.resources.graphio import convert_resources_from_type from unilabos.resources.graphio import convert_resources_from_type
from unilabos.config.config import BasicConfig from unilabos.config.config import BasicConfig
BasicConfig.ak = "4d5ce6ae-7234-4639-834e-93899b9caf94" BasicConfig.ak = "56bbed5b-6e30-438c-b06d-f69eaa63bb45"
BasicConfig.sk = "505d3b0a-620e-459a-9905-1efcffce382a" BasicConfig.sk = "238222fe-0bf7-4350-a426-e5ced8011dcf"
from unilabos.app.web.client import http_client from unilabos.app.web.client import http_client
resources = convert_resources_from_type([deck], [Resource]) resources = convert_resources_from_type([deck], [Resource])
json.dump({"nodes": resources, "links": []}, open("button_battery_station_resources_unilab.json", "w"), indent=2)
# 检查序列化后的资源
json.dump({"nodes": resources, "links": []}, open("button_battery_decks_unilab.json", "w"), indent=2)
#print(resources) #print(resources)

View File

@@ -1,114 +0,0 @@
#!/usr/bin/env python
#coding=utf-8
ENV = 'pro'#'test'
class MQConfig:
"""MQTT 配置类"""
lab_id: str = '9F05593C'
instance_id: str = 'mqtt-cn-dsr48m6jy02'
group_id: str = 'GID_prod'
broker_url: str = 'mqtt-cn-dsr48m6jy02.mqtt.aliyuncs.com'
port: int = 8883
ca_content: str = '''-----BEGIN CERTIFICATE-----
MIID3jCCAsagAwIBAgIUDyIgmg4qZtMPa8r2Vvn1b1fgJ+YwDQYJKoZIhvcNAQEL
BQAwSzELMAkGA1UEBhMCQ04xCzAJBgNVBAgMAkJKMQswCQYDVQQHDAJCSjEQMA4G
A1UECgwHZHAudGVjaDEQMA4GA1UECwwHdW5pLWxhYjAeFw0yNTA1MDYxNTE0Mjda
Fw0zNTA1MDQxNTE0MjdaMEsxCzAJBgNVBAYTAkNOMQswCQYDVQQIDAJCSjELMAkG
A1UEBwwCQkoxEDAOBgNVBAoMB2RwLnRlY2gxEDAOBgNVBAsMB3VuaS1sYWIwggEi
MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDnTBywX+6DJ2n+prNKvylBBJF6
NHQrCt2cztZfswHsW4QhAbDddp4PRzNVzKtIfHX5ZrXGbxNT1/TqQYXKiFjKbfPC
VHTrS6+95LP3MxNTlBWHP6d2uI45KwrGgQ7D1uPDG1wZsfuJxvOkfAIxZRCDUMJr
erYYK/p2/GVMAO5YKE7wENUMN+iLfVQRqQJRgte9z0B35DxUeOUblJDun0Dpl/6L
0km/YRrjUKA/5+u/h+Ko9+36L1DAi+9rm3eyp+BQHBy5aiVhAG6uAJeMjbZMxwxz
ixg9cWNxP1BW+aQQzixbEQ+YlO9+w/soJkLstiK7jF8uIg2QvmNUKNlqab0pAgMB
AAGjgbkwgbYwHQYDVR0OBBYEFAqg0r7f6ngWODyVxVWHWM06b8wDMIGGBgNVHSME
fzB9gBQKoNK+3+p4Fjg8lcVVh1jNOm/MA6FPpE0wSzELMAkGA1UEBhMCQ04xCzAJ
BgNVBAgMAkJKMQswCQYDVQQHDAJCSjEQMA4GA1UECgwHZHAudGVjaDEQMA4GA1UE
CwwHdW5pLWxhYoIUDyIgmg4qZtMPa8r2Vvn1b1fgJ+YwDAYDVR0TBAUwAwEB/zAN
BgkqhkiG9w0BAQsFAAOCAQEAMylGHHhRCI8JLTizxqk2HaOFkF/WfnYC3XyNx3bK
9KqwVcvaqES+C058lits5nCV1qjjSnKt6xU11S8C6E28Kazh+wMqnSw63fz4UOY5
4cekPCPy8XcWlOY6UW2N27GR0c9JDo9ovruOn1Y4KjATpAQI4W2tPAQ2gCVSNpu1
bw5uw35yJSRzdQIHlsVbslvj2wcugK3GZHmmxJK+q9ww7G6xXtE2Y0+vl6AZRj+I
lcTy5TNNDZiiboIlAt+K3m4hxzSgGPbmFPJX3Lw3i+YMR/0PrWfXqxZgicO/V6/d
SgGBqq/tH1caiaEjCFudSZcOiZvHIlb09O4qL7mCtWEiEQ==
-----END CERTIFICATE-----
'''
cert_content: str = '''-----BEGIN CERTIFICATE-----
MIIDlTCCAn2gAwIBAgIUa/ce6dpJ8K7XNvT0LknVmLgfJMIwDQYJKoZIhvcNAQEL
BQAwSzELMAkGA1UEBhMCQ04xCzAJBgNVBAgMAkJKMQswCQYDVQQHDAJCSjEQMA4G
A1UECgwHZHAudGVjaDEQMA4GA1UECwwHdW5pLWxhYjAeFw0yNTA4MTMwNDQ0MDVa
Fw0yNjEyMjYwNDQ0MDVaMHoxCzAJBgNVBAYTAkNOMQswCQYDVQQIDAJCSjELMAkG
A1UEBwwCQkoxDDAKBgNVBAoMA0RQVDENMAsGA1UECwwETVFUVDEhMB8GCSqGSIb3
DQEJARYSaHVhaGFpbWluZ0BkcC50ZWNoMREwDwYDVQQDDAg0NzJBMjZBQzCCASIw
DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAPc4NHXcwaFX3jSN6DnDYoY7ON6c
AeVIlcQp3CMHnulh4t3I3Fnsyelpc809s7l5vEpAjMIuZ40DJKZQmV9ckmeylMiY
bAk851+i8YcRQPeYYY7Ggt0sfkY3TWIIqptZtlIhXtkTCWw6xpHAPkYxqNTiUN/0
vwQWwiBS7WqD8NVjNhhHootYLsMjnQYc162L8nUwzG2pjB3UYqOldC3FkHXvBkG2
Oeex8VM8Urblv0huCmoFRyuMmNol0QWqp+6nwAgdvf89Z38NJByPI9VHaBB/VV1F
HiAZe3H8Ph7wzgUSXBuVHJ4BaeJbg4+ax6BccpaQn26jgpJGUEj+YR+NwdMCAwEA
AaNCMEAwDAYDVR0TAQH/BAIwADAOBgNVHQ8BAf8EBAMCAvQwIAYDVR0lAQH/BBYw
FAYIKwYBBQUHAwIGCCsGAQUFBwMBMA0GCSqGSIb3DQEBCwUAA4IBAQAZaF8puP0/
OcRM7Gcd4LrF8H/WG0Q7WM0T9BWGvee6A+Fcd4ajBC0S0tIfdsfYat0+g4U57jrr
vaQeZGFKc4YKVui8vSuth82fcsFk5fpyhz4JJRggzeoby+0gNx9eYDJwLIvbVy4Y
2LKGq+rsO07QF54jtwB4WpDNFnEIadXyjPBsMy/0Ssbetp827WYZygXYyAcUlCfN
Wns7K0phfZJwIMQgPs3d4mGwCC+xaRIB3GGjUGFXV1sFItjkTUHCvm+phw/MTpRp
pauplyDcWYux7z1dKhbuHElzCEqxZNwyI0nGJlRFP13Oo+jnuDO7gQh2lyz/AFyX
KyTA3xFZduHO
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIID3jCCAsagAwIBAgIUDyIgmg4qZtMPa8r2Vvn1b1fgJ+YwDQYJKoZIhvcNAQEL
BQAwSzELMAkGA1UEBhMCQ04xCzAJBgNVBAgMAkJKMQswCQYDVQQHDAJCSjEQMA4G
A1UECgwHZHAudGVjaDEQMA4GA1UECwwHdW5pLWxhYjAeFw0yNTA1MDYxNTE0Mjda
Fw0zNTA1MDQxNTE0MjdaMEsxCzAJBgNVBAYTAkNOMQswCQYDVQQIDAJCSjELMAkG
A1UEBwwCQkoxEDAOBgNVBAoMB2RwLnRlY2gxEDAOBgNVBAsMB3VuaS1sYWIwggEi
MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDnTBywX+6DJ2n+prNKvylBBJF6
NHQrCt2cztZfswHsW4QhAbDddp4PRzNVzKtIfHX5ZrXGbxNT1/TqQYXKiFjKbfPC
VHTrS6+95LP3MxNTlBWHP6d2uI45KwrGgQ7D1uPDG1wZsfuJxvOkfAIxZRCDUMJr
erYYK/p2/GVMAO5YKE7wENUMN+iLfVQRqQJRgte9z0B35DxUeOUblJDun0Dpl/6L
0km/YRrjUKA/5+u/h+Ko9+36L1DAi+9rm3eyp+BQHBy5aiVhAG6uAJeMjbZMxwxz
ixg9cWNxP1BW+aQQzixbEQ+YlO9+w/soJkLstiK7jF8uIg2QvmNUKNlqab0pAgMB
AAGjgbkwgbYwHQYDVR0OBBYEFAqg0r7f6ngWODyVxVWHWM06b8wDMIGGBgNVHSME
fzB9gBQKoNK+3+p4Fjg8lcVVh1jNOm/MA6FPpE0wSzELMAkGA1UEBhMCQ04xCzAJ
BgNVBAgMAkJKMQswCQYDVQQHDAJCSjEQMA4GA1UECgwHZHAudGVjaDEQMA4GA1UE
CwwHdW5pLWxhYoIUDyIgmg4qZtMPa8r2Vvn1b1fgJ+YwDAYDVR0TBAUwAwEB/zAN
BgkqhkiG9w0BAQsFAAOCAQEAMylGHHhRCI8JLTizxqk2HaOFkF/WfnYC3XyNx3bK
9KqwVcvaqES+C058lits5nCV1qjjSnKt6xU11S8C6E28Kazh+wMqnSw63fz4UOY5
4cekPCPy8XcWlOY6UW2N27GR0c9JDo9ovruOn1Y4KjATpAQI4W2tPAQ2gCVSNpu1
bw5uw35yJSRzdQIHlsVbslvj2wcugK3GZHmmxJK+q9ww7G6xXtE2Y0+vl6AZRj+I
lcTy5TNNDZiiboIlAt+K3m4hxzSgGPbmFPJX3Lw3i+YMR/0PrWfXqxZgicO/V6/d
SgGBqq/tH1caiaEjCFudSZcOiZvHIlb09O4qL7mCtWEiEQ==
-----END CERTIFICATE-----
'''
key_content: str = '''-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA9zg0ddzBoVfeNI3oOcNihjs43pwB5UiVxCncIwee6WHi3cjc
WezJ6WlzzT2zuXm8SkCMwi5njQMkplCZX1ySZ7KUyJhsCTznX6LxhxFA95hhjsaC
3Sx+RjdNYgiqm1m2UiFe2RMJbDrGkcA+RjGo1OJQ3/S/BBbCIFLtaoPw1WM2GEei
i1guwyOdBhzXrYvydTDMbamMHdRio6V0LcWQde8GQbY557HxUzxStuW/SG4KagVH
K4yY2iXRBaqn7qfACB29/z1nfw0kHI8j1UdoEH9VXUUeIBl7cfw+HvDOBRJcG5Uc
ngFp4luDj5rHoFxylpCfbqOCkkZQSP5hH43B0wIDAQABAoIBAAPzz0ZUcqmR1Eva
5PH98gQzp2wB9snLY86HY3Z/JVAPf5Ht9sbAUWHhT8PVoWpIasSmFbuJxz6DRk3S
M8VVVipxxgcTWqo/JOD4HZiCNfcRru6+5dHxZ4p2B/n4EWfoy+KyEZkgd5jQFONj
jIX+rDR3qZzFqoBRhQSHLuD+i66eZ7l1LOqsnk51r3nTCnGmdyV8fll56MMB5D6+
8LN2rwbmSYX/UIBBqHUthgEt2onFNaetTLgSa3RSNGZ3xEZt4N32vw1SARxItuso
npAAY77POMUwWe3666fETI+yr/gJuppvTF4sQUXy7I4iz7I18n2SYivHabdgnk6H
7y1TcGECgYEA/NbBLFz1YPOQiT6TsuitIlfWcFWXYI4yHh/Mwwm/heHV683HrUti
RSHWbFxggW70BYJbGAQprEe9UIRVdP9YNi3aPeN5WNfnTFHlN2HRiKenlETM1tw9
yaSWjNbAyc2ka+l1EblMJy92xoCkErS8riEPW83o+3+LqJwnjsJ8tVECgYEA+k93
AyNpXuOZldAoqSHF3wHgzgd2jhfVdQcNlz9sLfT8TAdoR01mdBtdytcYAH+FHplW
wlkCfpT1RPf3fEd0Asy727pJnL9v/QfY/BB+vfgWKUQg9CWNIevwItCaTNOSekis
lKl5dxNGOyouU7rPbTj9BC26OHA50Z3vLMKmi+MCgYEAy0Sb6N6TJ26pNK1qcNs+
1e1oKMem+6lWAYHvTJ35q9jz8q9taJTCXHHnwRZDP8vDwuoZ8iTmm+rQ+HprebQP
Zv9WBYtrc1GgUmtErFGn8wVWZI0rYVGPGx2HK5M7SwJYvajixW0DHD28b7ncLm2/
gv5xKo1QUWEpFlT0OIGDYQECgYEA8WRlH6+s1Iel++ZM8B7T1ibXh5mG6a1ue3eb
0bqmNwPFtASIugqYvWwO3ajlSsWvuTyjgLWaRDye9C42i7HU3UZX/KUAjJvKAjjp
Nt0pfUadCJrdNNZp7sa8RLbrtx9qaWdgl9WAgCckWbZqCvFjTK/iwX7f0cHY4J/w
ojftqYUCgYARM6YaEJuBJEBmZV1I0rweiguqWssZz2j1awSlsfYxckwnci4VtSMI
D/sp0Wp0yn2N4cgqp49BFD0rCQCTsASVICEf9HWdMQXsUhzWsz4SjVEhjWWC1VAk
sEL+BOcbsHy3qMbV2uKBHrhuZShDdy5KtCm9TB+7zTWyDVHwE24nig==
-----END RSA PRIVATE KEY-----
'''
# HTTP配置
class HTTPConfig:
remote_addr = "https://uni-lab.test.bohrium.com/api/v1"

File diff suppressed because it is too large Load Diff

View File

@@ -21,7 +21,7 @@ from unilabos.ros.nodes.presets.workstation import ROS2WorkstationNode
class CoinCellAssemblyWorkstation(WorkstationBase): class CoinCellAssemblyWorkstation(WorkstationBase):
def __init__( def __init__(
self, self,
station_resource: CoincellDeck, deck: CoincellDeck,
address: str = "192.168.1.20", address: str = "192.168.1.20",
port: str = "502", port: str = "502",
debug_mode: bool = True, debug_mode: bool = True,
@@ -30,12 +30,12 @@ class CoinCellAssemblyWorkstation(WorkstationBase):
): ):
super().__init__( super().__init__(
#桌子 #桌子
station_resource=station_resource, deck=deck,
*args, *args,
**kwargs, **kwargs,
) )
self.debug_mode = debug_mode self.debug_mode = debug_mode
self.station_resource = station_resource self.deck = deck
""" 连接初始化 """ """ 连接初始化 """
modbus_client = TCPClient(addr=address, port=port) modbus_client = TCPClient(addr=address, port=port)
print("modbus_client", modbus_client) print("modbus_client", modbus_client)
@@ -60,7 +60,6 @@ class CoinCellAssemblyWorkstation(WorkstationBase):
self.csv_export_thread = None self.csv_export_thread = None
self.csv_export_running = False self.csv_export_running = False
self.csv_export_file = None self.csv_export_file = None
self.coin_num_N = 0 #已组装电池数量
#创建一个物料台面,包含两个极片板 #创建一个物料台面,包含两个极片板
#self.deck = create_a_coin_cell_deck() #self.deck = create_a_coin_cell_deck()
@@ -75,7 +74,7 @@ class CoinCellAssemblyWorkstation(WorkstationBase):
self._ros_node = ros_node self._ros_node = ros_node
#self.deck = create_a_coin_cell_deck() #self.deck = create_a_coin_cell_deck()
ROS2DeviceNode.run_async_func(self._ros_node.update_resource, True, **{ ROS2DeviceNode.run_async_func(self._ros_node.update_resource, True, **{
"resources": [self.station_resource] "resources": [self.deck]
}) })
# 批量操作在这里写 # 批量操作在这里写
@@ -85,7 +84,7 @@ class CoinCellAssemblyWorkstation(WorkstationBase):
async def fill_plate(self): async def fill_plate(self):
plate_1: MaterialPlate = self.station_resource.children[0].children[0] plate_1: MaterialPlate = self.deck.children[0].children[0]
#plate_1 #plate_1
return await self._ros_node.update_resource(plate_1) return await self._ros_node.update_resource(plate_1)
@@ -342,7 +341,7 @@ class CoinCellAssemblyWorkstation(WorkstationBase):
def modify_deck_name(self, resource_name: str): def modify_deck_name(self, resource_name: str):
# figure_res = self._ros_node.resource_tracker.figure_resource({"name": resource_name}) # figure_res = self._ros_node.resource_tracker.figure_resource({"name": resource_name})
# print(f"!!! figure_res: {type(figure_res)}") # print(f"!!! figure_res: {type(figure_res)}")
self.station_resource.children[1] self.deck.children[1]
return return
@property @property
@@ -607,8 +606,7 @@ class CoinCellAssemblyWorkstation(WorkstationBase):
print("waiting for start_cmd") print("waiting for start_cmd")
time.sleep(1) time.sleep(1)
def func_pack_send_bottle_num(self, bottle_num): def func_pack_send_bottle_num(self, bottle_num: int):
bottle_num = int(bottle_num)
#发送电解液平台数 #发送电解液平台数
print("启动") print("启动")
while (self._unilab_rece_electrolyte_bottle_num()) == False: while (self._unilab_rece_electrolyte_bottle_num()) == False:
@@ -699,23 +697,6 @@ class CoinCellAssemblyWorkstation(WorkstationBase):
print("data_electrolyte_code", data_electrolyte_code) print("data_electrolyte_code", data_electrolyte_code)
print("data_coin_cell_code", data_coin_cell_code) print("data_coin_cell_code", data_coin_cell_code)
#接收完信息后读取完毕标志位置True #接收完信息后读取完毕标志位置True
liaopan3 = self.station_resource.get_resource("\u7535\u6c60\u6599\u76d8")
#把物料解绑后放到另一盘上
battery = ElectrodeSheet(name=f"battery_{self.coin_num_N}", size_x=14, size_y=14, size_z=2)
battery._unilabos_state = {
"electrolyte_name": data_coin_cell_code,
"data_electrolyte_code": data_electrolyte_code,
"open_circuit_voltage": data_open_circuit_voltage,
"assembly_pressure": data_assembly_pressure,
"electrolyte_volume": data_electrolyte_volume
}
liaopan3.children[self.coin_num_N].assign_child_resource(battery, location=None)
#print(jipian2.parent)
ROS2DeviceNode.run_async_func(self._ros_node.update_resource, True, **{
"resources": [self.station_resource]
})
self._unilab_rec_msg_succ_cmd(True) self._unilab_rec_msg_succ_cmd(True)
time.sleep(1) time.sleep(1)
#等待允许读取标志位置False #等待允许读取标志位置False
@@ -776,7 +757,6 @@ class CoinCellAssemblyWorkstation(WorkstationBase):
def func_allpack_cmd(self, elec_num, elec_use_num, file_path: str="D:\\coin_cell_data") -> bool: def func_allpack_cmd(self, elec_num, elec_use_num, file_path: str="D:\\coin_cell_data") -> bool:
elec_num, elec_use_num = int(elec_num), int(elec_use_num)
summary_csv_file = os.path.join(file_path, "duandian.csv") summary_csv_file = os.path.join(file_path, "duandian.csv")
# 如果断点文件存在,先读取之前的进度 # 如果断点文件存在,先读取之前的进度
if os.path.exists(summary_csv_file): if os.path.exists(summary_csv_file):
@@ -804,22 +784,20 @@ class CoinCellAssemblyWorkstation(WorkstationBase):
elec_num_N = 0 elec_num_N = 0
elec_use_num_N = 0 elec_use_num_N = 0
coin_num_N = 0 coin_num_N = 0
for i in range(20):
print(f"剩余电解液瓶数: {elec_num}, 已组装电池数: {elec_use_num}") print(f"剩余电解液瓶数: {elec_num}, 已组装电池数: {elec_use_num}")
print(f"剩余电解液瓶数: {type(elec_num)}, 已组装电池数: {type(elec_use_num)}")
print(f"剩余电解液瓶数: {type(int(elec_num))}, 已组装电池数: {type(int(elec_use_num))}")
#如果是第一次运行,则进行初始化、切换自动、启动, 如果是断点重启则跳过。 #如果是第一次运行,则进行初始化、切换自动、启动, 如果是断点重启则跳过。
if read_status_flag == False: if read_status_flag == False:
pass
#初始化 #初始化
#self.func_pack_device_init() self.func_pack_device_init()
#切换自动 #切换自动
#self.func_pack_device_auto() self.func_pack_device_auto()
#启动,小车收回 #启动,小车收回
#self.func_pack_device_start() self.func_pack_device_start()
#发送电解液瓶数量,启动搬运,多搬运没事 #发送电解液瓶数量,启动搬运,多搬运没事
#self.func_pack_send_bottle_num(elec_num) self.func_pack_send_bottle_num(elec_num)
last_i = elec_num_N last_i = elec_num_N
last_j = elec_use_num_N last_j = elec_use_num_N
for i in range(last_i, elec_num): for i in range(last_i, elec_num):
@@ -833,8 +811,26 @@ class CoinCellAssemblyWorkstation(WorkstationBase):
#读取电池组装数据并存入csv #读取电池组装数据并存入csv
self.func_pack_get_msg_cmd(file_path) self.func_pack_get_msg_cmd(file_path)
time.sleep(1) time.sleep(1)
# TODO:读完再将电池数加一还是进入循环就将电池数加一需要考虑
#这里定义物料系统
# TODO:读完再将电池数加一还是进入循环就将电池数加一需要考虑
liaopan1 = self.deck.get_resource("liaopan1")
liaopan4 = self.deck.get_resource("liaopan4")
jipian1 = liaopan1.children[coin_num_N].children[0]
jipian4 = liaopan4.children[coin_num_N].children[0]
#print(jipian1)
#从料盘上去物料解绑后放到另一盘上
jipian1.parent.unassign_child_resource(jipian1)
jipian4.parent.unassign_child_resource(jipian4)
#print(jipian2.parent)
battery = Battery(name = f"battery_{coin_num_N}")
battery.assign_child_resource(jipian1, location=None)
battery.assign_child_resource(jipian4, location=None)
zidanjia6 = self.deck.get_resource("zi_dan_jia6")
zidanjia6.children[0].assign_child_resource(battery, location=None)
# 生成断点文件 # 生成断点文件
@@ -846,7 +842,6 @@ class CoinCellAssemblyWorkstation(WorkstationBase):
writer.writerow([elec_num, elec_use_num, elec_num_N, elec_use_num_N, coin_num_N, timestamp]) writer.writerow([elec_num, elec_use_num, elec_num_N, elec_use_num_N, coin_num_N, timestamp])
csvfile.flush() csvfile.flush()
coin_num_N += 1 coin_num_N += 1
self.coin_num_N = coin_num_N
elec_use_num_N += 1 elec_use_num_N += 1
elec_num_N += 1 elec_num_N += 1
elec_use_num_N = 0 elec_use_num_N = 0
@@ -883,22 +878,34 @@ class CoinCellAssemblyWorkstation(WorkstationBase):
def fun_wuliao_test(self) -> bool: def fun_wuliao_test(self) -> bool:
#找到data_init中构建的2个物料盘 #找到data_init中构建的2个物料盘
liaopan3 = self.station_resource.get_resource("\u7535\u6c60\u6599\u76d8") #liaopan1 = self.deck.get_resource("liaopan1")
for i in range(16): #liaopan4 = self.deck.get_resource("liaopan4")
battery = ElectrodeSheet(name=f"battery_{i}", size_x=16, size_y=16, size_z=2) #for coin_num_N in range(16):
battery._unilabos_state = { # liaopan1 = self.deck.get_resource("liaopan1")
"diameter": 20.0, # liaopan4 = self.deck.get_resource("liaopan4")
"height": 20.0, # jipian1 = liaopan1.children[coin_num_N].children[0]
"assembly_pressure": i, # jipian4 = liaopan4.children[coin_num_N].children[0]
"electrolyte_volume": 20.0, # #print(jipian1)
"electrolyte_name": f"DP{i}" # #从料盘上去物料解绑后放到另一盘上
} # jipian1.parent.unassign_child_resource(jipian1)
liaopan3.children[i].assign_child_resource(battery, location=None) # jipian4.parent.unassign_child_resource(jipian4)
#
# #print(jipian2.parent)
# battery = Battery(name = f"battery_{coin_num_N}")
# battery.assign_child_resource(jipian1, location=None)
# battery.assign_child_resource(jipian4, location=None)
#
# zidanjia6 = self.deck.get_resource("zi_dan_jia6")
# zidanjia6.children[0].assign_child_resource(battery, location=None)
# ROS2DeviceNode.run_async_func(self._ros_node.update_resource, True, **{
# "resources": [self.deck]
# })
# time.sleep(2)
for i in range(20):
print(f"输出{i}")
time.sleep(2)
ROS2DeviceNode.run_async_func(self._ros_node.update_resource, True, **{
"resources": [self.station_resource]
})
time.sleep(4)
# 数据读取与输出 # 数据读取与输出
def func_read_data_and_output(self, file_path: str="D:\\coin_cell_data"): def func_read_data_and_output(self, file_path: str="D:\\coin_cell_data"):
# 检查CSV导出是否正在运行已运行则跳出防止同时启动两个while循环 # 检查CSV导出是否正在运行已运行则跳出防止同时启动两个while循环
@@ -1005,7 +1012,7 @@ class CoinCellAssemblyWorkstation(WorkstationBase):
# else: # else:
# print("子弹夹洞位0没有极片") # print("子弹夹洞位0没有极片")
# #
# # TODO:#把电解液从瓶中取到电池夹子中 # #把电解液从瓶中取到电池夹子中
# battery_site = deck.get_resource("battery_press_1") # battery_site = deck.get_resource("battery_press_1")
# clip_magazine_battery = deck.get_resource("clip_magazine_battery") # clip_magazine_battery = deck.get_resource("clip_magazine_battery")
# if battery_site.has_battery(): # if battery_site.has_battery():
@@ -1112,52 +1119,24 @@ if __name__ == "__main__":
#print("success") #print("success")
#创建一个物料台面 #创建一个物料台面
deck = create_a_coin_cell_deck() #deck = create_a_coin_cell_deck()
#deck = create_a_full_coin_cell_deck()
##在台面上找到料盘和极片 ##在台面上找到料盘和极片
#liaopan1 = deck.get_resource("liaopan1") #liaopan1 = deck.get_resource("liaopan1")
#liaopan2 = deck.get_resource("liaopan2") #liaopan2 = deck.get_resource("liaopan2")
#jipian1 = liaopan1.children[1].children[0] #jipian1 = liaopan1.children[1].children[0]
## #
#print(jipian1) ##print(jipian1)
##把物料解绑后放到另一盘上 ##把物料解绑后放到另一盘上
#jipian1.parent.unassign_child_resource(jipian1) #jipian1.parent.unassign_child_resource(jipian1)
#liaopan2.children[1].assign_child_resource(jipian1, location=None) #liaopan2.children[1].assign_child_resource(jipian1, location=None)
##print(jipian2.parent) ##print(jipian2.parent)
liaopan1 = deck.get_resource("liaopan1")
liaopan2 = deck.get_resource("liaopan2")
for i in range(16):
#找到liaopan1上每一个jipian
jipian_linshi = liaopan1.children[i].children[0]
#把物料解绑后放到另一盘上
print("极片:", jipian_linshi)
jipian_linshi.parent.unassign_child_resource(jipian_linshi)
liaopan2.children[i].assign_child_resource(jipian_linshi, location=None)
from unilabos.resources.graphio import resource_ulab_to_plr, convert_resources_to_type from unilabos.resources.graphio import resource_ulab_to_plr, convert_resources_to_type
#with open("./button_battery_station_resources_unilab.json", "r", encoding="utf-8") as f:
# bioyond_resources_unilab = json.load(f)
#print(f"成功读取 JSON 文件,包含 {len(bioyond_resources_unilab)} 个资源")
#ulab_resources = convert_resources_to_type(bioyond_resources_unilab, List[PLRResource])
#print(f"转换结果类型: {type(ulab_resources)}")
#print(ulab_resources)
with open("./button_battery_decks_unilab.json", "r", encoding="utf-8") as f:
bioyond_resources_unilab = json.load(f)
print(f"成功读取 JSON 文件,包含 {len(bioyond_resources_unilab)} 个资源")
ulab_resources = convert_resources_to_type(bioyond_resources_unilab, List[PLRResource])
print(f"转换结果类型: {type(ulab_resources)}")
print(ulab_resources)
from unilabos.resources.graphio import convert_resources_from_type
from unilabos.config.config import BasicConfig
BasicConfig.ak = "beb0c15f-2279-46a1-aba5-00eaf89aef55"
BasicConfig.sk = "15d4f25e-3512-4f9c-9bfb-43ab85e7b561"
from unilabos.app.web.client import http_client
resources = convert_resources_from_type([deck], [Resource])
json.dump({"nodes": resources, "links": []}, open("button_battery_station_resources_unilab.json", "w"), indent=2)
#print(resources)
http_client.remote_addr = "https://uni-lab.test.bohrium.com/api/v1"
http_client.resource_add(resources)

View File

@@ -1,44 +0,0 @@
Name,DataType,InitValue,Comment,Attribute,DeviceType,Address
COIL_SYS_START_CMD,BOOL,,设备启动命令,,coil,8010
COIL_SYS_STOP_CMD,BOOL,,设备停止命令,,coil,8020
COIL_SYS_RESET_CMD,BOOL,,设备复位命令,,coil,8030
COIL_SYS_HAND_CMD,BOOL,,设备手动模式命令,,coil,8040
COIL_SYS_AUTO_CMD,BOOL,,设备自动模式命令,,coil,8050
COIL_SYS_INIT_CMD,BOOL,,设备初始化模式命令,,coil,8060
COIL_UNILAB_SEND_MSG_SUCC_CMD,BOOL,,UNILAB发送配方完毕,,coil,8700
COIL_UNILAB_REC_MSG_SUCC_CMD,BOOL,,UNILAB接受测试数据完毕,,coil,8710
COIL_SYS_START_STATUS,BOOL,,设备启动中,,coil,8210
COIL_SYS_STOP_STATUS,BOOL,,设备停止中,,coil,8220
COIL_SYS_RESET_STATUS,BOOL,,设备复位中,,coil,8230
COIL_SYS_HAND_STATUS,BOOL,,设备手动模式,,coil,8240
COIL_SYS_AUTO_STATUS,BOOL,,设备自动模式,,coil,8250
COIL_SYS_INIT_STATUS,BOOL,,设备初始化完成,,coil,8260
COIL_REQUEST_REC_MSG_STATUS,BOOL,,设备请求接受配方,,coil,8510
COIL_REQUEST_SEND_MSG_STATUS,BOOL,,设备请求发送测试数据,,coil,8500
REG_MSG_ELECTROLYTE_USE_NUM,INT16,,单瓶电解液使用次数,,hold_register,11000
REG_MSG_ELECTROLYTE_NUM,INT16,,电解液使用瓶数,,hold_register,11002
REG_MSG_ELECTROLYTE_VOLUME,INT16,,电解液吸取量,,hold_register,11004
REG_MSG_ASSEMBLY_TYPE,INT16,,组装参数:极片堆叠方式,,hold_register,11006
REG_MSG_ASSEMBLY_PRESSURE,INT16,,电池组装压制力,,hold_register,11008
REG_DATA_ASSEMBLY_COIN_CELL_NUM,INT16,,当前完成组装电池数量,,hold_register,10000
REG_DATA_OPEN_CIRCUIT_VOLTAGE,FLOAT32,,当前电池电压数据,,hold_register,10002
REG_DATA_AXIS_X_POS,FLOAT32,,分液X轴当前位置,,hold_register,10004
REG_DATA_AXIS_Y_POS,FLOAT32,,分液Z轴当前位置,,hold_register,10006
REG_DATA_AXIS_Z_POS,FLOAT32,,分液Y轴当前位置,,hold_register,10008
REG_DATA_POLE_WEIGHT,FLOAT32,,当前电池正极片称重数据,,hold_register,10010
REG_DATA_ASSEMBLY_PER_TIME,FLOAT32,,当前单颗电池组装时间,,hold_register,10012
REG_DATA_ASSEMBLY_PRESSURE,INT16,,当前电池组装压制力,,hold_register,10014
REG_DATA_ELECTROLYTE_VOLUME,INT16,,当前电解液加注量,,hold_register,10016
REG_DATA_COIN_NUM,INT16,,当前电池物料数,,hold_register,10018
REG_DATA_ELECTROLYTE_CODE,STRING,,电解液二维码序列号,,hold_register,10020
REG_DATA_COIN_CELL_CODE,STRING,,电池二维码序列号,,hold_register,10030
REG_DATA_STACK_VISON_CODE,STRING,,物料堆叠复检图片编码,,hold_register,12004
REG_DATA_GLOVE_BOX_PRESSURE,FLOAT32,,手套箱压力,,hold_register,10050
REG_DATA_GLOVE_BOX_WATER_CONTENT,FLOAT32,,手套箱水含量,,hold_register,10052
REG_DATA_GLOVE_BOX_O2_CONTENT,FLOAT32,,手套箱氧含量,,hold_register,10054
UNILAB_SEND_ELECTROLYTE_BOTTLE_NUM,BOOL,,Unilab确认已发送电解液瓶数信号,,coil,8720
UNILAB_RECE_ELECTROLYTE_BOTTLE_NUM,BOOL,,Unilab可接受电解液瓶数,,coil,8520
REG_MSG_ELECTROLYTE_NUM_USED,INT16,,电解液组装电池平行样数,,hold_register,496
REG_DATA_ELECTROLYTE_USE_NUM,INT16,,当前已组装平行样数,,hold_register,10000
UNILAB_SEND_FINISHED_CMD,BOOL,,Unilab发送收到完成信号,,coil,8730
UNILAB_RECE_FINISHED_CMD,BOOL,,告知unilab结束信号,,coil,8530
1 Name DataType InitValue Comment Attribute DeviceType Address
2 COIL_SYS_START_CMD BOOL 设备启动命令 coil 8010
3 COIL_SYS_STOP_CMD BOOL 设备停止命令 coil 8020
4 COIL_SYS_RESET_CMD BOOL 设备复位命令 coil 8030
5 COIL_SYS_HAND_CMD BOOL 设备手动模式命令 coil 8040
6 COIL_SYS_AUTO_CMD BOOL 设备自动模式命令 coil 8050
7 COIL_SYS_INIT_CMD BOOL 设备初始化模式命令 coil 8060
8 COIL_UNILAB_SEND_MSG_SUCC_CMD BOOL UNILAB发送配方完毕 coil 8700
9 COIL_UNILAB_REC_MSG_SUCC_CMD BOOL UNILAB接受测试数据完毕 coil 8710
10 COIL_SYS_START_STATUS BOOL 设备启动中 coil 8210
11 COIL_SYS_STOP_STATUS BOOL 设备停止中 coil 8220
12 COIL_SYS_RESET_STATUS BOOL 设备复位中 coil 8230
13 COIL_SYS_HAND_STATUS BOOL 设备手动模式 coil 8240
14 COIL_SYS_AUTO_STATUS BOOL 设备自动模式 coil 8250
15 COIL_SYS_INIT_STATUS BOOL 设备初始化完成 coil 8260
16 COIL_REQUEST_REC_MSG_STATUS BOOL 设备请求接受配方 coil 8510
17 COIL_REQUEST_SEND_MSG_STATUS BOOL 设备请求发送测试数据 coil 8500
18 REG_MSG_ELECTROLYTE_USE_NUM INT16 单瓶电解液使用次数 hold_register 11000
19 REG_MSG_ELECTROLYTE_NUM INT16 电解液使用瓶数 hold_register 11002
20 REG_MSG_ELECTROLYTE_VOLUME INT16 电解液吸取量 hold_register 11004
21 REG_MSG_ASSEMBLY_TYPE INT16 组装参数:极片堆叠方式 hold_register 11006
22 REG_MSG_ASSEMBLY_PRESSURE INT16 电池组装压制力 hold_register 11008
23 REG_DATA_ASSEMBLY_COIN_CELL_NUM INT16 当前完成组装电池数量 hold_register 10000
24 REG_DATA_OPEN_CIRCUIT_VOLTAGE FLOAT32 当前电池电压数据 hold_register 10002
25 REG_DATA_AXIS_X_POS FLOAT32 分液X轴当前位置 hold_register 10004
26 REG_DATA_AXIS_Y_POS FLOAT32 分液Z轴当前位置 hold_register 10006
27 REG_DATA_AXIS_Z_POS FLOAT32 分液Y轴当前位置 hold_register 10008
28 REG_DATA_POLE_WEIGHT FLOAT32 当前电池正极片称重数据 hold_register 10010
29 REG_DATA_ASSEMBLY_PER_TIME FLOAT32 当前单颗电池组装时间 hold_register 10012
30 REG_DATA_ASSEMBLY_PRESSURE INT16 当前电池组装压制力 hold_register 10014
31 REG_DATA_ELECTROLYTE_VOLUME INT16 当前电解液加注量 hold_register 10016
32 REG_DATA_COIN_NUM INT16 当前电池物料数 hold_register 10018
33 REG_DATA_ELECTROLYTE_CODE STRING 电解液二维码序列号 hold_register 10020
34 REG_DATA_COIN_CELL_CODE STRING 电池二维码序列号 hold_register 10030
35 REG_DATA_STACK_VISON_CODE STRING 物料堆叠复检图片编码 hold_register 12004
36 REG_DATA_GLOVE_BOX_PRESSURE FLOAT32 手套箱压力 hold_register 10050
37 REG_DATA_GLOVE_BOX_WATER_CONTENT FLOAT32 手套箱水含量 hold_register 10052
38 REG_DATA_GLOVE_BOX_O2_CONTENT FLOAT32 手套箱氧含量 hold_register 10054
39 UNILAB_SEND_ELECTROLYTE_BOTTLE_NUM BOOL Unilab确认已发送电解液瓶数信号 coil 8720
40 UNILAB_RECE_ELECTROLYTE_BOTTLE_NUM BOOL Unilab可接受电解液瓶数 coil 8520
41 REG_MSG_ELECTROLYTE_NUM_USED INT16 电解液组装电池平行样数 hold_register 496
42 REG_DATA_ELECTROLYTE_USE_NUM INT16 当前已组装平行样数 hold_register 10000
43 UNILAB_SEND_FINISHED_CMD BOOL Unilab发送收到完成信号 coil 8730
44 UNILAB_RECE_FINISHED_CMD BOOL 告知unilab结束信号 coil 8530

View File

@@ -1,46 +0,0 @@
Name,DataType,InitValue,Comment,Attribute,DeviceType,Address,
COIL_SYS_START_CMD,BOOL,,,,coil,8010,
COIL_SYS_STOP_CMD,BOOL,,,,coil,8020,
COIL_SYS_RESET_CMD,BOOL,,,,coil,8030,
COIL_SYS_HAND_CMD,BOOL,,,,coil,8040,
COIL_SYS_AUTO_CMD,BOOL,,,,coil,8050,
COIL_SYS_INIT_CMD,BOOL,,,,coil,8060,
COIL_UNILAB_SEND_MSG_SUCC_CMD,BOOL,,,,coil,8700,
COIL_UNILAB_REC_MSG_SUCC_CMD,BOOL,,,,coil,8710,unilab_rec_msg_succ_cmd
COIL_SYS_START_STATUS,BOOL,,,,coil,8210,
COIL_SYS_STOP_STATUS,BOOL,,,,coil,8220,
COIL_SYS_RESET_STATUS,BOOL,,,,coil,8230,
COIL_SYS_HAND_STATUS,BOOL,,,,coil,8240,
COIL_SYS_AUTO_STATUS,BOOL,,,,coil,8250,
COIL_SYS_INIT_STATUS,BOOL,,,,coil,8260,
COIL_REQUEST_REC_MSG_STATUS,BOOL,,,,coil,8500,
COIL_REQUEST_SEND_MSG_STATUS,BOOL,,,,coil,8510,request_send_msg_status
REG_MSG_ELECTROLYTE_USE_NUM,INT16,,,,hold_register,11000,
REG_MSG_ELECTROLYTE_NUM,INT16,,,,hold_register,11002,unilab_send_msg_electrolyte_num
REG_MSG_ELECTROLYTE_VOLUME,INT16,,,,hold_register,11004,unilab_send_msg_electrolyte_vol
REG_MSG_ASSEMBLY_TYPE,INT16,,,,hold_register,11006,unilab_send_msg_assembly_type
REG_MSG_ASSEMBLY_PRESSURE,INT16,,,,hold_register,11008,unilab_send_msg_assembly_pressure
REG_DATA_ASSEMBLY_COIN_CELL_NUM,INT16,,,,hold_register,10000,data_assembly_coin_cell_num
REG_DATA_OPEN_CIRCUIT_VOLTAGE,FLOAT32,,,,hold_register,10002,data_open_circuit_voltage
REG_DATA_AXIS_X_POS,FLOAT32,,,,hold_register,10004,
REG_DATA_AXIS_Y_POS,FLOAT32,,,,hold_register,10006,
REG_DATA_AXIS_Z_POS,FLOAT32,,,,hold_register,10008,
REG_DATA_POLE_WEIGHT,FLOAT32,,,,hold_register,10010,data_pole_weight
REG_DATA_ASSEMBLY_PER_TIME,FLOAT32,,,,hold_register,10012,data_assembly_time
REG_DATA_ASSEMBLY_PRESSURE,INT16,,,,hold_register,10014,data_assembly_pressure
REG_DATA_ELECTROLYTE_VOLUME,INT16,,,,hold_register,10016,data_electrolyte_volume
REG_DATA_COIN_NUM,INT16,,,,hold_register,10018,data_coin_num
REG_DATA_ELECTROLYTE_CODE,STRING,,,,hold_register,10020,data_electrolyte_code()
REG_DATA_COIN_CELL_CODE,STRING,,,,hold_register,10030,data_coin_cell_code()
REG_DATA_STACK_VISON_CODE,STRING,,,,hold_register,12004,data_stack_vision_code()
REG_DATA_GLOVE_BOX_PRESSURE,FLOAT32,,,,hold_register,10050,data_glove_box_pressure
REG_DATA_GLOVE_BOX_WATER_CONTENT,FLOAT32,,,,hold_register,10052,data_glove_box_water_content
REG_DATA_GLOVE_BOX_O2_CONTENT,FLOAT32,,,,hold_register,10054,data_glove_box_o2_content
UNILAB_SEND_ELECTROLYTE_BOTTLE_NUM,BOOL,,,,coil,8720,
UNILAB_RECE_ELECTROLYTE_BOTTLE_NUM,BOOL,,,,coil,8520,
REG_MSG_ELECTROLYTE_NUM_USED,INT16,,,,hold_register,496,
REG_DATA_ELECTROLYTE_USE_NUM,INT16,,,,hold_register,10000,
UNILAB_SEND_FINISHED_CMD,BOOL,,,,coil,8730,
UNILAB_RECE_FINISHED_CMD,BOOL,,,,coil,8530,
REG_DATA_ASSEMBLY_TYPE,INT16,,,,hold_register,10018,ASSEMBLY_TYPE7or8
COIL_ALUMINUM_FOIL,BOOL,,,,coil,8340,
1 Name DataType InitValue Comment Attribute DeviceType Address
2 COIL_SYS_START_CMD BOOL coil 8010
3 COIL_SYS_STOP_CMD BOOL coil 8020
4 COIL_SYS_RESET_CMD BOOL coil 8030
5 COIL_SYS_HAND_CMD BOOL coil 8040
6 COIL_SYS_AUTO_CMD BOOL coil 8050
7 COIL_SYS_INIT_CMD BOOL coil 8060
8 COIL_UNILAB_SEND_MSG_SUCC_CMD BOOL coil 8700
9 COIL_UNILAB_REC_MSG_SUCC_CMD BOOL coil 8710 unilab_rec_msg_succ_cmd
10 COIL_SYS_START_STATUS BOOL coil 8210
11 COIL_SYS_STOP_STATUS BOOL coil 8220
12 COIL_SYS_RESET_STATUS BOOL coil 8230
13 COIL_SYS_HAND_STATUS BOOL coil 8240
14 COIL_SYS_AUTO_STATUS BOOL coil 8250
15 COIL_SYS_INIT_STATUS BOOL coil 8260
16 COIL_REQUEST_REC_MSG_STATUS BOOL coil 8500
17 COIL_REQUEST_SEND_MSG_STATUS BOOL coil 8510 request_send_msg_status
18 REG_MSG_ELECTROLYTE_USE_NUM INT16 hold_register 11000
19 REG_MSG_ELECTROLYTE_NUM INT16 hold_register 11002 unilab_send_msg_electrolyte_num
20 REG_MSG_ELECTROLYTE_VOLUME INT16 hold_register 11004 unilab_send_msg_electrolyte_vol
21 REG_MSG_ASSEMBLY_TYPE INT16 hold_register 11006 unilab_send_msg_assembly_type
22 REG_MSG_ASSEMBLY_PRESSURE INT16 hold_register 11008 unilab_send_msg_assembly_pressure
23 REG_DATA_ASSEMBLY_COIN_CELL_NUM INT16 hold_register 10000 data_assembly_coin_cell_num
24 REG_DATA_OPEN_CIRCUIT_VOLTAGE FLOAT32 hold_register 10002 data_open_circuit_voltage
25 REG_DATA_AXIS_X_POS FLOAT32 hold_register 10004
26 REG_DATA_AXIS_Y_POS FLOAT32 hold_register 10006
27 REG_DATA_AXIS_Z_POS FLOAT32 hold_register 10008
28 REG_DATA_POLE_WEIGHT FLOAT32 hold_register 10010 data_pole_weight
29 REG_DATA_ASSEMBLY_PER_TIME FLOAT32 hold_register 10012 data_assembly_time
30 REG_DATA_ASSEMBLY_PRESSURE INT16 hold_register 10014 data_assembly_pressure
31 REG_DATA_ELECTROLYTE_VOLUME INT16 hold_register 10016 data_electrolyte_volume
32 REG_DATA_COIN_NUM INT16 hold_register 10018 data_coin_num
33 REG_DATA_ELECTROLYTE_CODE STRING hold_register 10020 data_electrolyte_code()
34 REG_DATA_COIN_CELL_CODE STRING hold_register 10030 data_coin_cell_code()
35 REG_DATA_STACK_VISON_CODE STRING hold_register 12004 data_stack_vision_code()
36 REG_DATA_GLOVE_BOX_PRESSURE FLOAT32 hold_register 10050 data_glove_box_pressure
37 REG_DATA_GLOVE_BOX_WATER_CONTENT FLOAT32 hold_register 10052 data_glove_box_water_content
38 REG_DATA_GLOVE_BOX_O2_CONTENT FLOAT32 hold_register 10054 data_glove_box_o2_content
39 UNILAB_SEND_ELECTROLYTE_BOTTLE_NUM BOOL coil 8720
40 UNILAB_RECE_ELECTROLYTE_BOTTLE_NUM BOOL coil 8520
41 REG_MSG_ELECTROLYTE_NUM_USED INT16 hold_register 496
42 REG_DATA_ELECTROLYTE_USE_NUM INT16 hold_register 10000
43 UNILAB_SEND_FINISHED_CMD BOOL coil 8730
44 UNILAB_RECE_FINISHED_CMD BOOL coil 8530
45 REG_DATA_ASSEMBLY_TYPE INT16 hold_register 10018 ASSEMBLY_TYPE7or8
46 COIL_ALUMINUM_FOIL BOOL coil 8340

View File

@@ -1,135 +0,0 @@
import json
import re
from pymodbus.client import ModbusTcpClient
from unilabos.device_comms.modbus_plc.modbus import WorderOrder, Coil, DiscreteInputs, HoldRegister, InputRegister, DataType
from pymodbus.constants import Endian
import time
import threading
import csv
import os
from datetime import datetime
from typing import Callable
from unilabos.device_comms.modbus_plc.client import TCPClient, ModbusNode, PLCWorkflow, ModbusWorkflow, WorkflowAction, BaseClient
from unilabos.device_comms.modbus_plc.modbus import DeviceType, Base as ModbusNodeBase, DataType, WorderOrder
class Coin_Cell_Assembly:
if __name__ == '__main__':
coin_cell_assmbly = Coin_Cell_Assembly(address="192.168.1.20", port="502")
#params = {
# "elec_num": 32
#}
#str_data = json.dumps(params, ensure_ascii=False)
#print('param:', coin_cell_assmbly.func_pack_device_write_batch_elec_param(params))
#time.sleep(1)
print(coin_cell_assmbly.func_pack_device_write_per_elec_param(
elec_use_num=4,
elec_num=5,
elec_vol=55,
assembly_type=25,
assembly_pressure=550))
time.sleep(1)
'''
print('start:', coin_cell_assmbly.func_pack_device_start())
time.sleep(1)
print('start:', coin_cell_assmbly.func_pack_device_start())
time.sleep(1)
print('stop:', coin_cell_assmbly.func_pack_device_stop())
time.sleep(1)
while True:
# cmd coil
print('start cmd:', coin_cell_assmbly.sys_start_cmd(True))
time.sleep(1)
print('stop cmd:', coin_cell_assmbly.sys_stop_cmd(False))
time.sleep(1)
print('reset cmd:', coin_cell_assmbly.sys_reset_cmd(True))
time.sleep(1)
print('hand cmd:', coin_cell_assmbly.sys_hand_cmd(False))
time.sleep(1)
print('auto cmd:', coin_cell_assmbly.sys_auto_cmd(True))
time.sleep(1)
print('init cmd:', coin_cell_assmbly.sys_init_cmd(False))
time.sleep(1)
print('send msg succ cmd:', coin_cell_assmbly.unilab_send_msg_succ_cmd(False))
time.sleep(1)
print('rec msg succ cmd:', coin_cell_assmbly.unilab_rec_msg_succ_cmd(True))
time.sleep(1)
# cmd reg
print('elec use num msg:', coin_cell_assmbly.unilab_send_msg_electrolyte_use_num(8))
time.sleep(1)
print('elec num msg:', coin_cell_assmbly.unilab_send_msg_electrolyte_num(4))
time.sleep(1)
print('elec vol msg:', coin_cell_assmbly.unilab_send_msg_electrolyte_vol(3.3))
time.sleep(1)
print('assembly type msg:', coin_cell_assmbly.unilab_send_msg_assembly_type(1))
time.sleep(1)
print('assembly pressure msg:', coin_cell_assmbly.unilab_send_msg_assembly_pressure(1))
time.sleep(1)
# status coil
print('start status:',coin_cell_assmbly.sys_start_status)
time.sleep(1)
print('stop status:',coin_cell_assmbly.sys_stop_status)
time.sleep(1)
print('reset status:',coin_cell_assmbly.sys_reset_status)
time.sleep(1)
print('hand status:',coin_cell_assmbly.sys_hand_status)
time.sleep(1)
print('auto status:', coin_cell_assmbly.sys_auto_status)
time.sleep(1)
print('init ok:', coin_cell_assmbly.sys_init_status)
time.sleep(1)
print('request rec msg:', coin_cell_assmbly.request_rec_msg_status)
time.sleep(1)
print('request send msg:', coin_cell_assmbly.request_send_msg_status)
time.sleep(1)
# status reg
print('assembly coin cell num:', coin_cell_assmbly.data_assembly_coin_cell_num)
time.sleep(1)
print('assembly coin assembly per time:', coin_cell_assmbly.data_assembly_time)
time.sleep(1)
print('open circuit vol:', coin_cell_assmbly.data_open_circuit_voltage)
time.sleep(1)
print('axis x pos:', coin_cell_assmbly.data_axis_x_pos)
time.sleep(1)
print('axis y pos:', coin_cell_assmbly.data_axis_y_pos)
time.sleep(1)
print('axis z pos:', coin_cell_assmbly.data_axis_z_pos)
time.sleep(1)
print('pole weight:', coin_cell_assmbly.data_pole_weight)
time.sleep(1)
print('assembly pressure:', coin_cell_assmbly.data_assembly_coin_cell_num)
time.sleep(1)
print('assembly electrolyte vol:', coin_cell_assmbly.data_electrolyte_volume)
time.sleep(1)
print('assembly coin num:', coin_cell_assmbly.data_coin_num)
time.sleep(1)
print('coin cell code:', coin_cell_assmbly.data_coin_cell_code)
time.sleep(1)
print('elec code:', coin_cell_assmbly.data_electrolyte_code)
time.sleep(1)
print('glove box pressure:', coin_cell_assmbly.data_glove_box_pressure)
time.sleep(1)
print('glove box o2:', coin_cell_assmbly.data_glove_box_o2_content)
time.sleep(1)
print('glove box water:', coin_cell_assmbly.data_glove_box_water_content)
time.sleep(1)
'''

View File

@@ -1,691 +0,0 @@
{
"nodes": [
{
"id": "BatteryStation",
"name": "扣电工作站",
"children": [
"coin_cell_deck"
],
"parent": null,
"type": "device",
"class": "bettery_station_registry",
"position": {
"x": 600,
"y": 400,
"z": 0
},
"config": {
"debug_mode": false,
"_comment": "protocol_type接外部工站固定写法字段一般为空station_resource写法也固定",
"protocol_type": [],
"station_resource": {
"data": {
"_resource_child_name": "coin_cell_deck",
"_resource_type": "unilabos.devices.workstation.coin_cell_assembly.button_battery_station:CoincellDeck"
}
},
"address": "192.168.1.20",
"port": 502
},
"data": {}
},
{
"id": "coin_cell_deck",
"name": "coin_cell_deck",
"sample_id": null,
"children": [
"\u7535\u6c60\u6599\u76d8"
],
"parent": null,
"type": "container",
"class": "",
"position": {
"x": 0,
"y": 0,
"z": 0
},
"config": {
"type": "CoincellDeck",
"size_x": 1000,
"size_y": 1000,
"size_z": 900,
"rotation": {
"x": 0,
"y": 0,
"z": 0,
"type": "Rotation"
},
"category": "coin_cell_deck",
"barcode": null
},
"data": {}
},
{
"id": "\u7535\u6c60\u6599\u76d8",
"name": "\u7535\u6c60\u6599\u76d8",
"sample_id": null,
"children": [
"\u7535\u6c60\u6599\u76d8_materialhole_0_0",
"\u7535\u6c60\u6599\u76d8_materialhole_0_1",
"\u7535\u6c60\u6599\u76d8_materialhole_0_2",
"\u7535\u6c60\u6599\u76d8_materialhole_0_3",
"\u7535\u6c60\u6599\u76d8_materialhole_1_0",
"\u7535\u6c60\u6599\u76d8_materialhole_1_1",
"\u7535\u6c60\u6599\u76d8_materialhole_1_2",
"\u7535\u6c60\u6599\u76d8_materialhole_1_3",
"\u7535\u6c60\u6599\u76d8_materialhole_2_0",
"\u7535\u6c60\u6599\u76d8_materialhole_2_1",
"\u7535\u6c60\u6599\u76d8_materialhole_2_2",
"\u7535\u6c60\u6599\u76d8_materialhole_2_3",
"\u7535\u6c60\u6599\u76d8_materialhole_3_0",
"\u7535\u6c60\u6599\u76d8_materialhole_3_1",
"\u7535\u6c60\u6599\u76d8_materialhole_3_2",
"\u7535\u6c60\u6599\u76d8_materialhole_3_3"
],
"parent": "coin_cell_deck",
"type": "container",
"class": "",
"position": {
"x": 100,
"y": 100,
"z": 0
},
"config": {
"type": "MaterialPlate",
"size_x": 120.8,
"size_y": 160.5,
"size_z": 10.0,
"rotation": {
"x": 0,
"y": 0,
"z": 0,
"type": "Rotation"
},
"category": "material_plate",
"model": null,
"barcode": null,
"ordering": {
"A1": "\u7535\u6c60\u6599\u76d8_materialhole_0_0",
"B1": "\u7535\u6c60\u6599\u76d8_materialhole_0_1",
"C1": "\u7535\u6c60\u6599\u76d8_materialhole_0_2",
"D1": "\u7535\u6c60\u6599\u76d8_materialhole_0_3",
"A2": "\u7535\u6c60\u6599\u76d8_materialhole_1_0",
"B2": "\u7535\u6c60\u6599\u76d8_materialhole_1_1",
"C2": "\u7535\u6c60\u6599\u76d8_materialhole_1_2",
"D2": "\u7535\u6c60\u6599\u76d8_materialhole_1_3",
"A3": "\u7535\u6c60\u6599\u76d8_materialhole_2_0",
"B3": "\u7535\u6c60\u6599\u76d8_materialhole_2_1",
"C3": "\u7535\u6c60\u6599\u76d8_materialhole_2_2",
"D3": "\u7535\u6c60\u6599\u76d8_materialhole_2_3",
"A4": "\u7535\u6c60\u6599\u76d8_materialhole_3_0",
"B4": "\u7535\u6c60\u6599\u76d8_materialhole_3_1",
"C4": "\u7535\u6c60\u6599\u76d8_materialhole_3_2",
"D4": "\u7535\u6c60\u6599\u76d8_materialhole_3_3"
}
},
"data": {}
},
{
"id": "\u7535\u6c60\u6599\u76d8_materialhole_0_0",
"name": "\u7535\u6c60\u6599\u76d8_materialhole_0_0",
"sample_id": null,
"children": [],
"parent": "\u7535\u6c60\u6599\u76d8",
"type": "container",
"class": "",
"position": {
"x": 12.4,
"y": 104.25,
"z": 10.0
},
"config": {
"type": "MaterialHole",
"size_x": 16,
"size_y": 16,
"size_z": 16,
"rotation": {
"x": 0,
"y": 0,
"z": 0,
"type": "Rotation"
},
"category": "material_hole",
"model": null,
"barcode": null
},
"data": {
"diameter": 20,
"depth": 10,
"max_sheets": 1,
"info": null
}
},
{
"id": "\u7535\u6c60\u6599\u76d8_materialhole_0_1",
"name": "\u7535\u6c60\u6599\u76d8_materialhole_0_1",
"sample_id": null,
"children": [],
"parent": "\u7535\u6c60\u6599\u76d8",
"type": "container",
"class": "",
"position": {
"x": 12.4,
"y": 80.25,
"z": 10.0
},
"config": {
"type": "MaterialHole",
"size_x": 16,
"size_y": 16,
"size_z": 16,
"rotation": {
"x": 0,
"y": 0,
"z": 0,
"type": "Rotation"
},
"category": "material_hole",
"model": null,
"barcode": null
},
"data": {
"diameter": 20,
"depth": 10,
"max_sheets": 1,
"info": null
}
},
{
"id": "\u7535\u6c60\u6599\u76d8_materialhole_0_2",
"name": "\u7535\u6c60\u6599\u76d8_materialhole_0_2",
"sample_id": null,
"children": [],
"parent": "\u7535\u6c60\u6599\u76d8",
"type": "container",
"class": "",
"position": {
"x": 12.4,
"y": 56.25,
"z": 10.0
},
"config": {
"type": "MaterialHole",
"size_x": 16,
"size_y": 16,
"size_z": 16,
"rotation": {
"x": 0,
"y": 0,
"z": 0,
"type": "Rotation"
},
"category": "material_hole",
"model": null,
"barcode": null
},
"data": {
"diameter": 20,
"depth": 10,
"max_sheets": 1,
"info": null
}
},
{
"id": "\u7535\u6c60\u6599\u76d8_materialhole_0_3",
"name": "\u7535\u6c60\u6599\u76d8_materialhole_0_3",
"sample_id": null,
"children": [],
"parent": "\u7535\u6c60\u6599\u76d8",
"type": "container",
"class": "",
"position": {
"x": 12.4,
"y": 32.25,
"z": 10.0
},
"config": {
"type": "MaterialHole",
"size_x": 16,
"size_y": 16,
"size_z": 16,
"rotation": {
"x": 0,
"y": 0,
"z": 0,
"type": "Rotation"
},
"category": "material_hole",
"model": null,
"barcode": null
},
"data": {
"diameter": 20,
"depth": 10,
"max_sheets": 1,
"info": null
}
},
{
"id": "\u7535\u6c60\u6599\u76d8_materialhole_1_0",
"name": "\u7535\u6c60\u6599\u76d8_materialhole_1_0",
"sample_id": null,
"children": [],
"parent": "\u7535\u6c60\u6599\u76d8",
"type": "container",
"class": "",
"position": {
"x": 36.4,
"y": 104.25,
"z": 10.0
},
"config": {
"type": "MaterialHole",
"size_x": 16,
"size_y": 16,
"size_z": 16,
"rotation": {
"x": 0,
"y": 0,
"z": 0,
"type": "Rotation"
},
"category": "material_hole",
"model": null,
"barcode": null
},
"data": {
"diameter": 20,
"depth": 10,
"max_sheets": 1,
"info": null
}
},
{
"id": "\u7535\u6c60\u6599\u76d8_materialhole_1_1",
"name": "\u7535\u6c60\u6599\u76d8_materialhole_1_1",
"sample_id": null,
"children": [],
"parent": "\u7535\u6c60\u6599\u76d8",
"type": "container",
"class": "",
"position": {
"x": 36.4,
"y": 80.25,
"z": 10.0
},
"config": {
"type": "MaterialHole",
"size_x": 16,
"size_y": 16,
"size_z": 16,
"rotation": {
"x": 0,
"y": 0,
"z": 0,
"type": "Rotation"
},
"category": "material_hole",
"model": null,
"barcode": null
},
"data": {
"diameter": 20,
"depth": 10,
"max_sheets": 1,
"info": null
}
},
{
"id": "\u7535\u6c60\u6599\u76d8_materialhole_1_2",
"name": "\u7535\u6c60\u6599\u76d8_materialhole_1_2",
"sample_id": null,
"children": [],
"parent": "\u7535\u6c60\u6599\u76d8",
"type": "container",
"class": "",
"position": {
"x": 36.4,
"y": 56.25,
"z": 10.0
},
"config": {
"type": "MaterialHole",
"size_x": 16,
"size_y": 16,
"size_z": 16,
"rotation": {
"x": 0,
"y": 0,
"z": 0,
"type": "Rotation"
},
"category": "material_hole",
"model": null,
"barcode": null
},
"data": {
"diameter": 20,
"depth": 10,
"max_sheets": 1,
"info": null
}
},
{
"id": "\u7535\u6c60\u6599\u76d8_materialhole_1_3",
"name": "\u7535\u6c60\u6599\u76d8_materialhole_1_3",
"sample_id": null,
"children": [],
"parent": "\u7535\u6c60\u6599\u76d8",
"type": "container",
"class": "",
"position": {
"x": 36.4,
"y": 32.25,
"z": 10.0
},
"config": {
"type": "MaterialHole",
"size_x": 16,
"size_y": 16,
"size_z": 16,
"rotation": {
"x": 0,
"y": 0,
"z": 0,
"type": "Rotation"
},
"category": "material_hole",
"model": null,
"barcode": null
},
"data": {
"diameter": 20,
"depth": 10,
"max_sheets": 1,
"info": null
}
},
{
"id": "\u7535\u6c60\u6599\u76d8_materialhole_2_0",
"name": "\u7535\u6c60\u6599\u76d8_materialhole_2_0",
"sample_id": null,
"children": [],
"parent": "\u7535\u6c60\u6599\u76d8",
"type": "container",
"class": "",
"position": {
"x": 60.4,
"y": 104.25,
"z": 10.0
},
"config": {
"type": "MaterialHole",
"size_x": 16,
"size_y": 16,
"size_z": 16,
"rotation": {
"x": 0,
"y": 0,
"z": 0,
"type": "Rotation"
},
"category": "material_hole",
"model": null,
"barcode": null
},
"data": {
"diameter": 20,
"depth": 10,
"max_sheets": 1,
"info": null
}
},
{
"id": "\u7535\u6c60\u6599\u76d8_materialhole_2_1",
"name": "\u7535\u6c60\u6599\u76d8_materialhole_2_1",
"sample_id": null,
"children": [],
"parent": "\u7535\u6c60\u6599\u76d8",
"type": "container",
"class": "",
"position": {
"x": 60.4,
"y": 80.25,
"z": 10.0
},
"config": {
"type": "MaterialHole",
"size_x": 16,
"size_y": 16,
"size_z": 16,
"rotation": {
"x": 0,
"y": 0,
"z": 0,
"type": "Rotation"
},
"category": "material_hole",
"model": null,
"barcode": null
},
"data": {
"diameter": 20,
"depth": 10,
"max_sheets": 1,
"info": null
}
},
{
"id": "\u7535\u6c60\u6599\u76d8_materialhole_2_2",
"name": "\u7535\u6c60\u6599\u76d8_materialhole_2_2",
"sample_id": null,
"children": [],
"parent": "\u7535\u6c60\u6599\u76d8",
"type": "container",
"class": "",
"position": {
"x": 60.4,
"y": 56.25,
"z": 10.0
},
"config": {
"type": "MaterialHole",
"size_x": 16,
"size_y": 16,
"size_z": 16,
"rotation": {
"x": 0,
"y": 0,
"z": 0,
"type": "Rotation"
},
"category": "material_hole",
"model": null,
"barcode": null
},
"data": {
"diameter": 20,
"depth": 10,
"max_sheets": 1,
"info": null
}
},
{
"id": "\u7535\u6c60\u6599\u76d8_materialhole_2_3",
"name": "\u7535\u6c60\u6599\u76d8_materialhole_2_3",
"sample_id": null,
"children": [],
"parent": "\u7535\u6c60\u6599\u76d8",
"type": "container",
"class": "",
"position": {
"x": 60.4,
"y": 32.25,
"z": 10.0
},
"config": {
"type": "MaterialHole",
"size_x": 16,
"size_y": 16,
"size_z": 16,
"rotation": {
"x": 0,
"y": 0,
"z": 0,
"type": "Rotation"
},
"category": "material_hole",
"model": null,
"barcode": null
},
"data": {
"diameter": 20,
"depth": 10,
"max_sheets": 1,
"info": null
}
},
{
"id": "\u7535\u6c60\u6599\u76d8_materialhole_3_0",
"name": "\u7535\u6c60\u6599\u76d8_materialhole_3_0",
"sample_id": null,
"children": [],
"parent": "\u7535\u6c60\u6599\u76d8",
"type": "container",
"class": "",
"position": {
"x": 84.4,
"y": 104.25,
"z": 10.0
},
"config": {
"type": "MaterialHole",
"size_x": 16,
"size_y": 16,
"size_z": 16,
"rotation": {
"x": 0,
"y": 0,
"z": 0,
"type": "Rotation"
},
"category": "material_hole",
"model": null,
"barcode": null
},
"data": {
"diameter": 20,
"depth": 10,
"max_sheets": 1,
"info": null
}
},
{
"id": "\u7535\u6c60\u6599\u76d8_materialhole_3_1",
"name": "\u7535\u6c60\u6599\u76d8_materialhole_3_1",
"sample_id": null,
"children": [],
"parent": "\u7535\u6c60\u6599\u76d8",
"type": "container",
"class": "",
"position": {
"x": 84.4,
"y": 80.25,
"z": 10.0
},
"config": {
"type": "MaterialHole",
"size_x": 16,
"size_y": 16,
"size_z": 16,
"rotation": {
"x": 0,
"y": 0,
"z": 0,
"type": "Rotation"
},
"category": "material_hole",
"model": null,
"barcode": null
},
"data": {
"diameter": 20,
"depth": 10,
"max_sheets": 1,
"info": null
}
},
{
"id": "\u7535\u6c60\u6599\u76d8_materialhole_3_2",
"name": "\u7535\u6c60\u6599\u76d8_materialhole_3_2",
"sample_id": null,
"children": [],
"parent": "\u7535\u6c60\u6599\u76d8",
"type": "container",
"class": "",
"position": {
"x": 84.4,
"y": 56.25,
"z": 10.0
},
"config": {
"type": "MaterialHole",
"size_x": 16,
"size_y": 16,
"size_z": 16,
"rotation": {
"x": 0,
"y": 0,
"z": 0,
"type": "Rotation"
},
"category": "material_hole",
"model": null,
"barcode": null
},
"data": {
"diameter": 20,
"depth": 10,
"max_sheets": 1,
"info": null
}
},
{
"id": "\u7535\u6c60\u6599\u76d8_materialhole_3_3",
"name": "\u7535\u6c60\u6599\u76d8_materialhole_3_3",
"sample_id": null,
"children": [],
"parent": "\u7535\u6c60\u6599\u76d8",
"type": "container",
"class": "",
"position": {
"x": 84.4,
"y": 32.25,
"z": 10.0
},
"config": {
"type": "MaterialHole",
"size_x": 16,
"size_y": 16,
"size_z": 16,
"rotation": {
"x": 0,
"y": 0,
"z": 0,
"type": "Rotation"
},
"category": "material_hole",
"model": null,
"barcode": null
},
"data": {
"diameter": 20,
"depth": 10,
"max_sheets": 1,
"info": null
}
}
],
"links": []
}

View File

@@ -16,9 +16,9 @@
}, },
"config": { "config": {
"debug_mode": false, "debug_mode": false,
"_comment": "protocol_type接外部工站固定写法字段一般为空station_resource写法也固定", "_comment": "protocol_type接外部工站固定写法字段一般为空deck写法也固定",
"protocol_type": [], "protocol_type": [],
"station_resource": { "deck": {
"data": { "data": {
"_resource_child_name": "coin_cell_deck", "_resource_child_name": "coin_cell_deck",
"_resource_type": "unilabos.devices.workstation.coin_cell_assembly.button_battery_station:CoincellDeck" "_resource_type": "unilabos.devices.workstation.coin_cell_assembly.button_battery_station:CoincellDeck"

View File

@@ -1,750 +0,0 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": 22,
"id": "80bc9500",
"metadata": {},
"outputs": [],
"source": [
"from __future__ import annotations\n",
"\n",
"from collections import OrderedDict\n",
"from typing import Any, Dict, List, Optional, TypedDict, Union, cast\n",
"\n",
"from pylabrobot.resources.coordinate import Coordinate\n",
"from pylabrobot.resources.container import Container\n",
"from pylabrobot.resources.deck import Deck\n",
"from pylabrobot.resources.itemized_resource import ItemizedResource\n",
"from pylabrobot.resources.resource import Resource\n",
"from pylabrobot.resources.resource_stack import ResourceStack\n",
"from pylabrobot.resources.tip_rack import TipRack, TipSpot\n",
"from pylabrobot.resources.trash import Trash\n",
"from pylabrobot.resources.utils import create_ordered_items_2d"
]
},
{
"cell_type": "markdown",
"id": "498a9159",
"metadata": {},
"source": [
"物料类型构建"
]
},
{
"cell_type": "code",
"execution_count": 23,
"id": "f4a27241",
"metadata": {},
"outputs": [],
"source": [
"\n",
"class ElectrodeSheetState(TypedDict):\n",
" diameter: float # 直径 (mm)\n",
" thickness: float # 厚度 (mm)\n",
" mass: float # 质量 (g)\n",
" material_type: str # 材料类型(正极、负极、隔膜、弹片、垫片、铝箔等)\n",
" info: Optional[str] # 附加信息\n",
"\n",
"class ElectrodeSheet(Resource):\n",
" \"\"\"极片类 - 包含正负极片、隔膜、弹片、垫片、铝箔等所有片状材料\"\"\"\n",
"\n",
" def __init__(\n",
" self,\n",
" name: str = \"极片\",\n",
" size_x=10,\n",
" size_y=10,\n",
" size_z=10,\n",
" category: str = \"electrode_sheet\",\n",
" model: Optional[str] = None,\n",
" ):\n",
" \"\"\"初始化极片\n",
"\n",
" Args:\n",
" name: 极片名称\n",
" category: 类别\n",
" model: 型号\n",
" \"\"\"\n",
" super().__init__(\n",
" name=name,\n",
" size_x=size_x,\n",
" size_y=size_y,\n",
" size_z=size_z,\n",
" category=category,\n",
" model=model,\n",
" )\n",
" self._unilabos_state: ElectrodeSheetState = ElectrodeSheetState(\n",
" diameter=14,\n",
" thickness=0.1,\n",
" mass=0.5,\n",
" material_type=\"copper\",\n",
" info=None\n",
" )\n",
"\n",
" # TODO: 这个还要不要给self._unilabos_state赋值的\n",
" def load_state(self, state: Dict[str, Any]) -> None:\n",
" \"\"\"格式不变\"\"\"\n",
" super().load_state(state)\n",
" self._unilabos_state = state\n",
" #序列化\n",
" def serialize_state(self) -> Dict[str, Dict[str, Any]]:\n",
" \"\"\"格式不变\"\"\"\n",
" data = super().serialize_state()\n",
" data.update(self._unilabos_state) # Container自身的信息云端物料将保存这一data本地也通过这里的data进行读写当前类用来表示这个物料的长宽高大小的属性而datastate用来表示物料的内容细节等\n",
" return data\n"
]
},
{
"cell_type": "code",
"execution_count": 24,
"id": "830f052e",
"metadata": {},
"outputs": [],
"source": [
"# TODO: 这个应该只能放一个极片\n",
"class MaterialHoleState(TypedDict):\n",
" diameter: int\n",
" depth: int\n",
" max_sheets: int\n",
" info: Optional[str] # 附加信息\n",
"\n",
"class MaterialHole(Resource):\n",
" \"\"\"料板洞位类\"\"\"\n",
" children: List[ElectrodeSheet] = []\n",
"\n",
" def __init__(\n",
" self,\n",
" name: str,\n",
" size_x: float,\n",
" size_y: float,\n",
" size_z: float,\n",
" category: str = \"material_hole\",\n",
" **kwargs\n",
" ):\n",
" super().__init__(\n",
" name=name,\n",
" size_x=size_x,\n",
" size_y=size_y,\n",
" size_z=size_z,\n",
" category=category,\n",
" )\n",
" self._unilabos_state: MaterialHoleState = MaterialHoleState(\n",
" diameter=20,\n",
" depth=10,\n",
" max_sheets=1,\n",
" info=None\n",
" )\n",
"\n",
" def get_all_sheet_info(self):\n",
" info_list = []\n",
" for sheet in self.children:\n",
" info_list.append(sheet._unilabos_state[\"info\"])\n",
" return info_list\n",
" \n",
" #这个函数函数好像没用,一般不会集中赋值质量\n",
" def set_all_sheet_mass(self):\n",
" for sheet in self.children:\n",
" sheet._unilabos_state[\"mass\"] = 0.5 # 示例设置质量为0.5g\n",
"\n",
" def load_state(self, state: Dict[str, Any]) -> None:\n",
" \"\"\"格式不变\"\"\"\n",
" super().load_state(state)\n",
" self._unilabos_state = state\n",
"\n",
" def serialize_state(self) -> Dict[str, Dict[str, Any]]:\n",
" \"\"\"格式不变\"\"\"\n",
" data = super().serialize_state()\n",
" data.update(self._unilabos_state) # Container自身的信息云端物料将保存这一data本地也通过这里的data进行读写当前类用来表示这个物料的长宽高大小的属性而datastate用来表示物料的内容细节等\n",
" return data\n",
" #移动极片前先取出对象\n",
" def get_sheet_with_name(self, name: str) -> Optional[ElectrodeSheet]:\n",
" for sheet in self.children:\n",
" if sheet.name == name:\n",
" return sheet\n",
" return None\n",
"\n",
" def has_electrode_sheet(self) -> bool:\n",
" \"\"\"检查洞位是否有极片\"\"\"\n",
" return len(self.children) > 0\n",
"\n",
" def assign_child_resource(\n",
" self,\n",
" resource: ElectrodeSheet,\n",
" location: Optional[Coordinate],\n",
" reassign: bool = True,\n",
" ):\n",
" \"\"\"放置极片\"\"\"\n",
" # TODO: 这里要改diameter找不到加入._unilabos_state后应该没问题\n",
" if resource._unilabos_state[\"diameter\"] > self._unilabos_state[\"diameter\"]:\n",
" raise ValueError(f\"极片直径 {resource._unilabos_state['diameter']} 超过洞位直径 {self._unilabos_state['diameter']}\")\n",
" if len(self.children) >= self._unilabos_state[\"max_sheets\"]:\n",
" raise ValueError(f\"洞位已满,无法放置更多极片\")\n",
" super().assign_child_resource(resource, location, reassign)\n",
"\n",
" # 根据children的编号取物料对象。\n",
" def get_electrode_sheet_info(self, index: int) -> ElectrodeSheet:\n",
" return self.children[index]\n",
"\n",
"\n",
"#料板\n",
"class MaterialPlateState(TypedDict):\n",
" hole_spacing_x: float\n",
" hole_spacing_y: float\n",
" hole_diameter: float\n",
" info: Optional[str] # 附加信息\n",
"\n",
"class MaterialPlate(ItemizedResource[MaterialHole]):\n",
" \"\"\"料板类 - 4x4个洞位每个洞位放1个极片\"\"\"\n",
" \n",
" children: List[MaterialHole]\n",
"\n",
" def __init__(\n",
" self,\n",
" name: str,\n",
" size_x: float,\n",
" size_y: float,\n",
" size_z: float,\n",
" ordered_items: Optional[Dict[str, MaterialHole]] = None,\n",
" ordering: Optional[OrderedDict[str, str]] = None,\n",
" category: str = \"material_plate\",\n",
" model: Optional[str] = None,\n",
" fill: bool = False\n",
" ):\n",
" \"\"\"初始化料板\n",
"\n",
" Args:\n",
" name: 料板名称\n",
" size_x: 长度 (mm)\n",
" size_y: 宽度 (mm)\n",
" size_z: 高度 (mm)\n",
" hole_diameter: 洞直径 (mm)\n",
" hole_depth: 洞深度 (mm)\n",
" hole_spacing_x: X方向洞位间距 (mm)\n",
" hole_spacing_y: Y方向洞位间距 (mm)\n",
" number: 编号\n",
" category: 类别\n",
" model: 型号\n",
" \"\"\"\n",
" self._unilabos_state: MaterialPlateState = MaterialPlateState(\n",
" hole_spacing_x=24.0,\n",
" hole_spacing_y=24.0,\n",
" hole_diameter=20.0,\n",
" info=\"\",\n",
" )\n",
" # 创建4x4的洞位\n",
" # TODO: 这里要改,对应不同形状\n",
" holes = create_ordered_items_2d(\n",
" klass=MaterialHole,\n",
" num_items_x=4,\n",
" num_items_y=4,\n",
" dx=(size_x - 4 * self._unilabos_state[\"hole_spacing_x\"]) / 2, # 居中\n",
" dy=(size_y - 4 * self._unilabos_state[\"hole_spacing_y\"]) / 2, # 居中\n",
" dz=size_z,\n",
" item_dx=self._unilabos_state[\"hole_spacing_x\"],\n",
" item_dy=self._unilabos_state[\"hole_spacing_y\"],\n",
" size_x = 16,\n",
" size_y = 16,\n",
" size_z = 16,\n",
" )\n",
" if fill:\n",
" super().__init__(\n",
" name=name,\n",
" size_x=size_x,\n",
" size_y=size_y,\n",
" size_z=size_z,\n",
" ordered_items=holes,\n",
" category=category,\n",
" model=model,\n",
" )\n",
" else:\n",
" super().__init__(\n",
" name=name,\n",
" size_x=size_x,\n",
" size_y=size_y,\n",
" size_z=size_z,\n",
" ordered_items=ordered_items,\n",
" ordering=ordering,\n",
" category=category,\n",
" model=model,\n",
" )\n",
"\n",
" def update_locations(self):\n",
" # TODO:调多次相加\n",
" holes = create_ordered_items_2d(\n",
" klass=MaterialHole,\n",
" num_items_x=4,\n",
" num_items_y=4,\n",
" dx=(self._size_x - 3 * self._unilabos_state[\"hole_spacing_x\"]) / 2, # 居中\n",
" dy=(self._size_y - 3 * self._unilabos_state[\"hole_spacing_y\"]) / 2, # 居中\n",
" dz=self._size_z,\n",
" item_dx=self._unilabos_state[\"hole_spacing_x\"],\n",
" item_dy=self._unilabos_state[\"hole_spacing_y\"],\n",
" size_x = 1,\n",
" size_y = 1,\n",
" size_z = 1,\n",
" )\n",
" for item, original_item in zip(holes.items(), self.children):\n",
" original_item.location = item[1].location"
]
},
{
"cell_type": "code",
"execution_count": 25,
"id": "8318ccca",
"metadata": {},
"outputs": [],
"source": [
"class CoincellDeck(Deck):\n",
" \"\"\"纽扣电池组装工作站台面类\"\"\"\n",
"\n",
" def __init__(\n",
" self,\n",
" name: str = \"coin_cell_deck\",\n",
" size_x: float = 1620.0, # 3.66m\n",
" size_y: float = 1270.0, # 1.23m\n",
" size_z: float = 500.0,\n",
" origin: Coordinate = Coordinate(0, 0, 0),\n",
" category: str = \"coin_cell_deck\",\n",
" ):\n",
" \"\"\"初始化纽扣电池组装工作站台面\n",
"\n",
" Args:\n",
" name: 台面名称\n",
" size_x: 长度 (mm) - 3.66m\n",
" size_y: 宽度 (mm) - 1.23m\n",
" size_z: 高度 (mm)\n",
" origin: 原点坐标\n",
" category: 类别\n",
" \"\"\"\n",
" super().__init__(\n",
" name=name,\n",
" size_x=size_x,\n",
" size_y=size_y,\n",
" size_z=size_z,\n",
" origin=origin,\n",
" category=category,\n",
" )"
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "c73bae21",
"metadata": {},
"outputs": [],
"source": [
"import json"
]
},
{
"cell_type": "code",
"execution_count": 17,
"id": "3369a1dd",
"metadata": {},
"outputs": [],
"source": [
"def upload_resources_to_unilab(wuliao: List[Resource]):\n",
" from unilabos.resources.graphio import convert_resources_from_type\n",
" from unilabos.config.config import BasicConfig \n",
" BasicConfig.ak = \"beb0c15f-2279-46a1-aba5-00eaf89aef55\"\n",
" BasicConfig.sk = \"15d4f25e-3512-4f9c-9bfb-43ab85e7b561\"\n",
" from unilabos.app.web.client import http_client\n",
" resources = convert_resources_from_type(wuliao, [Resource])\n",
" json.dump({\"nodes\": resources, \"links\": []}, open(\"button_battery_station_resources_unilab.json\", \"w\"), indent=2)\n",
" \n",
" #print(resources)\n",
" http_client.remote_addr = \"https://uni-lab.test.bohrium.com/api/v1\"\n",
" \n",
" http_client.resource_add(resources)"
]
},
{
"cell_type": "code",
"execution_count": 29,
"id": "1543ddab",
"metadata": {},
"outputs": [],
"source": [
"liaopan1 = MaterialPlate(name=\"liaopan1\", size_x=120.8, size_y=120.5, size_z=10.0, fill=True)"
]
},
{
"cell_type": "code",
"execution_count": 30,
"id": "b732754a",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"MaterialPlate(name=liaopan1, size_x=120.8, size_y=120.5, size_z=10.0, location=None)\n"
]
}
],
"source": [
"print(liaopan1)"
]
},
{
"cell_type": "code",
"execution_count": 31,
"id": "7e6e7252",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[MaterialHole(name=liaopan1_materialhole_0_0, location=Coordinate(012.400, 084.250, 010.000), size_x=16, size_y=16, size_z=16, category=material_hole), MaterialHole(name=liaopan1_materialhole_0_1, location=Coordinate(012.400, 060.250, 010.000), size_x=16, size_y=16, size_z=16, category=material_hole), MaterialHole(name=liaopan1_materialhole_0_2, location=Coordinate(012.400, 036.250, 010.000), size_x=16, size_y=16, size_z=16, category=material_hole), MaterialHole(name=liaopan1_materialhole_0_3, location=Coordinate(012.400, 012.250, 010.000), size_x=16, size_y=16, size_z=16, category=material_hole), MaterialHole(name=liaopan1_materialhole_1_0, location=Coordinate(036.400, 084.250, 010.000), size_x=16, size_y=16, size_z=16, category=material_hole), MaterialHole(name=liaopan1_materialhole_1_1, location=Coordinate(036.400, 060.250, 010.000), size_x=16, size_y=16, size_z=16, category=material_hole), MaterialHole(name=liaopan1_materialhole_1_2, location=Coordinate(036.400, 036.250, 010.000), size_x=16, size_y=16, size_z=16, category=material_hole), MaterialHole(name=liaopan1_materialhole_1_3, location=Coordinate(036.400, 012.250, 010.000), size_x=16, size_y=16, size_z=16, category=material_hole), MaterialHole(name=liaopan1_materialhole_2_0, location=Coordinate(060.400, 084.250, 010.000), size_x=16, size_y=16, size_z=16, category=material_hole), MaterialHole(name=liaopan1_materialhole_2_1, location=Coordinate(060.400, 060.250, 010.000), size_x=16, size_y=16, size_z=16, category=material_hole), MaterialHole(name=liaopan1_materialhole_2_2, location=Coordinate(060.400, 036.250, 010.000), size_x=16, size_y=16, size_z=16, category=material_hole), MaterialHole(name=liaopan1_materialhole_2_3, location=Coordinate(060.400, 012.250, 010.000), size_x=16, size_y=16, size_z=16, category=material_hole), MaterialHole(name=liaopan1_materialhole_3_0, location=Coordinate(084.400, 084.250, 010.000), size_x=16, size_y=16, size_z=16, category=material_hole), MaterialHole(name=liaopan1_materialhole_3_1, location=Coordinate(084.400, 060.250, 010.000), size_x=16, size_y=16, size_z=16, category=material_hole), MaterialHole(name=liaopan1_materialhole_3_2, location=Coordinate(084.400, 036.250, 010.000), size_x=16, size_y=16, size_z=16, category=material_hole), MaterialHole(name=liaopan1_materialhole_3_3, location=Coordinate(084.400, 012.250, 010.000), size_x=16, size_y=16, size_z=16, category=material_hole)]\n"
]
}
],
"source": [
"print(liaopan1.children)"
]
},
{
"cell_type": "code",
"execution_count": 26,
"id": "836ff68d",
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"\u001b[37m25-09-22 [15:15:08,950]\u001b[0m \u001b[1;36m[DEBUG]\u001b[0m \u001b[37mStarting new HTTPS connection (1): uni-lab.test.bohrium.com:443\u001b[37m [_new_conn:1049] [urllib3.connectionpool.connectionpool]\u001b[0m\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"CoincellDeck(name=coin_cell_deck, location=Coordinate(000.000, 000.000, 000.000), size_x=1620.0, size_y=1270.0, size_z=500.0, category=coin_cell_deck)\n",
"转换pylabrobot的时候出现未知类型 electrode_sheet\n",
"转换pylabrobot的时候出现未知类型 material_hole\n",
"转换pylabrobot的时候出现未知类型 electrode_sheet\n",
"转换pylabrobot的时候出现未知类型 material_hole\n",
"转换pylabrobot的时候出现未知类型 electrode_sheet\n",
"转换pylabrobot的时候出现未知类型 material_hole\n",
"转换pylabrobot的时候出现未知类型 electrode_sheet\n",
"转换pylabrobot的时候出现未知类型 material_hole\n",
"转换pylabrobot的时候出现未知类型 electrode_sheet\n",
"转换pylabrobot的时候出现未知类型 material_hole\n",
"转换pylabrobot的时候出现未知类型 electrode_sheet\n",
"转换pylabrobot的时候出现未知类型 material_hole\n",
"转换pylabrobot的时候出现未知类型 electrode_sheet\n",
"转换pylabrobot的时候出现未知类型 material_hole\n",
"转换pylabrobot的时候出现未知类型 electrode_sheet\n",
"转换pylabrobot的时候出现未知类型 material_hole\n",
"转换pylabrobot的时候出现未知类型 electrode_sheet\n",
"转换pylabrobot的时候出现未知类型 material_hole\n",
"转换pylabrobot的时候出现未知类型 electrode_sheet\n",
"转换pylabrobot的时候出现未知类型 material_hole\n",
"转换pylabrobot的时候出现未知类型 electrode_sheet\n",
"转换pylabrobot的时候出现未知类型 material_hole\n",
"转换pylabrobot的时候出现未知类型 electrode_sheet\n",
"转换pylabrobot的时候出现未知类型 material_hole\n",
"转换pylabrobot的时候出现未知类型 electrode_sheet\n",
"转换pylabrobot的时候出现未知类型 material_hole\n",
"转换pylabrobot的时候出现未知类型 electrode_sheet\n",
"转换pylabrobot的时候出现未知类型 material_hole\n",
"转换pylabrobot的时候出现未知类型 electrode_sheet\n",
"转换pylabrobot的时候出现未知类型 material_hole\n",
"转换pylabrobot的时候出现未知类型 electrode_sheet\n",
"转换pylabrobot的时候出现未知类型 material_hole\n",
"转换pylabrobot的时候出现未知类型 material_plate\n",
"转换pylabrobot的时候出现未知类型 material_hole\n",
"转换pylabrobot的时候出现未知类型 material_hole\n",
"转换pylabrobot的时候出现未知类型 material_hole\n",
"转换pylabrobot的时候出现未知类型 material_hole\n",
"转换pylabrobot的时候出现未知类型 material_hole\n",
"转换pylabrobot的时候出现未知类型 material_hole\n",
"转换pylabrobot的时候出现未知类型 material_hole\n",
"转换pylabrobot的时候出现未知类型 material_hole\n",
"转换pylabrobot的时候出现未知类型 material_hole\n",
"转换pylabrobot的时候出现未知类型 material_hole\n",
"转换pylabrobot的时候出现未知类型 material_hole\n",
"转换pylabrobot的时候出现未知类型 material_hole\n",
"转换pylabrobot的时候出现未知类型 material_hole\n",
"转换pylabrobot的时候出现未知类型 material_hole\n",
"转换pylabrobot的时候出现未知类型 material_hole\n",
"转换pylabrobot的时候出现未知类型 material_hole\n",
"转换pylabrobot的时候出现未知类型 material_plate\n",
"转换pylabrobot的时候出现未知类型 coin_cell_deck\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
"\u001b[37m25-09-22 [15:15:09,218]\u001b[0m \u001b[1;36m[DEBUG]\u001b[0m \u001b[37mhttps://uni-lab.test.bohrium.com:443 \"POST /api/v1/lab/material HTTP/1.1\" 200 10\u001b[37m [_make_request:544] [urllib3.connectionpool.connectionpool]\u001b[0m\n"
]
}
],
"source": [
"deck = CoincellDeck()\n",
"#创建一个4*4的物料板\n",
"liaopan1 = MaterialPlate(name=\"liaopan1\", size_x=120.8, size_y=120.5, size_z=10.0, fill=True)\n",
"#把物料板放到桌子上\n",
"deck.assign_child_resource(liaopan1, Coordinate(x=0, y=0, z=0))\n",
"#创建一个极片\n",
"for i in range(16):\n",
" jipian = ElectrodeSheet(name=f\"jipian_{i}\", size_x= 12, size_y=12, size_z=0.1)\n",
" liaopan1.children[i].assign_child_resource(jipian, location=None)\n",
"#创建一个4*4的物料板\n",
"liaopan2 = MaterialPlate(name=\"liaopan2\", size_x=120.8, size_y=120.5, size_z=10.0, fill=True)\n",
"#把物料板放到桌子上\n",
"deck.assign_child_resource(liaopan2, Coordinate(x=500, y=0, z=0))\n",
"#liaopan.children[3].assign_child_resource(jipian, location=None)\n",
"print(deck)\n",
"\n",
"upload_resources_to_unilab([deck])"
]
},
{
"cell_type": "code",
"execution_count": 20,
"id": "00aab9cf",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"MaterialPlate(name=liaopan1, size_x=120.8, size_y=120.5, size_z=10.0, location=Coordinate(000.000, 000.000, 000.000))\n"
]
}
],
"source": [
"liaopan1 = deck.get_resource(\"liaopan1\")\n",
"print(liaopan1)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7409969c",
"metadata": {},
"outputs": [],
"source": [
"liaopan1 = deck.get_resource(\"liaopan1\")\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "096dde04",
"metadata": {},
"outputs": [],
"source": [
"print()"
]
},
{
"cell_type": "code",
"execution_count": 27,
"id": "5528df96",
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"\u001b[37m25-09-22 [15:17:44,322]\u001b[0m \u001b[1;36m[DEBUG]\u001b[0m \u001b[37mStarting new HTTPS connection (1): uni-lab.test.bohrium.com:443\u001b[37m [_new_conn:1049] [urllib3.connectionpool.connectionpool]\u001b[0m\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"ElectrodeSheet(name=jipian_1, location=None, size_x=12, size_y=12, size_z=0.1, category=electrode_sheet)\n",
"转换pylabrobot的时候出现未知类型 electrode_sheet\n",
"转换pylabrobot的时候出现未知类型 material_hole\n",
"转换pylabrobot的时候出现未知类型 material_hole\n",
"转换pylabrobot的时候出现未知类型 electrode_sheet\n",
"转换pylabrobot的时候出现未知类型 material_hole\n",
"转换pylabrobot的时候出现未知类型 electrode_sheet\n",
"转换pylabrobot的时候出现未知类型 material_hole\n",
"转换pylabrobot的时候出现未知类型 electrode_sheet\n",
"转换pylabrobot的时候出现未知类型 material_hole\n",
"转换pylabrobot的时候出现未知类型 electrode_sheet\n",
"转换pylabrobot的时候出现未知类型 material_hole\n",
"转换pylabrobot的时候出现未知类型 electrode_sheet\n",
"转换pylabrobot的时候出现未知类型 material_hole\n",
"转换pylabrobot的时候出现未知类型 electrode_sheet\n",
"转换pylabrobot的时候出现未知类型 material_hole\n",
"转换pylabrobot的时候出现未知类型 electrode_sheet\n",
"转换pylabrobot的时候出现未知类型 material_hole\n",
"转换pylabrobot的时候出现未知类型 electrode_sheet\n",
"转换pylabrobot的时候出现未知类型 material_hole\n",
"转换pylabrobot的时候出现未知类型 electrode_sheet\n",
"转换pylabrobot的时候出现未知类型 material_hole\n",
"转换pylabrobot的时候出现未知类型 electrode_sheet\n",
"转换pylabrobot的时候出现未知类型 material_hole\n",
"转换pylabrobot的时候出现未知类型 electrode_sheet\n",
"转换pylabrobot的时候出现未知类型 material_hole\n",
"转换pylabrobot的时候出现未知类型 electrode_sheet\n",
"转换pylabrobot的时候出现未知类型 material_hole\n",
"转换pylabrobot的时候出现未知类型 electrode_sheet\n",
"转换pylabrobot的时候出现未知类型 material_hole\n",
"转换pylabrobot的时候出现未知类型 electrode_sheet\n",
"转换pylabrobot的时候出现未知类型 material_hole\n",
"转换pylabrobot的时候出现未知类型 material_plate\n",
"转换pylabrobot的时候出现未知类型 material_hole\n",
"转换pylabrobot的时候出现未知类型 electrode_sheet\n",
"转换pylabrobot的时候出现未知类型 material_hole\n",
"转换pylabrobot的时候出现未知类型 material_hole\n",
"转换pylabrobot的时候出现未知类型 material_hole\n",
"转换pylabrobot的时候出现未知类型 material_hole\n",
"转换pylabrobot的时候出现未知类型 material_hole\n",
"转换pylabrobot的时候出现未知类型 material_hole\n",
"转换pylabrobot的时候出现未知类型 material_hole\n",
"转换pylabrobot的时候出现未知类型 material_hole\n",
"转换pylabrobot的时候出现未知类型 material_hole\n",
"转换pylabrobot的时候出现未知类型 material_hole\n",
"转换pylabrobot的时候出现未知类型 material_hole\n",
"转换pylabrobot的时候出现未知类型 material_hole\n",
"转换pylabrobot的时候出现未知类型 material_hole\n",
"转换pylabrobot的时候出现未知类型 material_hole\n",
"转换pylabrobot的时候出现未知类型 material_hole\n",
"转换pylabrobot的时候出现未知类型 material_plate\n",
"转换pylabrobot的时候出现未知类型 coin_cell_deck\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
"\u001b[37m25-09-22 [15:17:44,599]\u001b[0m \u001b[1;36m[DEBUG]\u001b[0m \u001b[37mhttps://uni-lab.test.bohrium.com:443 \"POST /api/v1/lab/material HTTP/1.1\" 200 10\u001b[37m [_make_request:544] [urllib3.connectionpool.connectionpool]\u001b[0m\n"
]
}
],
"source": [
"#在台面上找到料盘和极片\n",
"liaopan1 = deck.get_resource(\"liaopan1\")\n",
"liaopan2 = deck.get_resource(\"liaopan2\")\n",
"jipian1 = liaopan1.children[1].children[0]\n",
"#\n",
"print(jipian1)\n",
"#把物料解绑后放到另一盘上\n",
"jipian1.parent.unassign_child_resource(jipian1)\n",
"liaopan2.children[1].assign_child_resource(jipian1, location=None)\n",
"#print(jipian2.parent)\n",
"upload_resources_to_unilab([deck])"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "43736700",
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"\u001b[37m25-09-22 [14:31:50,027]\u001b[0m \u001b[1;36m[DEBUG]\u001b[0m \u001b[37mStarting new HTTPS connection (1): uni-lab.test.bohrium.com:443\u001b[37m [_new_conn:1049] [urllib3.connectionpool.connectionpool]\u001b[0m\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"转换pylabrobot的时候出现未知类型 electrode_sheet\n",
"转换pylabrobot的时候出现未知类型 material_hole\n",
"转换pylabrobot的时候出现未知类型 material_hole\n",
"转换pylabrobot的时候出现未知类型 electrode_sheet\n",
"转换pylabrobot的时候出现未知类型 material_hole\n",
"转换pylabrobot的时候出现未知类型 electrode_sheet\n",
"转换pylabrobot的时候出现未知类型 material_hole\n",
"转换pylabrobot的时候出现未知类型 electrode_sheet\n",
"转换pylabrobot的时候出现未知类型 material_hole\n",
"转换pylabrobot的时候出现未知类型 electrode_sheet\n",
"转换pylabrobot的时候出现未知类型 material_hole\n",
"转换pylabrobot的时候出现未知类型 electrode_sheet\n",
"转换pylabrobot的时候出现未知类型 material_hole\n",
"转换pylabrobot的时候出现未知类型 electrode_sheet\n",
"转换pylabrobot的时候出现未知类型 material_hole\n",
"转换pylabrobot的时候出现未知类型 electrode_sheet\n",
"转换pylabrobot的时候出现未知类型 material_hole\n",
"转换pylabrobot的时候出现未知类型 electrode_sheet\n",
"转换pylabrobot的时候出现未知类型 material_hole\n",
"转换pylabrobot的时候出现未知类型 electrode_sheet\n",
"转换pylabrobot的时候出现未知类型 material_hole\n",
"转换pylabrobot的时候出现未知类型 electrode_sheet\n",
"转换pylabrobot的时候出现未知类型 material_hole\n",
"转换pylabrobot的时候出现未知类型 electrode_sheet\n",
"转换pylabrobot的时候出现未知类型 material_hole\n",
"转换pylabrobot的时候出现未知类型 electrode_sheet\n",
"转换pylabrobot的时候出现未知类型 material_hole\n",
"转换pylabrobot的时候出现未知类型 electrode_sheet\n",
"转换pylabrobot的时候出现未知类型 material_hole\n",
"转换pylabrobot的时候出现未知类型 electrode_sheet\n",
"转换pylabrobot的时候出现未知类型 material_hole\n",
"转换pylabrobot的时候出现未知类型 material_plate\n",
"转换pylabrobot的时候出现未知类型 material_hole\n",
"转换pylabrobot的时候出现未知类型 electrode_sheet\n",
"转换pylabrobot的时候出现未知类型 material_hole\n",
"转换pylabrobot的时候出现未知类型 material_hole\n",
"转换pylabrobot的时候出现未知类型 material_hole\n",
"转换pylabrobot的时候出现未知类型 material_hole\n",
"转换pylabrobot的时候出现未知类型 material_hole\n",
"转换pylabrobot的时候出现未知类型 material_hole\n",
"转换pylabrobot的时候出现未知类型 material_hole\n",
"转换pylabrobot的时候出现未知类型 material_hole\n",
"转换pylabrobot的时候出现未知类型 material_hole\n",
"转换pylabrobot的时候出现未知类型 material_hole\n",
"转换pylabrobot的时候出现未知类型 material_hole\n",
"转换pylabrobot的时候出现未知类型 material_hole\n",
"转换pylabrobot的时候出现未知类型 material_hole\n",
"转换pylabrobot的时候出现未知类型 material_hole\n",
"转换pylabrobot的时候出现未知类型 material_hole\n",
"转换pylabrobot的时候出现未知类型 material_plate\n",
"转换pylabrobot的时候出现未知类型 coin_cell_deck\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
"\u001b[37m25-09-22 [14:31:50,358]\u001b[0m \u001b[1;36m[DEBUG]\u001b[0m \u001b[37mhttps://uni-lab.test.bohrium.com:443 \"POST /api/v1/lab/material HTTP/1.1\" 200 10\u001b[37m [_make_request:544] [urllib3.connectionpool.connectionpool]\u001b[0m\n"
]
},
{
"data": {
"text/plain": [
"<Response [200]>"
]
},
"execution_count": 16,
"metadata": {},
"output_type": "execute_result"
}
],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "unilab",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.11"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -171,6 +171,7 @@ class WorkstationBase(ABC):
def post_init(self, ros_node: ROS2WorkstationNode) -> None: def post_init(self, ros_node: ROS2WorkstationNode) -> None:
# 初始化物料系统 # 初始化物料系统
self._ros_node = ros_node self._ros_node = ros_node
self._ros_node.update_resource([self.deck])
def _build_resource_mappings(self, deck: Deck): def _build_resource_mappings(self, deck: Deck):
"""递归构建资源映射""" """递归构建资源映射"""

View File

@@ -1,255 +0,0 @@
workstation.bioyond_dispensing_station:
category:
- workstation
- bioyond
class:
action_value_mappings:
create_90_10_vial_feeding_task:
feedback: {}
goal:
delay_time: delay_time
hold_m_name: hold_m_name
order_name: order_name
percent_10_1_assign_material_name: percent_10_1_assign_material_name
percent_10_1_liquid_material_name: percent_10_1_liquid_material_name
percent_10_1_target_weigh: percent_10_1_target_weigh
percent_10_1_volume: percent_10_1_volume
percent_10_2_assign_material_name: percent_10_2_assign_material_name
percent_10_2_liquid_material_name: percent_10_2_liquid_material_name
percent_10_2_target_weigh: percent_10_2_target_weigh
percent_10_2_volume: percent_10_2_volume
percent_10_3_assign_material_name: percent_10_3_assign_material_name
percent_10_3_liquid_material_name: percent_10_3_liquid_material_name
percent_10_3_target_weigh: percent_10_3_target_weigh
percent_10_3_volume: percent_10_3_volume
percent_90_1_assign_material_name: percent_90_1_assign_material_name
percent_90_1_target_weigh: percent_90_1_target_weigh
percent_90_2_assign_material_name: percent_90_2_assign_material_name
percent_90_2_target_weigh: percent_90_2_target_weigh
percent_90_3_assign_material_name: percent_90_3_assign_material_name
percent_90_3_target_weigh: percent_90_3_target_weigh
speed: speed
temperature: temperature
goal_default:
delay_time: ''
hold_m_name: ''
order_name: ''
percent_10_1_assign_material_name: ''
percent_10_1_liquid_material_name: ''
percent_10_1_target_weigh: ''
percent_10_1_volume: ''
percent_10_2_assign_material_name: ''
percent_10_2_liquid_material_name: ''
percent_10_2_target_weigh: ''
percent_10_2_volume: ''
percent_10_3_assign_material_name: ''
percent_10_3_liquid_material_name: ''
percent_10_3_target_weigh: ''
percent_10_3_volume: ''
percent_90_1_assign_material_name: ''
percent_90_1_target_weigh: ''
percent_90_2_assign_material_name: ''
percent_90_2_target_weigh: ''
percent_90_3_assign_material_name: ''
percent_90_3_target_weigh: ''
speed: ''
temperature: ''
handles: {}
result:
return_info: return_info
schema:
description: ''
properties:
feedback:
properties: {}
required: []
title: DispenStationVialFeed_Feedback
type: object
goal:
properties:
delay_time:
type: string
hold_m_name:
type: string
order_name:
type: string
percent_10_1_assign_material_name:
type: string
percent_10_1_liquid_material_name:
type: string
percent_10_1_target_weigh:
type: string
percent_10_1_volume:
type: string
percent_10_2_assign_material_name:
type: string
percent_10_2_liquid_material_name:
type: string
percent_10_2_target_weigh:
type: string
percent_10_2_volume:
type: string
percent_10_3_assign_material_name:
type: string
percent_10_3_liquid_material_name:
type: string
percent_10_3_target_weigh:
type: string
percent_10_3_volume:
type: string
percent_90_1_assign_material_name:
type: string
percent_90_1_target_weigh:
type: string
percent_90_2_assign_material_name:
type: string
percent_90_2_target_weigh:
type: string
percent_90_3_assign_material_name:
type: string
percent_90_3_target_weigh:
type: string
speed:
type: string
temperature:
type: string
required:
- order_name
- percent_90_1_assign_material_name
- percent_90_1_target_weigh
- percent_90_2_assign_material_name
- percent_90_2_target_weigh
- percent_90_3_assign_material_name
- percent_90_3_target_weigh
- percent_10_1_assign_material_name
- percent_10_1_target_weigh
- percent_10_1_volume
- percent_10_1_liquid_material_name
- percent_10_2_assign_material_name
- percent_10_2_target_weigh
- percent_10_2_volume
- percent_10_2_liquid_material_name
- percent_10_3_assign_material_name
- percent_10_3_target_weigh
- percent_10_3_volume
- percent_10_3_liquid_material_name
- speed
- temperature
- delay_time
- hold_m_name
title: DispenStationVialFeed_Goal
type: object
result:
properties:
return_info:
type: string
required:
- return_info
title: DispenStationVialFeed_Result
type: object
required:
- goal
title: DispenStationVialFeed
type: object
type: DispenStationVialFeed
create_diamine_solution_task:
feedback: {}
goal:
delay_time: delay_time
hold_m_name: hold_m_name
liquid_material_name: liquid_material_name
material_name: material_name
order_name: order_name
speed: speed
target_weigh: target_weigh
temperature: temperature
volume: volume
goal_default:
delay_time: ''
hold_m_name: ''
liquid_material_name: ''
material_name: ''
order_name: ''
speed: ''
target_weigh: ''
temperature: ''
volume: ''
handles: {}
result:
return_info: return_info
schema:
description: ''
properties:
feedback:
properties: {}
required: []
title: DispenStationSolnPrep_Feedback
type: object
goal:
properties:
delay_time:
type: string
hold_m_name:
type: string
liquid_material_name:
type: string
material_name:
type: string
order_name:
type: string
speed:
type: string
target_weigh:
type: string
temperature:
type: string
volume:
type: string
required:
- order_name
- material_name
- target_weigh
- volume
- liquid_material_name
- speed
- temperature
- delay_time
- hold_m_name
title: DispenStationSolnPrep_Goal
type: object
result:
properties:
return_info:
type: string
required:
- return_info
title: DispenStationSolnPrep_Result
type: object
required:
- goal
title: DispenStationSolnPrep
type: object
type: DispenStationSolnPrep
module: unilabos.devices.workstation.bioyond_studio.dispensing_station:BioyondDispensingStation
status_types: {}
type: python
config_info: []
description: ''
handles: []
icon: ''
init_param_schema:
config:
properties:
config:
type: string
deck:
type: string
required:
- config
- deck
type: object
data:
properties: {}
required: []
type: object
version: 1.0.0

View File

@@ -1,834 +0,0 @@
bioyond_cell:
category:
- bioyond_cell
class:
action_value_mappings:
auto-auto_batch_outbound_from_xlsx:
feedback: {}
goal: {}
goal_default:
xlsx_path: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: ''
properties:
feedback: {}
goal:
properties:
xlsx_path:
type: string
required:
- xlsx_path
type: object
result: {}
required:
- goal
title: auto_batch_outbound_from_xlsx参数
type: object
type: UniLabJsonCommand
auto-auto_feeding4to3:
feedback: {}
goal: {}
goal_default:
WH3_x1_y1_z3_1_materialId: ''
WH3_x1_y1_z3_1_materialType: ''
WH3_x1_y1_z3_1_quantity: 0
WH3_x1_y2_z3_4_materialId: ''
WH3_x1_y2_z3_4_materialType: ''
WH3_x1_y2_z3_4_quantity: 0
WH3_x1_y3_z3_7_materialId: ''
WH3_x1_y3_z3_7_materialType: ''
WH3_x1_y3_z3_7_quantity: 0
WH3_x1_y4_z3_10_materialId: ''
WH3_x1_y4_z3_10_materialType: ''
WH3_x1_y4_z3_10_quantity: 0
WH3_x1_y5_z3_13_materialId: ''
WH3_x1_y5_z3_13_materialType: ''
WH3_x1_y5_z3_13_quantity: 0
WH3_x2_y1_z3_2_materialId: ''
WH3_x2_y1_z3_2_materialType: ''
WH3_x2_y1_z3_2_quantity: 0
WH3_x2_y2_z3_5_materialId: ''
WH3_x2_y2_z3_5_materialType: ''
WH3_x2_y2_z3_5_quantity: 0
WH3_x2_y3_z3_8_materialId: ''
WH3_x2_y3_z3_8_materialType: ''
WH3_x2_y3_z3_8_quantity: 0
WH3_x2_y4_z3_11_materialId: ''
WH3_x2_y4_z3_11_materialType: ''
WH3_x2_y4_z3_11_quantity: 0
WH3_x2_y5_z3_14_materialId: ''
WH3_x2_y5_z3_14_materialType: ''
WH3_x2_y5_z3_14_quantity: 0
WH3_x3_y1_z3_3_materialId: ''
WH3_x3_y1_z3_3_materialType: ''
WH3_x3_y1_z3_3_quantity: 0
WH3_x3_y2_z3_6_materialId: ''
WH3_x3_y2_z3_6_materialType: ''
WH3_x3_y2_z3_6_quantity: 0
WH3_x3_y3_z3_9_materialId: ''
WH3_x3_y3_z3_9_materialType: ''
WH3_x3_y3_z3_9_quantity: 0
WH3_x3_y4_z3_12_materialId: ''
WH3_x3_y4_z3_12_materialType: ''
WH3_x3_y4_z3_12_quantity: 0
WH3_x3_y5_z3_15_materialId: ''
WH3_x3_y5_z3_15_materialType: ''
WH3_x3_y5_z3_15_quantity: 0
WH4_x1_y1_z1_1_materialName: ''
WH4_x1_y1_z1_1_quantity: 0.0
WH4_x1_y1_z2_1_materialName: ''
WH4_x1_y1_z2_1_materialType: ''
WH4_x1_y1_z2_1_quantity: 0.0
WH4_x1_y1_z2_1_targetWH: ''
WH4_x1_y2_z1_6_materialName: ''
WH4_x1_y2_z1_6_quantity: 0.0
WH4_x1_y2_z2_4_materialName: ''
WH4_x1_y2_z2_4_materialType: ''
WH4_x1_y2_z2_4_quantity: 0.0
WH4_x1_y2_z2_4_targetWH: ''
WH4_x1_y3_z1_11_materialName: ''
WH4_x1_y3_z1_11_quantity: 0.0
WH4_x1_y3_z2_7_materialName: ''
WH4_x1_y3_z2_7_materialType: ''
WH4_x1_y3_z2_7_quantity: 0.0
WH4_x1_y3_z2_7_targetWH: ''
WH4_x2_y1_z1_2_materialName: ''
WH4_x2_y1_z1_2_quantity: 0.0
WH4_x2_y1_z2_2_materialName: ''
WH4_x2_y1_z2_2_materialType: ''
WH4_x2_y1_z2_2_quantity: 0.0
WH4_x2_y1_z2_2_targetWH: ''
WH4_x2_y2_z1_7_materialName: ''
WH4_x2_y2_z1_7_quantity: 0.0
WH4_x2_y2_z2_5_materialName: ''
WH4_x2_y2_z2_5_materialType: ''
WH4_x2_y2_z2_5_quantity: 0.0
WH4_x2_y2_z2_5_targetWH: ''
WH4_x2_y3_z1_12_materialName: ''
WH4_x2_y3_z1_12_quantity: 0.0
WH4_x2_y3_z2_8_materialName: ''
WH4_x2_y3_z2_8_materialType: ''
WH4_x2_y3_z2_8_quantity: 0.0
WH4_x2_y3_z2_8_targetWH: ''
WH4_x3_y1_z1_3_materialName: ''
WH4_x3_y1_z1_3_quantity: 0.0
WH4_x3_y1_z2_3_materialName: ''
WH4_x3_y1_z2_3_materialType: ''
WH4_x3_y1_z2_3_quantity: 0.0
WH4_x3_y1_z2_3_targetWH: ''
WH4_x3_y2_z1_8_materialName: ''
WH4_x3_y2_z1_8_quantity: 0.0
WH4_x3_y2_z2_6_materialName: ''
WH4_x3_y2_z2_6_materialType: ''
WH4_x3_y2_z2_6_quantity: 0.0
WH4_x3_y2_z2_6_targetWH: ''
WH4_x3_y3_z2_9_materialName: ''
WH4_x3_y3_z2_9_materialType: ''
WH4_x3_y3_z2_9_quantity: 0.0
WH4_x3_y3_z2_9_targetWH: ''
WH4_x4_y1_z1_4_materialName: ''
WH4_x4_y1_z1_4_quantity: 0.0
WH4_x4_y2_z1_9_materialName: ''
WH4_x4_y2_z1_9_quantity: 0.0
WH4_x5_y1_z1_5_materialName: ''
WH4_x5_y1_z1_5_quantity: 0.0
WH4_x5_y2_z1_10_materialName: ''
WH4_x5_y2_z1_10_quantity: 0.0
xlsx_path: unilabos/devices/workstation/bioyond_cell/样品导入模板.xlsx
handles: {}
placeholder_keys: {}
result: {}
schema:
description: ''
properties:
feedback: {}
goal:
properties:
WH3_x1_y1_z3_1_materialId:
default: ''
type: string
WH3_x1_y1_z3_1_materialType:
default: ''
type: string
WH3_x1_y1_z3_1_quantity:
default: 0
type: number
WH3_x1_y2_z3_4_materialId:
default: ''
type: string
WH3_x1_y2_z3_4_materialType:
default: ''
type: string
WH3_x1_y2_z3_4_quantity:
default: 0
type: number
WH3_x1_y3_z3_7_materialId:
default: ''
type: string
WH3_x1_y3_z3_7_materialType:
default: ''
type: string
WH3_x1_y3_z3_7_quantity:
default: 0
type: number
WH3_x1_y4_z3_10_materialId:
default: ''
type: string
WH3_x1_y4_z3_10_materialType:
default: ''
type: string
WH3_x1_y4_z3_10_quantity:
default: 0
type: number
WH3_x1_y5_z3_13_materialId:
default: ''
type: string
WH3_x1_y5_z3_13_materialType:
default: ''
type: string
WH3_x1_y5_z3_13_quantity:
default: 0
type: number
WH3_x2_y1_z3_2_materialId:
default: ''
type: string
WH3_x2_y1_z3_2_materialType:
default: ''
type: string
WH3_x2_y1_z3_2_quantity:
default: 0
type: number
WH3_x2_y2_z3_5_materialId:
default: ''
type: string
WH3_x2_y2_z3_5_materialType:
default: ''
type: string
WH3_x2_y2_z3_5_quantity:
default: 0
type: number
WH3_x2_y3_z3_8_materialId:
default: ''
type: string
WH3_x2_y3_z3_8_materialType:
default: ''
type: string
WH3_x2_y3_z3_8_quantity:
default: 0
type: number
WH3_x2_y4_z3_11_materialId:
default: ''
type: string
WH3_x2_y4_z3_11_materialType:
default: ''
type: string
WH3_x2_y4_z3_11_quantity:
default: 0
type: number
WH3_x2_y5_z3_14_materialId:
default: ''
type: string
WH3_x2_y5_z3_14_materialType:
default: ''
type: string
WH3_x2_y5_z3_14_quantity:
default: 0
type: number
WH3_x3_y1_z3_3_materialId:
default: ''
type: string
WH3_x3_y1_z3_3_materialType:
default: ''
type: string
WH3_x3_y1_z3_3_quantity:
default: 0
type: number
WH3_x3_y2_z3_6_materialId:
default: ''
type: string
WH3_x3_y2_z3_6_materialType:
default: ''
type: string
WH3_x3_y2_z3_6_quantity:
default: 0
type: number
WH3_x3_y3_z3_9_materialId:
default: ''
type: string
WH3_x3_y3_z3_9_materialType:
default: ''
type: string
WH3_x3_y3_z3_9_quantity:
default: 0
type: number
WH3_x3_y4_z3_12_materialId:
default: ''
type: string
WH3_x3_y4_z3_12_materialType:
default: ''
type: string
WH3_x3_y4_z3_12_quantity:
default: 0
type: number
WH3_x3_y5_z3_15_materialId:
default: ''
type: string
WH3_x3_y5_z3_15_materialType:
default: ''
type: string
WH3_x3_y5_z3_15_quantity:
default: 0
type: number
WH4_x1_y1_z1_1_materialName:
default: ''
type: string
WH4_x1_y1_z1_1_quantity:
default: 0.0
type: number
WH4_x1_y1_z2_1_materialName:
default: ''
type: string
WH4_x1_y1_z2_1_materialType:
default: ''
type: string
WH4_x1_y1_z2_1_quantity:
default: 0.0
type: number
WH4_x1_y1_z2_1_targetWH:
default: ''
type: string
WH4_x1_y2_z1_6_materialName:
default: ''
type: string
WH4_x1_y2_z1_6_quantity:
default: 0.0
type: number
WH4_x1_y2_z2_4_materialName:
default: ''
type: string
WH4_x1_y2_z2_4_materialType:
default: ''
type: string
WH4_x1_y2_z2_4_quantity:
default: 0.0
type: number
WH4_x1_y2_z2_4_targetWH:
default: ''
type: string
WH4_x1_y3_z1_11_materialName:
default: ''
type: string
WH4_x1_y3_z1_11_quantity:
default: 0.0
type: number
WH4_x1_y3_z2_7_materialName:
default: ''
type: string
WH4_x1_y3_z2_7_materialType:
default: ''
type: string
WH4_x1_y3_z2_7_quantity:
default: 0.0
type: number
WH4_x1_y3_z2_7_targetWH:
default: ''
type: string
WH4_x2_y1_z1_2_materialName:
default: ''
type: string
WH4_x2_y1_z1_2_quantity:
default: 0.0
type: number
WH4_x2_y1_z2_2_materialName:
default: ''
type: string
WH4_x2_y1_z2_2_materialType:
default: ''
type: string
WH4_x2_y1_z2_2_quantity:
default: 0.0
type: number
WH4_x2_y1_z2_2_targetWH:
default: ''
type: string
WH4_x2_y2_z1_7_materialName:
default: ''
type: string
WH4_x2_y2_z1_7_quantity:
default: 0.0
type: number
WH4_x2_y2_z2_5_materialName:
default: ''
type: string
WH4_x2_y2_z2_5_materialType:
default: ''
type: string
WH4_x2_y2_z2_5_quantity:
default: 0.0
type: number
WH4_x2_y2_z2_5_targetWH:
default: ''
type: string
WH4_x2_y3_z1_12_materialName:
default: ''
type: string
WH4_x2_y3_z1_12_quantity:
default: 0.0
type: number
WH4_x2_y3_z2_8_materialName:
default: ''
type: string
WH4_x2_y3_z2_8_materialType:
default: ''
type: string
WH4_x2_y3_z2_8_quantity:
default: 0.0
type: number
WH4_x2_y3_z2_8_targetWH:
default: ''
type: string
WH4_x3_y1_z1_3_materialName:
default: ''
type: string
WH4_x3_y1_z1_3_quantity:
default: 0.0
type: number
WH4_x3_y1_z2_3_materialName:
default: ''
type: string
WH4_x3_y1_z2_3_materialType:
default: ''
type: string
WH4_x3_y1_z2_3_quantity:
default: 0.0
type: number
WH4_x3_y1_z2_3_targetWH:
default: ''
type: string
WH4_x3_y2_z1_8_materialName:
default: ''
type: string
WH4_x3_y2_z1_8_quantity:
default: 0.0
type: number
WH4_x3_y2_z2_6_materialName:
default: ''
type: string
WH4_x3_y2_z2_6_materialType:
default: ''
type: string
WH4_x3_y2_z2_6_quantity:
default: 0.0
type: number
WH4_x3_y2_z2_6_targetWH:
default: ''
type: string
WH4_x3_y3_z2_9_materialName:
default: ''
type: string
WH4_x3_y3_z2_9_materialType:
default: ''
type: string
WH4_x3_y3_z2_9_quantity:
default: 0.0
type: number
WH4_x3_y3_z2_9_targetWH:
default: ''
type: string
WH4_x4_y1_z1_4_materialName:
default: ''
type: string
WH4_x4_y1_z1_4_quantity:
default: 0.0
type: number
WH4_x4_y2_z1_9_materialName:
default: ''
type: string
WH4_x4_y2_z1_9_quantity:
default: 0.0
type: number
WH4_x5_y1_z1_5_materialName:
default: ''
type: string
WH4_x5_y1_z1_5_quantity:
default: 0.0
type: number
WH4_x5_y2_z1_10_materialName:
default: ''
type: string
WH4_x5_y2_z1_10_quantity:
default: 0.0
type: number
xlsx_path:
default: unilabos/devices/workstation/bioyond_cell/样品导入模板.xlsx
type: string
required: []
type: object
result: {}
required:
- goal
title: auto_feeding4to3参数
type: object
type: UniLabJsonCommand
auto-auto_feeding4to3_from_xlsx:
feedback: {}
goal: {}
goal_default:
xlsx_path: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: ''
properties:
feedback: {}
goal:
properties:
xlsx_path:
type: string
required:
- xlsx_path
type: object
result: {}
required:
- goal
title: auto_feeding4to3_from_xlsx参数
type: object
type: UniLabJsonCommand
auto-create_orders:
feedback: {}
goal: {}
goal_default:
xlsx_path: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: ''
properties:
feedback: {}
goal:
properties:
xlsx_path:
type: string
required:
- xlsx_path
type: object
result: {}
required:
- goal
title: create_orders参数
type: object
type: UniLabJsonCommand
auto-order_list_v2:
feedback: {}
goal: {}
goal_default:
beginTime: ''
endTime: ''
filter: ''
pageCount: 1
skipCount: 0
sorting: ''
status: ''
timeType: ''
handles: {}
placeholder_keys: {}
result: {}
schema:
description: ''
properties:
feedback: {}
goal:
properties:
beginTime:
default: ''
type: string
endTime:
default: ''
type: string
filter:
default: ''
type: string
pageCount:
default: 1
type: integer
skipCount:
default: 0
type: integer
sorting:
default: ''
type: string
status:
default: ''
type: string
timeType:
default: ''
type: string
required: []
type: object
result: {}
required:
- goal
title: order_list_v2参数
type: object
type: UniLabJsonCommand
auto-report_material_change:
feedback: {}
goal: {}
goal_default:
material_obj: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: ''
properties:
feedback: {}
goal:
properties:
material_obj:
type: object
required:
- material_obj
type: object
result: {}
required:
- goal
title: report_material_change参数
type: object
type: UniLabJsonCommand
auto-scheduler_continue:
feedback: {}
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: ''
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: scheduler_continue参数
type: object
type: UniLabJsonCommand
auto-scheduler_start:
feedback: {}
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: ''
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: scheduler_start参数
type: object
type: UniLabJsonCommand
auto-scheduler_stop:
feedback: {}
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: ''
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: scheduler_stop参数
type: object
type: UniLabJsonCommand
auto-storage_batch_inbound:
feedback: {}
goal: {}
goal_default:
items: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: ''
properties:
feedback: {}
goal:
properties:
items:
items:
type: object
type: array
required:
- items
type: object
result: {}
required:
- goal
title: storage_batch_inbound参数
type: object
type: UniLabJsonCommand
auto-storage_inbound:
feedback: {}
goal: {}
goal_default:
location_id: null
material_id: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: ''
properties:
feedback: {}
goal:
properties:
location_id:
type: string
material_id:
type: string
required:
- material_id
- location_id
type: object
result: {}
required:
- goal
title: storage_inbound参数
type: object
type: UniLabJsonCommand
auto-transfer_1_to_2:
feedback: {}
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: ''
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: transfer_1_to_2参数
type: object
type: UniLabJsonCommand
auto-transfer_3_to_2_to_1:
feedback: {}
goal: {}
goal_default:
source_wh_id: 3a19debc-84b4-0359-e2d4-b3beea49348b
source_x: 1
source_y: 1
source_z: 1
handles: {}
placeholder_keys: {}
result: {}
schema:
description: ''
properties:
feedback: {}
goal:
properties:
source_wh_id:
default: 3a19debc-84b4-0359-e2d4-b3beea49348b
type: string
source_x:
default: 1
type: integer
source_y:
default: 1
type: integer
source_z:
default: 1
type: integer
required: []
type: object
result: {}
required:
- goal
title: transfer_3_to_2_to_1参数
type: object
type: UniLabJsonCommand
auto-wait_for_transfer_task:
feedback: {}
goal: {}
goal_default:
filter_text: null
interval: 5
timeout: 3000
handles: {}
placeholder_keys: {}
result: {}
schema:
description: ''
properties:
feedback: {}
goal:
properties:
filter_text:
type: string
interval:
default: 5
type: integer
timeout:
default: 3000
type: integer
required: []
type: object
result: {}
required:
- goal
title: wait_for_transfer_task参数
type: object
type: UniLabJsonCommand
module: unilabos.devices.workstation.bioyond_studio.bioyond_cell.bioyond_cell_workstation:BioyondCellWorkstation
status_types: {}
type: python
config_info: []
description: ''
handles: []
icon: ''
init_param_schema:
config:
properties:
bioyond_config:
type: string
station_resource:
type: string
required: []
type: object
data:
properties: {}
required: []
type: object
registry_type: device
version: 1.0.0

View File

@@ -1,3 +1,231 @@
hplc.agilent:
category:
- characterization_chromatic
class:
action_value_mappings:
auto-check_status:
feedback: {}
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: 检查安捷伦HPLC设备状态的函数。用于监控设备的运行状态、连接状态、错误信息等关键指标。该函数定期查询设备状态确保系统稳定运行及时发现和报告设备异常。适用于自动化流程中的设备监控、故障诊断、系统维护等场景。
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: check_status参数
type: object
type: UniLabJsonCommand
auto-extract_data_from_txt:
feedback: {}
goal: {}
goal_default:
file_path: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: 从文本文件中提取分析数据的函数。用于解析安捷伦HPLC生成的结果文件提取峰面积、保留时间、浓度等关键分析数据。支持多种文件格式的自动识别和数据结构化处理为后续数据分析和报告生成提供标准化的数据格式。适用于批量数据处理、结果验证、质量控制等分析工作流程。
properties:
feedback: {}
goal:
properties:
file_path:
type: string
required:
- file_path
type: object
result: {}
required:
- goal
title: extract_data_from_txt参数
type: object
type: UniLabJsonCommand
auto-start_sequence:
feedback: {}
goal: {}
goal_default:
params: null
resource: null
wf_name: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: 启动安捷伦HPLC分析序列的函数。用于执行预定义的分析方法序列包括样品进样、色谱分离、检测等完整的分析流程。支持参数配置、资源分配、工作流程管理等功能实现全自动的样品分析。适用于批量样品处理、标准化分析、质量检测等需要连续自动分析的应用场景。
properties:
feedback: {}
goal:
properties:
params:
type: string
resource:
type: object
wf_name:
type: string
required:
- wf_name
type: object
result: {}
required:
- goal
title: start_sequence参数
type: object
type: UniLabJsonCommand
auto-try_close_sub_device:
feedback: {}
goal: {}
goal_default:
device_name: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: 尝试关闭HPLC子设备的函数。用于安全地关闭泵、检测器、进样器等各个子模块确保设备正常断开连接并保护硬件安全。该函数提供错误处理和状态确认机制避免强制关闭可能造成的设备损坏。适用于设备维护、系统重启、紧急停机等需要安全关闭设备的场景。
properties:
feedback: {}
goal:
properties:
device_name:
type: string
required: []
type: object
result: {}
required:
- goal
title: try_close_sub_device参数
type: object
type: UniLabJsonCommand
auto-try_open_sub_device:
feedback: {}
goal: {}
goal_default:
device_name: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: 尝试打开HPLC子设备的函数。用于初始化和连接泵、检测器、进样器等各个子模块建立设备通信并进行自检。该函数提供连接验证和错误恢复机制确保子设备正常启动并准备就绪。适用于设备初始化、系统启动、设备重连等需要建立设备连接的场景。
properties:
feedback: {}
goal:
properties:
device_name:
type: string
required: []
type: object
result: {}
required:
- goal
title: try_open_sub_device参数
type: object
type: UniLabJsonCommand
execute_command_from_outer:
feedback: {}
goal:
command: command
goal_default:
command: ''
handles: {}
result:
success: success
schema:
description: ''
properties:
feedback:
properties:
status:
type: string
required:
- status
title: SendCmd_Feedback
type: object
goal:
properties:
command:
type: string
required:
- command
title: SendCmd_Goal
type: object
result:
properties:
return_info:
type: string
success:
type: boolean
required:
- return_info
- success
title: SendCmd_Result
type: object
required:
- goal
title: SendCmd
type: object
type: SendCmd
module: unilabos.devices.hplc.AgilentHPLC:HPLCDriver
status_types:
could_run: bool
data_file: String
device_status: str
driver_init_ok: bool
finish_status: str
is_running: bool
status_text: str
success: bool
type: python
config_info: []
description: 安捷伦高效液相色谱HPLC分析设备用于复杂化合物的分离、检测和定量分析。该设备通过UI自动化技术控制安捷伦ChemStation软件实现全自动的样品分析流程。具备序列启动、设备状态监控、数据文件提取、结果处理等功能。支持多样品批量处理和实时状态反馈适用于药物分析、环境检测、食品安全、化学研究等需要高精度色谱分析的实验室应用。
handles: []
icon: ''
init_param_schema:
config:
properties:
driver_debug:
default: false
type: string
required: []
type: object
data:
properties:
could_run:
type: boolean
data_file:
items:
type: string
type: array
device_status:
type: string
driver_init_ok:
type: boolean
finish_status:
type: string
is_running:
type: boolean
status_text:
type: string
success:
type: boolean
required:
- status_text
- device_status
- could_run
- driver_init_ok
- is_running
- success
- finish_status
- data_file
type: object
version: 1.0.0
hplc.agilent-zhida: hplc.agilent-zhida:
category: category:
- characterization_chromatic - characterization_chromatic

View File

@@ -1 +1,194 @@
{} raman.home_made:
category:
- characterization_optic
class:
action_value_mappings:
auto-ccd_time:
feedback: {}
goal: {}
goal_default:
int_time: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: 设置CCD检测器积分时间的函数。用于配置拉曼光谱仪的信号采集时间控制光谱数据的质量和信噪比。较长的积分时间可获得更高的信号强度和更好的光谱质量但会增加测量时间。该函数允许根据样品特性和测量要求动态调整检测参数优化测量效果。
properties:
feedback: {}
goal:
properties:
int_time:
type: string
required:
- int_time
type: object
result: {}
required:
- goal
title: ccd_time参数
type: object
type: UniLabJsonCommand
auto-laser_on_power:
feedback: {}
goal: {}
goal_default:
output_voltage_laser: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: 设置激光器输出功率的函数。用于控制拉曼光谱仪激光器的功率输出,调节激光强度以适应不同样品的测量需求。适当的激光功率能够获得良好的拉曼信号同时避免样品损伤。该函数支持精确的功率控制,确保测量结果的稳定性和重现性。
properties:
feedback: {}
goal:
properties:
output_voltage_laser:
type: string
required:
- output_voltage_laser
type: object
result: {}
required:
- goal
title: laser_on_power参数
type: object
type: UniLabJsonCommand
auto-raman_without_background:
feedback: {}
goal: {}
goal_default:
int_time: null
laser_power: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: 执行无背景扣除的拉曼光谱测量函数。用于直接采集样品的拉曼光谱信号,不进行背景校正处理。该函数配置积分时间和激光功率参数,获取原始光谱数据用于后续的数据处理分析。适用于对光谱数据质量要求较高或需要自定义背景处理流程的测量场景。
properties:
feedback: {}
goal:
properties:
int_time:
type: string
laser_power:
type: string
required:
- int_time
- laser_power
type: object
result: {}
required:
- goal
title: raman_without_background参数
type: object
type: UniLabJsonCommand
auto-raman_without_background_average:
feedback: {}
goal: {}
goal_default:
average: null
int_time: null
laser_power: null
sample_name: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: 执行多次平均的无背景拉曼光谱测量函数。通过多次测量取平均值来提高光谱数据的信噪比和测量精度,减少随机噪声影响。该函数支持自定义平均次数、积分时间、激光功率等参数,并可为样品指定名称便于数据管理。适用于对测量精度要求较高的定量分析和研究应用。
properties:
feedback: {}
goal:
properties:
average:
type: string
int_time:
type: string
laser_power:
type: string
sample_name:
type: string
required:
- sample_name
- int_time
- laser_power
- average
type: object
result: {}
required:
- goal
title: raman_without_background_average参数
type: object
type: UniLabJsonCommand
raman_cmd:
feedback: {}
goal:
command: command
goal_default:
command: ''
handles: {}
result:
success: success
schema:
description: ''
properties:
feedback:
properties:
status:
type: string
required:
- status
title: SendCmd_Feedback
type: object
goal:
properties:
command:
type: string
required:
- command
title: SendCmd_Goal
type: object
result:
properties:
return_info:
type: string
success:
type: boolean
required:
- return_info
- success
title: SendCmd_Result
type: object
required:
- goal
title: SendCmd
type: object
type: SendCmd
module: unilabos.devices.raman_uv.home_made_raman:RamanObj
status_types: {}
type: python
config_info: []
description: 拉曼光谱分析设备用于物质的分子结构和化学成分表征。该设备集成激光器和CCD检测器通过串口通信控制激光功率和光谱采集。具备背景扣除、多次平均、自动数据处理等功能支持高精度的拉曼光谱测量。适用于材料表征、化学分析、质量控制、研究开发等需要分子指纹识别和结构分析的实验应用。
handles: []
icon: ''
init_param_schema:
config:
properties:
baudrate_ccd:
default: 921600
type: string
baudrate_laser:
default: 9600
type: string
port_ccd:
type: string
port_laser:
type: string
required:
- port_laser
- port_ccd
type: object
data:
properties: {}
required: []
type: object
version: 1.0.0

File diff suppressed because it is too large Load Diff

View File

@@ -1361,7 +1361,8 @@ laiyu_liquid:
mix_liquid_height: 0.0 mix_liquid_height: 0.0
mix_rate: 0 mix_rate: 0
mix_stage: '' mix_stage: ''
mix_times: 0 mix_times:
- 0
mix_vol: 0 mix_vol: 0
none_keys: none_keys:
- '' - ''
@@ -1491,9 +1492,11 @@ laiyu_liquid:
mix_stage: mix_stage:
type: string type: string
mix_times: mix_times:
items:
maximum: 2147483647 maximum: 2147483647
minimum: -2147483648 minimum: -2147483648
type: integer type: integer
type: array
mix_vol: mix_vol:
maximum: 2147483647 maximum: 2147483647
minimum: -2147483648 minimum: -2147483648

View File

@@ -3994,7 +3994,8 @@ liquid_handler:
mix_liquid_height: 0.0 mix_liquid_height: 0.0
mix_rate: 0 mix_rate: 0
mix_stage: '' mix_stage: ''
mix_times: 0 mix_times:
- 0
mix_vol: 0 mix_vol: 0
none_keys: none_keys:
- '' - ''
@@ -4150,9 +4151,11 @@ liquid_handler:
mix_stage: mix_stage:
type: string type: string
mix_times: mix_times:
items:
maximum: 2147483647 maximum: 2147483647
minimum: -2147483648 minimum: -2147483648
type: integer type: integer
type: array
mix_vol: mix_vol:
maximum: 2147483647 maximum: 2147483647
minimum: -2147483648 minimum: -2147483648
@@ -5012,7 +5015,8 @@ liquid_handler.biomek:
mix_liquid_height: 0.0 mix_liquid_height: 0.0
mix_rate: 0 mix_rate: 0
mix_stage: '' mix_stage: ''
mix_times: 0 mix_times:
- 0
mix_vol: 0 mix_vol: 0
none_keys: none_keys:
- '' - ''
@@ -5155,9 +5159,11 @@ liquid_handler.biomek:
mix_stage: mix_stage:
type: string type: string
mix_times: mix_times:
items:
maximum: 2147483647 maximum: 2147483647
minimum: -2147483648 minimum: -2147483648
type: integer type: integer
type: array
mix_vol: mix_vol:
maximum: 2147483647 maximum: 2147483647
minimum: -2147483648 minimum: -2147483648
@@ -7801,7 +7807,8 @@ liquid_handler.prcxi:
mix_liquid_height: 0.0 mix_liquid_height: 0.0
mix_rate: 0 mix_rate: 0
mix_stage: '' mix_stage: ''
mix_times: 0 mix_times:
- 0
mix_vol: 0 mix_vol: 0
none_keys: none_keys:
- '' - ''
@@ -7930,9 +7937,11 @@ liquid_handler.prcxi:
mix_stage: mix_stage:
type: string type: string
mix_times: mix_times:
items:
maximum: 2147483647 maximum: 2147483647
minimum: -2147483648 minimum: -2147483648
type: integer type: integer
type: array
mix_vol: mix_vol:
maximum: 2147483647 maximum: 2147483647
minimum: -2147483648 minimum: -2147483648

View File

@@ -4,11 +4,11 @@ reaction_station.bioyond:
- reaction_station_bioyond - reaction_station_bioyond
class: class:
action_value_mappings: action_value_mappings:
auto-append_to_workflow_sequence: auto-add_material:
feedback: {} feedback: {}
goal: {} goal: {}
goal_default: goal_default:
web_workflow_name: null material_data: null
handles: {} handles: {}
placeholder_keys: {} placeholder_keys: {}
result: {} result: {}
@@ -18,21 +18,22 @@ reaction_station.bioyond:
feedback: {} feedback: {}
goal: goal:
properties: properties:
web_workflow_name: material_data:
type: string type: object
required: required:
- web_workflow_name - material_data
type: object type: object
result: {} result: {}
required: required:
- goal - goal
title: append_to_workflow_sequence参数 title: add_material参数
type: object type: object
type: UniLabJsonCommand type: UniLabJsonCommand
auto-clear_workflows: auto-create_90_10_vial_feeding_task:
feedback: {} feedback: {}
goal: {} goal: {}
goal_default: {} goal_default:
task_data: null
handles: {} handles: {}
placeholder_keys: {} placeholder_keys: {}
result: {} result: {}
@@ -41,13 +42,470 @@ reaction_station.bioyond:
properties: properties:
feedback: {} feedback: {}
goal: goal:
properties: {} properties:
task_data:
type: string
required:
- task_data
type: object
result: {}
required:
- goal
title: create_90_10_vial_feeding_task参数
type: object
type: UniLabJsonCommand
auto-create_batch_90_10_vial_feeding_task:
feedback: {}
goal: {}
goal_default:
batch_data: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: ''
properties:
feedback: {}
goal:
properties:
batch_data:
type: string
required:
- batch_data
type: object
result: {}
required:
- goal
title: create_batch_90_10_vial_feeding_task参数
type: object
type: UniLabJsonCommand
auto-create_batch_diamine_solution_task:
feedback: {}
goal: {}
goal_default:
batch_data: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: ''
properties:
feedback: {}
goal:
properties:
batch_data:
type: string
required:
- batch_data
type: object
result: {}
required:
- goal
title: create_batch_diamine_solution_task参数
type: object
type: UniLabJsonCommand
auto-create_diamine_solution_task:
feedback: {}
goal: {}
goal_default:
solution_data: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: ''
properties:
feedback: {}
goal:
properties:
solution_data:
type: string
required:
- solution_data
type: object
result: {}
required:
- goal
title: create_diamine_solution_task参数
type: object
type: UniLabJsonCommand
auto-create_order:
feedback: {}
goal: {}
goal_default:
parameters: null
task_name: null
workflow_name: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: ''
properties:
feedback: {}
goal:
properties:
parameters:
type: object
task_name:
type: string
workflow_name:
type: string
required:
- workflow_name
- task_name
type: object
result: {}
required:
- goal
title: create_order参数
type: object
type: UniLabJsonCommand
auto-create_resource:
feedback: {}
goal: {}
goal_default:
resource_data: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: ''
properties:
feedback: {}
goal:
properties:
resource_data:
type: string
required:
- resource_data
type: object
result: {}
required:
- goal
title: create_resource参数
type: object
type: UniLabJsonCommand
auto-delete_material:
feedback: {}
goal: {}
goal_default:
material_data: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: ''
properties:
feedback: {}
goal:
properties:
material_data:
type: string
required:
- material_data
type: object
result: {}
required:
- goal
title: delete_material参数
type: object
type: UniLabJsonCommand
auto-device_operation:
feedback: {}
goal: {}
goal_default:
device_id: null
operation: null
parameters: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: ''
properties:
feedback: {}
goal:
properties:
device_id:
type: string
operation:
type: string
parameters:
type: object
required:
- device_id
- operation
type: object
result: {}
required:
- goal
title: device_operation参数
type: object
type: UniLabJsonCommand
auto-dispensing_material_inbound:
feedback: {}
goal: {}
goal_default:
material_data: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: ''
properties:
feedback: {}
goal:
properties:
material_data:
type: string
required:
- material_data
type: object
result: {}
required:
- goal
title: dispensing_material_inbound参数
type: object
type: UniLabJsonCommand
auto-dispensing_material_outbound:
feedback: {}
goal: {}
goal_default:
material_data: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: ''
properties:
feedback: {}
goal:
properties:
material_data:
type: string
required:
- material_data
type: object
result: {}
required:
- goal
title: dispensing_material_outbound参数
type: object
type: UniLabJsonCommand
auto-drip_back:
feedback: {}
goal: {}
goal_default:
assign_material_name: Reactor
temperature: 25.0
time: '0'
torque_variation: '1'
handles: {}
placeholder_keys: {}
result: {}
schema:
description: ''
properties:
feedback: {}
goal:
properties:
assign_material_name:
default: Reactor
type: string
temperature:
default: 25.0
type: number
time:
default: '0'
type: string
torque_variation:
default: '1'
type: string
required: [] required: []
type: object type: object
result: {} result: {}
required: required:
- goal - goal
title: clear_workflows参数 title: drip_back参数
type: object
type: UniLabJsonCommand
auto-execute_bioyond_sync_workflow:
feedback: {}
goal: {}
goal_default:
parameters: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: ''
properties:
feedback: {}
goal:
properties:
parameters:
type: object
required:
- parameters
type: object
result: {}
required:
- goal
title: execute_bioyond_sync_workflow参数
type: object
type: UniLabJsonCommandAsync
auto-execute_bioyond_update_workflow:
feedback: {}
goal: {}
goal_default:
parameters: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: ''
properties:
feedback: {}
goal:
properties:
parameters:
type: object
required:
- parameters
type: object
result: {}
required:
- goal
title: execute_bioyond_update_workflow参数
type: object
type: UniLabJsonCommandAsync
auto-liquid_feeding_beaker:
feedback: {}
goal: {}
goal_default:
material_name: ''
volume: ''
handles: {}
placeholder_keys: {}
result: {}
schema:
description: ''
properties:
feedback: {}
goal:
properties:
material_name:
default: ''
type: string
volume:
default: ''
type: string
required: []
type: object
result: {}
required:
- goal
title: liquid_feeding_beaker参数
type: object
type: UniLabJsonCommand
auto-liquid_feeding_solvents:
feedback: {}
goal: {}
goal_default:
material_name: ''
volume: ''
handles: {}
placeholder_keys: {}
result: {}
schema:
description: ''
properties:
feedback: {}
goal:
properties:
material_name:
default: ''
type: string
volume:
default: ''
type: string
required: []
type: object
result: {}
required:
- goal
title: liquid_feeding_solvents参数
type: object
type: UniLabJsonCommand
auto-liquid_feeding_titration:
feedback: {}
goal: {}
goal_default:
material_name: ''
time: '120'
titration_type: '1'
torque_variation: '2'
volume: ''
handles: {}
placeholder_keys: {}
result: {}
schema:
description: ''
properties:
feedback: {}
goal:
properties:
material_name:
default: ''
type: string
time:
default: '120'
type: string
titration_type:
default: '1'
type: string
torque_variation:
default: '2'
type: string
volume:
default: ''
type: string
required: []
type: object
result: {}
required:
- goal
title: liquid_feeding_titration参数
type: object
type: UniLabJsonCommand
auto-liquid_feeding_vials_non_titration:
feedback: {}
goal: {}
goal_default:
material_name: ''
volume: ''
handles: {}
placeholder_keys: {}
result: {}
schema:
description: ''
properties:
feedback: {}
goal:
properties:
material_name:
default: ''
type: string
volume:
default: ''
type: string
required: []
type: object
result: {}
required:
- goal
title: liquid_feeding_vials_non_titration参数
type: object type: object
type: UniLabJsonCommand type: UniLabJsonCommand
auto-load_bioyond_data_from_file: auto-load_bioyond_data_from_file:
@@ -75,6 +533,124 @@ reaction_station.bioyond:
title: load_bioyond_data_from_file参数 title: load_bioyond_data_from_file参数
type: object type: object
type: UniLabJsonCommand type: UniLabJsonCommand
auto-material_inbound:
feedback: {}
goal: {}
goal_default:
location_name: null
material_id: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: ''
properties:
feedback: {}
goal:
properties:
location_name:
type: string
material_id:
type: string
required:
- material_id
- location_name
type: object
result: {}
required:
- goal
title: material_inbound参数
type: object
type: UniLabJsonCommand
auto-material_outbound:
feedback: {}
goal: {}
goal_default:
location_name: null
material_id: null
quantity: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: ''
properties:
feedback: {}
goal:
properties:
location_name:
type: string
material_id:
type: string
quantity:
type: integer
required:
- material_id
- location_name
- quantity
type: object
result: {}
required:
- goal
title: material_outbound参数
type: object
type: UniLabJsonCommand
auto-merge_workflow_with_parameters:
feedback: {}
goal: {}
goal_default:
name: null
workflows: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: ''
properties:
feedback: {}
goal:
properties:
name:
type: string
workflows:
items:
type: object
type: array
required:
- name
- workflows
type: object
result: {}
required:
- goal
title: merge_workflow_with_parameters参数
type: object
type: UniLabJsonCommand
auto-order_query:
feedback: {}
goal: {}
goal_default:
query_data: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: ''
properties:
feedback: {}
goal:
properties:
query_data:
type: string
required:
- query_data
type: object
result: {}
required:
- goal
title: order_query参数
type: object
type: UniLabJsonCommand
auto-post_init: auto-post_init:
feedback: {} feedback: {}
goal: {} goal: {}
@@ -100,11 +676,33 @@ reaction_station.bioyond:
title: post_init参数 title: post_init参数
type: object type: object
type: UniLabJsonCommand type: UniLabJsonCommand
auto-process_web_workflows: auto-reactor_taken_in:
feedback: {}
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: ''
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: reactor_taken_in参数
type: object
type: UniLabJsonCommand
auto-reactor_taken_out:
feedback: {} feedback: {}
goal: {} goal: {}
goal_default: goal_default:
json_str: null order_id: ''
preintake_id: ''
handles: {} handles: {}
placeholder_keys: {} placeholder_keys: {}
result: {} result: {}
@@ -114,15 +712,18 @@ reaction_station.bioyond:
feedback: {} feedback: {}
goal: goal:
properties: properties:
json_str: order_id:
default: ''
type: string type: string
required: preintake_id:
- json_str default: ''
type: string
required: []
type: object type: object
result: {} result: {}
required: required:
- goal - goal
title: process_web_workflows参数 title: reactor_taken_out参数
type: object type: object
type: UniLabJsonCommand type: UniLabJsonCommand
auto-reset_workstation: auto-reset_workstation:
@@ -146,11 +747,11 @@ reaction_station.bioyond:
title: reset_workstation参数 title: reset_workstation参数
type: object type: object
type: UniLabJsonCommand type: UniLabJsonCommand
auto-resource_tree_add: auto-sample_waste_removal:
feedback: {} feedback: {}
goal: {} goal: {}
goal_default: goal_default:
resources: null waste_data: null
handles: {} handles: {}
placeholder_keys: {} placeholder_keys: {}
result: {} result: {}
@@ -160,42 +761,119 @@ reaction_station.bioyond:
feedback: {} feedback: {}
goal: goal:
properties: properties:
resources: waste_data:
items:
type: object
type: array
required:
- resources
type: object
result: {}
required:
- goal
title: resource_tree_add参数
type: object
type: UniLabJsonCommand
auto-set_workflow_sequence:
feedback: {}
goal: {}
goal_default:
json_str: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: ''
properties:
feedback: {}
goal:
properties:
json_str:
type: string type: string
required: required:
- json_str - waste_data
type: object type: object
result: {} result: {}
required: required:
- goal - goal
title: set_workflow_sequence参数 title: sample_waste_removal参数
type: object
type: UniLabJsonCommand
auto-solid_feeding_vials:
feedback: {}
goal: {}
goal_default:
material_name: ''
volume: ''
handles: {}
placeholder_keys: {}
result: {}
schema:
description: ''
properties:
feedback: {}
goal:
properties:
material_name:
default: ''
type: string
volume:
default: ''
type: string
required: []
type: object
result: {}
required:
- goal
title: solid_feeding_vials参数
type: object
type: UniLabJsonCommand
auto-start_scheduler:
feedback: {}
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: ''
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: start_scheduler参数
type: object
type: UniLabJsonCommand
auto-stock_material:
feedback: {}
goal: {}
goal_default:
location: null
material_id: null
quantity: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: ''
properties:
feedback: {}
goal:
properties:
location:
type: string
material_id:
type: string
quantity:
type: integer
required:
- material_id
- location
- quantity
type: object
result: {}
required:
- goal
title: stock_material参数
type: object
type: UniLabJsonCommand
auto-stop_scheduler:
feedback: {}
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: ''
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: stop_scheduler参数
type: object type: object
type: UniLabJsonCommand type: UniLabJsonCommand
auto-transfer_resource_to_another: auto-transfer_resource_to_another:
@@ -386,6 +1064,33 @@ reaction_station.bioyond:
title: transfer_resource_to_another参数 title: transfer_resource_to_another参数
type: object type: object
type: UniLabJsonCommand type: UniLabJsonCommand
auto-validate_workflow_parameters:
feedback: {}
goal: {}
goal_default:
workflows: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: ''
properties:
feedback: {}
goal:
properties:
workflows:
items:
type: object
type: array
required:
- workflows
type: object
result: {}
required:
- goal
title: validate_workflow_parameters参数
type: object
type: UniLabJsonCommand
bioyond_sync: bioyond_sync:
feedback: {} feedback: {}
goal: goal:
@@ -702,9 +1407,11 @@ reaction_station.bioyond:
module: unilabos.devices.workstation.bioyond_studio.station:BioyondWorkstation module: unilabos.devices.workstation.bioyond_studio.station:BioyondWorkstation
protocol_type: [] protocol_type: []
status_types: status_types:
all_workflows: dict
bioyond_status: dict bioyond_status: dict
device_list: dict
scheduler_status: dict
station_info: dict station_info: dict
workflow_parameter_template: dict
workstation_status: dict workstation_status: dict
type: python type: python
config_info: [] config_info: []
@@ -718,22 +1425,30 @@ reaction_station.bioyond:
type: string type: string
deck: deck:
type: string type: string
station_config:
type: string
required: [] required: []
type: object type: object
data: data:
properties: properties:
all_workflows:
type: object
bioyond_status: bioyond_status:
type: object type: object
device_list:
type: object
scheduler_status:
type: object
station_info: station_info:
type: object type: object
workflow_parameter_template:
type: object
workstation_status: workstation_status:
type: object type: object
required: required:
- bioyond_status
- all_workflows
- station_info - station_info
- bioyond_status
- workflow_parameter_template
- scheduler_status
- device_list
- workstation_status - workstation_status
type: object type: object
version: 1.0.0 version: 1.0.0

View File

@@ -834,3 +834,174 @@ linear_motion.toyo_xyz.sim:
mesh: toyo_xyz mesh: toyo_xyz
type: device type: device
version: 1.0.0 version: 1.0.0
motor.iCL42:
category:
- robot_linear_motion
class:
action_value_mappings:
auto-execute_run_motor:
feedback: {}
goal: {}
goal_default:
mode: null
position: null
velocity: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: 步进电机执行运动函数。直接执行电机运动命令,包括位置设定、速度控制和路径规划。该函数处理底层的电机控制协议,消除警告信息,设置运动参数并启动电机运行。适用于需要直接控制电机运动的应用场景。
properties:
feedback: {}
goal:
properties:
mode:
type: string
position:
type: number
velocity:
type: integer
required:
- mode
- position
- velocity
type: object
result: {}
required:
- goal
title: execute_run_motor参数
type: object
type: UniLabJsonCommand
auto-init_device:
feedback: {}
goal: {}
goal_default: {}
handles: {}
placeholder_keys: {}
result: {}
schema:
description: iCL42电机设备初始化函数。建立与iCL42步进电机驱动器的串口通信连接配置通信参数包括波特率、数据位、校验位等。该函数是电机使用前的必要步骤确保驱动器处于可控状态并准备接收运动指令。
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: init_device参数
type: object
type: UniLabJsonCommand
auto-run_motor:
feedback: {}
goal: {}
goal_default:
mode: null
position: null
velocity: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: 步进电机运动控制函数。根据指定的运动模式、目标位置和速度参数控制电机运动。支持多种运动模式和精确的位置控制,自动处理运动轨迹规划和执行。该函数提供异步执行和状态反馈,确保运动的准确性和可靠性。
properties:
feedback: {}
goal:
properties:
mode:
type: string
position:
type: number
velocity:
type: integer
required:
- mode
- position
- velocity
type: object
result: {}
required:
- goal
title: run_motor参数
type: object
type: UniLabJsonCommand
execute_command_from_outer:
feedback: {}
goal:
command: command
goal_default:
command: ''
handles: {}
result:
success: success
schema:
description: ''
properties:
feedback:
properties:
status:
type: string
required:
- status
title: SendCmd_Feedback
type: object
goal:
properties:
command:
type: string
required:
- command
title: SendCmd_Goal
type: object
result:
properties:
return_info:
type: string
success:
type: boolean
required:
- return_info
- success
title: SendCmd_Result
type: object
required:
- goal
title: SendCmd
type: object
type: SendCmd
module: unilabos.devices.motor.iCL42:iCL42Driver
status_types:
is_executing_run: bool
motor_position: int
success: bool
type: python
config_info: []
description: iCL42步进电机驱动器用于实验室设备的精密线性运动控制。该设备通过串口通信控制iCL42型步进电机驱动器支持多种运动模式和精确的位置、速度控制。具备位置反馈、运行状态监控和故障检测功能。适用于自动进样器、样品传送、精密定位平台等需要准确线性运动控制的实验室自动化设备。
handles: []
icon: ''
init_param_schema:
config:
properties:
device_address:
default: 1
type: integer
device_com:
default: COM9
type: string
required: []
type: object
data:
properties:
is_executing_run:
type: boolean
motor_position:
type: integer
success:
type: boolean
required:
- motor_position
- is_executing_run
- success
type: object
version: 1.0.0

File diff suppressed because it is too large Load Diff

View File

@@ -708,8 +708,6 @@ class Registry:
for status_name, status_type in device_config["class"]["status_types"].items(): for status_name, status_type in device_config["class"]["status_types"].items():
device_config["class"]["status_types"][status_name] = status_str_type_mapping[status_type] device_config["class"]["status_types"][status_name] = status_str_type_mapping[status_type]
for action_name, action_config in device_config["class"]["action_value_mappings"].items(): for action_name, action_config in device_config["class"]["action_value_mappings"].items():
if action_config["type"] not in action_str_type_mapping:
continue
action_config["type"] = action_str_type_mapping[action_config["type"]] action_config["type"] = action_str_type_mapping[action_config["type"]]
# 添加内置的驱动命令动作 # 添加内置的驱动命令动作
self._add_builtin_actions(device_config, device_id) self._add_builtin_actions(device_config, device_id)

View File

@@ -1,130 +1,46 @@
1BottleCarrier: BIOYOND_PolymerStation_1BottleCarrier:
category: category:
- bottle_carriers - bottle_carriers
class: class:
module: unilabos.resources.bioyond.bottle_carriers:YB_1BottleCarrier module: unilabos.resources.bioyond.bottle_carriers:BIOYOND_PolymerStation_1BottleCarrier
type: pylabrobot type: pylabrobot
description: 1BottleCarrier description: BIOYOND_PolymerStation_1BottleCarrier
handles: [] handles: []
icon: '' icon: ''
init_param_schema: {} init_param_schema: {}
registry_type: resource registry_type: resource
version: 1.0.0 version: 1.0.0
1FlaskCarrier: BIOYOND_PolymerStation_1FlaskCarrier:
category: category:
- bottle_carriers - bottle_carriers
class: class:
module: unilabos.resources.bioyond.bottle_carriers:YB_1FlaskCarrier module: unilabos.resources.bioyond.bottle_carriers:BIOYOND_PolymerStation_1FlaskCarrier
type: pylabrobot type: pylabrobot
description: 1FlaskCarrier description: BIOYOND_PolymerStation_1FlaskCarrier
handles: [] handles: []
icon: '' icon: ''
init_param_schema: {} init_param_schema: {}
registry_type: resource registry_type: resource
version: 1.0.0 version: 1.0.0
6StockCarrier: BIOYOND_PolymerStation_6StockCarrier:
category: category:
- bottle_carriers - bottle_carriers
class: class:
module: unilabos.resources.bioyond.bottle_carriers:YB_6StockCarrier module: unilabos.resources.bioyond.bottle_carriers:BIOYOND_PolymerStation_6StockCarrier
type: pylabrobot type: pylabrobot
description: 6StockCarrier description: BIOYOND_PolymerStation_6StockCarrier
handles: [] handles: []
icon: '' icon: ''
init_param_schema: {} init_param_schema: {}
registry_type: resource registry_type: resource
version: 1.0.0 version: 1.0.0
6VialCarrier: BIOYOND_PolymerStation_6VialCarrier:
category: category:
- bottle_carriers - bottle_carriers
class: class:
module: unilabos.resources.bioyond.bottle_carriers:YB_6VialCarrier module: unilabos.resources.bioyond.bottle_carriers:BIOYOND_PolymerStation_6VialCarrier
type: pylabrobot type: pylabrobot
description: 6VialCarrier description: BIOYOND_PolymerStation_6VialCarrier
handles: []
icon: ''
init_param_schema: {}
registry_type: resource
version: 1.0.0
6x5ml_DispensingVialCarrier:
category:
- yb3
class:
module: unilabos.resources.bioyond.bottle_carriers:YB_6x5ml_DispensingVialCarrier
type: pylabrobot
description: 6x5ml_DispensingVialCarrier
handles: []
icon: ''
init_param_schema: {}
registry_type: resource
version: 1.0.0
6x20ml_DispensingVialCarrier:
category:
- yb3
class:
module: unilabos.resources.bioyond.bottle_carriers:YB_6x20ml_DispensingVialCarrier
type: pylabrobot
description: 6x20ml_DispensingVialCarrier
handles: []
icon: ''
init_param_schema: {}
registry_type: resource
version: 1.0.0
6x_SmallSolutionBottleCarrier:
category:
- yb3
class:
module: unilabos.resources.bioyond.bottle_carriers:YB_6x_SmallSolutionBottleCarrier
type: pylabrobot
description: 6x_SmallSolutionBottleCarrier
handles: []
icon: ''
init_param_schema: {}
registry_type: resource
version: 1.0.0
4x_LargeSolutionBottleCarrier:
category:
- yb3
class:
module: unilabos.resources.bioyond.bottle_carriers:YB_4x_LargeSolutionBottleCarrier
type: pylabrobot
description: 4x_LargeSolutionBottleCarrier
handles: []
icon: ''
init_param_schema: {}
registry_type: resource
version: 1.0.0
6x_LargeDispenseHeadCarrier:
category:
- yb3
class:
module: unilabos.resources.bioyond.bottle_carriers:YB_6x_LargeDispenseHeadCarrier
type: pylabrobot
description: 6x_LargeDispenseHeadCarrier
handles: []
icon: ''
init_param_schema: {}
registry_type: resource
version: 1.0.0
AdapterBlock:
category:
- yb3
class:
module: unilabos.resources.bioyond.bottle_carriers:YB_AdapterBlock
type: pylabrobot
description: AdapterBlock
handles: []
icon: ''
init_param_schema: {}
registry_type: resource
version: 1.0.0
TipBox:
category:
- yb3
class:
module: unilabos.resources.bioyond.bottle_carriers:YB_TipBox
type: pylabrobot
description: TipBox
handles: [] handles: []
icon: '' icon: ''
init_param_schema: {} init_param_schema: {}

View File

@@ -1,279 +1,48 @@
Liquid_Vial: BIOYOND_PolymerStation_Liquid_Vial:
category: category:
- bottles - bottles
class: class:
module: unilabos.resources.bioyond.bottles:YB_Liquid_Vial module: unilabos.resources.bioyond.bottles:BIOYOND_PolymerStation_Liquid_Vial
type: pylabrobot type: pylabrobot
handles: [] handles: []
icon: '' icon: ''
init_param_schema: {} init_param_schema: {}
version: 1.0.0 version: 1.0.0
Reagent_Bottle: BIOYOND_PolymerStation_Reagent_Bottle:
category: category:
- bottles - bottles
class: class:
module: unilabos.resources.bioyond.bottles:YB_Reagent_Bottle module: unilabos.resources.bioyond.bottles:BIOYOND_PolymerStation_Reagent_Bottle
type: pylabrobot type: pylabrobot
handles: [] handles: []
icon: '' icon: ''
init_param_schema: {} init_param_schema: {}
version: 1.0.0 version: 1.0.0
Solid_Stock: BIOYOND_PolymerStation_Solid_Stock:
category: category:
- bottles - bottles
class: class:
module: unilabos.resources.bioyond.bottles:YB_Solid_Stock module: unilabos.resources.bioyond.bottles:BIOYOND_PolymerStation_Solid_Stock
type: pylabrobot type: pylabrobot
handles: [] handles: []
icon: '' icon: ''
init_param_schema: {} init_param_schema: {}
version: 1.0.0 version: 1.0.0
Solid_Vial: BIOYOND_PolymerStation_Solid_Vial:
category: category:
- bottles - bottles
class: class:
module: unilabos.resources.bioyond.bottles:YB_Solid_Vial module: unilabos.resources.bioyond.bottles:BIOYOND_PolymerStation_Solid_Vial
type: pylabrobot type: pylabrobot
handles: [] handles: []
icon: '' icon: ''
init_param_schema: {} init_param_schema: {}
version: 1.0.0 version: 1.0.0
Solution_Beaker: BIOYOND_PolymerStation_Solution_Beaker:
category: category:
- bottles - bottles
class: class:
module: unilabos.resources.bioyond.bottles:YB_Solution_Beaker module: unilabos.resources.bioyond.bottles:BIOYOND_PolymerStation_Solution_Beaker
type: pylabrobot
handles: []
icon: ''
init_param_schema: {}
version: 1.0.0
100ml_Liquid_Bottle:
category:
- yb3
class:
module: unilabos.resources.bioyond.bottles:YB_100ml_Liquid_Bottle
type: pylabrobot
handles: []
icon: ''
init_param_schema: {}
version: 1.0.0
Liquid_Bottle:
category:
- yb3
class:
module: unilabos.resources.bioyond.bottles:YB_Liquid_Bottle
type: pylabrobot
handles: []
icon: ''
init_param_schema: {}
version: 1.0.0
High_Viscosity_Liquid_Bottle:
category:
- yb3
class:
module: unilabos.resources.bioyond.bottles:YB_High_Viscosity_Liquid_Bottle
type: pylabrobot
handles: []
icon: ''
init_param_schema: {}
version: 1.0.0
Large_Dispense_Head:
category:
- yb3
class:
module: unilabos.resources.bioyond.bottles:YB_Large_Dispense_Head
type: pylabrobot
handles: []
icon: ''
init_param_schema: {}
version: 1.0.0
5ml_Dispensing_Vial:
category:
- yb3
class:
module: unilabos.resources.bioyond.bottles:YB_5ml_Dispensing_Vial
type: pylabrobot
handles: []
icon: ''
init_param_schema: {}
version: 1.0.0
20ml_Dispensing_Vial:
category:
- yb3
class:
module: unilabos.resources.bioyond.bottles:YB_20ml_Dispensing_Vial
type: pylabrobot
handles: []
icon: ''
init_param_schema: {}
version: 1.0.0
Small_Solution_Bottle:
category:
- yb3
class:
module: unilabos.resources.bioyond.bottles:YB_Small_Solution_Bottle
type: pylabrobot
handles: []
icon: ''
init_param_schema: {}
version: 1.0.0
Large_Solution_Bottle:
category:
- yb3
class:
module: unilabos.resources.bioyond.bottles:YB_Large_Solution_Bottle
type: pylabrobot
handles: []
icon: ''
init_param_schema: {}
version: 1.0.0
Pipette_Tip:
category:
- yb3
class:
module: unilabos.resources.bioyond.bottles:YB_Pipette_Tip
type: pylabrobot
handles: []
icon: ''
init_param_schema: {}
version: 1.0.0
YB_Liquid_Vial:
category:
- bottles
class:
module: unilabos.resources.bioyond.bottles:YB_Liquid_Vial
type: pylabrobot
handles: []
icon: ''
init_param_schema: {}
version: 1.0.0
YB_Reagent_Bottle:
category:
- bottles
class:
module: unilabos.resources.bioyond.bottles:YB_Reagent_Bottle
type: pylabrobot
handles: []
icon: ''
init_param_schema: {}
version: 1.0.0
YB_Solid_Stock:
category:
- bottles
class:
module: unilabos.resources.bioyond.bottles:YB_Solid_Stock
type: pylabrobot
handles: []
icon: ''
init_param_schema: {}
version: 1.0.0
YB_Solid_Vial:
category:
- bottles
class:
module: unilabos.resources.bioyond.bottles:YB_Solid_Vial
type: pylabrobot
handles: []
icon: ''
init_param_schema: {}
version: 1.0.0
YB_Solution_Beaker:
category:
- bottles
class:
module: unilabos.resources.bioyond.bottles:YB_Solution_Beaker
type: pylabrobot
handles: []
icon: ''
init_param_schema: {}
version: 1.0.0
YB_100ml_Liquid_Bottle:
category:
- yb3
class:
module: unilabos.resources.bioyond.bottles:YB_100ml_Liquid_Bottle
type: pylabrobot
handles: []
icon: ''
init_param_schema: {}
version: 1.0.0
YB_Liquid_Bottle:
category:
- yb3
class:
module: unilabos.resources.bioyond.bottles:YB_Liquid_Bottle
type: pylabrobot
handles: []
icon: ''
init_param_schema: {}
version: 1.0.0
YB_High_Viscosity_Liquid_Bottle:
category:
- yb3
class:
module: unilabos.resources.bioyond.bottles:YB_High_Viscosity_Liquid_Bottle
type: pylabrobot
handles: []
icon: ''
init_param_schema: {}
version: 1.0.0
YB_Large_Dispense_Head:
category:
- yb3
class:
module: unilabos.resources.bioyond.bottles:YB_Large_Dispense_Head
type: pylabrobot
handles: []
icon: ''
init_param_schema: {}
version: 1.0.0
YB_5ml_Dispensing_Vial:
category:
- yb3
class:
module: unilabos.resources.bioyond.bottles:YB_5ml_Dispensing_Vial
type: pylabrobot
handles: []
icon: ''
init_param_schema: {}
version: 1.0.0
YB_20ml_Dispensing_Vial:
category:
- yb3
class:
module: unilabos.resources.bioyond.bottles:YB_20ml_Dispensing_Vial
type: pylabrobot
handles: []
icon: ''
init_param_schema: {}
version: 1.0.0
YB_Small_Solution_Bottle:
category:
- yb3
class:
module: unilabos.resources.bioyond.bottles:YB_Small_Solution_Bottle
type: pylabrobot
handles: []
icon: ''
init_param_schema: {}
version: 1.0.0
YB_Large_Solution_Bottle:
category:
- yb3
class:
module: unilabos.resources.bioyond.bottles:YB_Large_Solution_Bottle
type: pylabrobot
handles: []
icon: ''
init_param_schema: {}
version: 1.0.0
YB_Pipette_Tip:
category:
- yb3
class:
module: unilabos.resources.bioyond.bottles:YB_Pipette_Tip
type: pylabrobot type: pylabrobot
handles: [] handles: []
icon: '' icon: ''

View File

@@ -22,15 +22,3 @@ BIOYOND_PolymerReactionStation_Deck:
init_param_schema: {} init_param_schema: {}
registry_type: resource registry_type: resource
version: 1.0.0 version: 1.0.0
YB_Deck16:
category:
- deck
class:
module: unilabos.resources.bioyond.decks:YB_Deck
type: pylabrobot
description: BIOYOND PolymerReactionStation Deck
handles: []
icon: 配液站.webp
init_param_schema: {}
registry_type: resource
version: 1.0.0

View File

@@ -3,7 +3,7 @@ container:
- container - container
class: class:
module: unilabos.resources.container:RegularContainer module: unilabos.resources.container:RegularContainer
type: pylabrobot type: unilabos
description: regular organic container description: regular organic container
handles: handles:
- data_key: fluid_in - data_key: fluid_in

View File

@@ -2,17 +2,11 @@ from pylabrobot.resources import create_homogeneous_resources, Coordinate, Resou
from unilabos.resources.itemized_carrier import Bottle, BottleCarrier from unilabos.resources.itemized_carrier import Bottle, BottleCarrier
from unilabos.resources.bioyond.bottles import ( from unilabos.resources.bioyond.bottles import (
YB_Solid_Stock, BIOYOND_PolymerStation_Solid_Stock,
YB_Solid_Vial, BIOYOND_PolymerStation_Solid_Vial,
YB_Liquid_Vial, BIOYOND_PolymerStation_Liquid_Vial,
YB_Solution_Beaker, BIOYOND_PolymerStation_Solution_Beaker,
YB_Reagent_Bottle, BIOYOND_PolymerStation_Reagent_Bottle
YB_5ml_Dispensing_Vial,
YB_20ml_Dispensing_Vial,
YB_Small_Solution_Bottle,
YB_Large_Solution_Bottle,
YB_Large_Dispense_Head,
YB_Pipette_Tip
) )
# 命名约定:试剂瓶-Bottle烧杯-Beaker烧瓶-Flask小瓶-Vial # 命名约定:试剂瓶-Bottle烧杯-Beaker烧瓶-Flask小瓶-Vial
@@ -57,13 +51,13 @@ def BIOYOND_Electrolyte_6VialCarrier(name: str) -> BottleCarrier:
size_y=carrier_size_y, size_y=carrier_size_y,
size_z=carrier_size_z, size_z=carrier_size_z,
sites=sites, sites=sites,
model="Electrolyte_6VialCarrier", model="BIOYOND_Electrolyte_6VialCarrier",
) )
carrier.num_items_x = 3 carrier.num_items_x = 3
carrier.num_items_y = 2 carrier.num_items_y = 2
carrier.num_items_z = 1 carrier.num_items_z = 1
for i in range(6): for i in range(6):
carrier[i] = YB_Solid_Vial(f"{name}_vial_{i+1}") carrier[i] = BIOYOND_PolymerStation_Solid_Vial(f"{name}_vial_{i+1}")
return carrier return carrier
@@ -95,16 +89,16 @@ def BIOYOND_Electrolyte_1BottleCarrier(name: str) -> BottleCarrier:
resource_size_y=beaker_diameter, resource_size_y=beaker_diameter,
name_prefix=name, name_prefix=name,
), ),
model="Electrolyte_1BottleCarrier", model="BIOYOND_Electrolyte_1BottleCarrier",
) )
carrier.num_items_x = 1 carrier.num_items_x = 1
carrier.num_items_y = 1 carrier.num_items_y = 1
carrier.num_items_z = 1 carrier.num_items_z = 1
carrier[0] = YB_Solution_Beaker(f"{name}_beaker_1") carrier[0] = BIOYOND_PolymerStation_Solution_Beaker(f"{name}_beaker_1")
return carrier return carrier
def YB_6StockCarrier(name: str) -> BottleCarrier: def BIOYOND_PolymerStation_6StockCarrier(name: str) -> BottleCarrier:
"""6瓶载架 - 2x3布局""" """6瓶载架 - 2x3布局"""
# 载架尺寸 (mm) # 载架尺寸 (mm)
@@ -144,18 +138,18 @@ def YB_6StockCarrier(name: str) -> BottleCarrier:
size_y=carrier_size_y, size_y=carrier_size_y,
size_z=carrier_size_z, size_z=carrier_size_z,
sites=sites, sites=sites,
model="6StockCarrier", model="BIOYOND_PolymerStation_6VialCarrier",
) )
carrier.num_items_x = 3 carrier.num_items_x = 3
carrier.num_items_y = 2 carrier.num_items_y = 2
carrier.num_items_z = 1 carrier.num_items_z = 1
ordering = ["A1", "A2", "A3", "B1", "B2", "B3"] # 自定义顺序 ordering = ["A1", "A2", "A3", "B1", "B2", "B3"] # 自定义顺序
for i in range(6): for i in range(6):
carrier[i] = YB_Solid_Stock(f"{name}_vial_{ordering[i]}") carrier[i] = BIOYOND_PolymerStation_Solid_Stock(f"{name}_vial_{ordering[i]}")
return carrier return carrier
def YB_6VialCarrier(name: str) -> BottleCarrier: def BIOYOND_PolymerStation_6VialCarrier(name: str) -> BottleCarrier:
"""6瓶载架 - 2x3布局""" """6瓶载架 - 2x3布局"""
# 载架尺寸 (mm) # 载架尺寸 (mm)
@@ -195,20 +189,20 @@ def YB_6VialCarrier(name: str) -> BottleCarrier:
size_y=carrier_size_y, size_y=carrier_size_y,
size_z=carrier_size_z, size_z=carrier_size_z,
sites=sites, sites=sites,
model="6VialCarrier", model="BIOYOND_PolymerStation_6VialCarrier",
) )
carrier.num_items_x = 3 carrier.num_items_x = 3
carrier.num_items_y = 2 carrier.num_items_y = 2
carrier.num_items_z = 1 carrier.num_items_z = 1
ordering = ["A1", "A2", "A3", "B1", "B2", "B3"] # 自定义顺序 ordering = ["A1", "A2", "A3", "B1", "B2", "B3"] # 自定义顺序
for i in range(3): for i in range(3):
carrier[i] = YB_Solid_Vial(f"{name}_solidvial_{ordering[i]}") carrier[i] = BIOYOND_PolymerStation_Solid_Vial(f"{name}_solidvial_{ordering[i]}")
for i in range(3, 6): for i in range(3, 6):
carrier[i] = YB_Liquid_Vial(f"{name}_liquidvial_{ordering[i]}") carrier[i] = BIOYOND_PolymerStation_Liquid_Vial(f"{name}_liquidvial_{ordering[i]}")
return carrier return carrier
def YB_1BottleCarrier(name: str) -> BottleCarrier: def BIOYOND_PolymerStation_1BottleCarrier(name: str) -> BottleCarrier:
"""1瓶载架 - 单个中央位置""" """1瓶载架 - 单个中央位置"""
# 载架尺寸 (mm) # 载架尺寸 (mm)
@@ -236,16 +230,16 @@ def YB_1BottleCarrier(name: str) -> BottleCarrier:
resource_size_y=beaker_diameter, resource_size_y=beaker_diameter,
name_prefix=name, name_prefix=name,
), ),
model="1BottleCarrier", model="BIOYOND_PolymerStation_1BottleCarrier",
) )
carrier.num_items_x = 1 carrier.num_items_x = 1
carrier.num_items_y = 1 carrier.num_items_y = 1
carrier.num_items_z = 1 carrier.num_items_z = 1
carrier[0] = YB_Reagent_Bottle(f"{name}_flask_1") carrier[0] = BIOYOND_PolymerStation_Reagent_Bottle(f"{name}_flask_1")
return carrier return carrier
def YB_1FlaskCarrier(name: str) -> BottleCarrier: def BIOYOND_PolymerStation_1FlaskCarrier(name: str) -> BottleCarrier:
"""1瓶载架 - 单个中央位置""" """1瓶载架 - 单个中央位置"""
# 载架尺寸 (mm) # 载架尺寸 (mm)
@@ -273,348 +267,10 @@ def YB_1FlaskCarrier(name: str) -> BottleCarrier:
resource_size_y=beaker_diameter, resource_size_y=beaker_diameter,
name_prefix=name, name_prefix=name,
), ),
model="1FlaskCarrier", model="BIOYOND_PolymerStation_1FlaskCarrier",
) )
carrier.num_items_x = 1 carrier.num_items_x = 1
carrier.num_items_y = 1 carrier.num_items_y = 1
carrier.num_items_z = 1 carrier.num_items_z = 1
carrier[0] = YB_Reagent_Bottle(f"{name}_bottle_1") carrier[0] = BIOYOND_PolymerStation_Reagent_Bottle(f"{name}_bottle_1")
return carrier return carrier
def YB_6x5ml_DispensingVialCarrier(name: str) -> BottleCarrier:
"""5ml分液瓶板 - 4x2布局8个位置"""
# 载架尺寸 (mm)
carrier_size_x = 127.8
carrier_size_y = 85.5
carrier_size_z = 50.0
# 瓶位尺寸
bottle_diameter = 15.0
bottle_spacing_x = 42.0 # X方向间距
bottle_spacing_y = 35.0 # Y方向间距
# 计算起始位置 (居中排列)
start_x = (carrier_size_x - (4 - 1) * bottle_spacing_x - bottle_diameter) / 2
start_y = (carrier_size_y - (2 - 1) * bottle_spacing_y - bottle_diameter) / 2
sites = create_ordered_items_2d(
klass=ResourceHolder,
num_items_x=4,
num_items_y=2,
dx=start_x,
dy=start_y,
dz=5.0,
item_dx=bottle_spacing_x,
item_dy=bottle_spacing_y,
size_x=bottle_diameter,
size_y=bottle_diameter,
size_z=carrier_size_z,
)
for k, v in sites.items():
v.name = f"{name}_{v.name}"
carrier = BottleCarrier(
name=name,
size_x=carrier_size_x,
size_y=carrier_size_y,
size_z=carrier_size_z,
sites=sites,
model="6x5ml_DispensingVialCarrier",
)
carrier.num_items_x = 4
carrier.num_items_y = 2
carrier.num_items_z = 1
ordering = ["A1", "A2", "A3", "A4", "B1", "B2", "B3", "B4"]
for i in range(8):
carrier[i] = YB_5ml_Dispensing_Vial(f"{name}_vial_{ordering[i]}")
return carrier
def YB_6x20ml_DispensingVialCarrier(name: str) -> BottleCarrier:
"""20ml分液瓶板 - 4x2布局8个位置"""
# 载架尺寸 (mm)
carrier_size_x = 127.8
carrier_size_y = 85.5
carrier_size_z = 70.0
# 瓶位尺寸
bottle_diameter = 20.0
bottle_spacing_x = 42.0 # X方向间距
bottle_spacing_y = 35.0 # Y方向间距
# 计算起始位置 (居中排列)
start_x = (carrier_size_x - (4 - 1) * bottle_spacing_x - bottle_diameter) / 2
start_y = (carrier_size_y - (2 - 1) * bottle_spacing_y - bottle_diameter) / 2
sites = create_ordered_items_2d(
klass=ResourceHolder,
num_items_x=4,
num_items_y=2,
dx=start_x,
dy=start_y,
dz=5.0,
item_dx=bottle_spacing_x,
item_dy=bottle_spacing_y,
size_x=bottle_diameter,
size_y=bottle_diameter,
size_z=carrier_size_z,
)
for k, v in sites.items():
v.name = f"{name}_{v.name}"
carrier = BottleCarrier(
name=name,
size_x=carrier_size_x,
size_y=carrier_size_y,
size_z=carrier_size_z,
sites=sites,
model="6x20ml_DispensingVialCarrier",
)
carrier.num_items_x = 4
carrier.num_items_y = 2
carrier.num_items_z = 1
ordering = ["A1", "A2", "A3", "A4", "B1", "B2", "B3", "B4"]
for i in range(8):
carrier[i] = YB_20ml_Dispensing_Vial(f"{name}_vial_{ordering[i]}")
return carrier
def YB_6x_SmallSolutionBottleCarrier(name: str) -> BottleCarrier:
"""配液瓶(小)板 - 4x2布局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
sites = create_ordered_items_2d(
klass=ResourceHolder,
num_items_x=4,
num_items_y=2,
dx=start_x,
dy=start_y,
dz=5.0,
item_dx=bottle_spacing_x,
item_dy=bottle_spacing_y,
size_x=bottle_diameter,
size_y=bottle_diameter,
size_z=carrier_size_z,
)
for k, v in sites.items():
v.name = f"{name}_{v.name}"
carrier = BottleCarrier(
name=name,
size_x=carrier_size_x,
size_y=carrier_size_y,
size_z=carrier_size_z,
sites=sites,
model="6x_SmallSolutionBottleCarrier",
)
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_Small_Solution_Bottle(f"{name}_bottle_{ordering[i]}")
return carrier
def YB_4x_LargeSolutionBottleCarrier(name: str) -> BottleCarrier:
"""配液瓶(大)板 - 2x2布局4个位置"""
# 载架尺寸 (mm)
carrier_size_x = 127.8
carrier_size_y = 85.5
carrier_size_z = 95.0
# 瓶位尺寸
bottle_diameter = 55.0
bottle_spacing_x = 60.0 # X方向间距
bottle_spacing_y = 60.0 # Y方向间距
# 计算起始位置 (居中排列)
start_x = (carrier_size_x - (2 - 1) * bottle_spacing_x - bottle_diameter) / 2
start_y = (carrier_size_y - (2 - 1) * bottle_spacing_y - bottle_diameter) / 2
sites = create_ordered_items_2d(
klass=ResourceHolder,
num_items_x=2,
num_items_y=2,
dx=start_x,
dy=start_y,
dz=5.0,
item_dx=bottle_spacing_x,
item_dy=bottle_spacing_y,
size_x=bottle_diameter,
size_y=bottle_diameter,
size_z=carrier_size_z,
)
for k, v in sites.items():
v.name = f"{name}_{v.name}"
carrier = BottleCarrier(
name=name,
size_x=carrier_size_x,
size_y=carrier_size_y,
size_z=carrier_size_z,
sites=sites,
model="4x_LargeSolutionBottleCarrier",
)
carrier.num_items_x = 2
carrier.num_items_y = 2
carrier.num_items_z = 1
ordering = ["A1", "A2", "B1", "B2"]
for i in range(4):
carrier[i] = YB_Large_Solution_Bottle(f"{name}_bottle_{ordering[i]}")
return carrier
def YB_6x_LargeDispenseHeadCarrier(name: str) -> BottleCarrier:
"""加样头(大)板 - 1x1布局1个位置"""
# 载架尺寸 (mm)
carrier_size_x = 127.8
carrier_size_y = 85.5
carrier_size_z = 95.0
# 瓶位尺寸
bottle_diameter = 35.0
bottle_spacing_x = 42.0 # X方向间距
bottle_spacing_y = 35.0 # Y方向间距
# 计算起始位置 (居中排列)
start_x = (carrier_size_x - (1 - 1) * bottle_spacing_x - bottle_diameter) / 2
start_y = (carrier_size_y - (1 - 1) * bottle_spacing_y - bottle_diameter) / 2
sites = create_ordered_items_2d(
klass=ResourceHolder,
num_items_x=1,
num_items_y=1,
dx=start_x,
dy=start_y,
dz=5.0,
item_dx=bottle_spacing_x,
item_dy=bottle_spacing_y,
size_x=bottle_diameter,
size_y=bottle_diameter,
size_z=carrier_size_z,
)
for k, v in sites.items():
v.name = f"{name}_{v.name}"
carrier = BottleCarrier(
name=name,
size_x=carrier_size_x,
size_y=carrier_size_y,
size_z=carrier_size_z,
sites=sites,
model="6x_LargeDispenseHeadCarrier",
)
carrier.num_items_x = 1
carrier.num_items_y = 1
carrier.num_items_z = 1
carrier[0] = YB_Large_Dispense_Head(f"{name}_head_1")
return carrier
def YB_AdapterBlock(name: str) -> BottleCarrier:
"""适配器块 - 单个中央位置"""
# 载架尺寸 (mm)
carrier_size_x = 127.8
carrier_size_y = 85.5
carrier_size_z = 30.0
# 适配器尺寸
adapter_diameter = 80.0
# 计算中央位置
center_x = (carrier_size_x - adapter_diameter) / 2
center_y = (carrier_size_y - adapter_diameter) / 2
center_z = 0.0
carrier = BottleCarrier(
name=name,
size_x=carrier_size_x,
size_y=carrier_size_y,
size_z=carrier_size_z,
sites=create_homogeneous_resources(
klass=ResourceHolder,
locations=[Coordinate(center_x, center_y, center_z)],
resource_size_x=adapter_diameter,
resource_size_y=adapter_diameter,
name_prefix=name,
),
model="AdapterBlock",
)
carrier.num_items_x = 1
carrier.num_items_y = 1
carrier.num_items_z = 1
# 适配器块本身不包含瓶子,只是一个支撑结构
return carrier
def YB_TipBox(name: str) -> BottleCarrier:
"""枪头盒 - 8x12布局96个位置"""
# 载架尺寸 (mm)
carrier_size_x = 127.8
carrier_size_y = 85.5
carrier_size_z = 55.0
# 枪头尺寸
tip_diameter = 10.0
tip_spacing_x = 9.0 # X方向间距
tip_spacing_y = 9.0 # Y方向间距
# 计算起始位置 (居中排列)
start_x = (carrier_size_x - (12 - 1) * tip_spacing_x - tip_diameter) / 2
start_y = (carrier_size_y - (8 - 1) * tip_spacing_y - tip_diameter) / 2
sites = create_ordered_items_2d(
klass=ResourceHolder,
num_items_x=12,
num_items_y=8,
dx=start_x,
dy=start_y,
dz=5.0,
item_dx=tip_spacing_x,
item_dy=tip_spacing_y,
size_x=tip_diameter,
size_y=tip_diameter,
size_z=carrier_size_z,
)
for k, v in sites.items():
v.name = f"{name}_{v.name}"
carrier = BottleCarrier(
name=name,
size_x=carrier_size_x,
size_y=carrier_size_y,
size_z=carrier_size_z,
sites=sites,
model="TipBox",
)
carrier.num_items_x = 12
carrier.num_items_y = 8
carrier.num_items_z = 1
# 创建96个枪头
for i in range(96):
row = chr(65 + i // 12) # A-H
col = (i % 12) + 1 # 1-12
carrier[i] = YB_Pipette_Tip(f"{name}_tip_{row}{col}")
return carrier

View File

@@ -2,7 +2,7 @@ from unilabos.resources.itemized_carrier import Bottle, BottleCarrier
# 工厂函数 # 工厂函数
def YB_Solid_Stock( def BIOYOND_PolymerStation_Solid_Stock(
name: str, name: str,
diameter: float = 20.0, diameter: float = 20.0,
height: float = 100.0, height: float = 100.0,
@@ -12,15 +12,15 @@ def YB_Solid_Stock(
"""创建粉末瓶""" """创建粉末瓶"""
return Bottle( return Bottle(
name=name, name=name,
diameter=diameter,# 未知 diameter=diameter,
height=height, height=height,
max_volume=max_volume, max_volume=max_volume,
barcode=barcode, barcode=barcode,
model="Solid_Stock", model="BIOYOND_PolymerStation_Solid_Stock",
) )
def YB_Solid_Vial( def BIOYOND_PolymerStation_Solid_Vial(
name: str, name: str,
diameter: float = 25.0, diameter: float = 25.0,
height: float = 60.0, height: float = 60.0,
@@ -34,11 +34,11 @@ def YB_Solid_Vial(
height=height, height=height,
max_volume=max_volume, max_volume=max_volume,
barcode=barcode, barcode=barcode,
model="Solid_Vial", model="BIOYOND_PolymerStation_Solid_Vial",
) )
def YB_Liquid_Vial( def BIOYOND_PolymerStation_Liquid_Vial(
name: str, name: str,
diameter: float = 25.0, diameter: float = 25.0,
height: float = 60.0, height: float = 60.0,
@@ -52,11 +52,11 @@ def YB_Liquid_Vial(
height=height, height=height,
max_volume=max_volume, max_volume=max_volume,
barcode=barcode, barcode=barcode,
model="Liquid_Vial", model="BIOYOND_PolymerStation_Liquid_Vial",
) )
def YB_Solution_Beaker( def BIOYOND_PolymerStation_Solution_Beaker(
name: str, name: str,
diameter: float = 60.0, diameter: float = 60.0,
height: float = 70.0, height: float = 70.0,
@@ -70,11 +70,11 @@ def YB_Solution_Beaker(
height=height, height=height,
max_volume=max_volume, max_volume=max_volume,
barcode=barcode, barcode=barcode,
model="Solution_Beaker", model="BIOYOND_PolymerStation_Solution_Beaker",
) )
def YB_Reagent_Bottle( def BIOYOND_PolymerStation_Reagent_Bottle(
name: str, name: str,
diameter: float = 70.0, diameter: float = 70.0,
height: float = 120.0, height: float = 120.0,
@@ -88,168 +88,5 @@ def YB_Reagent_Bottle(
height=height, height=height,
max_volume=max_volume, max_volume=max_volume,
barcode=barcode, barcode=barcode,
model="Reagent_Bottle", model="BIOYOND_PolymerStation_Reagent_Bottle",
) )
def YB_100ml_Liquid_Bottle(
name: str,
diameter: float = 50.0,
height: float = 80.0,
max_volume: float = 100000.0, # 100mL
barcode: str = None,
) -> Bottle:
"""创建100ml液体瓶"""
return Bottle(
name=name,
diameter=diameter,
height=height,
max_volume=max_volume,
barcode=barcode,
model="100ml_Liquid_Bottle",
)
def YB_Liquid_Bottle(
name: str,
diameter: float = 40.0,
height: float = 70.0,
max_volume: float = 50000.0, # 50mL
barcode: str = None,
) -> Bottle:
"""创建液体瓶"""
return Bottle(
name=name,
diameter=diameter,
height=height,
max_volume=max_volume,
barcode=barcode,
model="Liquid_Bottle",
)
def YB_High_Viscosity_Liquid_Bottle(
name: str,
diameter: float = 45.0,
height: float = 75.0,
max_volume: float = 60000.0, # 60mL
barcode: str = None,
) -> Bottle:
"""创建高粘液瓶"""
return Bottle(
name=name,
diameter=diameter,
height=height,
max_volume=max_volume,
barcode=barcode,
model="High_Viscosity_Liquid_Bottle",
)
def YB_Large_Dispense_Head(
name: str,
diameter: float = 35.0,
height: float = 90.0,
max_volume: float = 50000.0, # 50mL
barcode: str = None,
) -> Bottle:
"""创建加样头(大)"""
return Bottle(
name=name,
diameter=diameter,
height=height,
max_volume=max_volume,
barcode=barcode,
model="Large_Dispense_Head",
)
def YB_5ml_Dispensing_Vial(
name: str,
diameter: float = 15.0,
height: float = 45.0,
max_volume: float = 5000.0, # 5mL
barcode: str = None,
) -> Bottle:
"""创建5ml分液瓶"""
return Bottle(
name=name,
diameter=diameter,
height=height,
max_volume=max_volume,
barcode=barcode,
model="5ml_Dispensing_Vial",
)
def YB_20ml_Dispensing_Vial(
name: str,
diameter: float = 20.0,
height: float = 65.0,
max_volume: float = 20000.0, # 20mL
barcode: str = None,
) -> Bottle:
"""创建20ml分液瓶"""
return Bottle(
name=name,
diameter=diameter,
height=height,
max_volume=max_volume,
barcode=barcode,
model="20ml_Dispensing_Vial",
)
def YB_Small_Solution_Bottle(
name: str,
diameter: float = 35.0,
height: float = 60.0,
max_volume: float = 40000.0, # 40mL
barcode: str = None,
) -> Bottle:
"""创建配液瓶(小)"""
return Bottle(
name=name,
diameter=diameter,
height=height,
max_volume=max_volume,
barcode=barcode,
model="Small_Solution_Bottle",
)
def YB_Large_Solution_Bottle(
name: str,
diameter: float = 55.0,
height: float = 90.0,
max_volume: float = 150000.0, # 150mL
barcode: str = None,
) -> Bottle:
"""创建配液瓶(大)"""
return Bottle(
name=name,
diameter=diameter,
height=height,
max_volume=max_volume,
barcode=barcode,
model="Large_Solution_Bottle",
)
def YB_Pipette_Tip(
name: str,
diameter: float = 10.0,
height: float = 50.0,
max_volume: float = 1000.0, # 1mL
barcode: str = None,
) -> Bottle:
"""创建枪头"""
return Bottle(
name=name,
diameter=diameter,
height=height,
max_volume=max_volume,
barcode=barcode,
model="Pipette_Tip",
)

View File

@@ -1,7 +1,6 @@
from os import name
from pylabrobot.resources import Deck, Coordinate, Rotation from pylabrobot.resources import Deck, Coordinate, Rotation
from unilabos.resources.bioyond.warehouses import bioyond_warehouse_1x4x4, bioyond_warehouse_1x4x2, bioyond_warehouse_liquid_and_lid_handling, bioyond_warehouse_1x2x2, bioyond_warehouse_1x3x3, bioyond_warehouse_10x1x1, bioyond_warehouse_3x3x1, bioyond_warehouse_3x3x1_2, bioyond_warehouse_5x1x1 from unilabos.resources.bioyond.warehouses import bioyond_warehouse_1x4x4, bioyond_warehouse_1x4x2, bioyond_warehouse_liquid_and_lid_handling
class BIOYOND_PolymerReactionStation_Deck(Deck): class BIOYOND_PolymerReactionStation_Deck(Deck):
@@ -67,60 +66,3 @@ class BIOYOND_PolymerPreparationStation_Deck(Deck):
for warehouse_name, warehouse in self.warehouses.items(): for warehouse_name, warehouse in self.warehouses.items():
self.assign_child_resource(warehouse, location=self.warehouse_locations[warehouse_name]) self.assign_child_resource(warehouse, location=self.warehouse_locations[warehouse_name])
class BIOYOND_YB_Deck(Deck):
def __init__(
self,
name: str = "YB_Deck",
size_x: float = 4150,
size_y: float = 1400.0,
size_z: float = 2670.0,
category: str = "deck",
setup: bool = False
) -> None:
super().__init__(name=name, size_x=4150.0, size_y=1400.0, size_z=2670.0)
if setup:
self.setup()
def setup(self) -> None:
# 添加仓库
self.warehouses = {
"321窗口": bioyond_warehouse_1x2x2("321窗口"),
"43窗口": bioyond_warehouse_1x2x2("43窗口"),
"手动传递窗左": bioyond_warehouse_1x3x3("手动传递窗左"),
"手动传递窗右": bioyond_warehouse_1x3x3("手动传递窗右"),
"加样头堆栈左": bioyond_warehouse_10x1x1("加样头堆栈左"),
"加样头堆栈右": bioyond_warehouse_10x1x1("加样头堆栈右"),
"15ml配液堆栈左": bioyond_warehouse_3x3x1("15ml配液堆栈左"),
"母液加样右": bioyond_warehouse_3x3x1_2("母液加样右"),
"大瓶母液堆栈左": bioyond_warehouse_5x1x1("大瓶母液堆栈左"),
"大瓶母液堆栈右": bioyond_warehouse_5x1x1("大瓶母液堆栈右"),
}
# warehouse 的位置
self.warehouse_locations = {
"321窗口": Coordinate(-150.0, 158.0, 0.0),
"43窗口": Coordinate(4160.0, 158.0, 0.0),
"手动传递窗左": Coordinate(-150.0, 877.0, 0.0),
"手动传递窗右": Coordinate(4160.0, 877.0, 0.0),
"加样头堆栈左": Coordinate(385.0, 1300.0, 0.0),
"加样头堆栈右": Coordinate(2187.0, 1300.0, 0.0),
"15ml配液堆栈左": Coordinate(749.0, 355.0, 0.0),
"母液加样右": Coordinate(2152.0, 333.0, 0.0),
"大瓶母液堆栈左": Coordinate(1164.0, 676.0, 0.0),
"大瓶母液堆栈右": Coordinate(2717.0, 676.0, 0.0),
}
for warehouse_name, warehouse in self.warehouses.items():
self.assign_child_resource(warehouse, location=self.warehouse_locations[warehouse_name])
def YB_Deck(name: str) -> Deck:
by=BIOYOND_YB_Deck(name=name)
by.setup()
return by

View File

@@ -34,113 +34,7 @@ def bioyond_warehouse_1x4x2(name: str) -> WareHouse:
category="warehouse", category="warehouse",
removed_positions=None removed_positions=None
) )
# 定义benyond的堆栈
def bioyond_warehouse_1x2x2(name: str) -> WareHouse:
"""创建BioYond 4x1x4仓库"""
return warehouse_factory(
name=name,
num_items_x=2,
num_items_y=2,
num_items_z=1,
dx=10.0,
dy=10.0,
dz=10.0,
item_dx=137.0,
item_dy=96.0,
item_dz=120.0,
category="YB_warehouse",
)
def bioyond_warehouse_10x1x1(name: str) -> WareHouse:
"""创建BioYond 4x1x4仓库"""
return warehouse_factory(
name=name,
num_items_x=10,
num_items_y=1,
num_items_z=1,
dx=10.0,
dy=10.0,
dz=10.0,
item_dx=137.0,
item_dy=96.0,
item_dz=120.0,
category="warehouse",
)
def bioyond_warehouse_1x3x3(name: str) -> WareHouse:
"""创建BioYond 4x1x4仓库"""
return warehouse_factory(
name=name,
num_items_x=1,
num_items_y=3,
num_items_z=3,
dx=10.0,
dy=10.0,
dz=10.0,
item_dx=137.0,
item_dy=96.0,
item_dz=120.0,
category="warehouse",
)
def bioyond_warehouse_2x1x3(name: str) -> WareHouse:
"""创建BioYond 4x1x4仓库"""
return warehouse_factory(
name=name,
num_items_x=2,
num_items_y=1,
num_items_z=3,
dx=10.0,
dy=10.0,
dz=10.0,
item_dx=137.0,
item_dy=96.0,
item_dz=120.0,
category="warehouse",
)
def bioyond_warehouse_3x3x1(name: str) -> WareHouse:
"""创建BioYond 4x1x4仓库"""
return warehouse_factory(
name=name,
num_items_x=3,
num_items_y=3,
num_items_z=1,
dx=10.0,
dy=10.0,
dz=10.0,
item_dx=137.0,
item_dy=96.0,
item_dz=120.0,
category="warehouse",
)
def bioyond_warehouse_5x1x1(name: str) -> WareHouse:
"""创建BioYond 4x1x4仓库"""
return warehouse_factory(
name=name,
num_items_x=5,
num_items_y=1,
num_items_z=1,
dx=10.0,
dy=10.0,
dz=10.0,
item_dx=137.0,
item_dy=96.0,
item_dz=120.0,
category="warehouse",
)
def bioyond_warehouse_3x3x1_2(name: str) -> WareHouse:
"""创建BioYond 4x1x4仓库"""
return warehouse_factory(
name=name,
num_items_x=3,
num_items_y=3,
num_items_z=1,
dx=12.0,
dy=12.0,
dz=12.0,
item_dx=137.0,
item_dy=96.0,
item_dz=120.0,
category="warehouse",
)
def bioyond_warehouse_liquid_and_lid_handling(name: str) -> WareHouse: def bioyond_warehouse_liquid_and_lid_handling(name: str) -> WareHouse:
"""创建BioYond开关盖加液模块台面""" """创建BioYond开关盖加液模块台面"""

View File

@@ -1,84 +1,67 @@
import json import json
from typing import Dict, Any
from pylabrobot.resources import Container
from unilabos_msgs.msg import Resource from unilabos_msgs.msg import Resource
from unilabos.ros.msgs.message_converter import convert_from_ros_msg from unilabos.ros.msgs.message_converter import convert_from_ros_msg
class RegularContainer(Container): class RegularContainer(object):
def __init__(self, *args, **kwargs): # 第一个参数必须是id传入
if "size_x" not in kwargs: # noinspection PyShadowingBuiltins
kwargs["size_x"] = 0 def __init__(self, id: str):
if "size_y" not in kwargs: self.id = id
kwargs["size_y"] = 0 self.ulr_resource = Resource()
if "size_z" not in kwargs: self._data = None
kwargs["size_z"] = 0
self.kwargs = kwargs
self.state = {}
super().__init__(*args, **kwargs)
def load_state(self, state: Dict[str, Any]): @property
self.state = state def ulr_resource_data(self):
# if self._data is None:
# class RegularContainer(object): self._data = json.loads(self.ulr_resource.data) if self.ulr_resource.data else {}
# # 第一个参数必须是id传入 return self._data
# # noinspection PyShadowingBuiltins
# def __init__(self, id: str): @ulr_resource_data.setter
# self.id = id def ulr_resource_data(self, value: dict):
# self.ulr_resource = Resource() self._data = value
# self._data = None self.ulr_resource.data = json.dumps(self._data)
#
# @property @property
# def ulr_resource_data(self): def liquid_type(self):
# if self._data is None: return self.ulr_resource_data.get("liquid_type", None)
# self._data = json.loads(self.ulr_resource.data) if self.ulr_resource.data else {}
# return self._data @liquid_type.setter
# def liquid_type(self, value: str):
# @ulr_resource_data.setter if value is not None:
# def ulr_resource_data(self, value: dict): self.ulr_resource_data["liquid_type"] = value
# self._data = value else:
# self.ulr_resource.data = json.dumps(self._data) self.ulr_resource_data.pop("liquid_type", None)
#
# @property @property
# def liquid_type(self): def liquid_volume(self):
# return self.ulr_resource_data.get("liquid_type", None) return self.ulr_resource_data.get("liquid_volume", None)
#
# @liquid_type.setter @liquid_volume.setter
# def liquid_type(self, value: str): def liquid_volume(self, value: float):
# if value is not None: if value is not None:
# self.ulr_resource_data["liquid_type"] = value self.ulr_resource_data["liquid_volume"] = value
# else: else:
# self.ulr_resource_data.pop("liquid_type", None) self.ulr_resource_data.pop("liquid_volume", None)
#
# @property def get_ulr_resource(self) -> Resource:
# def liquid_volume(self): """
# return self.ulr_resource_data.get("liquid_volume", None) 获取UlrResource对象
# :return: UlrResource对象
# @liquid_volume.setter """
# def liquid_volume(self, value: float): self.ulr_resource_data = self.ulr_resource_data # 确保数据被更新
# if value is not None: return self.ulr_resource
# self.ulr_resource_data["liquid_volume"] = value
# else: def get_ulr_resource_as_dict(self) -> Resource:
# self.ulr_resource_data.pop("liquid_volume", None) """
# 获取UlrResource对象
# def get_ulr_resource(self) -> Resource: :return: UlrResource对象
# """ """
# 获取UlrResource对象 to_dict = convert_from_ros_msg(self.get_ulr_resource())
# :return: UlrResource对象 to_dict["type"] = "container"
# """ return to_dict
# self.ulr_resource_data = self.ulr_resource_data # 确保数据被更新
# return self.ulr_resource def __str__(self):
# return f"{self.id}"
# def get_ulr_resource_as_dict(self) -> Resource:
# """
# 获取UlrResource对象
# :return: UlrResource对象
# """
# to_dict = convert_from_ros_msg(self.get_ulr_resource())
# to_dict["type"] = "container"
# return to_dict
#
# def __str__(self):
# return f"{self.id}"

View File

@@ -1,23 +1,18 @@
import importlib import importlib
import inspect import inspect
import json import json
import os.path
import traceback import traceback
from typing import Union, Any, Dict, List, Tuple from typing import Union, Any, Dict, List
import uuid
import networkx as nx import networkx as nx
from pylabrobot.resources import ResourceHolder from pylabrobot.resources import ResourceHolder
from unilabos_msgs.msg import Resource from unilabos_msgs.msg import Resource
from unilabos.config.config import BasicConfig
from unilabos.resources.container import RegularContainer from unilabos.resources.container import RegularContainer
from unilabos.resources.itemized_carrier import ItemizedCarrier
from unilabos.ros.msgs.message_converter import convert_to_ros_msg from unilabos.ros.msgs.message_converter import convert_to_ros_msg
from unilabos.ros.nodes.resource_tracker import ( from unilabos.ros.nodes.resource_tracker import (
ResourceDictInstance, ResourceDictInstance,
ResourceTreeSet, ResourceTreeSet,
) )
from unilabos.utils import logger
from unilabos.utils.banner_print import print_status from unilabos.utils.banner_print import print_status
try: try:
@@ -49,33 +44,6 @@ def canonicalize_nodes_data(
if node.get("label") is not None: if node.get("label") is not None:
node_id = node.pop("label") node_id = node.pop("label")
node["id"] = node["name"] = node_id node["id"] = node["name"] = node_id
if not isinstance(node.get("config"), dict):
node["config"] = {}
if not node.get("type"):
node["type"] = "device"
print_status(f"Warning: Node {node.get('id', 'unknown')} missing 'type', defaulting to 'device'", "warning")
if node.get("name", None) is None:
node["name"] = node.get("id")
print_status(f"Warning: Node {node.get('id', 'unknown')} missing 'name', defaulting to {node['name']}", "warning")
if not isinstance(node.get("position"), dict):
node["position"] = {"position": {}}
x = node.pop("x", None)
if x is not None:
node["position"]["position"]["x"] = x
y = node.pop("y", None)
if y is not None:
node["position"]["position"]["y"] = y
z = node.pop("z", None)
if z is not None:
node["position"]["position"]["z"] = z
if "sample_id" in node:
sample_id = node.pop("sample_id")
if sample_id:
logger.error(f"{node}的sample_id参数已弃用sample_id: {sample_id}")
for k in list(node.keys()):
if k not in ["id", "uuid", "name", "description", "schema", "model", "icon", "parent_uuid", "parent", "type", "class", "position", "config", "data", "children"]:
v = node.pop(k)
node["config"][k] = v
# 第二步处理parent_relation # 第二步处理parent_relation
id2idx = {node["id"]: idx for idx, node in enumerate(nodes)} id2idx = {node["id"]: idx for idx, node in enumerate(nodes)}
@@ -333,10 +301,6 @@ def read_graphml(graphml_file: str) -> tuple[nx.Graph, ResourceTreeSet, List[Dic
"nodes": [node.res_content.model_dump(by_alias=True) for node in resource_tree_set.all_nodes], "nodes": [node.res_content.model_dump(by_alias=True) for node in resource_tree_set.all_nodes],
"links": standardized_links, "links": standardized_links,
} }
dump_json_path = os.path.join(BasicConfig.working_dir, os.path.basename(graphml_file).rsplit(".")[0] + ".json")
with open(dump_json_path, "w", encoding="utf-8") as f:
f.write(json.dumps(graph_data, indent=4, ensure_ascii=False))
print_status(f"GraphML converted to JSON and saved to {dump_json_path}", "info")
physical_setup_graph = nx.node_link_graph(graph_data, link="links", multigraph=False) physical_setup_graph = nx.node_link_graph(graph_data, link="links", multigraph=False)
handle_communications(physical_setup_graph) handle_communications(physical_setup_graph)
@@ -612,49 +576,47 @@ def resource_plr_to_ulab(resource_plr: "ResourcePLR", parent_name: str = None, w
return r return r
def resource_bioyond_to_plr(bioyond_materials: list[dict], type_mapping: Dict[str, Tuple[str, str]] = {}, deck: Any = None) -> list[dict]: def resource_bioyond_to_plr(bioyond_materials: list[dict], type_mapping: dict = {}, deck: Any = None) -> list[dict]:
""" """
将 bioyond 物料格式转换为 ulab 物料格式 将 bioyond 物料格式转换为 ulab 物料格式
Args: Args:
bioyond_materials: bioyond 系统的物料查询结果列表 bioyond_materials: bioyond 系统的物料查询结果列表
type_mapping: 物料类型映射字典,格式 {bioyond_type: [plr_class_name, class_uuid]} type_mapping: 物料类型映射字典,格式 {bioyond_type: plr_class_name}
location_id_mapping: 库位 ID 到名称的映射字典,格式 {location_id: location_name} location_id_mapping: 库位 ID 到名称的映射字典,格式 {location_id: location_name}
Returns: Returns:
pylabrobot 格式的物料列表 pylabrobot 格式的物料列表
""" """
print("1:bioyond_materials:",bioyond_materials)
# print("2:type_mapping:",type_mapping)
plr_materials = [] plr_materials = []
for material in bioyond_materials: for material in bioyond_materials:
className = ( className = (
type_mapping.get(material.get("typeName"), ("RegularContainer", ""))[0] if type_mapping else "RegularContainer" type_mapping.get(material.get("typeName"), "RegularContainer") if type_mapping else "RegularContainer"
) )
plr_material: ResourcePLR = initialize_resource( plr_material: ResourcePLR = initialize_resource(
{"name": material["name"], "class": className}, resource_type=ResourcePLR {"name": material["name"], "class": className}, resource_type=ResourcePLR
) )
plr_material.code = material.get("code", "") and material.get("barCode", "") or "" plr_material.code = material.get("code", "") and material.get("barCode", "") or ""
plr_material.unilabos_uuid = str(uuid.uuid4())
# 处理子物料detail # 处理子物料detail
if material.get("detail") and len(material["detail"]) > 0: if material.get("detail") and len(material["detail"]) > 0:
for bottle in reversed(plr_material.children):
plr_material.unassign_child_resource(bottle)
child_ids = [] child_ids = []
for detail in material["detail"]: for detail in material["detail"]:
number = ( number = (
(detail.get("z", 0) - 1) * plr_material.num_items_x * plr_material.num_items_y (detail.get("z", 0) - 1) * plr_material.num_items_x * plr_material.num_items_y
+ (detail.get("y", 0) - 1) * plr_material.num_items_y + (detail.get("x", 0) - 1) * plr_material.num_items_x
+ (detail.get("x", 0) - 1) + (detail.get("y", 0) - 1)
) )
typeName = detail.get("typeName", detail.get("name", "")) bottle = plr_material[number]
if typeName in type_mapping: if detail["name"] in type_mapping:
bottle = plr_material[number] = initialize_resource( # plr_material.unassign_child_resource(bottle)
{"name": f'{detail["name"]}_{number}', "class": type_mapping[typeName][0]}, resource_type=ResourcePLR plr_material.sites[number] = None
plr_material[number] = initialize_resource(
{"name": f'{detail["name"]}_{number}', "class": type_mapping[detail["name"]]}, resource_type=ResourcePLR
) )
else:
bottle.tracker.liquids = [ bottle.tracker.liquids = [
(detail["name"], float(detail.get("quantity", 0)) if detail.get("quantity") else 0) (detail["name"], float(detail.get("quantity", 0)) if detail.get("quantity") else 0)
] ]
@@ -683,59 +645,32 @@ def resource_bioyond_to_plr(bioyond_materials: list[dict], type_mapping: Dict[st
return plr_materials return plr_materials
def resource_plr_to_bioyond(plr_resources: list[ResourcePLR], type_mapping: dict = {}, warehouse_mapping: dict = {}) -> list[dict]: def resource_plr_to_bioyond(plr_materials: list[ResourcePLR], type_mapping: dict = {}, warehouse_mapping: dict = {}) -> list[dict]:
bioyond_materials = [] bioyond_materials = []
for resource in plr_resources: for plr_material in plr_materials:
if hasattr(resource, "capacity") and resource.capacity > 1:
material = { material = {
"typeId": type_mapping.get(resource.model)[1], "name": plr_material.name,
"name": resource.name, "typeName": plr_material.__class__.__name__,
"unit": "", "code": plr_material.code,
"quantity": 1, "quantity": 0,
"details": [], "detail": [],
"Parameters": "{}" "locations": [],
} }
for bottle in resource.children: if hasattr(plr_material, "capacity") and plr_material.capacity > 1:
if isinstance(resource, ItemizedCarrier): for idx in range(plr_material.capacity):
site = resource.get_child_identifier(bottle) bottle = plr_material[idx]
else: detail = {
site = {"x": bottle.location.x - 1, "y": bottle.location.y - 1} "x": (idx // (plr_material.num_items_x * plr_material.num_items_y)) + 1,
detail_item = { "y": ((idx % (plr_material.num_items_x * plr_material.num_items_y)) // plr_material.num_items_x) + 1,
"typeId": type_mapping.get(bottle.model)[1], "z": (idx % plr_material.num_items_x) + 1,
"name": bottle.name,
"code": bottle.code if hasattr(bottle, "code") else "", "code": bottle.code if hasattr(bottle, "code") else "",
"quantity": sum(qty for _, qty in bottle.tracker.liquids) if hasattr(bottle, "tracker") else 0, "quantity": sum(qty for _, qty in bottle.tracker.liquids) if hasattr(bottle, "tracker") else 0,
"x": site["x"] + 1,
"y": site["y"] + 1,
"molecular": 1,
"Parameters": json.dumps({"molecular": 1})
} }
material["details"].append(detail_item) material["detail"].append(detail)
material["quantity"] = 1.0
else: else:
bottle = resource[0] if resource.capacity > 0 else resource bottle = plr_material[0] if plr_material.capacity > 0 else plr_material
material = { material["quantity"] = sum(qty for _, qty in bottle.tracker.liquids) if hasattr(plr_material, "tracker") else 0
"typeId": "3a14196b-24f2-ca49-9081-0cab8021bf1a",
"name": resource.get("name", ""),
"unit": "",
"quantity": sum(qty for _, qty in bottle.tracker.liquids) if hasattr(bottle, "tracker") else 0,
"Parameters": "{}"
}
if resource.parent is not None and isinstance(resource.parent, ItemizedCarrier):
site_in_parent = resource.parent.get_child_identifier(resource)
material["locations"] = [
{
"id": warehouse_mapping[resource.parent.name]["site_uuids"][site_in_parent["identifier"]],
"whid": warehouse_mapping[resource.parent.name]["uuid"],
"whName": resource.parent.name,
"x": site_in_parent["z"] + 1,
"y": site_in_parent["y"] + 1,
"z": 1,
"quantity": 0
}
],
print(f"material_data: {material}")
bioyond_materials.append(material) bioyond_materials.append(material)
return bioyond_materials return bioyond_materials
@@ -782,7 +717,6 @@ def initialize_resource(resource_config: dict, resource_type: Any = None) -> Uni
else: else:
r = resource_plr r = resource_plr
elif resource_class_config["type"] == "unilabos": elif resource_class_config["type"] == "unilabos":
raise ValueError(f"No more support for unilabos Resource class {resource_class_config}")
res_instance: RegularContainer = RESOURCE(id=resource_config["name"]) res_instance: RegularContainer = RESOURCE(id=resource_config["name"])
res_instance.ulr_resource = convert_to_ros_msg( res_instance.ulr_resource = convert_to_ros_msg(
Resource, {k: v for k, v in resource_config.items() if k != "class"} Resource, {k: v for k, v in resource_config.items() if k != "class"}

View File

@@ -32,7 +32,6 @@ class Bottle(Well):
barcode: Optional[str] = "", barcode: Optional[str] = "",
category: str = "container", category: str = "container",
model: Optional[str] = None, model: Optional[str] = None,
**kwargs,
): ):
super().__init__( super().__init__(
name=name, name=name,
@@ -74,7 +73,6 @@ class ItemizedCarrier(ResourcePLR):
num_items_x: int = 0, num_items_x: int = 0,
num_items_y: int = 0, num_items_y: int = 0,
num_items_z: int = 0, num_items_z: int = 0,
layout: str = "x-y",
sites: Optional[Dict[Union[int, str], Optional[ResourcePLR]]] = None, sites: Optional[Dict[Union[int, str], Optional[ResourcePLR]]] = None,
category: Optional[str] = "carrier", category: Optional[str] = "carrier",
model: Optional[str] = None, model: Optional[str] = None,
@@ -89,8 +87,6 @@ class ItemizedCarrier(ResourcePLR):
) )
self.num_items = len(sites) self.num_items = len(sites)
self.num_items_x, self.num_items_y, self.num_items_z = num_items_x, num_items_y, num_items_z self.num_items_x, self.num_items_y, self.num_items_z = num_items_x, num_items_y, num_items_z
self.layout = "z-y" if self.num_items_z > 1 and self.num_items_x == 1 else "x-z" if self.num_items_z > 1 and self.num_items_y == 1 else "x-y"
if isinstance(sites, dict): if isinstance(sites, dict):
sites = sites or {} sites = sites or {}
self.sites: List[Optional[ResourcePLR]] = list(sites.values()) self.sites: List[Optional[ResourcePLR]] = list(sites.values())
@@ -153,7 +149,7 @@ class ItemizedCarrier(ResourcePLR):
def assign_resource_to_site(self, resource: ResourcePLR, spot: int): def assign_resource_to_site(self, resource: ResourcePLR, spot: int):
if self.sites[spot] is not None and not isinstance(self.sites[spot], ResourceHolder): if self.sites[spot] is not None and not isinstance(self.sites[spot], ResourceHolder):
raise ValueError(f"spot {spot} already has a resource, {resource}") raise ValueError(f"spot {spot} already has a resource, {resource}")
self.assign_child_resource(resource, location=self.child_locations.get(list(self._ordering.keys())[spot]), spot=spot) self.assign_child_resource(resource, location=self.child_locations.get(str(spot)), spot=spot)
def unassign_child_resource(self, resource: ResourcePLR): def unassign_child_resource(self, resource: ResourcePLR):
found = False found = False
@@ -164,92 +160,8 @@ class ItemizedCarrier(ResourcePLR):
break break
if not found: if not found:
raise ValueError(f"Resource {resource} is not assigned to this carrier") raise ValueError(f"Resource {resource} is not assigned to this carrier")
super().unassign_child_resource(resource) if hasattr(resource, "unassign"):
# if hasattr(resource, "unassign"): resource.unassign()
# resource.unassign()
def get_child_identifier(self, child: ResourcePLR):
"""Get the identifier information for a given child resource.
Args:
child: The Resource object to find the identifier for
Returns:
dict: A dictionary containing:
- identifier: The string identifier (e.g. "A1", "B2")
- idx: The integer index in the sites list
- x: The x index (column index, 0-based)
- y: The y index (row index, 0-based)
- z: The z index (layer index, 0-based)
Raises:
ValueError: If the child resource is not found in this carrier
"""
# Find the child resource in sites
for idx, resource in enumerate(self.sites):
if resource is child:
# Get the identifier from ordering keys
identifier = list(self._ordering.keys())[idx]
# Parse identifier to get x, y, z indices
x_idx, y_idx, z_idx = self._parse_identifier_to_indices(identifier, idx)
return {
"identifier": identifier,
"idx": idx,
"x": x_idx,
"y": y_idx,
"z": z_idx
}
# If not found, raise an error
raise ValueError(f"Resource {child} is not assigned to this carrier")
def _parse_identifier_to_indices(self, identifier: str, idx: int) -> Tuple[int, int, int]:
"""Parse identifier string to get x, y, z indices.
Args:
identifier: String identifier like "A1", "B2", etc.
idx: Linear index as fallback for calculation
Returns:
Tuple of (x_idx, y_idx, z_idx)
"""
# If we have explicit dimensions, calculate from idx
if self.num_items_x > 0 and self.num_items_y > 0:
# Calculate 3D indices from linear index
z_idx = idx // (self.num_items_x * self.num_items_y) if self.num_items_z > 0 else 0
remaining = idx % (self.num_items_x * self.num_items_y)
y_idx = remaining // self.num_items_x
x_idx = remaining % self.num_items_x
return x_idx, y_idx, z_idx
# Fallback: parse from Excel-style identifier
if isinstance(identifier, str) and len(identifier) >= 2:
# Extract row (letter) and column (number)
row_letters = ""
col_numbers = ""
for char in identifier:
if char.isalpha():
row_letters += char
elif char.isdigit():
col_numbers += char
if row_letters and col_numbers:
# Convert letter(s) to row index (A=0, B=1, etc.)
y_idx = 0
for char in row_letters:
y_idx = y_idx * 26 + (ord(char.upper()) - ord('A'))
# Convert number to column index (1-based to 0-based)
x_idx = int(col_numbers) - 1
z_idx = 0 # Default layer
return x_idx, y_idx, z_idx
# If all else fails, assume linear arrangement
return idx, 0, 0
def __getitem__( def __getitem__(
self, self,
@@ -407,7 +319,6 @@ class ItemizedCarrier(ResourcePLR):
"num_items_x": self.num_items_x, "num_items_x": self.num_items_x,
"num_items_y": self.num_items_y, "num_items_y": self.num_items_y,
"num_items_z": self.num_items_z, "num_items_z": self.num_items_z,
"layout": self.layout,
"sites": [{ "sites": [{
"label": str(identifier), "label": str(identifier),
"visible": True if self[identifier] is not None else False, "visible": True if self[identifier] is not None else False,
@@ -433,7 +344,6 @@ class BottleCarrier(ItemizedCarrier):
sites: Optional[Dict[Union[int, str], ResourceHolder]] = None, sites: Optional[Dict[Union[int, str], ResourceHolder]] = None,
category: str = "bottle_carrier", category: str = "bottle_carrier",
model: Optional[str] = None, model: Optional[str] = None,
**kwargs,
): ):
super().__init__( super().__init__(
name=name, name=name,

View File

@@ -5,13 +5,10 @@ from pylabrobot.resources.carrier import ResourceHolder, create_homogeneous_reso
from unilabos.resources.itemized_carrier import ItemizedCarrier, ResourcePLR from unilabos.resources.itemized_carrier import ItemizedCarrier, ResourcePLR
LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
def warehouse_factory( def warehouse_factory(
name: str, name: str,
num_items_x: int = 1, num_items_x: int = 4,
num_items_y: int = 4, num_items_y: int = 1,
num_items_z: int = 4, num_items_z: int = 4,
dx: float = 137.0, dx: float = 137.0,
dy: float = 96.0, dy: float = 96.0,
@@ -36,16 +33,13 @@ def warehouse_factory(
locations.append(Coordinate(x, y, z)) locations.append(Coordinate(x, y, z))
if removed_positions: if removed_positions:
locations = [loc for i, loc in enumerate(locations) if i not in removed_positions] locations = [loc for i, loc in enumerate(locations) if i not in removed_positions]
_sites = create_homogeneous_resources( sites = create_homogeneous_resources(
klass=ResourceHolder, klass=ResourceHolder,
locations=locations, locations=locations,
resource_size_x=127.0, resource_size_x=127.0,
resource_size_y=86.0, resource_size_y=86.0,
name_prefix=name, name_prefix=name,
) )
len_x, len_y = (num_items_x, num_items_y) if num_items_z == 1 else (num_items_y, num_items_z) if num_items_x == 1 else (num_items_x, num_items_z)
keys = [f"{LETTERS[j]}{i + 1}" for i in range(len_x) for j in range(len_y)]
sites = {i: site for i, site in zip(keys, _sites.values())}
return WareHouse( return WareHouse(
name=name, name=name,
@@ -74,7 +68,6 @@ class WareHouse(ItemizedCarrier):
num_items_x: int, num_items_x: int,
num_items_y: int, num_items_y: int,
num_items_z: int, num_items_z: int,
layout: str = "x-y",
sites: Optional[Dict[Union[int, str], Optional[ResourcePLR]]] = None, sites: Optional[Dict[Union[int, str], Optional[ResourcePLR]]] = None,
category: str = "warehouse", category: str = "warehouse",
model: Optional[str] = None, model: Optional[str] = None,
@@ -90,7 +83,6 @@ class WareHouse(ItemizedCarrier):
num_items_x=num_items_x, num_items_x=num_items_x,
num_items_y=num_items_y, num_items_y=num_items_y,
num_items_z=num_items_z, num_items_z=num_items_z,
layout=layout,
sites=sites, sites=sites,
category=category, category=category,
model=model, model=model,

View File

@@ -26,7 +26,6 @@ def initialize_device_from_dict(device_id, device_config) -> Optional[ROS2Device
d = None d = None
original_device_config = copy.deepcopy(device_config) original_device_config = copy.deepcopy(device_config)
device_class_config = device_config["class"] device_class_config = device_config["class"]
uid = device_config["uuid"]
if isinstance(device_class_config, str): # 如果是字符串则直接去lab_registry中查找获取class if isinstance(device_class_config, str): # 如果是字符串则直接去lab_registry中查找获取class
if len(device_class_config) == 0: if len(device_class_config) == 0:
raise DeviceClassInvalid(f"Device [{device_id}] class cannot be an empty string. {device_config}") raise DeviceClassInvalid(f"Device [{device_id}] class cannot be an empty string. {device_config}")
@@ -51,7 +50,7 @@ def initialize_device_from_dict(device_id, device_config) -> Optional[ROS2Device
) )
try: try:
d = DEVICE( d = DEVICE(
device_id=device_id, device_uuid=uid, driver_is_ros=device_class_config["type"] == "ros2", driver_params=device_config.get("config", {}) device_id=device_id, driver_is_ros=device_class_config["type"] == "ros2", driver_params=device_config.get("config", {})
) )
except DeviceInitError as ex: except DeviceInitError as ex:
return d return d

View File

@@ -6,7 +6,7 @@ import threading
import time import time
import traceback import traceback
import uuid import uuid
from typing import get_type_hints, TypeVar, Generic, Dict, Any, Type, TypedDict, Optional, List, TYPE_CHECKING, Union from typing import get_type_hints, TypeVar, Generic, Dict, Any, Type, TypedDict, Optional, List, TYPE_CHECKING
from concurrent.futures import ThreadPoolExecutor from concurrent.futures import ThreadPoolExecutor
import asyncio import asyncio
@@ -132,7 +132,6 @@ class ROSLoggerAdapter:
def init_wrapper( def init_wrapper(
self, self,
device_id: str, device_id: str,
device_uuid: str,
driver_class: type[T], driver_class: type[T],
device_config: Dict[str, Any], device_config: Dict[str, Any],
status_types: Dict[str, Any], status_types: Dict[str, Any],
@@ -151,7 +150,6 @@ def init_wrapper(
if children is None: if children is None:
children = [] children = []
kwargs["device_id"] = device_id kwargs["device_id"] = device_id
kwargs["device_uuid"] = device_uuid
kwargs["driver_class"] = driver_class kwargs["driver_class"] = driver_class
kwargs["device_config"] = device_config kwargs["device_config"] = device_config
kwargs["driver_params"] = driver_params kwargs["driver_params"] = driver_params
@@ -268,7 +266,6 @@ class BaseROS2DeviceNode(Node, Generic[T]):
self, self,
driver_instance: T, driver_instance: T,
device_id: str, device_id: str,
device_uuid: str,
status_types: Dict[str, Any], status_types: Dict[str, Any],
action_value_mappings: Dict[str, Any], action_value_mappings: Dict[str, Any],
hardware_interface: Dict[str, Any], hardware_interface: Dict[str, Any],
@@ -281,7 +278,6 @@ class BaseROS2DeviceNode(Node, Generic[T]):
Args: Args:
driver_instance: 设备实例 driver_instance: 设备实例
device_id: 设备标识符 device_id: 设备标识符
device_uuid: 设备标识符
status_types: 需要发布的状态和传感器信息 status_types: 需要发布的状态和传感器信息
action_value_mappings: 设备动作 action_value_mappings: 设备动作
hardware_interface: 硬件接口配置 hardware_interface: 硬件接口配置
@@ -289,7 +285,7 @@ class BaseROS2DeviceNode(Node, Generic[T]):
""" """
self.driver_instance = driver_instance self.driver_instance = driver_instance
self.device_id = device_id self.device_id = device_id
self.uuid = device_uuid self.uuid = str(uuid.uuid4())
self.publish_high_frequency = False self.publish_high_frequency = False
self.callback_group = ReentrantCallbackGroup() self.callback_group = ReentrantCallbackGroup()
self.resource_tracker = resource_tracker self.resource_tracker = resource_tracker
@@ -558,11 +554,6 @@ class BaseROS2DeviceNode(Node, Generic[T]):
async def update_resource(self, resources: List["ResourcePLR"]): async def update_resource(self, resources: List["ResourcePLR"]):
r = SerialCommand.Request() r = SerialCommand.Request()
tree_set = ResourceTreeSet.from_plr_resources(resources) tree_set = ResourceTreeSet.from_plr_resources(resources)
for tree in tree_set.trees:
root_node = tree.root_node
if not root_node.res_content.uuid_parent:
logger.warning(f"更新无父节点物料{root_node},自动以当前设备作为根节点")
root_node.res_content.parent_uuid = self.uuid
r.command = json.dumps({"data": {"data": tree_set.dump()}, "action": "update"}) r.command = json.dumps({"data": {"data": tree_set.dump()}, "action": "update"})
response: SerialCommand_Response = await self._resource_clients["c2s_update_resource_tree"].call_async(r) # type: ignore response: SerialCommand_Response = await self._resource_clients["c2s_update_resource_tree"].call_async(r) # type: ignore
try: try:
@@ -582,7 +573,6 @@ class BaseROS2DeviceNode(Node, Generic[T]):
- update: 更新现有资源 - update: 更新现有资源
- remove: 从资源树中移除资源 - remove: 从资源树中移除资源
""" """
from pylabrobot.resources.resource import Resource as ResourcePLR
try: try:
data = json.loads(req.command) data = json.loads(req.command)
results = [] results = []
@@ -658,28 +648,15 @@ class BaseROS2DeviceNode(Node, Generic[T]):
results.append({"success": True, "action": "update"}) results.append({"success": True, "action": "update"})
elif action == "remove": elif action == "remove":
# 移除资源 # 移除资源
found_resources: List[List[Union[ResourcePLR, dict]]] = self.resource_tracker.figure_resource( plr_resources: List[ResourcePLR] = [
[{"uuid": uid} for uid in resources_uuid], try_mode=True self.resource_tracker.uuid_to_resources[i] for i in resources_uuid
) ]
found_plr_resources = []
other_plr_resources = []
for found_resource in found_resources:
for resource in found_resource:
if issubclass(resource.__class__, ResourcePLR):
found_plr_resources.append(resource)
else:
other_plr_resources.append(resource)
func = getattr(self.driver_instance, "resource_tree_remove", None) func = getattr(self.driver_instance, "resource_tree_remove", None)
if callable(func): if callable(func):
func(found_plr_resources) func(plr_resources)
for plr_resource in found_plr_resources: for plr_resource in plr_resources:
if plr_resource.parent is not None:
plr_resource.parent.unassign_child_resource(plr_resource) plr_resource.parent.unassign_child_resource(plr_resource)
self.resource_tracker.remove_resource(plr_resource) self.resource_tracker.remove_resource(plr_resource)
self.lab_logger().info(f"移除物料 {plr_resource} 及其子节点")
for other_plr_resource in other_plr_resources:
self.resource_tracker.remove_resource(other_plr_resource)
self.lab_logger().info(f"移除物料 {other_plr_resource} 及其子节点")
results.append({"success": True, "action": "remove"}) results.append({"success": True, "action": "remove"})
except Exception as e: except Exception as e:
error_msg = f"Error processing {action} operation: {str(e)}" error_msg = f"Error processing {action} operation: {str(e)}"
@@ -943,7 +920,7 @@ class BaseROS2DeviceNode(Node, Generic[T]):
queried_resources = [] queried_resources = []
for resource_data in resource_inputs: for resource_data in resource_inputs:
r = SerialCommand.Request() r = SerialCommand.Request()
r.command = json.dumps({"id": resource_data["id"], "uuid": resource_data.get("uuid", None), "with_children": True}) r.command = json.dumps({"id": resource_data["id"], "with_children": True})
# 发送请求并等待响应 # 发送请求并等待响应
response: SerialCommand_Response = await self._resource_clients[ response: SerialCommand_Response = await self._resource_clients[
"resource_get" "resource_get"
@@ -959,10 +936,7 @@ class BaseROS2DeviceNode(Node, Generic[T]):
# 通过资源跟踪器获取本地实例 # 通过资源跟踪器获取本地实例
final_resources = queried_resources if is_sequence else queried_resources[0] final_resources = queried_resources if is_sequence else queried_resources[0]
final_resources = self.resource_tracker.figure_resource({"name": final_resources.name}, try_mode=False) if not is_sequence else [ action_kwargs[k] = self.resource_tracker.figure_resource(final_resources, try_mode=False)
self.resource_tracker.figure_resource({"name": res.name}, try_mode=False) for res in queried_resources
]
action_kwargs[k] = final_resources
except Exception as e: except Exception as e:
self.lab_logger().error(f"{action_name} 物料实例获取失败: {e}\n{traceback.format_exc()}") self.lab_logger().error(f"{action_name} 物料实例获取失败: {e}\n{traceback.format_exc()}")
@@ -1373,7 +1347,6 @@ class ROS2DeviceNode:
def __init__( def __init__(
self, self,
device_id: str, device_id: str,
device_uuid: str,
driver_class: Type[T], driver_class: Type[T],
device_config: Dict[str, Any], device_config: Dict[str, Any],
driver_params: Dict[str, Any], driver_params: Dict[str, Any],
@@ -1389,7 +1362,6 @@ class ROS2DeviceNode:
Args: Args:
device_id: 设备标识符 device_id: 设备标识符
device_uuid: 设备uuid
driver_class: 设备类 driver_class: 设备类
device_config: 原始初始化的json device_config: 原始初始化的json
driver_params: driver初始化的参数 driver_params: driver初始化的参数
@@ -1464,7 +1436,6 @@ class ROS2DeviceNode:
children=children, children=children,
driver_instance=self._driver_instance, # type: ignore driver_instance=self._driver_instance, # type: ignore
device_id=device_id, device_id=device_id,
device_uuid=device_uuid,
status_types=status_types, status_types=status_types,
action_value_mappings=action_value_mappings, action_value_mappings=action_value_mappings,
hardware_interface=hardware_interface, hardware_interface=hardware_interface,
@@ -1475,7 +1446,6 @@ class ROS2DeviceNode:
self._ros_node = BaseROS2DeviceNode( self._ros_node = BaseROS2DeviceNode(
driver_instance=self._driver_instance, driver_instance=self._driver_instance,
device_id=device_id, device_id=device_id,
device_uuid=device_uuid,
status_types=status_types, status_types=status_types,
action_value_mappings=action_value_mappings, action_value_mappings=action_value_mappings,
hardware_interface=hardware_interface, hardware_interface=hardware_interface,

View File

@@ -18,7 +18,7 @@ from unilabos_msgs.srv import (
ResourceDelete, ResourceDelete,
ResourceUpdate, ResourceUpdate,
ResourceList, ResourceList,
SerialCommand, ResourceGet, SerialCommand,
) # type: ignore ) # type: ignore
from unilabos_msgs.srv._serial_command import SerialCommand_Request, SerialCommand_Response from unilabos_msgs.srv._serial_command import SerialCommand_Request, SerialCommand_Response
from unique_identifier_msgs.msg import UUID from unique_identifier_msgs.msg import UUID
@@ -41,7 +41,6 @@ from unilabos.ros.nodes.resource_tracker import (
ResourceTreeSet, ResourceTreeSet,
ResourceTreeInstance, ResourceTreeInstance,
) )
from unilabos.utils import logger
from unilabos.utils.exception import DeviceClassInvalid from unilabos.utils.exception import DeviceClassInvalid
from unilabos.utils.type_check import serialize_result_info from unilabos.utils.type_check import serialize_result_info
from unilabos.registry.placeholder_type import ResourceSlot, DeviceSlot from unilabos.registry.placeholder_type import ResourceSlot, DeviceSlot
@@ -100,6 +99,17 @@ class HostNode(BaseROS2DeviceNode):
""" """
if self._instance is not None: if self._instance is not None:
self._instance.lab_logger().critical("[Host Node] HostNode instance already exists.") self._instance.lab_logger().critical("[Host Node] HostNode instance already exists.")
# 初始化Node基类传递空参数覆盖列表
BaseROS2DeviceNode.__init__(
self,
driver_instance=self,
device_id=device_id,
status_types={},
action_value_mappings=lab_registry.device_type_registry["host_node"]["class"]["action_value_mappings"],
hardware_interface={},
print_publish=False,
resource_tracker=self._resource_tracker, # host node并不是通过initialize 包一层传进来的
)
# 设置单例实例 # 设置单例实例
self.__class__._instance = self self.__class__._instance = self
@@ -117,91 +127,6 @@ class HostNode(BaseROS2DeviceNode):
bridges = [] bridges = []
self.bridges = bridges self.bridges = bridges
# 创建 host_node 作为一个单独的 ResourceTree
host_node_dict = {
"id": "host_node",
"uuid": str(uuid.uuid4()),
"parent_uuid": "",
"name": "host_node",
"type": "device",
"class": "host_node",
"config": {},
"data": {},
"children": [],
"description": "",
"schema": {},
"model": {},
"icon": "",
}
# 创建 host_node 的 ResourceTree
host_node_instance = ResourceDictInstance.get_resource_instance_from_dict(host_node_dict)
host_node_tree = ResourceTreeInstance(host_node_instance)
resources_config.trees.insert(0, host_node_tree)
try:
for bridge in self.bridges:
if hasattr(bridge, "resource_tree_add") and resources_config:
from unilabos.app.web.client import HTTPClient
client: HTTPClient = bridge
resource_start_time = time.time()
# 传递 ResourceTreeSet 对象,在 client 中转换为字典并获取 UUID 映射
uuid_mapping = client.resource_tree_add(resources_config, "", True)
device_uuid = resources_config.root_nodes[0].res_content.uuid
resource_end_time = time.time()
logger.info(
f"[Host Node-Resource] 物料上传 {round(resource_end_time - resource_start_time, 5) * 1000} ms"
)
for edge in self.resources_edge_config:
edge["source_uuid"] = uuid_mapping.get(edge["source_uuid"], edge["source_uuid"])
edge["target_uuid"] = uuid_mapping.get(edge["target_uuid"], edge["target_uuid"])
resource_add_res = client.resource_edge_add(self.resources_edge_config)
resource_edge_end_time = time.time()
logger.info(
f"[Host Node-Resource] 物料关系上传 {round(resource_edge_end_time - resource_end_time, 5) * 1000} ms"
)
# resources_config 通过各个设备的 resource_tracker 进行uuid更新利用uuid_mapping
# resources_config 的 root node 是
# # 创建反向映射new_uuid -> old_uuid
# reverse_uuid_mapping = {new_uuid: old_uuid for old_uuid, new_uuid in uuid_mapping.items()}
# for tree in resources_config.trees:
# node = tree.root_node
# if node.res_content.type == "device":
# if node.res_content.id == "host_node":
# continue
# # slave节点走c2s更新接口拿到add自行update uuid
# device_tracker = self.devices_instances[node.res_content.id].resource_tracker
# old_uuid = reverse_uuid_mapping.get(node.res_content.uuid)
# if old_uuid:
# # 找到旧UUID使用UUID查找
# resource_instance = device_tracker.uuid_to_resources.get(old_uuid)
# else:
# # 未找到旧UUID使用name查找
# resource_instance = device_tracker.figure_resource(
# {"name": node.res_content.name}
# )
# device_tracker.loop_update_uuid(resource_instance, uuid_mapping)
# else:
# try:
# for plr_resource in ResourceTreeSet([tree]).to_plr_resources():
# self.resource_tracker.add_resource(plr_resource)
# except Exception as ex:
# self.lab_logger().warning("[Host Node-Resource] 根节点物料序列化失败!")
except Exception as ex:
logger.error(f"[Host Node-Resource] 添加物料出错!\n{traceback.format_exc()}")
# 初始化Node基类传递空参数覆盖列表
BaseROS2DeviceNode.__init__(
self,
driver_instance=self,
device_id=device_id,
device_uuid=host_node_dict["uuid"],
status_types={},
action_value_mappings=lab_registry.device_type_registry["host_node"]["class"]["action_value_mappings"],
hardware_interface={},
print_publish=False,
resource_tracker=self._resource_tracker, # host node并不是通过initialize 包一层传进来的
)
# 创建设备、动作客户端和目标存储 # 创建设备、动作客户端和目标存储
self.devices_names: Dict[str, str] = {device_id: self.namespace} # 存储设备名称和命名空间的映射 self.devices_names: Dict[str, str] = {device_id: self.namespace} # 存储设备名称和命名空间的映射
self.devices_instances: Dict[str, ROS2DeviceNode] = {} # 存储设备实例 self.devices_instances: Dict[str, ROS2DeviceNode] = {} # 存储设备实例
@@ -282,7 +207,81 @@ class HostNode(BaseROS2DeviceNode):
].items(): ].items():
controller_config["update_rate"] = update_rate controller_config["update_rate"] = update_rate
self.initialize_controller(controller_id, controller_config) self.initialize_controller(controller_id, controller_config)
# 创建 host_node 作为一个单独的 ResourceTree
host_node_dict = {
"id": "host_node",
"uuid": str(uuid.uuid4()),
"parent_uuid": "",
"name": "host_node",
"type": "device",
"class": "host_node",
"config": {},
"data": {},
"children": [],
"description": "",
"schema": {},
"model": {},
"icon": "",
}
# 创建 host_node 的 ResourceTree
host_node_instance = ResourceDictInstance.get_resource_instance_from_dict(host_node_dict)
host_node_tree = ResourceTreeInstance(host_node_instance)
resources_config.trees.insert(0, host_node_tree)
try:
for bridge in self.bridges:
if hasattr(bridge, "resource_tree_add") and resources_config:
from unilabos.app.web.client import HTTPClient
client: HTTPClient = bridge
resource_start_time = time.time()
# 传递 ResourceTreeSet 对象,在 client 中转换为字典并获取 UUID 映射
uuid_mapping = client.resource_tree_add(resources_config, "", True)
resource_end_time = time.time()
self.lab_logger().info(
f"[Host Node-Resource] 物料上传 {round(resource_end_time - resource_start_time, 5) * 1000} ms"
)
for edge in self.resources_edge_config:
edge["source_uuid"] = uuid_mapping.get(edge["source_uuid"], edge["source_uuid"])
edge["target_uuid"] = uuid_mapping.get(edge["target_uuid"], edge["target_uuid"])
resource_add_res = client.resource_edge_add(self.resources_edge_config)
resource_edge_end_time = time.time()
self.lab_logger().info(
f"[Host Node-Resource] 物料关系上传 {round(resource_edge_end_time - resource_end_time, 5) * 1000} ms"
)
# resources_config 通过各个设备的 resource_tracker 进行uuid更新利用uuid_mapping
# resources_config 的 root node 是
# 创建反向映射new_uuid -> old_uuid
reverse_uuid_mapping = {new_uuid: old_uuid for old_uuid, new_uuid in uuid_mapping.items()}
for tree in resources_config.trees:
node = tree.root_node
if node.res_content.type == "device":
for sub_node in node.children:
# 只有二级子设备
if sub_node.res_content.type != "device":
# slave节点走c2s更新接口拿到add自行update uuid
device_tracker = self.devices_instances[node.res_content.id].resource_tracker
# sub_node.res_content.uuid 已经是新UUID需要用旧UUID去查找
old_uuid = reverse_uuid_mapping.get(sub_node.res_content.uuid)
if old_uuid:
# 找到旧UUID使用UUID查找
resource_instance = device_tracker.figure_resource({"uuid": old_uuid})
else:
# 未找到旧UUID使用name查找
resource_instance = device_tracker.figure_resource(
{"name": sub_node.res_content.name}
)
device_tracker.loop_update_uuid(resource_instance, uuid_mapping)
else:
try:
for plr_resource in ResourceTreeSet([tree]).to_plr_resources():
self.resource_tracker.add_resource(plr_resource)
except Exception as ex:
self.lab_logger().warning("[Host Node-Resource] 根节点物料序列化失败!")
except Exception as ex:
self.lab_logger().error("[Host Node-Resource] 添加物料出错!")
self.lab_logger().error(traceback.format_exc())
# 创建定时器,定期发现设备 # 创建定时器,定期发现设备
self._discovery_timer = self.create_timer( self._discovery_timer = self.create_timer(
discovery_interval, self._discovery_devices_callback, callback_group=ReentrantCallbackGroup() discovery_interval, self._discovery_devices_callback, callback_group=ReentrantCallbackGroup()
@@ -863,7 +862,7 @@ class HostNode(BaseROS2DeviceNode):
), ),
} }
async def _resource_tree_action_add_callback(self, data: dict, response: SerialCommand_Response): # OK def _resource_tree_action_add_callback(self, data: dict, response: SerialCommand_Response): # OK
resource_tree_set = ResourceTreeSet.load(data["data"]) resource_tree_set = ResourceTreeSet.load(data["data"])
mount_uuid = data["mount_uuid"] mount_uuid = data["mount_uuid"]
first_add = data["first_add"] first_add = data["first_add"]
@@ -904,7 +903,7 @@ class HostNode(BaseROS2DeviceNode):
response.response = json.dumps(uuid_mapping) if success else "FAILED" response.response = json.dumps(uuid_mapping) if success else "FAILED"
self.lab_logger().info(f"[Host Node-Resource] Resource tree add completed, success: {success}") self.lab_logger().info(f"[Host Node-Resource] Resource tree add completed, success: {success}")
async def _resource_tree_action_get_callback(self, data: dict, response: SerialCommand_Response): # OK def _resource_tree_action_get_callback(self, data: dict, response: SerialCommand_Response): # OK
uuid_list: List[str] = data["data"] uuid_list: List[str] = data["data"]
with_children: bool = data["with_children"] with_children: bool = data["with_children"]
from unilabos.app.web.client import http_client from unilabos.app.web.client import http_client
@@ -912,7 +911,7 @@ class HostNode(BaseROS2DeviceNode):
resource_response = http_client.resource_tree_get(uuid_list, with_children) resource_response = http_client.resource_tree_get(uuid_list, with_children)
response.response = json.dumps(resource_response) response.response = json.dumps(resource_response)
async def _resource_tree_action_remove_callback(self, data: dict, response: SerialCommand_Response): def _resource_tree_action_remove_callback(self, data: dict, response: SerialCommand_Response):
""" """
子节点通知Host物料树删除 子节点通知Host物料树删除
""" """
@@ -920,7 +919,7 @@ class HostNode(BaseROS2DeviceNode):
response.response = "OK" response.response = "OK"
self.lab_logger().info(f"[Host Node-Resource] Resource tree remove completed") self.lab_logger().info(f"[Host Node-Resource] Resource tree remove completed")
async def _resource_tree_action_update_callback(self, data: dict, response: SerialCommand_Response): def _resource_tree_action_update_callback(self, data: dict, response: SerialCommand_Response):
""" """
子节点通知Host物料树更新 子节点通知Host物料树更新
""" """
@@ -933,17 +932,8 @@ class HostNode(BaseROS2DeviceNode):
from unilabos.app.web.client import http_client from unilabos.app.web.client import http_client
uuid_to_trees: Dict[str, List[ResourceTreeInstance]] = collections.defaultdict(list)
for tree in resource_tree_set.trees:
uuid_to_trees[tree.root_node.res_content.parent_uuid].append(tree)
for uid, trees in uuid_to_trees.items():
new_tree_set = ResourceTreeSet(trees)
resource_start_time = time.time() resource_start_time = time.time()
self.lab_logger().info( uuid_mapping = http_client.resource_tree_update(resource_tree_set, "", False)
f"[Host Node-Resource] 物料 {[root_node.res_content.id for root_node in new_tree_set.root_nodes]} {uid} 挂载 {trees[0].root_node.res_content.parent_uuid} 请求更新上传"
)
uuid_mapping = http_client.resource_tree_add(new_tree_set, uid, False)
success = bool(uuid_mapping) success = bool(uuid_mapping)
resource_end_time = time.time() resource_end_time = time.time()
self.lab_logger().info( self.lab_logger().info(
@@ -955,7 +945,7 @@ class HostNode(BaseROS2DeviceNode):
response.response = json.dumps(uuid_mapping) response.response = json.dumps(uuid_mapping)
self.lab_logger().info(f"[Host Node-Resource] Resource tree add completed, success: {success}") self.lab_logger().info(f"[Host Node-Resource] Resource tree add completed, success: {success}")
async def _resource_tree_update_callback(self, request: SerialCommand_Request, response: SerialCommand_Response): def _resource_tree_update_callback(self, request: SerialCommand_Request, response: SerialCommand_Response):
""" """
子节点通知Host物料树更新 子节点通知Host物料树更新
@@ -968,13 +958,13 @@ class HostNode(BaseROS2DeviceNode):
action = data["action"] action = data["action"]
data = data["data"] data = data["data"]
if action == "add": if action == "add":
await self._resource_tree_action_add_callback(data, response) self._resource_tree_action_add_callback(data, response)
elif action == "get": elif action == "get":
await self._resource_tree_action_get_callback(data, response) self._resource_tree_action_get_callback(data, response)
elif action == "update": elif action == "update":
await self._resource_tree_action_update_callback(data, response) self._resource_tree_action_update_callback(data, response)
elif action == "remove": elif action == "remove":
await self._resource_tree_action_remove_callback(data, response) self._resource_tree_action_remove_callback(data, response)
else: else:
self.lab_logger().error(f"[Host Node-Resource] Invalid action: {action}") self.lab_logger().error(f"[Host Node-Resource] Invalid action: {action}")
response.response = "ERROR" response.response = "ERROR"
@@ -1070,12 +1060,7 @@ class HostNode(BaseROS2DeviceNode):
""" """
try: try:
data = json.loads(request.command) data = json.loads(request.command)
if "uuid" in data and data["uuid"] is not None:
http_req = self.bridges[-1].resource_tree_get([data["uuid"]], data["with_children"])
elif "id" in data and data["id"].startswith("/"):
http_req = self.bridges[-1].resource_get(data["id"], data["with_children"]) http_req = self.bridges[-1].resource_get(data["id"], data["with_children"])
else:
raise ValueError("没有使用正确的物料 id 或 uuid")
response.response = json.dumps(http_req["data"]) response.response = json.dumps(http_req["data"])
return response return response
except Exception as e: except Exception as e:

View File

@@ -6,14 +6,13 @@ from typing import List, Dict, Any, Optional, TYPE_CHECKING
import rclpy import rclpy
from rosidl_runtime_py import message_to_ordereddict from rosidl_runtime_py import message_to_ordereddict
from unilabos_msgs.msg import Resource
from unilabos_msgs.srv import ResourceUpdate
from unilabos.messages import * # type: ignore # protocol names from unilabos.messages import * # type: ignore # protocol names
from rclpy.action import ActionServer, ActionClient from rclpy.action import ActionServer, ActionClient
from rclpy.action.server import ServerGoalHandle from rclpy.action.server import ServerGoalHandle
from rclpy.callback_groups import ReentrantCallbackGroup from rclpy.callback_groups import ReentrantCallbackGroup
from unilabos_msgs.srv._serial_command import SerialCommand_Request, SerialCommand_Response from unilabos_msgs.msg import Resource # type: ignore
from unilabos_msgs.srv import ResourceGet, ResourceUpdate # type: ignore
from unilabos.compile import action_protocol_generators from unilabos.compile import action_protocol_generators
from unilabos.resources.graphio import list_to_nested_dict, nested_dict_to_list from unilabos.resources.graphio import list_to_nested_dict, nested_dict_to_list
@@ -21,11 +20,11 @@ from unilabos.ros.initialize_device import initialize_device_from_dict
from unilabos.ros.msgs.message_converter import ( from unilabos.ros.msgs.message_converter import (
get_action_type, get_action_type,
convert_to_ros_msg, convert_to_ros_msg,
convert_from_ros_msg,
convert_from_ros_msg_with_mapping, convert_from_ros_msg_with_mapping,
) )
from unilabos.ros.nodes.base_device_node import BaseROS2DeviceNode, DeviceNodeResourceTracker, ROS2DeviceNode from unilabos.ros.nodes.base_device_node import BaseROS2DeviceNode, DeviceNodeResourceTracker, ROS2DeviceNode
from unilabos.ros.nodes.resource_tracker import ResourceTreeSet from unilabos.utils.type_check import serialize_result_info, get_result_info_str
from unilabos.utils.type_check import get_result_info_str
if TYPE_CHECKING: if TYPE_CHECKING:
from unilabos.devices.workstation.workstation_base import WorkstationBase from unilabos.devices.workstation.workstation_base import WorkstationBase
@@ -51,7 +50,6 @@ class ROS2WorkstationNode(BaseROS2DeviceNode):
*, *,
driver_instance: "WorkstationBase", driver_instance: "WorkstationBase",
device_id: str, device_id: str,
device_uuid: str,
status_types: Dict[str, Any], status_types: Dict[str, Any],
action_value_mappings: Dict[str, Any], action_value_mappings: Dict[str, Any],
hardware_interface: Dict[str, Any], hardware_interface: Dict[str, Any],
@@ -66,7 +64,6 @@ class ROS2WorkstationNode(BaseROS2DeviceNode):
super().__init__( super().__init__(
driver_instance=driver_instance, driver_instance=driver_instance,
device_id=device_id, device_id=device_id,
device_uuid=device_uuid,
status_types=status_types, status_types=status_types,
action_value_mappings={**action_value_mappings, **self.protocol_action_mappings}, action_value_mappings={**action_value_mappings, **self.protocol_action_mappings},
hardware_interface=hardware_interface, hardware_interface=hardware_interface,
@@ -225,29 +222,16 @@ class ROS2WorkstationNode(BaseROS2DeviceNode):
# 向Host查询物料当前状态 # 向Host查询物料当前状态
for k, v in goal.get_fields_and_field_types().items(): for k, v in goal.get_fields_and_field_types().items():
if v in ["unilabos_msgs/Resource", "sequence<unilabos_msgs/Resource>"]: if v in ["unilabos_msgs/Resource", "sequence<unilabos_msgs/Resource>"]:
self.lab_logger().info(f"{protocol_name} 查询资源状态: Key: {k} Type: {v}") r = ResourceGet.Request()
try:
# 统一处理单个或多个资源
resource_id = ( resource_id = (
protocol_kwargs[k]["id"] if v == "unilabos_msgs/Resource" else protocol_kwargs[k][0]["id"] protocol_kwargs[k]["id"] if v == "unilabos_msgs/Resource" else protocol_kwargs[k][0]["id"]
) )
resource_uuid = protocol_kwargs[k].get("uuid", None) r.id = resource_id
r = SerialCommand_Request() r.with_children = True
r.command = json.dumps({"id": resource_id, "uuid": resource_uuid, "with_children": True}) response = await self._resource_clients["resource_get"].call_async(r)
# 发送请求并等待响应 protocol_kwargs[k] = list_to_nested_dict(
response: SerialCommand_Response = await self._resource_clients[ [convert_from_ros_msg(rs) for rs in response.resources]
"resource_get" )
].call_async(
r
) # type: ignore
raw_data = json.loads(response.response)
tree_set = ResourceTreeSet.from_raw_list(raw_data)
target = tree_set.dump()
protocol_kwargs[k] = target[0][0] if v == "unilabos_msgs/Resource" else target
except Exception as ex:
self.lab_logger().error(f"查询资源失败: {k}, 错误: {ex}\n{traceback.format_exc()}")
raise
self.lab_logger().info(f"🔍 最终的 vessel: {protocol_kwargs.get('vessel', 'NOT_FOUND')}") self.lab_logger().info(f"🔍 最终的 vessel: {protocol_kwargs.get('vessel', 'NOT_FOUND')}")

View File

@@ -1,4 +1,3 @@
import traceback
import uuid import uuid
from pydantic import BaseModel, field_serializer, field_validator from pydantic import BaseModel, field_serializer, field_validator
from pydantic import Field from pydantic import Field
@@ -32,7 +31,7 @@ class ResourceDictPositionObject(BaseModel):
class ResourceDictPosition(BaseModel): class ResourceDictPosition(BaseModel):
size: ResourceDictPositionSize = Field(description="Resource size", default_factory=ResourceDictPositionSize) size: ResourceDictPositionSize = Field(description="Resource size", default_factory=ResourceDictPositionSize)
scale: ResourceDictPositionScale = Field(description="Resource scale", default_factory=ResourceDictPositionScale) scale: ResourceDictPositionScale = Field(description="Resource scale", default_factory=ResourceDictPositionScale)
layout: Literal["2d", "x-y", "z-y", "x-z"] = Field(description="Resource layout", default="x-y") layout: Literal["2d"] = Field(description="Resource layout", default="2d")
position: ResourceDictPositionObject = Field( position: ResourceDictPositionObject = Field(
description="Resource position", default_factory=ResourceDictPositionObject description="Resource position", default_factory=ResourceDictPositionObject
) )
@@ -42,7 +41,6 @@ class ResourceDictPosition(BaseModel):
rotation: ResourceDictPositionObject = Field( rotation: ResourceDictPositionObject = Field(
description="Resource rotation", default_factory=ResourceDictPositionObject description="Resource rotation", default_factory=ResourceDictPositionObject
) )
cross_section_type: Literal["rectangle", "circle", "rounded_rectangle"] = Field(description="Cross section type", default="rectangle")
# 统一的资源字典模型parent 自动序列化为 parent_uuidchildren 不序列化 # 统一的资源字典模型parent 自动序列化为 parent_uuidchildren 不序列化
@@ -51,7 +49,7 @@ class ResourceDict(BaseModel):
uuid: str = Field(description="Resource UUID") uuid: str = Field(description="Resource UUID")
name: str = Field(description="Resource name") name: str = Field(description="Resource name")
description: str = Field(description="Resource description", default="") description: str = Field(description="Resource description", default="")
resource_schema: Dict[str, Any] = Field(description="Resource schema", default_factory=dict, serialization_alias="schema", validation_alias="schema") schema: Dict[str, Any] = Field(description="Resource schema", default_factory=dict)
model: Dict[str, Any] = Field(description="Resource model", default_factory=dict) model: Dict[str, Any] = Field(description="Resource model", default_factory=dict)
icon: str = Field(description="Resource icon", default="") icon: str = Field(description="Resource icon", default="")
parent_uuid: Optional["str"] = Field(description="Parent resource uuid", default=None) # 先设定parent_uuid parent_uuid: Optional["str"] = Field(description="Parent resource uuid", default=None) # 先设定parent_uuid
@@ -59,7 +57,6 @@ class ResourceDict(BaseModel):
type: Literal["device"] | str = Field(description="Resource type") type: Literal["device"] | str = Field(description="Resource type")
klass: str = Field(alias="class", description="Resource class name") klass: str = Field(alias="class", description="Resource class name")
position: ResourceDictPosition = Field(description="Resource position", default_factory=ResourceDictPosition) position: ResourceDictPosition = Field(description="Resource position", default_factory=ResourceDictPosition)
pose: ResourceDictPosition = Field(description="Resource position", default_factory=ResourceDictPosition)
config: Dict[str, Any] = Field(description="Resource configuration") config: Dict[str, Any] = Field(description="Resource configuration")
data: Dict[str, Any] = Field(description="Resource data") data: Dict[str, Any] = Field(description="Resource data")
@@ -138,14 +135,12 @@ class ResourceDictInstance(object):
content["config"] = {} content["config"] = {}
if not content.get("data"): if not content.get("data"):
content["data"] = {} content["data"] = {}
if "pose" not in content:
content["pose"] = content.get("position", {})
return ResourceDictInstance(ResourceDict.model_validate(content)) return ResourceDictInstance(ResourceDict.model_validate(content))
def get_nested_dict(self) -> Dict[str, Any]: def get_nested_dict(self) -> Dict[str, Any]:
"""获取资源实例的嵌套字典表示""" """获取资源实例的嵌套字典表示"""
res_dict = self.res_content.model_dump(by_alias=True) res_dict = self.res_content.model_dump(by_alias=True)
res_dict["children"] = {child.res_content.id: child.get_nested_dict() for child in self.children} res_dict["children"] = {child.res_content.name: child.get_nested_dict() for child in self.children}
res_dict["parent"] = self.res_content.parent_instance_name res_dict["parent"] = self.res_content.parent_instance_name
res_dict["position"] = self.res_content.position.position.model_dump() res_dict["position"] = self.res_content.position.position.model_dump()
return res_dict return res_dict
@@ -218,7 +213,7 @@ class ResourceTreeInstance(object):
if node.res_content.uuid: if node.res_content.uuid:
known_uuids.add(node.res_content.uuid) known_uuids.add(node.res_content.uuid)
else: else:
logger.warning(f"警告: 资源 {node.res_content.id} 没有uuid") print(f"警告: 资源 {node.res_content.id} 没有uuid")
# 验证并递归处理子节点 # 验证并递归处理子节点
for child in node.children: for child in node.children:
@@ -294,6 +289,8 @@ class ResourceTreeSet(object):
elif isinstance(resource_list[0], ResourceTreeInstance): elif isinstance(resource_list[0], ResourceTreeInstance):
# 已经是ResourceTree列表 # 已经是ResourceTree列表
self.trees = cast(List[ResourceTreeInstance], resource_list) self.trees = cast(List[ResourceTreeInstance], resource_list)
elif isinstance(resource_list[0], list):
pass
else: else:
raise TypeError( raise TypeError(
f"不支持的类型: {type(resource_list[0])}" f"不支持的类型: {type(resource_list[0])}"
@@ -310,7 +307,10 @@ class ResourceTreeSet(object):
replace_info = { replace_info = {
"plate": "plate", "plate": "plate",
"well": "well", "well": "well",
"tip_spot": "container",
"trash": "container",
"deck": "deck", "deck": "deck",
"tip_rack": "container",
} }
if source in replace_info: if source in replace_info:
return replace_info[source] return replace_info[source]
@@ -320,12 +320,7 @@ class ResourceTreeSet(object):
def build_uuid_mapping(res: "PLRResource", uuid_list: list): def build_uuid_mapping(res: "PLRResource", uuid_list: list):
"""递归构建uuid映射字典""" """递归构建uuid映射字典"""
uid = getattr(res, "unilabos_uuid", "") uuid_list.append(getattr(res, "unilabos_uuid", ""))
if not uid:
uid = str(uuid.uuid4())
res.unilabos_uuid = uid
logger.warning(f"{res}没有uuid请设置后再传入默认填充{uid}\n{traceback.format_exc()}")
uuid_list.append(uid)
for child in res.children: for child in res.children:
build_uuid_mapping(child, uuid_list) build_uuid_mapping(child, uuid_list)
@@ -334,21 +329,6 @@ class ResourceTreeSet(object):
) -> ResourceDictInstance: ) -> ResourceDictInstance:
current_uuid = uuids.pop(0) current_uuid = uuids.pop(0)
raw_pos = (
{"x": d["location"]["x"], "y": d["location"]["y"], "z": d["location"]["z"]}
if d["location"]
else {"x": 0, "y": 0, "z": 0}
)
pos = {
"size": {"width": d["size_x"], "height": d["size_y"], "depth": d["size_z"]},
"scale": {"x": 1.0, "y": 1.0, "z": 1.0},
"layout": d.get("layout", "x-y"),
"position": raw_pos,
"position3d": raw_pos,
"rotation": d["rotation"],
"cross_section_type": d.get("cross_section_type", "rectangle"),
}
# 先构建当前节点的字典不包含children # 先构建当前节点的字典不包含children
r_dict = { r_dict = {
"id": d["name"], "id": d["name"],
@@ -357,10 +337,12 @@ class ResourceTreeSet(object):
"parent": parent_resource, # 直接传入 ResourceDict 对象 "parent": parent_resource, # 直接传入 ResourceDict 对象
"type": replace_plr_type(d.get("category", "")), "type": replace_plr_type(d.get("category", "")),
"class": d.get("class", ""), "class": d.get("class", ""),
"position": pos, "position": (
"pose": pos, {"x": d["location"]["x"], "y": d["location"]["y"], "z": d["location"]["z"]}
"config": {k: v for k, v in d.items() if k not in if d["location"]
["name", "children", "parent_name", "location", "rotation", "size_x", "size_y", "size_z", "cross_section_type", "bottom_type"]}, else {"x": 0, "y": 0, "z": 0}
),
"config": {k: v for k, v in d.items() if k not in ["name", "children", "parent_name", "location"]},
"data": states[d["name"]], "data": states[d["name"]],
} }
@@ -402,7 +384,7 @@ class ResourceTreeSet(object):
import inspect import inspect
# 类型映射 # 类型映射
TYPE_MAP = {"plate": "Plate", "well": "Well", "deck": "Deck"} TYPE_MAP = {"plate": "plate", "well": "well", "container": "tip_spot", "deck": "deck", "tip_rack": "tip_rack"}
def collect_node_data(node: ResourceDictInstance, name_to_uuid: dict, all_states: dict): def collect_node_data(node: ResourceDictInstance, name_to_uuid: dict, all_states: dict):
"""一次遍历收集 name_to_uuid 和 all_states""" """一次遍历收集 name_to_uuid 和 all_states"""
@@ -414,13 +396,13 @@ class ResourceTreeSet(object):
def node_to_plr_dict(node: ResourceDictInstance, has_model: bool): def node_to_plr_dict(node: ResourceDictInstance, has_model: bool):
"""转换节点为 PLR 字典格式""" """转换节点为 PLR 字典格式"""
res = node.res_content res = node.res_content
plr_type = TYPE_MAP.get(res.type, res.type) plr_type = TYPE_MAP.get(res.type, "tip_spot")
if res.type not in TYPE_MAP: if res.type not in TYPE_MAP:
logger.warning(f"未知类型 {res.type}") logger.warning(f"未知类型 {res.type},使用默认类型 tip_spot")
d = { d = {
"name": res.name, "name": res.name,
"type": res.config.get("type", plr_type), "type": plr_type,
"size_x": res.config.get("size_x", 0), "size_x": res.config.get("size_x", 0),
"size_y": res.config.get("size_y", 0), "size_y": res.config.get("size_y", 0),
"size_z": res.config.get("size_z", 0), "size_z": res.config.get("size_z", 0),
@@ -431,7 +413,7 @@ class ResourceTreeSet(object):
"type": "Coordinate", "type": "Coordinate",
}, },
"rotation": {"x": 0, "y": 0, "z": 0, "type": "Rotation"}, "rotation": {"x": 0, "y": 0, "z": 0, "type": "Rotation"},
"category": res.config.get("category", plr_type), "category": plr_type,
"children": [node_to_plr_dict(child, has_model) for child in node.children], "children": [node_to_plr_dict(child, has_model) for child in node.children],
"parent_name": res.parent_instance_name, "parent_name": res.parent_instance_name,
**res.config, **res.config,
@@ -453,7 +435,7 @@ class ResourceTreeSet(object):
try: try:
sub_cls = find_subclass(plr_dict["type"], PLRResource) sub_cls = find_subclass(plr_dict["type"], PLRResource)
if sub_cls is None: if sub_cls is None:
raise ValueError(f"无法找到类型 {plr_dict['type']} 对应的 PLR 资源类。原始信息:{tree.root_node.res_content}") raise ValueError(f"无法找到类型 {plr_dict['type']} 对应的 PLR 资源类")
spec = inspect.signature(sub_cls) spec = inspect.signature(sub_cls)
if "category" not in spec.parameters: if "category" not in spec.parameters:
plr_dict.pop("category", None) plr_dict.pop("category", None)
@@ -733,9 +715,16 @@ class ResourceTreeSet(object):
Returns: Returns:
ResourceTreeSet: 反序列化后的资源树集合 ResourceTreeSet: 反序列化后的资源树集合
""" """
# 将每个字典转换为 ResourceInstanceDict
# FIXME: 需要重新确定parent关系
nested_lists = [] nested_lists = []
for tree_data in data: for tree_data in data:
nested_lists.extend(ResourceTreeSet.from_raw_list(tree_data).trees) flatten_instances = [
ResourceDictInstance.get_resource_instance_from_dict(node_dict) for node_dict in tree_data
]
nested_lists.append(flatten_instances)
# 使用现有的构造函数创建 ResourceTreeSet
return cls(nested_lists) return cls(nested_lists)
@@ -788,8 +777,7 @@ class DeviceNodeResourceTracker(object):
else: else:
return getattr(resource, uuid_attr, None) return getattr(resource, uuid_attr, None)
@classmethod def _set_resource_uuid(self, resource, new_uuid: str):
def set_resource_uuid(cls, resource, new_uuid: str):
""" """
设置资源的 uuid统一处理 dict 和 instance 两种类型 设置资源的 uuid统一处理 dict 和 instance 两种类型
@@ -842,7 +830,7 @@ class DeviceNodeResourceTracker(object):
resource_name = self._get_resource_attr(res, "name") resource_name = self._get_resource_attr(res, "name")
if resource_name and resource_name in name_to_uuid_map: if resource_name and resource_name in name_to_uuid_map:
new_uuid = name_to_uuid_map[resource_name] new_uuid = name_to_uuid_map[resource_name]
self.set_resource_uuid(res, new_uuid) self._set_resource_uuid(res, new_uuid)
self.uuid_to_resources[new_uuid] = res self.uuid_to_resources[new_uuid] = res
logger.debug(f"设置资源UUID: {resource_name} -> {new_uuid}") logger.debug(f"设置资源UUID: {resource_name} -> {new_uuid}")
return 1 return 1
@@ -854,7 +842,7 @@ class DeviceNodeResourceTracker(object):
""" """
递归遍历资源树更新所有节点的uuid 递归遍历资源树更新所有节点的uuid
Args:0 Args:
resource: 资源对象可以是dict或实例 resource: 资源对象可以是dict或实例
uuid_map: uuid映射字典{old_uuid: new_uuid} uuid_map: uuid映射字典{old_uuid: new_uuid}
@@ -864,18 +852,17 @@ class DeviceNodeResourceTracker(object):
def process(res): def process(res):
current_uuid = self._get_resource_attr(res, "uuid", "unilabos_uuid") current_uuid = self._get_resource_attr(res, "uuid", "unilabos_uuid")
replaced = 0
if current_uuid and current_uuid in uuid_map: if current_uuid and current_uuid in uuid_map:
new_uuid = uuid_map[current_uuid] new_uuid = uuid_map[current_uuid]
if current_uuid != new_uuid: if current_uuid != new_uuid:
self.set_resource_uuid(res, new_uuid) self._set_resource_uuid(res, new_uuid)
# 更新uuid_to_resources映射 # 更新uuid_to_resources映射
if current_uuid in self.uuid_to_resources: if current_uuid in self.uuid_to_resources:
self.uuid_to_resources.pop(current_uuid) self.uuid_to_resources.pop(current_uuid)
self.uuid_to_resources[new_uuid] = res self.uuid_to_resources[new_uuid] = res
logger.debug(f"更新uuid: {current_uuid} -> {new_uuid}") logger.debug(f"更新uuid: {current_uuid} -> {new_uuid}")
replaced = 1 return 1
return replaced return 0
return self._traverse_and_process(resource, process) return self._traverse_and_process(resource, process)
@@ -890,9 +877,8 @@ class DeviceNodeResourceTracker(object):
def process(res): def process(res):
current_uuid = self._get_resource_attr(res, "uuid", "unilabos_uuid") current_uuid = self._get_resource_attr(res, "uuid", "unilabos_uuid")
if current_uuid: if current_uuid:
old = self.uuid_to_resources.get(current_uuid)
self.uuid_to_resources[current_uuid] = res self.uuid_to_resources[current_uuid] = res
logger.debug(f"收集资源UUID映射: {current_uuid} -> {res} {'' if old is None else f'(覆盖旧值: {old})'}") logger.debug(f"收集资源UUID映射: {current_uuid} -> {res}")
return 0 return 0
self._traverse_and_process(resource, process) self._traverse_and_process(resource, process)
@@ -927,23 +913,9 @@ class DeviceNodeResourceTracker(object):
Args: Args:
resource: 资源对象可以是dict或实例 resource: 资源对象可以是dict或实例
""" """
root_uuids = {}
for r in self.resources: for r in self.resources:
res_uuid = r.get("uuid") if isinstance(r, dict) else getattr(r, "unilabos_uuid", None)
if res_uuid:
root_uuids[res_uuid] = r
if id(r) == id(resource): if id(r) == id(resource):
return return
# 这里只做uuid的根节点比较
if isinstance(resource, dict):
res_uuid = resource.get("uuid")
else:
res_uuid = getattr(resource, "unilabos_uuid", None)
if res_uuid in root_uuids:
old_res = root_uuids[res_uuid]
# self.remove_resource(old_res)
logger.warning(f"资源{resource}已存在,旧资源: {old_res}")
self.resources.append(resource) self.resources.append(resource)
# 递归收集uuid映射 # 递归收集uuid映射
self._collect_uuid_mapping(resource) self._collect_uuid_mapping(resource)
@@ -1074,19 +1046,13 @@ class DeviceNodeResourceTracker(object):
) -> List[Tuple[Any, Any]]: ) -> List[Tuple[Any, Any]]:
res_list = [] res_list = []
# print(resource, target_resource_cls_type, identifier_key, compare_value) # print(resource, target_resource_cls_type, identifier_key, compare_value)
children = []
if not isinstance(resource, dict):
children = getattr(resource, "children", []) children = getattr(resource, "children", [])
else:
children = resource.get("children")
if children is not None:
children = list(children.values()) if isinstance(children, dict) else children
for child in children: for child in children:
res_list.extend( res_list.extend(
self.loop_find_resource(child, target_resource_cls_type, identifier_key, compare_value, resource) self.loop_find_resource(child, target_resource_cls_type, identifier_key, compare_value, resource)
) )
if issubclass(type(resource), target_resource_cls_type): if issubclass(type(resource), target_resource_cls_type):
if type(resource) == dict: if target_resource_cls_type == dict:
# 对于字典类型,直接检查 identifier_key # 对于字典类型,直接检查 identifier_key
if identifier_key in resource: if identifier_key in resource:
if resource[identifier_key] == compare_value: if resource[identifier_key] == compare_value:

View File

@@ -336,9 +336,6 @@ class WorkstationNodeCreator(DeviceClassCreator[T]):
try: try:
# 创建实例额外补充一个给protocol node的字段后面考虑取消 # 创建实例额外补充一个给protocol node的字段后面考虑取消
data["children"] = self.children data["children"] = self.children
for material_id, child in self.children.items():
if child["type"] != "device":
self.resource_tracker.add_resource(self.children[material_id])
deck_dict = data.get("deck") deck_dict = data.get("deck")
if deck_dict: if deck_dict:
from pylabrobot.resources import Deck, Resource from pylabrobot.resources import Deck, Resource