diff --git a/docker/.coderabbit.yaml b/docker/.coderabbit.yaml new file mode 100644 index 00000000..8c9c45d2 --- /dev/null +++ b/docker/.coderabbit.yaml @@ -0,0 +1,18 @@ +--- +# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json +language: en-US +early_access: false +reviews: + profile: chill + request_changes_workflow: false + high_level_summary: true + poem: false + review_status: true + collapse_walkthrough: false + auto_review: + enabled: true + drafts: false + base_branches: + - ros2-devel +chat: + auto_reply: true diff --git a/docker/.dockerignore b/docker/.dockerignore new file mode 100644 index 00000000..06a4227a --- /dev/null +++ b/docker/.dockerignore @@ -0,0 +1,8 @@ +.devcontainer/ +.github/ +.git/ +.vscode/ +.dockerignore +.gitignore +Dockerfile.* +README.md diff --git a/docker/.github/workflows/protect-default-branch.yaml b/docker/.github/workflows/protect-default-branch.yaml new file mode 100644 index 00000000..678daebe --- /dev/null +++ b/docker/.github/workflows/protect-default-branch.yaml @@ -0,0 +1,24 @@ +--- +name: Validate PR head branch +on: + pull_request: + branches: + - ros2 + +jobs: + check-head-branch: + runs-on: ubuntu-latest + steps: + - name: Check allowed branches + run: | + pattern="^[0-9]+\.[0-9]+\.[0-9]+-[0-9]{8}$" # This regex matches the X.X.X-YYYYMMDD pattern + if [[ "${{ github.head_ref }}" == *"hotfix"* ]]; then + echo "PR from a branch containing 'hotfix' is allowed." + exit 0 + elif [[ "${{ github.head_ref }}" =~ $pattern ]]; then + echo "PR from a branch matching X.X.X-YYYYMMDD pattern is allowed." + exit 0 + else + echo "PRs must come from branches containing 'hotfix' phrase or matching X.X.X-YYYYMMDD pattern." + exit 1 + fi diff --git a/docker/.github/workflows/release-repository.yaml b/docker/.github/workflows/release-repository.yaml new file mode 100644 index 00000000..c742d184 --- /dev/null +++ b/docker/.github/workflows/release-repository.yaml @@ -0,0 +1,105 @@ +--- +name: Release repository + +on: + workflow_dispatch: + inputs: + release_candidate: + description: Branch name of the release candidate. + required: true + version: + description: New version (used for tag). + required: true + release_name: + description: Name of the release to be created. Version in the first place is recommended (e.g. + `2.0.0-alpha`). + required: true + automatic_mode: + type: boolean + default: false + description: Automatically merge PR and create release. + prerelease: + type: boolean + default: false + description: Mark the release as a prerelease. + +jobs: + release: + name: Release repository + runs-on: ubuntu-22.04 + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + MAIN_BRANCH: ros2 + DEVEL_BRANCH: ros2-devel + steps: + - name: Checkout to main branch + if: ${{ github.event.inputs.release_candidate != env.MAIN_BRANCH }} + uses: actions/checkout@v4 + with: + ref: ${{ env.MAIN_BRANCH }} + fetch-depth: 0 + + - name: Get git diff between main and release candidate + id: git_diff + run: | + git fetch origin ${{ github.event.inputs.release_candidate }}:${{ github.event.inputs.release_candidate }} + DIFF=$(git diff --name-only ${{ github.event.inputs.release_candidate }}) # Change to "master.." to diff against last common commit + if [ -z "$DIFF" ]; then + echo "DIFF=false" >> $GITHUB_ENV + else + echo "DIFF=true" >> $GITHUB_ENV + fi + + - name: Create PR to main branch + if: ${{ github.event.inputs.release_candidate != env.MAIN_BRANCH && env.DIFF == 'true' }} + run: | + gh pr create \ + --base ${{ env.MAIN_BRANCH }} \ + --head ${{ github.event.inputs.release_candidate }} \ + --title "Release ${{ github.event.inputs.version}} to ${{ env.MAIN_BRANCH }}" \ + --body "This PR incorporates release updates." + + - name: Merge PR to main branch + if: ${{ fromJSON(github.event.inputs.automatic_mode) == true && github.event.inputs.release_candidate + != env.MAIN_BRANCH && env.DIFF == 'true' }} + run: | + gh pr merge ${{ github.event.inputs.release_candidate }} \ + --merge --delete-branch + + - name: Delete branch + if: ${{ fromJSON(github.event.inputs.automatic_mode) == true && github.event.inputs.release_candidate + != env.MAIN_BRANCH && env.DIFF == 'false' }} + run: | + git push origin --delete ${{ github.event.inputs.release_candidate }} + + - name: Create prerelease + if: ${{ fromJSON(github.event.inputs.automatic_mode) == true && fromJSON(github.event.inputs.prerelease) + == true}} + run: | + gh release create ${{ github.event.inputs.version }} \ + --target ${{ env.MAIN_BRANCH }} \ + --title ${{ github.event.inputs.release_name }} \ + --generate-notes \ + --prerelease + + - name: Create release + if: ${{ fromJSON(github.event.inputs.automatic_mode) == true && fromJSON(github.event.inputs.prerelease) + == false}} + run: | + gh release create ${{ github.event.inputs.version }} \ + --target ${{ env.MAIN_BRANCH }} \ + --title ${{ github.event.inputs.release_name }} \ + --generate-notes + + - name: Checkout to devel branch + if: ${{ env.DEVEL_BRANCH != env.MAIN_BRANCH && fromJSON(inputs.automatic_mode) == true }} + uses: actions/checkout@v4 + with: + ref: ${{ env.DEVEL_BRANCH }} + fetch-depth: 0 + + - name: Update devel branch + if: ${{ env.DEVEL_BRANCH != env.MAIN_BRANCH && fromJSON(inputs.automatic_mode) == true }} + run: | + git pull origin ${{ env.MAIN_BRANCH }} + git push origin ${{ env.DEVEL_BRANCH }} diff --git a/docker/.github/workflows/ros-docker-image.yaml b/docker/.github/workflows/ros-docker-image.yaml new file mode 100644 index 00000000..7152f1e3 --- /dev/null +++ b/docker/.github/workflows/ros-docker-image.yaml @@ -0,0 +1,77 @@ +--- +name: Build/Publish ROS Docker Image + +on: + workflow_dispatch: + inputs: + panther_codebase_version: + description: Version of the panther_ros to be used in the docker image (branch/tag/commit). + required: true + type: string + default: ros2-devel + build_type: + description: Is it a "development" or a "stable" release? + required: true + default: development + type: choice + options: + - development + - stable + target_distro: + description: In case of "stable" release specify the ROS distro of the existing docker image (eg. + humble) + type: string + default: humble + target_release: + description: In case of "stable" release specify the version of the existing docker image (eg. + 1.0.12) + type: string + default: 0.0.0 + target_date: + description: In case of "stable" release specify the date of the existing docker image in format + YYYYMMDD (eg. 20220124) + type: string + default: '20131206' + feature_branch_tag_suffix: + description: In case of "development" release from a feature branch specify the custom tag suffix + for the docker image + type: string + default: '' + +jobs: + build: + runs-on: ubuntu-22.04 + strategy: + fail-fast: false + matrix: + include: + - dockerfile: Dockerfile.hardware + platforms: linux/arm64 + ros_distro: humble + - dockerfile: Dockerfile.simulation + repo_name: panther-gazebo + platforms: linux/amd64 + ros_distro: humble + + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Build Docker Image + uses: husarion-ci/ros-docker-img-action@v0.8 + with: + dockerhub_username: ${{ secrets.DOCKERHUB_USERNAME }} + dockerhub_token: ${{ secrets.DOCKERHUB_TOKEN }} + main_branch_name: ros2 + dockerfile: ${{ matrix.dockerfile }} + repo_name: ${{ matrix.repo_name }} + branch_name: ${{ inputs.panther_codebase_version }} + build_type: ${{ inputs.build_type }} + ros_distro: ${{ matrix.ros_distro }} + platforms: ${{ matrix.platforms }} + # variables important only for stable release + target_distro: ${{ inputs.target_distro }} + target_release: ${{ inputs.target_release }} + target_date: ${{ inputs.target_date }} + # variables important only for development release + feature_branch_tag_suffix: ${{ inputs.feature_branch_tag_suffix }} diff --git a/docker/.github/workflows/update-tags-in-compose.yaml b/docker/.github/workflows/update-tags-in-compose.yaml new file mode 100644 index 00000000..2a42bbad --- /dev/null +++ b/docker/.github/workflows/update-tags-in-compose.yaml @@ -0,0 +1,38 @@ +--- +name: Update tags in compose files + +on: + workflow_dispatch: + inputs: + branch_name: + description: Target branch for the update. + required: true + image_tag: + description: Docker image tag to be replaced with. + required: true + +jobs: + release: + name: Release repository + runs-on: ubuntu-22.04 + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + ref: ${{ github.event.inputs.branch_name }} + + - name: Update docker image tag + uses: mikefarah/yq@v4.43.1 + with: + cmd: | + yq -i '.services.panther_ros.image = "husarion/panther:${{ github.event.inputs.image_tag }}" | (... | select(tag == "!!merge")) tag = ""' demo/compose.minimal-setup.yaml + yq -i '.services.panther_gazebo.image = "husarion/panther-gazebo:${{ github.event.inputs.image_tag }}" | (... | select(tag == "!!merge")) tag = ""' demo/compose.simulation.yaml + + - name: Commit changes to target branch + uses: EndBug/add-and-commit@v9 + with: + message: Update docker image tag + author_name: action-bot + author_email: action-bot@action-bot.com diff --git a/docker/.gitignore b/docker/.gitignore new file mode 100644 index 00000000..126153b9 --- /dev/null +++ b/docker/.gitignore @@ -0,0 +1,28 @@ +# Docker project generated files to ignore +# if you want to ignore files created by your editor/tools, +# please consider a global .gitignore https://help.github.com/articles/ignoring-files +.vagrant* +bin +docker/docker +.*.swp +a.out +*.orig +build_src +.flymake* +.idea +.DS_Store +docs/_build +docs/_static +docs/_templates +.gopath/ +.dotcloud +*.test +bundles/ +.hg/ +.git/ +vendor/pkg/ +pyenv +Vagrantfile + +.vscode +__pycache__/ diff --git a/docker/.pre-commit-config.yaml b/docker/.pre-commit-config.yaml new file mode 100644 index 00000000..533c059e --- /dev/null +++ b/docker/.pre-commit-config.yaml @@ -0,0 +1,37 @@ +--- +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.5.0 + hooks: + - id: check-merge-conflict + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-yaml + - id: check-xml + - id: check-added-large-files + - id: check-ast + - id: check-json + - id: name-tests-test + files: ^.*\/test\/.*$ + args: [--pytest-test-first] + + - repo: https://github.com/codespell-project/codespell + rev: v2.2.6 + hooks: + - id: codespell + entry: codespell + + - repo: https://github.com/jumanjihouse/pre-commit-hook-yamlfmt + rev: 0.2.3 + hooks: + - id: yamlfmt + files: ^(?!.*compose).*$ + args: [--mapping, '2', --sequence, '4', --offset, '2', --width, '100'] + + # Docs - RestructuredText hooks + - repo: https://github.com/PyCQA/doc8 + rev: v1.1.1 + hooks: + - id: doc8 + args: [--max-line-length=100, --ignore=D001] + exclude: ^.*\/CHANGELOG\.rst/.*$ diff --git a/docker/Dockerfile.hardware b/docker/Dockerfile.hardware new file mode 100644 index 00000000..0ef0d060 --- /dev/null +++ b/docker/Dockerfile.hardware @@ -0,0 +1,44 @@ +ARG ROS_DISTRO=humble + +FROM husarnet/ros:${ROS_DISTRO}-ros-core + +ARG BRANCH_NAME=ros2-devel +ARG BUILD_TEST=OFF + +ENV HUSARION_ROS_BUILD_TYPE=hardware + +STOPSIGNAL SIGINT + +WORKDIR /ros2_ws + +RUN apt-get update && \ + apt-get install -y \ + ros-dev-tools && \ + # Setup workspace + git clone -b ${BRANCH_NAME} https://github.com/husarion/panther_ros.git src/panther_ros && \ + vcs import src < src/panther_ros/husarion_ugv/${HUSARION_ROS_BUILD_TYPE}_deps.repos && \ + cp -r src/ros2_controllers/diff_drive_controller src && \ + cp -r src/ros2_controllers/imu_sensor_broadcaster src && \ + rm -rf src/ros2_controllers && \ + # Install dependencies + rosdep init && \ + rosdep update --rosdistro $ROS_DISTRO && \ + rosdep install --from-paths src -y -i && \ + # Build + source /opt/ros/$ROS_DISTRO/setup.bash && \ + colcon build --symlink-install --packages-up-to husarion_ugv --cmake-args -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=$BUILD_TEST && \ + # Get version + echo $(cat /ros2_ws/src/panther_ros/husarion_ugv/package.xml | grep '' | sed -r 's/.*([0-9]+.[0-9]+.[0-9]+)<\/version>/\1/g') >> /version.txt && \ + # Size optimalization + export SUDO_FORCE_REMOVE=yes && \ + apt-get remove -y \ + ros-dev-tools && \ + apt-get autoremove -y && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* + +COPY update_config_directory.sh /usr/local/sbin/update_config_directory +COPY setup_environment.sh /usr/local/sbin/setup_environment + +# Setup envs from eeprom +RUN sed -i "/# /i set -e -a && source setup_environment" /*_entrypoint.sh diff --git a/docker/Dockerfile.simulation b/docker/Dockerfile.simulation new file mode 100644 index 00000000..881af7bc --- /dev/null +++ b/docker/Dockerfile.simulation @@ -0,0 +1,38 @@ +ARG ROS_DISTRO=humble + +FROM husarnet/ros:${ROS_DISTRO}-ros-core + +ARG BRANCH_NAME=ros2-devel +ARG BUILD_TEST=OFF + +ENV HUSARION_ROS_BUILD_TYPE=simulation + +STOPSIGNAL SIGINT + +WORKDIR /ros2_ws + +RUN apt-get update && \ + apt-get install -y \ + ros-dev-tools && \ + # Setup workspace + git clone -b ${BRANCH_NAME} https://github.com/husarion/panther_ros.git src/panther_ros && \ + vcs import src < src/panther_ros/husarion_ugv/${HUSARION_ROS_BUILD_TYPE}_deps.repos && \ + cp -r src/ros2_controllers/diff_drive_controller src && \ + cp -r src/ros2_controllers/imu_sensor_broadcaster src && \ + rm -rf src/ros2_controllers && \ + # Install dependencies + rosdep init && \ + rosdep update --rosdistro $ROS_DISTRO && \ + rosdep install --from-paths src -y -i && \ + # Build + source /opt/ros/$ROS_DISTRO/setup.bash && \ + colcon build --symlink-install --packages-up-to husarion_ugv --cmake-args -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=$BUILD_TEST && \ + # Get version + echo $(cat /ros2_ws/src/panther_ros/husarion_ugv/package.xml | grep '' | sed -r 's/.*([0-9]+.[0-9]+.[0-9]+)<\/version>/\1/g') >> /version.txt && \ + # Size optimalization + export SUDO_FORCE_REMOVE=yes && \ + apt-get remove -y \ + ros-dev-tools && \ + apt-get autoremove -y && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* diff --git a/docker/LICENSE b/docker/LICENSE new file mode 100644 index 00000000..261eeb9e --- /dev/null +++ b/docker/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/docker/README.md b/docker/README.md new file mode 100644 index 00000000..0a6c3b21 --- /dev/null +++ b/docker/README.md @@ -0,0 +1,72 @@ +# panther-docker + +Docker images dedicated to Husarion Panther ROS system and simulation. + +## Docker Images + +Docker images are automatically deployed to Docker Hub. The image tag includes information about the ROS distribution, the version of the [panther_ros](https://github.com/husarion/panther_ros/tree/ros2) repository, and the date of release. Additionally, stable image versions are tagged with `stable` and recommended for production use. +Below, you can find a list of available Docker images. To access the latest tag, simply follow the provided links: + +- [husarion/panther](https://hub.docker.com/r/husarion/panther) - ROS packages for Panther robot, +- [husarion/panther-gazebo](https://hub.docker.com/r/husarion/panther-gazebo) - Simulation for Panther robot in Gazebo-classic. + +## Updating Panther Software + +> [!Note] +> Latest Panther Docker images are compatible with Built-in Computer OS version 2.0.0 and newer. If your operating system is older, please ensure you update it before proceeding. **[COMING SOON]** Follow [operating system reinstallation](ros2-os-instalation-link) for more info. + +Connect to Panther's Built-in Computer: + +```bash +ssh husarion@10.15.20.2 +``` + +## Quick Start + +Depending on your needs, you can run Docker to quickly launch the physical robot or run a simulation. To do this, clone this repository to your robot or computer. + +```bash +git clone -b ros2 https://github.com/husarion/panther-docker.git +cd panther-docker/demo +``` + +### 🤖 Robot + +1. (Optional) Updating configuration files. + + > [!NOTE] + > This may overwrite your changes made in the `config` directory. If you want to keep your configuration you should skip this step or create backup of the `config` directory. + + ```bash + docker run --rm -v /home/husarion/config:/config husarion/panther:humble- update_config_directory + ``` + +2. Activate Panther + + ```bash + docker compose -f compose.minimal-setup.yaml up + ``` + +3. Launch Visualization on PC + + ```bash + xhost local:root + docker compose -f compose.rviz.yaml up + ``` + +> [!NOTE] +> To use the latest version of the image, run the `docker compose pull` command and rerun above commands. + +### 💻 Gazebo Simulation + +To give Docker access to your screen run: + +```bash +xhost local:docker +docker compose -f compose.simulation.yaml up +``` + +> [!NOTE] +> +> 1. You can change robot model and namespace by editing the launch command in `compose.simulation.yaml`. +> 2. If you have an NVIDIA GPU, it is worth changing the compose configuration from `cpu-config` to `gpu-config`. For this purpose, it is necessary to install [NVIDIA Container Toolkit](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html). With NVIDIA Container Toolkit installed, modify following Docker compose file by replacing `*cpu-config` with `*gpu-config`: [compose.simulation.yaml](./demo/compose.simulation.yaml). diff --git a/docker/demo/.env.cpu b/docker/demo/.env.cpu new file mode 100644 index 00000000..08406af9 --- /dev/null +++ b/docker/demo/.env.cpu @@ -0,0 +1,3 @@ +DISPLAY=${DISPLAY:?You need to define display env} +LIBGL_ALWAYS_SOFTWARE=1 +QT_X11_NO_MITSHM=1 diff --git a/docker/demo/.env.gpu b/docker/demo/.env.gpu new file mode 100644 index 00000000..4bec972a --- /dev/null +++ b/docker/demo/.env.gpu @@ -0,0 +1,3 @@ +DISPLAY=${DISPLAY:?You need to define display env} +NVIDIA_VISIBLE_DEVICES=all +NVIDIA_DRIVER_CAPABILITIES=all diff --git a/docker/demo/compose.minimal-setup.yaml b/docker/demo/compose.minimal-setup.yaml new file mode 100644 index 00000000..dba0f388 --- /dev/null +++ b/docker/demo/compose.minimal-setup.yaml @@ -0,0 +1,51 @@ +x-common-config: &common-config + network_mode: host + ipc: host + restart: always + env_file: + - /home/husarion/config/common/.env # env configuration including namespace, domain id, DDS, etc. + +services: + husarion_ugv_ros: + image: husarion/panther:humble-ros2-devel + container_name: husarion_ugv_ros + <<: *common-config + devices: + - /dev/bus/usb + - /dev/gpiochip0 + - /dev/spiled-channel1 + - /dev/spiled-channel2 + device_cgroup_rules: + - 'c 189:* rmw' # USB devices + - 'c 254:0 rmw' # gpiochip0 + - 'c 153:* rmw' # spiled-channel1, spiled-channel2 + volumes: + - /run/husarion/robot_config.env:/run/husarion/robot_config.env + - /run/husarion/robot_config.yaml:/run/husarion/robot_config.yaml + - /sys/bus/iio/devices:/sys/bus/iio/devices:ro # Read-only access to IIO devices + - ~/.ssh/id_rsa:/root/.ssh/id_rsa + - /home/husarion/config:/config + # Realtime hardware (https://control.ros.org/master/doc/ros2_control/controller_manager/doc/userdoc.html#determinism) + ulimits: + rtprio: + soft: 99 + hard: 99 + memlock: + soft: 102400 + hard: 102400 + command: > + ros2 launch husarion_ugv_bringup bringup.launch.py common_dir_path:=/config + + gamepad_controller: + image: husarion/joy2twist:humble-1.0.0-20241003-stable + container_name: gamepad_controller + <<: *common-config + volumes: + - /home/husarion/config/common/joy2twist.yaml:/joy2twist.yaml + devices: + - /dev/input/js0 + device_cgroup_rules: + - 'c 13:0 rmw' # gamepad + stop_signal: SIGINT + command: > + ros2 launch joy2twist gamepad_controller.launch.py joy2twist_params_file:=/joy2twist.yaml diff --git a/docker/demo/compose.rviz.yaml b/docker/demo/compose.rviz.yaml new file mode 100644 index 00000000..928368ea --- /dev/null +++ b/docker/demo/compose.rviz.yaml @@ -0,0 +1,28 @@ +x-common-config: + &common-config + network_mode: host + ipc: host + environment: + - RMW_IMPLEMENTATION=rmw_cyclonedds_cpp # Default FastDDS do not work + - ROS_DOMAIN_ID=${ROS_DOMAIN_ID:-0} + +x-cpu-config: + &cpu-config + env_file: .env.cpu + +x-gpu-config: + &gpu-config + runtime: nvidia + env_file: .env.gpu + +services: + rviz: + image: husarion/rviz2:humble-nightly + container_name: rviz2 + <<: + - *common-config + - *cpu-config + volumes: + - /tmp/.X11-unix:/tmp/.X11-unix:rw + - ./config/panther.rviz:/root/.rviz2/default.rviz + stop_signal: SIGINT diff --git a/docker/demo/compose.simulation.yaml b/docker/demo/compose.simulation.yaml new file mode 100644 index 00000000..24d35c3b --- /dev/null +++ b/docker/demo/compose.simulation.yaml @@ -0,0 +1,22 @@ +x-common-config: &common-config + network_mode: host + ipc: host + environment: + - RMW_IMPLEMENTATION=rmw_cyclonedds_cpp + - ROS_DOMAIN_ID=${ROS_DOMAIN_ID:-0} +x-cpu-config: &cpu-config + env_file: .env.cpu +x-gpu-config: &gpu-config + runtime: nvidia + env_file: .env.gpu +services: + panther_gazebo: + image: husarion/panther-gazebo:humble-2.1.2-20241125 + container_name: panther_gazebo + <<: + - *common-config + - *cpu-config + volumes: + - /tmp/.X11-unix:/tmp/.X11-unix:rw + command: > + ros2 launch panther_gazebo simulation.launch.py robot_model:=panther namespace:=panther diff --git a/docker/demo/config/panther.rviz b/docker/demo/config/panther.rviz new file mode 100644 index 00000000..67415133 --- /dev/null +++ b/docker/demo/config/panther.rviz @@ -0,0 +1,218 @@ +Panels: + - Class: rviz_common/Displays + Help Height: 78 + Name: Displays + Property Tree Widget: + Expanded: ~ + Splitter Ratio: 0.5 + Tree Height: 725 + - Class: rviz_common/Selection + Name: Selection + - Class: rviz_common/Tool Properties + Expanded: + - /2D Goal Pose1 + - /Publish Point1 + Name: Tool Properties + Splitter Ratio: 0.5886790156364441 + - Class: rviz_common/Views + Expanded: + - /Current View1 + - /Current View1/Focal Point1 + Name: Views + Splitter Ratio: 0.5 + - Class: rviz_common/Time + Experimental: false + Name: Time + SyncMode: 0 + SyncSource: "" +Visualization Manager: + Class: "" + Displays: + - Alpha: 0.5 + Cell Size: 1 + Class: rviz_default_plugins/Grid + Color: 160; 160; 164 + Enabled: true + Line Style: + Line Width: 0.029999999329447746 + Value: Lines + Name: Grid + Normal Cell Count: 0 + Offset: + X: 0 + Y: 0 + Z: 0 + Plane: XY + Plane Cell Count: 10 + Reference Frame: + Value: true + - Class: rviz_default_plugins/Axes + Enabled: true + Length: 1 + Name: Axes + Radius: 0.10000000149011612 + Reference Frame: + Value: true + - Alpha: 1 + Class: rviz_default_plugins/RobotModel + Collision Enabled: false + Description File: "" + Description Source: Topic + Description Topic: + Depth: 5 + Durability Policy: Volatile + History Policy: Keep Last + Reliability Policy: Reliable + Value: /panther/robot_description + Enabled: true + Links: + All Links Enabled: true + Expand Joint Details: false + Expand Link Details: false + Expand Tree: false + Link Tree Style: Links in Alphabetic Order + base_link: + Alpha: 1 + Show Axes: false + Show Trail: false + body_link: + Alpha: 1 + Show Axes: false + Show Trail: false + Value: true + cover_link: + Alpha: 1 + Show Axes: false + Show Trail: false + fl_wheel_link: + Alpha: 1 + Show Axes: false + Show Trail: false + Value: true + fr_wheel_link: + Alpha: 1 + Show Axes: false + Show Trail: false + Value: true + front_bumper_link: + Alpha: 1 + Show Axes: false + Show Trail: false + front_light_link: + Alpha: 1 + Show Axes: false + Show Trail: false + imu_link: + Alpha: 1 + Show Axes: false + Show Trail: false + rear_bumper_link: + Alpha: 1 + Show Axes: false + Show Trail: false + rear_light_link: + Alpha: 1 + Show Axes: false + Show Trail: false + rl_wheel_link: + Alpha: 1 + Show Axes: false + Show Trail: false + Value: true + rr_wheel_link: + Alpha: 1 + Show Axes: false + Show Trail: false + Value: true + Mass Properties: + Inertia: false + Mass: false + Name: RobotModel + TF Prefix: panther + Update Interval: 0 + Value: true + Visual Enabled: true + Enabled: true + Global Options: + Background Color: 48; 48; 48 + Fixed Frame: panther/odom + Frame Rate: 30 + Name: root + Tools: + - Class: rviz_default_plugins/Interact + Hide Inactive Objects: true + - Class: rviz_default_plugins/MoveCamera + - Class: rviz_default_plugins/Select + - Class: rviz_default_plugins/FocusCamera + - Class: rviz_default_plugins/Measure + Line color: 128; 128; 0 + - Class: rviz_default_plugins/SetInitialPose + Covariance x: 0.25 + Covariance y: 0.25 + Covariance yaw: 0.06853891909122467 + Topic: + Depth: 5 + Durability Policy: Volatile + History Policy: Keep Last + Reliability Policy: Reliable + Value: /initialpose + - Class: rviz_default_plugins/SetGoal + Topic: + Depth: 5 + Durability Policy: Volatile + History Policy: Keep Last + Reliability Policy: Reliable + Value: /goal_pose + - Class: rviz_default_plugins/PublishPoint + Single click: true + Topic: + Depth: 5 + Durability Policy: Volatile + History Policy: Keep Last + Reliability Policy: Reliable + Value: /clicked_point + Transformation: + Current: + Class: rviz_default_plugins/TF + Value: true + Views: + Current: + Class: rviz_default_plugins/ThirdPersonFollower + Distance: 3.5 + Enable Stereo Rendering: + Stereo Eye Separation: 0.05999999865889549 + Stereo Focal Distance: 1 + Swap Stereo Eyes: false + Value: false + Focal Point: + X: -0.10000000149011612 + Y: -0.05000000074505806 + Z: 0 + Focal Shape Fixed Size: true + Focal Shape Size: 0.05000000074505806 + Invert Z Axis: false + Name: Current View + Near Clip Distance: 0.009999999776482582 + Pitch: 0.5750001072883606 + Target Frame: base_link + Value: ThirdPersonFollower (rviz_default_plugins) + Yaw: 2.9749996662139893 + Saved: ~ +Window Geometry: + Displays: + collapsed: false + Height: 1016 + Hide Left Dock: false + Hide Right Dock: false + QMainWindow State: 000000ff00000000fd00000004000000000000016a0000035efc020000000bfb0000001200530065006c0065006300740069006f006e00000001e10000009b0000005c00fffffffb0000001e0054006f006f006c002000500072006f007000650072007400690065007302000001ed000001df00000185000000a3fb000000120056006900650077007300200054006f006f02000001df000002110000018500000122fb000000200054006f006f006c002000500072006f0070006500720074006900650073003203000002880000011d000002210000017afb000000100044006900730070006c006100790073010000003b0000035e000000c700fffffffb0000002000730065006c0065006300740069006f006e00200062007500660066006500720200000138000000aa0000023a00000294fb00000014005700690064006500530074006500720065006f02000000e6000000d2000003ee0000030bfb0000000c004b0069006e0065006300740200000186000001060000030c00000261fb0000000a0049006d006100670065010000026b000001470000000000000000fb0000000c00430061006d0065007200610000000233000000970000000000000000fb0000000a0049006d00610067006501000002a3000000f40000000000000000000000010000010f000002aafc0200000003fb0000001e0054006f006f006c002000500072006f00700065007200740069006500730100000041000000780000000000000000fb0000000a00560069006500770073000000003b000002aa000000a000fffffffb0000001200530065006c0065006300740069006f006e010000025a000000b200000000000000000000000200000490000000a9fc0100000001fb0000000a00560069006500770073030000004e00000080000002e100000197000000030000073a0000003efc0100000002fb0000000800540069006d006501000000000000073a0000025300fffffffb0000000800540069006d00650100000000000004500000000000000000000005ca0000035e00000004000000040000000800000008fc0000000100000002000000010000000a0054006f006f006c00730100000000ffffffff0000000000000000 + Selection: + collapsed: false + Time: + collapsed: false + Tool Properties: + collapsed: false + Views: + collapsed: false + Width: 1850 + X: 70 + Y: 27 diff --git a/docker/setup_environment.sh b/docker/setup_environment.sh new file mode 100755 index 00000000..4a2d6c23 --- /dev/null +++ b/docker/setup_environment.sh @@ -0,0 +1,11 @@ +#!/bin/bash +set -e + +# source robot environment +source /run/husarion/robot_config.env + +# check if /config directory is empty except it may contain common directory inside +if [ ! "$(ls -A /config | grep -v common)" ]; then + echo "Config directory is empty, copying files." + update_config_directory +fi diff --git a/docker/update_config_directory.sh b/docker/update_config_directory.sh new file mode 100755 index 00000000..aabd7782 --- /dev/null +++ b/docker/update_config_directory.sh @@ -0,0 +1,39 @@ +#!/bin/bash +set -e + +# method to copy directory/file using package name +copy_package_file() { + local package_name=$1 + local file_name=$2 + local package_file_path=$(ros2 pkg prefix $package_name)/share/$package_name/$file_name + + if [ ! -d "/config/$package_name" ]; then + mkdir -p /config/$package_name + fi + + # create subdirectories for files with subdirectories + if [[ $file_name == */* ]]; then + local subdirectory=$(dirname $file_name) + if [ ! -d "/config/$package_name/$subdirectory" ]; then + mkdir -p /config/$package_name/$subdirectory + fi + fi + + if [ -d "$package_file_path" ]; then + cp -rL $package_file_path /config/$package_name || true + else + cp -L $package_file_path /config/$package_name/$file_name || true + fi +} + +copy_package_file husarion_ugv_controller config +copy_package_file husarion_ugv_lights config/user_animations.yaml +copy_package_file husarion_ugv_localization config +rm /config/husarion_ugv_localization/config/nmea_navsat.yaml || true +copy_package_file husarion_ugv_manager behavior_trees/lights.xml +copy_package_file husarion_ugv_manager behavior_trees/LightsBT.btproj +copy_package_file panther_description config/components.yaml +copy_package_file lynx_description config/components.yaml + +# Change ownership of the copied files to host user +chown -R 1000:1001 /config