diff --git a/.conda/base/recipe.yaml b/.conda/base/recipe.yaml new file mode 100644 index 0000000..3c5bb88 --- /dev/null +++ b/.conda/base/recipe.yaml @@ -0,0 +1,60 @@ +# unilabos: Production package (depends on unilabos-env + pip unilabos) +# For production deployment + +package: + name: unilabos + version: 0.10.17 + +source: + path: ../../unilabos + target_directory: unilabos + +build: + python: + entry_points: + - unilab = unilabos.app.main:main + script: + - set PIP_NO_INDEX= + - if: win + then: + - copy %RECIPE_DIR%\..\..\MANIFEST.in %SRC_DIR% + - copy %RECIPE_DIR%\..\..\setup.cfg %SRC_DIR% + - copy %RECIPE_DIR%\..\..\setup.py %SRC_DIR% + - pip install %SRC_DIR% + - if: unix + then: + - cp $RECIPE_DIR/../../MANIFEST.in $SRC_DIR + - cp $RECIPE_DIR/../../setup.cfg $SRC_DIR + - cp $RECIPE_DIR/../../setup.py $SRC_DIR + - pip install $SRC_DIR + +requirements: + host: + - python ==3.11.14 + - pip + - setuptools + - zstd + - zstandard + run: + - zstd + - zstandard + - networkx + - typing_extensions + - websockets + - pint + - fastapi + - jinja2 + - requests + - uvicorn + - opcua + - pyserial + - pandas + - pymodbus + - matplotlib + - pylibftdi + - uni-lab::unilabos-env ==0.10.17 + +about: + repository: https://github.com/deepmodeling/Uni-Lab-OS + license: GPL-3.0-only + description: "UniLabOS - Production package with minimal ROS2 dependencies" diff --git a/.conda/environment/recipe.yaml b/.conda/environment/recipe.yaml new file mode 100644 index 0000000..3f8df0f --- /dev/null +++ b/.conda/environment/recipe.yaml @@ -0,0 +1,39 @@ +# unilabos-env: conda environment dependencies (ROS2 + conda packages) + +package: + name: unilabos-env + version: 0.10.17 + +build: + noarch: generic + +requirements: + run: + # Python + - zstd + - zstandard + - conda-forge::python ==3.11.14 + - conda-forge::opencv + # ROS2 dependencies (from ci-check.yml) + - robostack-staging::ros-humble-ros-core + - robostack-staging::ros-humble-action-msgs + - robostack-staging::ros-humble-std-msgs + - robostack-staging::ros-humble-geometry-msgs + - robostack-staging::ros-humble-control-msgs + - robostack-staging::ros-humble-nav2-msgs + - robostack-staging::ros-humble-cv-bridge + - robostack-staging::ros-humble-vision-opencv + - robostack-staging::ros-humble-tf-transformations + - robostack-staging::ros-humble-moveit-msgs + - robostack-staging::ros-humble-tf2-ros + - robostack-staging::ros-humble-tf2-ros-py + - conda-forge::transforms3d + - conda-forge::uv + + # UniLabOS custom messages + - uni-lab::ros-humble-unilabos-msgs + +about: + repository: https://github.com/deepmodeling/Uni-Lab-OS + license: GPL-3.0-only + description: "UniLabOS Environment - ROS2 and conda dependencies" diff --git a/.conda/full/recipe.yaml b/.conda/full/recipe.yaml new file mode 100644 index 0000000..037f5b4 --- /dev/null +++ b/.conda/full/recipe.yaml @@ -0,0 +1,42 @@ +# unilabos-full: Full package with all features +# Depends on unilabos + complete ROS2 desktop + dev tools + +package: + name: unilabos-full + version: 0.10.17 + +build: + noarch: generic + +requirements: + run: + # Base unilabos package (includes unilabos-env) + - uni-lab::unilabos ==0.10.17 + # Documentation tools + - sphinx + - sphinx_rtd_theme + # Web UI + - gradio + - flask + # Interactive development + - ipython + - jupyter + - jupyros + - colcon-common-extensions + # ROS2 full desktop (includes rviz2, gazebo, etc.) + - robostack-staging::ros-humble-desktop-full + # Navigation and motion control + - ros-humble-navigation2 + - ros-humble-ros2-control + - ros-humble-robot-state-publisher + - ros-humble-joint-state-publisher + # MoveIt motion planning + - ros-humble-moveit + - ros-humble-moveit-servo + # Simulation + - ros-humble-simulation + +about: + repository: https://github.com/deepmodeling/Uni-Lab-OS + license: GPL-3.0-only + description: "UniLabOS Full - Complete package with ROS2 Desktop, MoveIt, Navigation2, Gazebo, Jupyter" diff --git a/.conda/recipe.yaml b/.conda/recipe.yaml deleted file mode 100644 index 2b041c8..0000000 --- a/.conda/recipe.yaml +++ /dev/null @@ -1,91 +0,0 @@ -package: - name: unilabos - version: 0.10.15 - -source: - path: ../unilabos - target_directory: unilabos - -build: - python: - entry_points: - - unilab = unilabos.app.main:main - script: - - set PIP_NO_INDEX= - - if: win - then: - - copy %RECIPE_DIR%\..\MANIFEST.in %SRC_DIR% - - copy %RECIPE_DIR%\..\setup.cfg %SRC_DIR% - - copy %RECIPE_DIR%\..\setup.py %SRC_DIR% - - call %PYTHON% -m pip install %SRC_DIR% - - if: unix - then: - - cp $RECIPE_DIR/../MANIFEST.in $SRC_DIR - - cp $RECIPE_DIR/../setup.cfg $SRC_DIR - - cp $RECIPE_DIR/../setup.py $SRC_DIR - - $PYTHON -m pip install $SRC_DIR - -requirements: - host: - - python ==3.11.11 - - pip - - setuptools - - zstd - - zstandard - run: - - conda-forge::python ==3.11.11 - - compilers - - cmake - - zstd - - zstandard - - ninja - - if: unix - then: - - make - - sphinx - - sphinx_rtd_theme - - numpy - - scipy - - pandas - - networkx - - matplotlib - - pint - - pyserial - - pyusb - - pylibftdi - - pymodbus - - python-can - - pyvisa - - opencv - - pydantic - - fastapi - - uvicorn - - gradio - - flask - - websockets - - ipython - - jupyter - - jupyros - - colcon-common-extensions - - robostack-staging::ros-humble-desktop-full - - robostack-staging::ros-humble-control-msgs - - robostack-staging::ros-humble-sensor-msgs - - robostack-staging::ros-humble-trajectory-msgs - - ros-humble-navigation2 - - ros-humble-ros2-control - - ros-humble-robot-state-publisher - - ros-humble-joint-state-publisher - - ros-humble-rosbridge-server - - ros-humble-cv-bridge - - ros-humble-tf2 - - ros-humble-moveit - - ros-humble-moveit-servo - - ros-humble-simulation - - ros-humble-tf-transformations - - transforms3d - - uni-lab::ros-humble-unilabos-msgs - -about: - repository: https://github.com/deepmodeling/Uni-Lab-OS - license: GPL-3.0-only - description: "Uni-Lab-OS" diff --git a/.github/workflows/ci-check.yml b/.github/workflows/ci-check.yml new file mode 100644 index 0000000..57245d9 --- /dev/null +++ b/.github/workflows/ci-check.yml @@ -0,0 +1,67 @@ +name: CI Check + +on: + push: + branches: [main, dev] + pull_request: + branches: [main, dev] + +jobs: + registry-check: + runs-on: windows-latest + + env: + # Fix Unicode encoding issue on Windows runner (cp1252 -> utf-8) + PYTHONIOENCODING: utf-8 + PYTHONUTF8: 1 + + defaults: + run: + shell: cmd + + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Setup Miniforge + uses: conda-incubator/setup-miniconda@v3 + with: + miniforge-version: latest + use-mamba: true + channels: robostack-staging,conda-forge,uni-lab + channel-priority: flexible + activate-environment: check-env + auto-update-conda: false + show-channel-urls: true + + - name: Install ROS dependencies, uv and unilabos-msgs + run: | + echo Installing ROS dependencies... + mamba install -n check-env conda-forge::uv conda-forge::opencv robostack-staging::ros-humble-ros-core robostack-staging::ros-humble-action-msgs robostack-staging::ros-humble-std-msgs robostack-staging::ros-humble-geometry-msgs robostack-staging::ros-humble-control-msgs robostack-staging::ros-humble-nav2-msgs uni-lab::ros-humble-unilabos-msgs robostack-staging::ros-humble-cv-bridge robostack-staging::ros-humble-vision-opencv robostack-staging::ros-humble-tf-transformations robostack-staging::ros-humble-moveit-msgs robostack-staging::ros-humble-tf2-ros robostack-staging::ros-humble-tf2-ros-py conda-forge::transforms3d -c robostack-staging -c conda-forge -c uni-lab -y + + - name: Install pip dependencies and unilabos + run: | + call conda activate check-env + echo Installing pip dependencies... + uv pip install -r unilabos/utils/requirements.txt + uv pip install pywinauto git+https://github.com/Xuwznln/pylabrobot.git + uv pip uninstall enum34 || echo enum34 not installed, skipping + uv pip install . + + - name: Run check mode (complete_registry) + run: | + call conda activate check-env + echo Running check mode... + python -m unilabos --check_mode --skip_env_check + + - name: Check for uncommitted changes + shell: bash + run: | + if ! git diff --exit-code; then + echo "::error::检测到文件变化!请先在本地运行 'python -m unilabos --complete_registry' 并提交变更" + echo "变化的文件:" + git diff --name-only + exit 1 + fi + echo "检查通过:无文件变化" diff --git a/.github/workflows/conda-pack-build.yml b/.github/workflows/conda-pack-build.yml index 3a379fa..ed45db9 100644 --- a/.github/workflows/conda-pack-build.yml +++ b/.github/workflows/conda-pack-build.yml @@ -13,6 +13,11 @@ on: required: false default: 'win-64' type: string + build_full: + description: '是否构建完整版 unilabos-full (默认构建轻量版 unilabos)' + required: false + default: false + type: boolean jobs: build-conda-pack: @@ -57,7 +62,7 @@ jobs: echo "should_build=false" >> $GITHUB_OUTPUT fi - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 if: steps.should_build.outputs.should_build == 'true' with: ref: ${{ github.event.inputs.branch }} @@ -69,7 +74,7 @@ jobs: with: miniforge-version: latest use-mamba: true - python-version: '3.11.11' + python-version: '3.11.14' channels: conda-forge,robostack-staging,uni-lab,defaults channel-priority: flexible activate-environment: unilab @@ -81,7 +86,14 @@ jobs: run: | echo Installing unilabos and dependencies to unilab environment... echo Using mamba for faster and more reliable dependency resolution... - mamba install -n unilab uni-lab::unilabos conda-pack -c uni-lab -c robostack-staging -c conda-forge -y + echo Build full: ${{ github.event.inputs.build_full }} + if "${{ github.event.inputs.build_full }}"=="true" ( + echo Installing unilabos-full ^(complete package^)... + mamba install -n unilab uni-lab::unilabos-full conda-pack -c uni-lab -c robostack-staging -c conda-forge -y + ) else ( + echo Installing unilabos ^(minimal package^)... + mamba install -n unilab uni-lab::unilabos conda-pack -c uni-lab -c robostack-staging -c conda-forge -y + ) - name: Install conda-pack, unilabos and dependencies (Unix) if: steps.should_build.outputs.should_build == 'true' && matrix.platform != 'win-64' @@ -89,7 +101,14 @@ jobs: run: | echo "Installing unilabos and dependencies to unilab environment..." echo "Using mamba for faster and more reliable dependency resolution..." - mamba install -n unilab uni-lab::unilabos conda-pack -c uni-lab -c robostack-staging -c conda-forge -y + echo "Build full: ${{ github.event.inputs.build_full }}" + if [[ "${{ github.event.inputs.build_full }}" == "true" ]]; then + echo "Installing unilabos-full (complete package)..." + mamba install -n unilab uni-lab::unilabos-full conda-pack -c uni-lab -c robostack-staging -c conda-forge -y + else + echo "Installing unilabos (minimal package)..." + mamba install -n unilab uni-lab::unilabos conda-pack -c uni-lab -c robostack-staging -c conda-forge -y + fi - name: Get latest ros-humble-unilabos-msgs version (Windows) if: steps.should_build.outputs.should_build == 'true' && matrix.platform == 'win-64' @@ -293,7 +312,7 @@ jobs: - name: Upload distribution package if: steps.should_build.outputs.should_build == 'true' - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: unilab-pack-${{ matrix.platform }}-${{ github.event.inputs.branch }} path: dist-package/ @@ -308,7 +327,12 @@ jobs: echo ========================================== echo Platform: ${{ matrix.platform }} echo Branch: ${{ github.event.inputs.branch }} - echo Python version: 3.11.11 + echo Python version: 3.11.14 + if "${{ github.event.inputs.build_full }}"=="true" ( + echo Package: unilabos-full ^(complete^) + ) else ( + echo Package: unilabos ^(minimal^) + ) echo. echo Distribution package contents: dir dist-package @@ -328,7 +352,12 @@ jobs: echo "==========================================" echo "Platform: ${{ matrix.platform }}" echo "Branch: ${{ github.event.inputs.branch }}" - echo "Python version: 3.11.11" + echo "Python version: 3.11.14" + if [[ "${{ github.event.inputs.build_full }}" == "true" ]]; then + echo "Package: unilabos-full (complete)" + else + echo "Package: unilabos (minimal)" + fi echo "" echo "Distribution package contents:" ls -lh dist-package/ diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml index 66aef8d..f3ac4d1 100644 --- a/.github/workflows/deploy-docs.yml +++ b/.github/workflows/deploy-docs.yml @@ -1,10 +1,12 @@ name: Deploy Docs on: - push: - branches: [main] - pull_request: + # 在 CI Check 成功后自动触发(仅 main 分支) + workflow_run: + workflows: ["CI Check"] + types: [completed] branches: [main] + # 手动触发 workflow_dispatch: inputs: branch: @@ -33,12 +35,19 @@ concurrency: jobs: # Build documentation build: + # 只在以下情况运行: + # 1. workflow_run 触发且 CI Check 成功 + # 2. 手动触发 + if: | + github.event_name == 'workflow_dispatch' || + (github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success') runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: - ref: ${{ github.event.inputs.branch || github.ref }} + # workflow_run 时使用触发工作流的分支,手动触发时使用输入的分支 + ref: ${{ github.event.workflow_run.head_branch || github.event.inputs.branch || github.ref }} fetch-depth: 0 - name: Setup Miniforge (with mamba) @@ -46,7 +55,7 @@ jobs: with: miniforge-version: latest use-mamba: true - python-version: '3.11.11' + python-version: '3.11.14' channels: conda-forge,robostack-staging,uni-lab,defaults channel-priority: flexible activate-environment: unilab @@ -75,8 +84,10 @@ jobs: - name: Setup Pages id: pages - uses: actions/configure-pages@v4 - if: github.ref == 'refs/heads/main' || (github.event_name == 'workflow_dispatch' && github.event.inputs.deploy_to_pages == 'true') + uses: actions/configure-pages@v5 + if: | + github.event.workflow_run.head_branch == 'main' || + (github.event_name == 'workflow_dispatch' && github.event.inputs.deploy_to_pages == 'true') - name: Build Sphinx documentation run: | @@ -94,14 +105,18 @@ jobs: test -f docs/_build/html/index.html && echo "✓ index.html exists" || echo "✗ index.html missing" - name: Upload build artifacts - uses: actions/upload-pages-artifact@v3 - if: github.ref == 'refs/heads/main' || (github.event_name == 'workflow_dispatch' && github.event.inputs.deploy_to_pages == 'true') + uses: actions/upload-pages-artifact@v4 + if: | + github.event.workflow_run.head_branch == 'main' || + (github.event_name == 'workflow_dispatch' && github.event.inputs.deploy_to_pages == 'true') with: path: docs/_build/html # Deploy to GitHub Pages deploy: - if: github.ref == 'refs/heads/main' || (github.event_name == 'workflow_dispatch' && github.event.inputs.deploy_to_pages == 'true') + if: | + github.event.workflow_run.head_branch == 'main' || + (github.event_name == 'workflow_dispatch' && github.event.inputs.deploy_to_pages == 'true') environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} diff --git a/.github/workflows/multi-platform-build.yml b/.github/workflows/multi-platform-build.yml index bcba6db..4e1cf4f 100644 --- a/.github/workflows/multi-platform-build.yml +++ b/.github/workflows/multi-platform-build.yml @@ -1,11 +1,16 @@ name: Multi-Platform Conda Build on: + # 在 CI Check 工作流完成后触发(仅限 main/dev 分支) + workflow_run: + workflows: ["CI Check"] + types: + - completed + branches: [main, dev] + # 支持 tag 推送(不依赖 CI Check) push: - branches: [main, dev] tags: ['v*'] - pull_request: - branches: [main, dev] + # 手动触发 workflow_dispatch: inputs: platforms: @@ -17,9 +22,37 @@ on: required: false default: false type: boolean + skip_ci_check: + description: '跳过等待 CI Check (手动触发时可选)' + required: false + default: false + type: boolean jobs: + # 等待 CI Check 完成的 job (仅用于 workflow_run 触发) + wait-for-ci: + runs-on: ubuntu-latest + if: github.event_name == 'workflow_run' + outputs: + should_continue: ${{ steps.check.outputs.should_continue }} + steps: + - name: Check CI status + id: check + run: | + if [[ "${{ github.event.workflow_run.conclusion }}" == "success" ]]; then + echo "should_continue=true" >> $GITHUB_OUTPUT + echo "CI Check passed, proceeding with build" + else + echo "should_continue=false" >> $GITHUB_OUTPUT + echo "CI Check did not succeed (status: ${{ github.event.workflow_run.conclusion }}), skipping build" + fi + build: + needs: [wait-for-ci] + # 运行条件:workflow_run 触发且 CI 成功,或者其他触发方式 + if: | + always() && + (needs.wait-for-ci.result == 'skipped' || needs.wait-for-ci.outputs.should_continue == 'true') strategy: fail-fast: false matrix: @@ -44,8 +77,10 @@ jobs: shell: bash -l {0} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: + # 如果是 workflow_run 触发,使用触发 CI Check 的 commit + ref: ${{ github.event.workflow_run.head_sha || github.ref }} fetch-depth: 0 - name: Check if platform should be built @@ -69,7 +104,6 @@ jobs: channels: conda-forge,robostack-staging,defaults channel-priority: strict activate-environment: build-env - auto-activate-base: false auto-update-conda: false show-channel-urls: true @@ -115,7 +149,7 @@ jobs: - name: Upload conda package artifacts if: steps.should_build.outputs.should_build == 'true' - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: conda-package-${{ matrix.platform }} path: conda-packages-temp diff --git a/.github/workflows/unilabos-conda-build.yml b/.github/workflows/unilabos-conda-build.yml index 214f9bf..d116a67 100644 --- a/.github/workflows/unilabos-conda-build.yml +++ b/.github/workflows/unilabos-conda-build.yml @@ -1,25 +1,62 @@ name: UniLabOS Conda Build on: + # 在 CI Check 成功后自动触发 + workflow_run: + workflows: ["CI Check"] + types: [completed] + branches: [main, dev] + # 标签推送时直接触发(发布版本) push: - branches: [main, dev] tags: ['v*'] - pull_request: - branches: [main, dev] + # 手动触发 workflow_dispatch: inputs: platforms: description: '选择构建平台 (逗号分隔): linux-64, osx-64, osx-arm64, win-64' required: false default: 'linux-64' + build_full: + description: '是否构建 unilabos-full 完整包 (默认只构建 unilabos 基础包)' + required: false + default: false + type: boolean upload_to_anaconda: description: '是否上传到Anaconda.org' required: false default: false type: boolean + skip_ci_check: + description: '跳过等待 CI Check (手动触发时可选)' + required: false + default: false + type: boolean jobs: + # 等待 CI Check 完成的 job (仅用于 workflow_run 触发) + wait-for-ci: + runs-on: ubuntu-latest + if: github.event_name == 'workflow_run' + outputs: + should_continue: ${{ steps.check.outputs.should_continue }} + steps: + - name: Check CI status + id: check + run: | + if [[ "${{ github.event.workflow_run.conclusion }}" == "success" ]]; then + echo "should_continue=true" >> $GITHUB_OUTPUT + echo "CI Check passed, proceeding with build" + else + echo "should_continue=false" >> $GITHUB_OUTPUT + echo "CI Check did not succeed (status: ${{ github.event.workflow_run.conclusion }}), skipping build" + fi + build: + needs: [wait-for-ci] + # 运行条件:workflow_run 触发且 CI 成功,或者其他触发方式 + if: | + always() && + (needs.wait-for-ci.result == 'skipped' || needs.wait-for-ci.outputs.should_continue == 'true') strategy: fail-fast: false matrix: @@ -40,8 +77,10 @@ jobs: shell: bash -l {0} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: + # 如果是 workflow_run 触发,使用触发 CI Check 的 commit + ref: ${{ github.event.workflow_run.head_sha || github.ref }} fetch-depth: 0 - name: Check if platform should be built @@ -65,7 +104,6 @@ jobs: channels: conda-forge,robostack-staging,uni-lab,defaults channel-priority: strict activate-environment: build-env - auto-activate-base: false auto-update-conda: false show-channel-urls: true @@ -81,12 +119,61 @@ jobs: conda list | grep -E "(rattler-build|anaconda-client)" echo "Platform: ${{ matrix.platform }}" echo "OS: ${{ matrix.os }}" - echo "Building UniLabOS package" + echo "Build full package: ${{ github.event.inputs.build_full || 'false' }}" + echo "Building packages:" + echo " - unilabos-env (environment dependencies)" + echo " - unilabos (with pip package)" + if [[ "${{ github.event.inputs.build_full }}" == "true" ]]; then + echo " - unilabos-full (complete package)" + fi - - name: Build conda package + - name: Build unilabos-env (conda environment only, noarch) if: steps.should_build.outputs.should_build == 'true' run: | - rattler-build build -r .conda/recipe.yaml -c uni-lab -c robostack-staging -c conda-forge + echo "Building unilabos-env (conda environment dependencies)..." + rattler-build build -r .conda/environment/recipe.yaml -c uni-lab -c robostack-staging -c conda-forge + + - name: Upload unilabos-env to Anaconda.org (if enabled) + if: steps.should_build.outputs.should_build == 'true' && github.event.inputs.upload_to_anaconda == 'true' + run: | + echo "Uploading unilabos-env to uni-lab organization..." + for package in $(find ./output -name "unilabos-env*.conda"); do + anaconda -t ${{ secrets.ANACONDA_API_TOKEN }} upload --user uni-lab --force "$package" + done + + - name: Build unilabos (with pip package) + if: steps.should_build.outputs.should_build == 'true' + run: | + echo "Building unilabos package..." + # 如果已上传到 Anaconda,从 uni-lab channel 获取 unilabos-env;否则从本地 output 获取 + rattler-build build -r .conda/base/recipe.yaml -c uni-lab -c robostack-staging -c conda-forge --channel ./output + + - name: Upload unilabos to Anaconda.org (if enabled) + if: steps.should_build.outputs.should_build == 'true' && github.event.inputs.upload_to_anaconda == 'true' + run: | + echo "Uploading unilabos to uni-lab organization..." + for package in $(find ./output -name "unilabos-0*.conda" -o -name "unilabos-[0-9]*.conda"); do + anaconda -t ${{ secrets.ANACONDA_API_TOKEN }} upload --user uni-lab --force "$package" + done + + - name: Build unilabos-full - Only when explicitly requested + if: | + steps.should_build.outputs.should_build == 'true' && + github.event.inputs.build_full == 'true' + run: | + echo "Building unilabos-full package on ${{ matrix.platform }}..." + rattler-build build -r .conda/full/recipe.yaml -c uni-lab -c robostack-staging -c conda-forge --channel ./output + + - name: Upload unilabos-full to Anaconda.org (if enabled) + if: | + steps.should_build.outputs.should_build == 'true' && + github.event.inputs.build_full == 'true' && + github.event.inputs.upload_to_anaconda == 'true' + run: | + echo "Uploading unilabos-full to uni-lab organization..." + for package in $(find ./output -name "unilabos-full*.conda"); do + anaconda -t ${{ secrets.ANACONDA_API_TOKEN }} upload --user uni-lab --force "$package" + done - name: List built packages if: steps.should_build.outputs.should_build == 'true' @@ -108,17 +195,9 @@ jobs: - name: Upload conda package artifacts if: steps.should_build.outputs.should_build == 'true' - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: conda-package-unilabos-${{ matrix.platform }} path: conda-packages-temp if-no-files-found: warn retention-days: 30 - - - name: Upload to Anaconda.org (uni-lab organization) - if: github.event.inputs.upload_to_anaconda == 'true' - run: | - for package in $(find ./output -name "*.conda"); do - echo "Uploading $package to uni-lab organization..." - anaconda -t ${{ secrets.ANACONDA_API_TOKEN }} upload --user uni-lab --force "$package" - done diff --git a/MANIFEST.in b/MANIFEST.in index d81945e..156ca52 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,4 +1,5 @@ recursive-include unilabos/test * +recursive-include unilabos/utils * recursive-include unilabos/registry *.yaml recursive-include unilabos/app/web/static * recursive-include unilabos/app/web/templates * diff --git a/README.md b/README.md index f10cc0f..fa0d9dd 100644 --- a/README.md +++ b/README.md @@ -31,26 +31,46 @@ Detailed documentation can be found at: ## Quick Start -1. Setup Conda Environment +### 1. Setup Conda Environment -Uni-Lab-OS recommends using `mamba` for environment management: +Uni-Lab-OS recommends using `mamba` for environment management. Choose the package that fits your needs: + +| Package | Use Case | Contents | +|---------|----------|----------| +| `unilabos` | **Recommended for most users** | Complete package, ready to use | +| `unilabos-env` | Developers (editable install) | Environment only, install unilabos via pip | +| `unilabos-full` | Simulation/Visualization | unilabos + ROS2 Desktop + Gazebo + MoveIt | ```bash # Create new environment -mamba create -n unilab python=3.11.11 +mamba create -n unilab python=3.11.14 mamba activate unilab -mamba install -n unilab uni-lab::unilabos -c robostack-staging -c conda-forge + +# Option A: Standard installation (recommended for most users) +mamba install uni-lab::unilabos -c robostack-staging -c conda-forge + +# Option B: For developers (editable mode development) +mamba install uni-lab::unilabos-env -c robostack-staging -c conda-forge +# Then install unilabos and dependencies: +git clone https://github.com/deepmodeling/Uni-Lab-OS.git && cd Uni-Lab-OS +pip install -e . +uv pip install -r unilabos/utils/requirements.txt + +# Option C: Full installation (simulation/visualization) +mamba install uni-lab::unilabos-full -c robostack-staging -c conda-forge ``` -2. Install Dev Uni-Lab-OS +**When to use which?** +- **unilabos**: Standard installation for production deployment and general usage (recommended) +- **unilabos-env**: For developers who need `pip install -e .` editable mode, modify source code +- **unilabos-full**: For simulation (Gazebo), visualization (rviz2), and Jupyter notebooks + +### 2. Clone Repository (Optional, for developers) ```bash -# Clone the repository +# Clone the repository (only needed for development or examples) git clone https://github.com/deepmodeling/Uni-Lab-OS.git cd Uni-Lab-OS - -# Install Uni-Lab-OS -pip install . ``` 3. Start Uni-Lab System diff --git a/README_zh.md b/README_zh.md index c4dba7d..20b8f53 100644 --- a/README_zh.md +++ b/README_zh.md @@ -31,26 +31,46 @@ Uni-Lab-OS 是一个用于实验室自动化的综合平台,旨在连接和控 ## 快速开始 -1. 配置 Conda 环境 +### 1. 配置 Conda 环境 -Uni-Lab-OS 建议使用 `mamba` 管理环境。根据您的操作系统选择适当的环境文件: +Uni-Lab-OS 建议使用 `mamba` 管理环境。根据您的需求选择合适的安装包: + +| 安装包 | 适用场景 | 包含内容 | +|--------|----------|----------| +| `unilabos` | **推荐大多数用户** | 完整安装包,开箱即用 | +| `unilabos-env` | 开发者(可编辑安装) | 仅环境依赖,通过 pip 安装 unilabos | +| `unilabos-full` | 仿真/可视化 | unilabos + ROS2 桌面版 + Gazebo + MoveIt | ```bash # 创建新环境 -mamba create -n unilab python=3.11.11 +mamba create -n unilab python=3.11.14 mamba activate unilab -mamba install -n unilab uni-lab::unilabos -c robostack-staging -c conda-forge + +# 方案 A:标准安装(推荐大多数用户) +mamba install uni-lab::unilabos -c robostack-staging -c conda-forge + +# 方案 B:开发者环境(可编辑模式开发) +mamba install uni-lab::unilabos-env -c robostack-staging -c conda-forge +# 然后安装 unilabos 和依赖: +git clone https://github.com/deepmodeling/Uni-Lab-OS.git && cd Uni-Lab-OS +pip install -e . +uv pip install -r unilabos/utils/requirements.txt + +# 方案 C:完整安装(仿真/可视化) +mamba install uni-lab::unilabos-full -c robostack-staging -c conda-forge ``` -2. 安装开发版 Uni-Lab-OS: +**如何选择?** +- **unilabos**:标准安装,适用于生产部署和日常使用(推荐) +- **unilabos-env**:开发者使用,支持 `pip install -e .` 可编辑模式,可修改源代码 +- **unilabos-full**:需要仿真(Gazebo)、可视化(rviz2)或 Jupyter Notebook + +### 2. 克隆仓库(可选,供开发者使用) ```bash -# 克隆仓库 +# 克隆仓库(仅开发或查看示例时需要) git clone https://github.com/deepmodeling/Uni-Lab-OS.git cd Uni-Lab-OS - -# 安装 Uni-Lab-OS -pip install . ``` 3. 启动 Uni-Lab 系统 diff --git a/docs/user_guide/best_practice.md b/docs/user_guide/best_practice.md index e1ffc24..0fa4d1e 100644 --- a/docs/user_guide/best_practice.md +++ b/docs/user_guide/best_practice.md @@ -31,6 +31,14 @@ 详细的安装步骤请参考 [安装指南](installation.md)。 +**选择合适的安装包:** + +| 安装包 | 适用场景 | 包含组件 | +|--------|----------|----------| +| `unilabos` | **推荐大多数用户**,生产部署 | 完整安装包,开箱即用 | +| `unilabos-env` | 开发者(可编辑安装) | 仅环境依赖,通过 pip 安装 unilabos | +| `unilabos-full` | 仿真/可视化 | unilabos + 完整 ROS2 桌面版 + Gazebo + MoveIt | + **关键步骤:** ```bash @@ -38,15 +46,30 @@ # 下载 Miniforge: https://github.com/conda-forge/miniforge/releases # 2. 创建 Conda 环境 -mamba create -n unilab python=3.11.11 +mamba create -n unilab python=3.11.14 # 3. 激活环境 mamba activate unilab -# 4. 安装 Uni-Lab-OS +# 4. 安装 Uni-Lab-OS(选择其一) + +# 方案 A:标准安装(推荐大多数用户) mamba install uni-lab::unilabos -c robostack-staging -c conda-forge + +# 方案 B:开发者环境(可编辑模式开发) +mamba install uni-lab::unilabos-env -c robostack-staging -c conda-forge +pip install -e /path/to/Uni-Lab-OS # 可编辑安装 +uv pip install -r unilabos/utils/requirements.txt # 安装 pip 依赖 + +# 方案 C:完整版(仿真/可视化) +mamba install uni-lab::unilabos-full -c robostack-staging -c conda-forge ``` +**选择建议:** +- **日常使用/生产部署**:使用 `unilabos`(推荐),完整功能,开箱即用 +- **开发者**:使用 `unilabos-env` + `pip install -e .` + `uv pip install -r unilabos/utils/requirements.txt`,代码修改立即生效 +- **仿真/可视化**:使用 `unilabos-full`,含 Gazebo、rviz2、MoveIt + #### 1.2 验证安装 ```bash @@ -768,7 +791,43 @@ Waiting for host service... 详细的设备驱动编写指南请参考 [添加设备驱动](../developer_guide/add_device.md)。 -#### 9.1 为什么需要自定义设备? +#### 9.1 开发环境准备 + +**推荐使用 `unilabos-env` + `pip install -e .` + `uv pip install`** 进行设备开发: + +```bash +# 1. 创建环境并安装 unilabos-env(ROS2 + conda 依赖 + uv) +mamba create -n unilab python=3.11.14 +conda activate unilab +mamba install uni-lab::unilabos-env -c robostack-staging -c conda-forge + +# 2. 克隆代码 +git clone https://github.com/deepmodeling/Uni-Lab-OS.git +cd Uni-Lab-OS + +# 3. 以可编辑模式安装(推荐使用脚本,自动检测中文环境) +python scripts/dev_install.py + +# 或手动安装: +pip install -e . +uv pip install -r unilabos/utils/requirements.txt +``` + +**为什么使用这种方式?** +- `unilabos-env` 提供 ROS2 核心组件和 uv(通过 conda 安装,避免编译) +- `unilabos/utils/requirements.txt` 包含所有运行时需要的 pip 依赖 +- `dev_install.py` 自动检测中文环境,中文系统自动使用清华镜像 +- 使用 `uv` 替代 `pip`,安装速度更快 +- 可编辑模式:代码修改**立即生效**,无需重新安装 + +**如果安装失败或速度太慢**,可以手动执行(使用清华镜像): + +```bash +pip install -e . -i https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple +uv pip install -r unilabos/utils/requirements.txt -i https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple +``` + +#### 9.2 为什么需要自定义设备? Uni-Lab-OS 内置了常见设备,但您的实验室可能有特殊设备需要集成: @@ -777,7 +836,7 @@ Uni-Lab-OS 内置了常见设备,但您的实验室可能有特殊设备需要 - 特殊的实验流程 - 第三方设备集成 -#### 9.2 创建 Python 包 +#### 9.3 创建 Python 包 为了方便开发和管理,建议为您的实验室创建独立的 Python 包。 @@ -814,7 +873,7 @@ touch my_lab_devices/my_lab_devices/__init__.py touch my_lab_devices/my_lab_devices/devices/__init__.py ``` -#### 9.3 创建 setup.py +#### 9.4 创建 setup.py ```python # my_lab_devices/setup.py @@ -845,7 +904,7 @@ setup( ) ``` -#### 9.4 开发安装 +#### 9.5 开发安装 使用 `-e` 参数进行可编辑安装,这样代码修改后立即生效: @@ -860,7 +919,7 @@ pip install -e . -i https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple - 方便调试和测试 - 支持版本控制(git) -#### 9.5 编写设备驱动 +#### 9.6 编写设备驱动 创建设备驱动文件: @@ -1001,7 +1060,7 @@ class MyPump: - **返回 Dict**:所有动作方法返回字典类型 - **文档字符串**:详细说明参数和功能 -#### 9.6 测试设备驱动 +#### 9.7 测试设备驱动 创建简单的测试脚本: diff --git a/docs/user_guide/installation.md b/docs/user_guide/installation.md index 3f94f2f..acf8fb6 100644 --- a/docs/user_guide/installation.md +++ b/docs/user_guide/installation.md @@ -13,15 +13,26 @@ - 开发者需要 Git 和基本的 Python 开发知识 - 自定义 msgs 需要 GitHub 账号 +## 安装包选择 + +Uni-Lab-OS 提供三个安装包版本,根据您的需求选择: + +| 安装包 | 适用场景 | 包含组件 | 磁盘占用 | +|--------|----------|----------|----------| +| **unilabos** | **推荐大多数用户**,生产部署 | 完整安装包,开箱即用 | ~2-3 GB | +| **unilabos-env** | 开发者环境(可编辑安装) | 仅环境依赖,通过 pip 安装 unilabos | ~2 GB | +| **unilabos-full** | 仿真可视化、完整功能体验 | unilabos + 完整 ROS2 桌面版 + Gazebo + MoveIt | ~8-10 GB | + ## 安装方式选择 根据您的使用场景,选择合适的安装方式: -| 安装方式 | 适用人群 | 特点 | 安装时间 | -| ---------------------- | -------------------- | ------------------------------ | ---------------------------- | -| **方式一:一键安装** | 实验室用户、快速体验 | 预打包环境,离线可用,无需配置 | 5-10 分钟 (网络良好的情况下) | -| **方式二:手动安装** | 标准用户、生产环境 | 灵活配置,版本可控 | 10-20 分钟 | -| **方式三:开发者安装** | 开发者、需要修改源码 | 可编辑模式,支持自定义 msgs | 20-30 分钟 | +| 安装方式 | 适用人群 | 推荐安装包 | 特点 | 安装时间 | +| ---------------------- | -------------------- | ----------------- | ------------------------------ | ---------------------------- | +| **方式一:一键安装** | 快速体验、演示 | 预打包环境 | 离线可用,无需配置 | 5-10 分钟 (网络良好的情况下) | +| **方式二:手动安装** | **大多数用户** | `unilabos` | 完整功能,开箱即用 | 10-20 分钟 | +| **方式三:开发者安装** | 开发者、需要修改源码 | `unilabos-env` | 可编辑模式,支持自定义开发 | 20-30 分钟 | +| **仿真/可视化** | 仿真测试、可视化调试 | `unilabos-full` | 含 Gazebo、rviz2、MoveIt | 30-60 分钟 | --- @@ -144,17 +155,38 @@ bash Miniforge3-$(uname)-$(uname -m).sh 使用以下命令创建 Uni-Lab 专用环境: ```bash -mamba create -n unilab python=3.11.11 # 目前ros2组件依赖版本大多为3.11.11 +mamba create -n unilab python=3.11.14 # 目前ros2组件依赖版本大多为3.11.14 mamba activate unilab -mamba install -n unilab uni-lab::unilabos -c robostack-staging -c conda-forge + +# 选择安装包(三选一): + +# 方案 A:标准安装(推荐大多数用户) +mamba install uni-lab::unilabos -c robostack-staging -c conda-forge + +# 方案 B:开发者环境(可编辑模式开发) +mamba install uni-lab::unilabos-env -c robostack-staging -c conda-forge +# 然后安装 unilabos 和 pip 依赖: +git clone https://github.com/deepmodeling/Uni-Lab-OS.git && cd Uni-Lab-OS +pip install -e . +uv pip install -r unilabos/utils/requirements.txt + +# 方案 C:完整版(含仿真和可视化工具) +mamba install uni-lab::unilabos-full -c robostack-staging -c conda-forge ``` **参数说明**: - `-n unilab`: 创建名为 "unilab" 的环境 -- `uni-lab::unilabos`: 从 uni-lab channel 安装 unilabos 包 +- `uni-lab::unilabos`: 安装 unilabos 完整包,开箱即用(推荐) +- `uni-lab::unilabos-env`: 仅安装环境依赖,适合开发者使用 `pip install -e .` +- `uni-lab::unilabos-full`: 安装完整包(含 ROS2 Desktop、Gazebo、MoveIt 等) - `-c robostack-staging -c conda-forge`: 添加额外的软件源 +**包选择建议**: +- **日常使用/生产部署**:安装 `unilabos`(推荐,完整功能,开箱即用) +- **开发者**:安装 `unilabos-env`,然后使用 `uv pip install -r unilabos/utils/requirements.txt` 安装依赖,再 `pip install -e .` 进行可编辑安装 +- **仿真/可视化**:安装 `unilabos-full`(Gazebo、rviz2、MoveIt) + **如果遇到网络问题**,可以使用清华镜像源加速下载: ```bash @@ -163,8 +195,14 @@ mamba config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/m mamba config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free/ mamba config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/conda-forge/ -# 然后重新执行安装命令 +# 然后重新执行安装命令(推荐标准安装) mamba create -n unilab uni-lab::unilabos -c robostack-staging + +# 或完整版(仿真/可视化) +mamba create -n unilab uni-lab::unilabos-full -c robostack-staging + +# pip 安装时使用清华镜像(开发者安装时使用) +uv pip install -r unilabos/utils/requirements.txt -i https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple ``` ### 第三步:激活环境 @@ -203,58 +241,87 @@ cd Uni-Lab-OS cd Uni-Lab-OS ``` -### 第二步:安装基础环境 +### 第二步:安装开发环境(unilabos-env) -**推荐方式**:先通过**方式一(一键安装)**或**方式二(手动安装)**完成基础环境的安装,这将包含所有必需的依赖项(ROS2、msgs 等)。 - -#### 选项 A:通过一键安装(推荐) - -参考上文"方式一:一键安装",完成基础环境的安装后,激活环境: +**重要**:开发者请使用 `unilabos-env` 包,它专为开发者设计: +- 包含 ROS2 核心组件和消息包(ros-humble-ros-core、std-msgs、geometry-msgs 等) +- 包含 transforms3d、cv-bridge、tf2 等 conda 依赖 +- 包含 `uv` 工具,用于快速安装 pip 依赖 +- **不包含** pip 依赖和 unilabos 包(由 `pip install -e .` 和 `uv pip install` 安装) ```bash +# 创建并激活环境 +mamba create -n unilab python=3.11.14 conda activate unilab + +# 安装开发者环境包(ROS2 + conda 依赖 + uv) +mamba install uni-lab::unilabos-env -c robostack-staging -c conda-forge ``` -#### 选项 B:通过手动安装 +### 第三步:安装 pip 依赖和可编辑模式安装 -参考上文"方式二:手动安装",创建并安装环境: - -```bash -mamba create -n unilab python=3.11.11 -conda activate unilab -mamba install -n unilab uni-lab::unilabos -c robostack-staging -c conda-forge -``` - -**说明**:这会安装包括 Python 3.11.11、ROS2 Humble、ros-humble-unilabos-msgs 和所有必需依赖 - -### 第三步:切换到开发版本 - -现在你已经有了一个完整可用的 Uni-Lab 环境,接下来将 unilabos 包切换为开发版本: +克隆代码并安装依赖: ```bash # 确保环境已激活 conda activate unilab -# 卸载 pip 安装的 unilabos(保留所有 conda 依赖) -pip uninstall unilabos -y - -# 克隆 dev 分支(如果还未克隆) -cd /path/to/your/workspace -git clone -b dev https://github.com/deepmodeling/Uni-Lab-OS.git -# 或者如果已经克隆,切换到 dev 分支 +# 克隆仓库(如果还未克隆) +git clone https://github.com/deepmodeling/Uni-Lab-OS.git cd Uni-Lab-OS + +# 切换到 dev 分支(可选) git checkout dev git pull - -# 以可编辑模式安装开发版 unilabos -pip install -e . -i https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple ``` -**参数说明**: +**推荐:使用安装脚本**(自动检测中文环境,使用 uv 加速): -- `-e`: editable mode(可编辑模式),代码修改立即生效,无需重新安装 -- `-i`: 使用清华镜像源加速下载 -- `pip uninstall unilabos`: 只卸载 pip 安装的 unilabos 包,不影响 conda 安装的其他依赖(如 ROS2、msgs 等) +```bash +# 自动检测中文环境,如果是中文系统则使用清华镜像 +python scripts/dev_install.py + +# 或者手动指定: +python scripts/dev_install.py --china # 强制使用清华镜像 +python scripts/dev_install.py --no-mirror # 强制使用 PyPI +python scripts/dev_install.py --skip-deps # 跳过 pip 依赖安装 +python scripts/dev_install.py --use-pip # 使用 pip 而非 uv +``` + +**手动安装**(如果脚本安装失败或速度太慢): + +```bash +# 1. 安装 unilabos(可编辑模式) +pip install -e . + +# 2. 使用 uv 安装 pip 依赖(推荐,速度更快) +uv pip install -r unilabos/utils/requirements.txt + +# 国内用户使用清华镜像: +pip install -e . -i https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple +uv pip install -r unilabos/utils/requirements.txt -i https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple +``` + +**注意**: +- `uv` 已包含在 `unilabos-env` 中,无需单独安装 +- `unilabos/utils/requirements.txt` 包含运行 unilabos 所需的所有 pip 依赖 +- 部分特殊包(如 pylabrobot)会在运行时由 unilabos 自动检测并安装 + +**为什么使用可编辑模式?** + +- `-e` (editable mode):代码修改**立即生效**,无需重新安装 +- 适合开发调试:修改代码后直接运行测试 +- 与 `unilabos-env` 配合:环境依赖由 conda 管理,unilabos 代码由 pip 管理 + +**验证安装**: + +```bash +# 检查 unilabos 版本 +python -c "import unilabos; print(unilabos.__version__)" + +# 检查安装位置(应该指向你的代码目录) +pip show unilabos | grep Location +``` ### 第四步:安装或自定义 ros-humble-unilabos-msgs(可选) @@ -464,7 +531,45 @@ cd $CONDA_PREFIX/envs/unilab ### 问题 8: 环境很大,有办法减小吗? -**解决方案**: 预打包的环境包含所有依赖,通常较大(压缩后 2-5GB)。这是为了确保离线安装和完整功能。如果空间有限,考虑使用方式二手动安装,只安装需要的组件。 +**解决方案**: + +1. **使用 `unilabos` 标准版**(推荐大多数用户): + ```bash + mamba install uni-lab::unilabos -c robostack-staging -c conda-forge + ``` + 标准版包含完整功能,环境大小约 2-3GB(相比完整版的 8-10GB)。 + +2. **使用 `unilabos-env` 开发者版**(最小化): + ```bash + mamba install uni-lab::unilabos-env -c robostack-staging -c conda-forge + # 然后手动安装依赖 + pip install -e . + uv pip install -r unilabos/utils/requirements.txt + ``` + 开发者版只包含环境依赖,体积最小约 2GB。 + +3. **按需安装额外组件**: + 如果后续需要特定功能,可以单独安装: + ```bash + # 需要 Jupyter + mamba install jupyter jupyros + + # 需要可视化 + mamba install matplotlib opencv + + # 需要仿真(注意:这会安装大量依赖) + mamba install ros-humble-gazebo-ros + ``` + +4. **预打包环境问题**: + 预打包环境(方式一)包含所有依赖,通常较大(压缩后 2-5GB)。这是为了确保离线安装和完整功能。 + +**包选择建议**: +| 需求 | 推荐包 | 预估大小 | +|------|--------|----------| +| 日常使用/生产部署 | `unilabos` | ~2-3 GB | +| 开发调试(可编辑模式) | `unilabos-env` | ~2 GB | +| 仿真/可视化 | `unilabos-full` | ~8-10 GB | ### 问题 9: 如何更新到最新版本? @@ -511,6 +616,7 @@ mamba update ros-humble-unilabos-msgs -c uni-lab -c robostack-staging -c conda-f **提示**: -- 生产环境推荐使用方式二(手动安装)的稳定版本 -- 开发和测试推荐使用方式三(开发者安装) -- 快速体验和演示推荐使用方式一(一键安装) +- **大多数用户**推荐使用方式二(手动安装)的 `unilabos` 标准版 +- **开发者**推荐使用方式三(开发者安装),安装 `unilabos-env` 后使用 `uv pip install -r unilabos/utils/requirements.txt` 安装依赖 +- **仿真/可视化**推荐安装 `unilabos-full` 完整版 +- **快速体验和演示**推荐使用方式一(一键安装) diff --git a/recipes/msgs/recipe.yaml b/recipes/msgs/recipe.yaml index 6d32908..f78df2e 100644 --- a/recipes/msgs/recipe.yaml +++ b/recipes/msgs/recipe.yaml @@ -1,6 +1,6 @@ package: name: ros-humble-unilabos-msgs - version: 0.10.15 + version: 0.10.17 source: path: ../../unilabos_msgs target_directory: src @@ -25,7 +25,7 @@ requirements: build: - ${{ compiler('cxx') }} - ${{ compiler('c') }} - - python ==3.11.11 + - python ==3.11.14 - numpy - if: build_platform != target_platform then: @@ -63,14 +63,14 @@ requirements: - robostack-staging::ros-humble-rosidl-default-generators - robostack-staging::ros-humble-std-msgs - robostack-staging::ros-humble-geometry-msgs - - robostack-staging::ros2-distro-mutex=0.6 + - robostack-staging::ros2-distro-mutex=0.7 run: - robostack-staging::ros-humble-action-msgs - robostack-staging::ros-humble-ros-workspace - robostack-staging::ros-humble-rosidl-default-runtime - robostack-staging::ros-humble-std-msgs - robostack-staging::ros-humble-geometry-msgs - - robostack-staging::ros2-distro-mutex=0.6 + - robostack-staging::ros2-distro-mutex=0.7 - if: osx and x86_64 then: - __osx >=${{ MACOSX_DEPLOYMENT_TARGET|default('10.14') }} diff --git a/recipes/unilabos/recipe.yaml b/recipes/unilabos/recipe.yaml index be3f1a1..feca503 100644 --- a/recipes/unilabos/recipe.yaml +++ b/recipes/unilabos/recipe.yaml @@ -1,6 +1,6 @@ package: name: unilabos - version: "0.10.15" + version: "0.10.17" source: path: ../.. diff --git a/scripts/create_readme.py b/scripts/create_readme.py index c4f3933..e87c1d8 100644 --- a/scripts/create_readme.py +++ b/scripts/create_readme.py @@ -85,7 +85,7 @@ Verification: ------------- The verify_installation.py script will check: - - Python version (3.11.11) + - Python version (3.11.14) - ROS2 rclpy installation - UniLabOS installation and dependencies @@ -104,7 +104,7 @@ Build Information: Branch: {branch} Platform: {platform} - Python: 3.11.11 + Python: 3.11.14 Date: {build_date} Troubleshooting: diff --git a/scripts/dev_install.py b/scripts/dev_install.py new file mode 100644 index 0000000..002db24 --- /dev/null +++ b/scripts/dev_install.py @@ -0,0 +1,214 @@ +#!/usr/bin/env python3 +""" +Development installation script for UniLabOS. +Auto-detects Chinese locale and uses appropriate mirror. + +Usage: + python scripts/dev_install.py + python scripts/dev_install.py --no-mirror # Force no mirror + python scripts/dev_install.py --china # Force China mirror + python scripts/dev_install.py --skip-deps # Skip pip dependencies installation + +Flow: + 1. pip install -e . (install unilabos in editable mode) + 2. Detect Chinese locale + 3. Use uv to install pip dependencies from requirements.txt + 4. Special packages (like pylabrobot) are handled by environment_check.py at runtime +""" + +import locale +import subprocess +import sys +import argparse +from pathlib import Path + +# Tsinghua mirror URL +TSINGHUA_MIRROR = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" + + +def is_chinese_locale() -> bool: + """ + Detect if system is in Chinese locale. + Same logic as EnvironmentChecker._is_chinese_locale() + """ + try: + lang = locale.getdefaultlocale()[0] + if lang and ("zh" in lang.lower() or "chinese" in lang.lower()): + return True + except Exception: + pass + return False + + +def run_command(cmd: list, description: str, retry: int = 2) -> bool: + """Run command with retry support.""" + print(f"[INFO] {description}") + print(f"[CMD] {' '.join(cmd)}") + + for attempt in range(retry + 1): + try: + result = subprocess.run(cmd, check=True, timeout=600) + print(f"[OK] {description}") + return True + except subprocess.CalledProcessError as e: + if attempt < retry: + print(f"[WARN] Attempt {attempt + 1} failed, retrying...") + else: + print(f"[ERROR] {description} failed: {e}") + return False + except subprocess.TimeoutExpired: + print(f"[ERROR] {description} timed out") + return False + return False + + +def install_editable(project_root: Path, use_mirror: bool) -> bool: + """Install unilabos in editable mode using pip.""" + cmd = [sys.executable, "-m", "pip", "install", "-e", str(project_root)] + if use_mirror: + cmd.extend(["-i", TSINGHUA_MIRROR]) + + return run_command(cmd, "Installing unilabos in editable mode") + + +def install_requirements_uv(requirements_file: Path, use_mirror: bool) -> bool: + """Install pip dependencies using uv (installed via conda-forge::uv).""" + cmd = ["uv", "pip", "install", "-r", str(requirements_file)] + if use_mirror: + cmd.extend(["-i", TSINGHUA_MIRROR]) + + return run_command(cmd, "Installing pip dependencies with uv", retry=2) + + +def install_requirements_pip(requirements_file: Path, use_mirror: bool) -> bool: + """Fallback: Install pip dependencies using pip.""" + cmd = [sys.executable, "-m", "pip", "install", "-r", str(requirements_file)] + if use_mirror: + cmd.extend(["-i", TSINGHUA_MIRROR]) + + return run_command(cmd, "Installing pip dependencies with pip", retry=2) + + +def check_uv_available() -> bool: + """Check if uv is available (installed via conda-forge::uv).""" + try: + subprocess.run(["uv", "--version"], capture_output=True, check=True) + return True + except (subprocess.CalledProcessError, FileNotFoundError): + return False + + +def main(): + parser = argparse.ArgumentParser(description="Development installation script for UniLabOS") + parser.add_argument("--china", action="store_true", help="Force use China mirror (Tsinghua)") + parser.add_argument("--no-mirror", action="store_true", help="Force use default PyPI (no mirror)") + parser.add_argument( + "--skip-deps", action="store_true", help="Skip pip dependencies installation (only install unilabos)" + ) + parser.add_argument("--use-pip", action="store_true", help="Use pip instead of uv for dependencies") + args = parser.parse_args() + + # Determine project root + script_dir = Path(__file__).parent + project_root = script_dir.parent + requirements_file = project_root / "unilabos" / "utils" / "requirements.txt" + + if not (project_root / "setup.py").exists(): + print(f"[ERROR] setup.py not found in {project_root}") + sys.exit(1) + + print("=" * 60) + print("UniLabOS Development Installation") + print("=" * 60) + print(f"Project root: {project_root}") + print() + + # Determine mirror usage based on locale + if args.no_mirror: + use_mirror = False + print("[INFO] Mirror disabled by --no-mirror flag") + elif args.china: + use_mirror = True + print("[INFO] China mirror enabled by --china flag") + else: + use_mirror = is_chinese_locale() + if use_mirror: + print("[INFO] Chinese locale detected, using Tsinghua mirror") + else: + print("[INFO] Non-Chinese locale detected, using default PyPI") + + print() + + # Step 1: Install unilabos in editable mode + print("[STEP 1] Installing unilabos in editable mode...") + if not install_editable(project_root, use_mirror): + print("[ERROR] Failed to install unilabos") + print() + print("Manual fallback:") + if use_mirror: + print(f" pip install -e {project_root} -i {TSINGHUA_MIRROR}") + else: + print(f" pip install -e {project_root}") + sys.exit(1) + + print() + + # Step 2: Install pip dependencies + if args.skip_deps: + print("[INFO] Skipping pip dependencies installation (--skip-deps)") + else: + print("[STEP 2] Installing pip dependencies...") + + if not requirements_file.exists(): + print(f"[WARN] Requirements file not found: {requirements_file}") + print("[INFO] Skipping dependencies installation") + else: + # Try uv first (faster), fallback to pip + if args.use_pip: + print("[INFO] Using pip (--use-pip flag)") + success = install_requirements_pip(requirements_file, use_mirror) + elif check_uv_available(): + print("[INFO] Using uv (installed via conda-forge::uv)") + success = install_requirements_uv(requirements_file, use_mirror) + if not success: + print("[WARN] uv failed, falling back to pip...") + success = install_requirements_pip(requirements_file, use_mirror) + else: + print("[WARN] uv not available (should be installed via: mamba install conda-forge::uv)") + print("[INFO] Falling back to pip...") + success = install_requirements_pip(requirements_file, use_mirror) + + if not success: + print() + print("[WARN] Failed to install some dependencies automatically.") + print("You can manually install them:") + if use_mirror: + print(f" uv pip install -r {requirements_file} -i {TSINGHUA_MIRROR}") + print(" or:") + print(f" pip install -r {requirements_file} -i {TSINGHUA_MIRROR}") + else: + print(f" uv pip install -r {requirements_file}") + print(" or:") + print(f" pip install -r {requirements_file}") + + print() + print("=" * 60) + print("Installation complete!") + print("=" * 60) + print() + print("Note: Some special packages (like pylabrobot) are installed") + print("automatically at runtime by unilabos if needed.") + print() + print("Verify installation:") + print(' python -c "import unilabos; print(unilabos.__version__)"') + print() + print("If you encounter issues, you can manually install dependencies:") + if use_mirror: + print(f" uv pip install -r unilabos/utils/requirements.txt -i {TSINGHUA_MIRROR}") + else: + print(" uv pip install -r unilabos/utils/requirements.txt") + print() + + +if __name__ == "__main__": + main() diff --git a/setup.py b/setup.py index b6ae5ed..b3a00f1 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ package_name = 'unilabos' setup( name=package_name, - version='0.10.15', + version='0.10.17', packages=find_packages(), include_package_data=True, install_requires=['setuptools'], diff --git a/unilabos/__init__.py b/unilabos/__init__.py index d5ac10a..50ab2b0 100644 --- a/unilabos/__init__.py +++ b/unilabos/__init__.py @@ -1 +1 @@ -__version__ = "0.10.15" +__version__ = "0.10.17" diff --git a/unilabos/app/main.py b/unilabos/app/main.py index 8ec26c0..a6539c3 100644 --- a/unilabos/app/main.py +++ b/unilabos/app/main.py @@ -7,7 +7,6 @@ import sys import threading import time from typing import Dict, Any, List - import networkx as nx import yaml @@ -17,9 +16,9 @@ unilabos_dir = os.path.dirname(os.path.dirname(current_dir)) if unilabos_dir not in sys.path: sys.path.append(unilabos_dir) +from unilabos.app.utils import cleanup_for_restart from unilabos.utils.banner_print import print_status, print_unilab_banner from unilabos.config.config import load_config, BasicConfig, HTTPConfig -from unilabos.app.utils import cleanup_for_restart # Global restart flags (used by ws_client and web/server) _restart_requested: bool = False @@ -161,6 +160,12 @@ def parse_args(): default=False, help="Complete registry information", ) + parser.add_argument( + "--check_mode", + action="store_true", + default=False, + help="Run in check mode for CI: validates registry imports and ensures no file changes", + ) parser.add_argument( "--no_update_feedback", action="store_true", @@ -211,7 +216,10 @@ def main(): args_dict = vars(args) # 环境检查 - 检查并自动安装必需的包 (可选) - if not args_dict.get("skip_env_check", False): + skip_env_check = args_dict.get("skip_env_check", False) + check_mode = args_dict.get("check_mode", False) + + if not skip_env_check: from unilabos.utils.environment_check import check_environment if not check_environment(auto_install=True): @@ -222,7 +230,21 @@ def main(): # 加载配置文件,优先加载config,然后从env读取 config_path = args_dict.get("config") - if os.getcwd().endswith("unilabos_data"): + + if check_mode: + args_dict["working_dir"] = os.path.abspath(os.getcwd()) + # 当 skip_env_check 时,默认使用当前目录作为 working_dir + if skip_env_check and not args_dict.get("working_dir") and not config_path: + working_dir = os.path.abspath(os.getcwd()) + print_status(f"跳过环境检查模式:使用当前目录作为工作目录 {working_dir}", "info") + # 检查当前目录是否有 local_config.py + local_config_in_cwd = os.path.join(working_dir, "local_config.py") + if os.path.exists(local_config_in_cwd): + config_path = local_config_in_cwd + print_status(f"发现本地配置文件: {config_path}", "info") + else: + print_status(f"未指定config路径,可通过 --config 传入 local_config.py 文件路径", "info") + elif os.getcwd().endswith("unilabos_data"): working_dir = os.path.abspath(os.getcwd()) else: working_dir = os.path.abspath(os.path.join(os.getcwd(), "unilabos_data")) @@ -241,7 +263,7 @@ def main(): working_dir = os.path.dirname(config_path) elif os.path.exists(working_dir) and os.path.exists(os.path.join(working_dir, "local_config.py")): config_path = os.path.join(working_dir, "local_config.py") - elif not config_path and ( + elif not skip_env_check and not config_path and ( not os.path.exists(working_dir) or not os.path.exists(os.path.join(working_dir, "local_config.py")) ): print_status(f"未指定config路径,可通过 --config 传入 local_config.py 文件路径", "info") @@ -255,9 +277,11 @@ def main(): print_status(f"已创建 local_config.py 路径: {config_path}", "info") else: os._exit(1) - # 加载配置文件 + + # 加载配置文件 (check_mode 跳过) print_status(f"当前工作目录为 {working_dir}", "info") - load_config_from_file(config_path) + if not check_mode: + load_config_from_file(config_path) # 根据配置重新设置日志级别 from unilabos.utils.log import configure_logger, logger @@ -313,6 +337,7 @@ def main(): machine_name = "".join([c if c.isalnum() or c == "_" else "_" for c in machine_name]) BasicConfig.machine_name = machine_name BasicConfig.vis_2d_enable = args_dict["2d_vis"] + BasicConfig.check_mode = check_mode from unilabos.resources.graphio import ( read_node_link_json, @@ -331,10 +356,14 @@ def main(): # 显示启动横幅 print_unilab_banner(args_dict) - # 注册表 - lab_registry = build_registry( - args_dict["registry_path"], args_dict.get("complete_registry", False), BasicConfig.upload_registry - ) + # 注册表 - check_mode 时强制启用 complete_registry + complete_registry = args_dict.get("complete_registry", False) or check_mode + lab_registry = build_registry(args_dict["registry_path"], complete_registry, BasicConfig.upload_registry) + + # Check mode: complete_registry 完成后直接退出,git diff 检测由 CI workflow 执行 + if check_mode: + print_status("Check mode: complete_registry 完成,退出", "info") + os._exit(0) if BasicConfig.upload_registry: # 设备注册到服务端 - 需要 ak 和 sk diff --git a/unilabos/app/utils.py b/unilabos/app/utils.py index d10c2e0..f6114a1 100644 --- a/unilabos/app/utils.py +++ b/unilabos/app/utils.py @@ -4,8 +4,40 @@ UniLabOS 应用工具函数 提供清理、重启等工具函数 """ -import gc +import glob import os +import shutil +import sys + + +def patch_rclpy_dll_windows(): + """在 Windows + conda 环境下为 rclpy 打 DLL 加载补丁""" + if sys.platform != "win32" or not os.environ.get("CONDA_PREFIX"): + return + try: + import rclpy + + return + except ImportError as e: + if not str(e).startswith("DLL load failed"): + return + cp = os.environ["CONDA_PREFIX"] + impl = os.path.join(cp, "Lib", "site-packages", "rclpy", "impl", "implementation_singleton.py") + pyd = glob.glob(os.path.join(cp, "Lib", "site-packages", "rclpy", "_rclpy_pybind11*.pyd")) + if not os.path.exists(impl) or not pyd: + return + with open(impl, "r", encoding="utf-8") as f: + content = f.read() + lib_bin = os.path.join(cp, "Library", "bin").replace("\\", "/") + patch = f'# UniLabOS DLL Patch\nimport os,ctypes\nos.add_dll_directory("{lib_bin}") if hasattr(os,"add_dll_directory") else None\ntry: ctypes.CDLL("{pyd[0].replace(chr(92),"/")}")\nexcept: pass\n# End Patch\n' + shutil.copy2(impl, impl + ".bak") + with open(impl, "w", encoding="utf-8") as f: + f.write(patch + content) + + +patch_rclpy_dll_windows() + +import gc import threading import time diff --git a/unilabos/devices/Qone_nmr/__init__.py b/unilabos/devices/Qone_nmr/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/unilabos/devices/neware_battery_test_system/__init__.py b/unilabos/devices/neware_battery_test_system/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/unilabos/devices/workstation/bioyond_studio/bioyond_cell/__init__.py b/unilabos/devices/workstation/bioyond_studio/bioyond_cell/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/unilabos/devices/workstation/bioyond_studio/dispensing_station/__init__.py b/unilabos/devices/workstation/bioyond_studio/dispensing_station/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/unilabos/devices/workstation/bioyond_studio/reaction_station/__init__.py b/unilabos/devices/workstation/bioyond_studio/reaction_station/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/unilabos/devices/xrd_d7mate/__init__.py b/unilabos/devices/xrd_d7mate/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/unilabos/devices/zhida_hplc/__init__.py b/unilabos/devices/zhida_hplc/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/unilabos/registry/devices/liquid_handler.yaml b/unilabos/registry/devices/liquid_handler.yaml index 85891ff..1f8733d 100644 --- a/unilabos/registry/devices/liquid_handler.yaml +++ b/unilabos/registry/devices/liquid_handler.yaml @@ -9752,21 +9752,21 @@ liquid_handler.prcxi: - 0 handles: input: - - data_key: liquid + - data_key: sources data_source: handle data_type: resource - handler_key: sources - label: sources - - data_key: liquid - data_source: executor + handler_key: sources_identifier + label: 待移动液体 + - data_key: targets + data_source: handle data_type: resource - handler_key: targets - label: targets - - data_key: liquid - data_source: executor + handler_key: targets_identifier + label: 转移目标 + - data_key: tip_rack + data_source: handle data_type: resource - handler_key: tip_rack - label: tip_rack + handler_key: tip_rack_identifier + label: 墙头盒 output: - data_key: liquid data_source: handle diff --git a/unilabos/registry/devices/virtual_device.yaml b/unilabos/registry/devices/virtual_device.yaml index 77ac533..c38655c 100644 --- a/unilabos/registry/devices/virtual_device.yaml +++ b/unilabos/registry/devices/virtual_device.yaml @@ -5792,3 +5792,381 @@ virtual_vacuum_pump: - status type: object version: 1.0.0 +virtual_workbench: + category: + - virtual_device + class: + action_value_mappings: + auto-move_to_heating_station: + feedback: {} + goal: {} + goal_default: + material_number: null + handles: + input: + - data_key: material_number + data_source: handle + data_type: workbench_material + handler_key: material_input + label: 物料编号 + output: + - data_key: station_id + data_source: executor + data_type: workbench_station + handler_key: heating_station_output + label: 加热台ID + - data_key: material_number + data_source: executor + data_type: workbench_material + handler_key: material_number_output + label: 物料编号 + placeholder_keys: {} + result: {} + schema: + description: 将物料从An位置移动到空闲加热台,返回分配的加热台ID + properties: + feedback: {} + goal: + properties: + material_number: + description: 物料编号,1-5,物料ID自动生成为A{n} + type: integer + required: + - material_number + type: object + result: + description: move_to_heating_station 返回类型 + properties: + material_id: + title: Material Id + type: string + material_number: + title: Material Number + type: integer + message: + title: Message + type: string + station_id: + description: 分配的加热台ID + title: Station Id + type: integer + success: + title: Success + type: boolean + required: + - success + - station_id + - material_id + - material_number + - message + title: MoveToHeatingStationResult + type: object + required: + - goal + title: move_to_heating_station参数 + type: object + type: UniLabJsonCommand + auto-move_to_output: + feedback: {} + goal: {} + goal_default: + material_number: null + station_id: null + handles: + input: + - data_key: station_id + data_source: handle + data_type: workbench_station + handler_key: output_station_input + label: 加热台ID + - data_key: material_number + data_source: handle + data_type: workbench_material + handler_key: output_material_input + label: 物料编号 + placeholder_keys: {} + result: {} + schema: + description: 将物料从加热台移动到输出位置Cn + properties: + feedback: {} + goal: + properties: + material_number: + description: 物料编号,用于确定输出位置Cn + type: integer + station_id: + description: 加热台ID,1-3,从上一节点传入 + type: integer + required: + - station_id + - material_number + type: object + result: + description: move_to_output 返回类型 + properties: + material_id: + title: Material Id + type: string + station_id: + title: Station Id + type: integer + success: + title: Success + type: boolean + required: + - success + - station_id + - material_id + title: MoveToOutputResult + type: object + required: + - goal + title: move_to_output参数 + type: object + type: UniLabJsonCommand + auto-prepare_materials: + feedback: {} + goal: {} + goal_default: + count: 5 + handles: + output: + - data_key: material_1 + data_source: executor + data_type: workbench_material + handler_key: channel_1 + label: 实验1 + - data_key: material_2 + data_source: executor + data_type: workbench_material + handler_key: channel_2 + label: 实验2 + - data_key: material_3 + data_source: executor + data_type: workbench_material + handler_key: channel_3 + label: 实验3 + - data_key: material_4 + data_source: executor + data_type: workbench_material + handler_key: channel_4 + label: 实验4 + - data_key: material_5 + data_source: executor + data_type: workbench_material + handler_key: channel_5 + label: 实验5 + placeholder_keys: {} + result: {} + schema: + description: 批量准备物料 - 虚拟起始节点,生成A1-A5物料,输出5个handle供后续节点使用 + properties: + feedback: {} + goal: + properties: + count: + default: 5 + description: 待生成的物料数量,默认5 (生成 A1-A5) + type: integer + required: [] + type: object + result: + description: prepare_materials 返回类型 - 批量准备物料 + properties: + count: + title: Count + type: integer + material_1: + title: Material 1 + type: integer + material_2: + title: Material 2 + type: integer + material_3: + title: Material 3 + type: integer + material_4: + title: Material 4 + type: integer + material_5: + title: Material 5 + type: integer + message: + title: Message + type: string + success: + title: Success + type: boolean + required: + - success + - count + - material_1 + - material_2 + - material_3 + - material_4 + - material_5 + - message + title: PrepareMaterialsResult + type: object + required: + - goal + title: prepare_materials参数 + type: object + type: UniLabJsonCommand + auto-start_heating: + feedback: {} + goal: {} + goal_default: + material_number: null + station_id: null + handles: + input: + - data_key: station_id + data_source: handle + data_type: workbench_station + handler_key: station_id_input + label: 加热台ID + - data_key: material_number + data_source: handle + data_type: workbench_material + handler_key: material_number_input + label: 物料编号 + output: + - data_key: station_id + data_source: executor + data_type: workbench_station + handler_key: heating_done_station + label: 加热完成-加热台ID + - data_key: material_number + data_source: executor + data_type: workbench_material + handler_key: heating_done_material + label: 加热完成-物料编号 + placeholder_keys: {} + result: {} + schema: + description: 启动指定加热台的加热程序 + properties: + feedback: {} + goal: + properties: + material_number: + description: 物料编号,从上一节点传入 + type: integer + station_id: + description: 加热台ID,1-3,从上一节点传入 + type: integer + required: + - station_id + - material_number + type: object + result: + description: start_heating 返回类型 + properties: + material_id: + title: Material Id + type: string + material_number: + title: Material Number + type: integer + message: + title: Message + type: string + station_id: + title: Station Id + type: integer + success: + title: Success + type: boolean + required: + - success + - station_id + - material_id + - material_number + - message + title: StartHeatingResult + type: object + required: + - goal + title: start_heating参数 + type: object + type: UniLabJsonCommand + module: unilabos.devices.virtual.workbench:VirtualWorkbench + status_types: + active_tasks_count: int + arm_current_task: str + arm_state: str + heating_station_1_material: str + heating_station_1_progress: float + heating_station_1_state: str + heating_station_2_material: str + heating_station_2_progress: float + heating_station_2_state: str + heating_station_3_material: str + heating_station_3_progress: float + heating_station_3_state: str + message: str + status: str + type: python + config_info: [] + description: Virtual Workbench with 1 robotic arm and 3 heating stations for concurrent + material processing + handles: [] + icon: '' + init_param_schema: + config: + properties: + config: + type: string + device_id: + type: string + required: [] + type: object + data: + properties: + active_tasks_count: + type: integer + arm_current_task: + type: string + arm_state: + type: string + heating_station_1_material: + type: string + heating_station_1_progress: + type: number + heating_station_1_state: + type: string + heating_station_2_material: + type: string + heating_station_2_progress: + type: number + heating_station_2_state: + type: string + heating_station_3_material: + type: string + heating_station_3_progress: + type: number + heating_station_3_state: + type: string + message: + type: string + status: + type: string + required: + - status + - arm_state + - arm_current_task + - heating_station_1_state + - heating_station_1_material + - heating_station_1_progress + - heating_station_2_state + - heating_station_2_material + - heating_station_2_progress + - heating_station_3_state + - heating_station_3_material + - heating_station_3_progress + - active_tasks_count + - message + type: object + version: 1.0.0 diff --git a/unilabos/resources/graphio.py b/unilabos/resources/graphio.py index 252c8f4..2c586d7 100644 --- a/unilabos/resources/graphio.py +++ b/unilabos/resources/graphio.py @@ -597,6 +597,8 @@ def resource_plr_to_ulab(resource_plr: "ResourcePLR", parent_name: str = None, w "tube": "tube", "bottle_carrier": "bottle_carrier", "plate_adapter": "plate_adapter", + "electrode_sheet": "electrode_sheet", + "material_hole": "material_hole", } if source in replace_info: return replace_info[source] diff --git a/unilabos/ros/nodes/base_device_node.py b/unilabos/ros/nodes/base_device_node.py index d8e3f87..4550407 100644 --- a/unilabos/ros/nodes/base_device_node.py +++ b/unilabos/ros/nodes/base_device_node.py @@ -1320,19 +1320,32 @@ class BaseROS2DeviceNode(Node, Generic[T]): resource_inputs = action_kwargs[k] if is_sequence else [action_kwargs[k]] # 批量查询资源 - queried_resources = [] - for resource_data in resource_inputs: + queried_resources: list = [None] * len(resource_inputs) + uuid_indices: list[tuple[int, str, dict]] = [] # (index, uuid, resource_data) + + # 第一遍:处理没有uuid的资源,收集有uuid的资源信息 + for idx, resource_data in enumerate(resource_inputs): unilabos_uuid = resource_data.get("data", {}).get("unilabos_uuid") if unilabos_uuid is None: plr_resource = await self.get_resource_with_dir( resource_id=resource_data["id"], with_children=True ) + if "sample_id" in resource_data: + plr_resource.unilabos_extra["sample_uuid"] = resource_data["sample_id"] + queried_resources[idx] = plr_resource else: - resource_tree = await self.get_resource([unilabos_uuid]) - plr_resource = resource_tree.to_plr_resources()[0] - if "sample_id" in resource_data: - plr_resource.unilabos_extra["sample_uuid"] = resource_data["sample_id"] - queried_resources.append(plr_resource) + uuid_indices.append((idx, unilabos_uuid, resource_data)) + + # 第二遍:批量查询有uuid的资源 + if uuid_indices: + uuids = [item[1] for item in uuid_indices] + resource_tree = await self.get_resource(uuids) + plr_resources = resource_tree.to_plr_resources() + for i, (idx, _, resource_data) in enumerate(uuid_indices): + plr_resource = plr_resources[i] + if "sample_id" in resource_data: + plr_resource.unilabos_extra["sample_uuid"] = resource_data["sample_id"] + queried_resources[idx] = plr_resource self.lab_logger().debug(f"资源查询结果: 共 {len(queried_resources)} 个资源") diff --git a/unilabos/ros/nodes/presets/host_node.py b/unilabos/ros/nodes/presets/host_node.py index cf9fd70..f7e9674 100644 --- a/unilabos/ros/nodes/presets/host_node.py +++ b/unilabos/ros/nodes/presets/host_node.py @@ -795,6 +795,7 @@ class HostNode(BaseROS2DeviceNode): goal_msg = convert_to_ros_msg(action_client._action_type.Goal(), action_kwargs) self.lab_logger().info(f"[Host Node] Sending goal for {action_id}: {str(goal_msg)[:1000]}") + self.lab_logger().trace(f"[Host Node] Sending goal for {action_id}: {action_kwargs}") self.lab_logger().trace(f"[Host Node] Sending goal for {action_id}: {goal_msg}") action_client.wait_for_server() goal_uuid_obj = UUID(uuid=list(u.bytes)) diff --git a/unilabos/utils/environment_check.py b/unilabos/utils/environment_check.py index 3963b9e..73c0b10 100644 --- a/unilabos/utils/environment_check.py +++ b/unilabos/utils/environment_check.py @@ -24,6 +24,7 @@ class EnvironmentChecker: "msgcenterpy": "msgcenterpy", "opentrons_shared_data": "opentrons_shared_data", "typing_extensions": "typing_extensions", + "crcmod": "crcmod-plus", } # 特殊安装包(需要特殊处理的包) diff --git a/unilabos/utils/requirements.txt b/unilabos/utils/requirements.txt new file mode 100644 index 0000000..65d724f --- /dev/null +++ b/unilabos/utils/requirements.txt @@ -0,0 +1,18 @@ +networkx +typing_extensions +websockets +msgcenterpy>=0.1.5 +opentrons_shared_data +pint +fastapi +jinja2 +requests +uvicorn +pyautogui +opcua +pyserial +pandas +crcmod-plus +pymodbus +matplotlib +pylibftdi \ No newline at end of file diff --git a/unilabos_msgs/package.xml b/unilabos_msgs/package.xml index b9c2632..68ad132 100644 --- a/unilabos_msgs/package.xml +++ b/unilabos_msgs/package.xml @@ -2,7 +2,7 @@ unilabos_msgs - 0.10.15 + 0.10.17 ROS2 Messages package for unilabos devices Junhan Chang Xuwznln