diff --git a/.docker/Dockerfile b/.docker/Dockerfile index aa2d315..5df86ac 100644 --- a/.docker/Dockerfile +++ b/.docker/Dockerfile @@ -1,11 +1,11 @@ ARG ROS_DISTRO="jazzy" FROM ros:${ROS_DISTRO} -COPY . /ros2_dev/scara_tutorial_ros2 +COPY . /ros2_dev/ecat_ros2_workshop RUN apt update && apt upgrade -y RUN cd ros2_dev && \ apt update && \ - cd scara_tutorial_ros2 && \ + cd ecat_ros2_workshop && \ . /opt/ros/${ROS_DISTRO}/setup.sh && \ rosdep install --ignore-src --from-paths . -y -r && \ colcon build --symlink-install diff --git a/.docker/Dockerfile_novnc b/.docker/Dockerfile_novnc index 30aeedb..57d9db8 100644 --- a/.docker/Dockerfile_novnc +++ b/.docker/Dockerfile_novnc @@ -1,5 +1,5 @@ ARG ROS_DISTRO="jazzy" -FROM scara_tutorial_ros2:${ROS_DISTRO} +FROM ecat_ros2_workshop:${ROS_DISTRO} RUN apt update RUN DEBIAN_FRONTEND=noninteractive apt-get install -qqy \ diff --git a/.docker/README.md b/.docker/README.md index 8f8b043..1d20471 100644 --- a/.docker/README.md +++ b/.docker/README.md @@ -4,26 +4,26 @@ Provides a basic preconfigured docker container for tutorial purposes. To use it, make sure you have [Docker](https://docs.docker.com/get-docker/) installed, then build and run the image : ```shell -$ docker build --tag scara_tutorial_ros2:jazzy --file .docker/Dockerfile . -$ docker run scara_tutorial_ros2:jazzy ros2 launch scara_bringup scara.launch.py +$ docker build --tag ecat_ros2_workshop:jazzy --file .docker/Dockerfile . +$ docker run ecat_ros2_workshop:jazzy ros2 launch scara_bringup scara.launch.py ``` ### Run with GUI To run the docker image with GUI, use the [rocker tool](https://github.com/osrf/rocker): ```shell $ sudo apt install python3-rocker -$ rocker --net=host --x11 --devices /dev/dri --user scara_tutorial_ros2:jazzy ros2 launch scara_bringup scara.launch.py +$ rocker --net=host --x11 --devices /dev/dri --user ecat_ros2_workshop:jazzy ros2 launch scara_bringup scara.launch.py ``` ### Run with noVNC -To run the docker image with noVNC, make sure that `scara_tutorial_ros2:jazzy` is built then build and run the novnc docker : +To run the docker image with noVNC, make sure that `ecat_ros2_workshop:jazzy` is built then build and run the novnc docker : ```shell -$ docker build --tag scara_tutorial_ros2:jazzy_novnc --file .docker/Dockerfile_novnc . -$ docker run --rm -p 6080:6080 -it scara_tutorial_ros2:jazzy_novnc +$ docker build --tag ecat_ros2_workshop:jazzy_novnc --file .docker/Dockerfile_novnc . +$ docker run --rm -p 6080:6080 -it ecat_ros2_workshop:jazzy_novnc ``` Then open your browser and navigate to `http://localhost:6080/vnc.html` to access the desktop environment. Inside the noVNC session, you can open a terminal and run: ```shell -$ cd ros2_dev/scara_tutorial_ros2/ +$ cd ros2_dev/ecat_ros2_workshop/ $ source install/setup.bash $ ros2 launch scara_bringup scara.launch.py ``` @@ -33,12 +33,12 @@ $ ros2 launch scara_bringup scara.launch.py ### Run with bash To interact with the environment, run docker using: ```shell -$ docker run -it scara_tutorial_ros2:jazzy +$ docker run -it ecat_ros2_workshop:jazzy ``` and inside docker run: ```shell -$ cd ros2_dev/scara_tutorial_ros2/ +$ cd ros2_dev/ecat_ros2_workshop/ $ source install/setup.bash $ ros2 launch scara_bringup scara.launch.py ``` -The `scara_tutorial_ros2` nodes should now be running. \ No newline at end of file +The `ecat_ros2_workshop` nodes should now be running. \ No newline at end of file diff --git a/.docker/ros_entrypoint.sh b/.docker/ros_entrypoint.sh index 1fe5d7b..9fe3f2f 100644 --- a/.docker/ros_entrypoint.sh +++ b/.docker/ros_entrypoint.sh @@ -4,5 +4,5 @@ set -e # setup ros environment # export ROS_LOCALHOST_ONLY=1 source "/opt/ros/$ROS_DISTRO/setup.bash" -source "/ros2_dev/scara_tutorial_ros2/install/setup.bash" +source "/ros2_dev/ecat_ros2_workshop/install/setup.bash" exec "$@" \ No newline at end of file diff --git a/.docker/ros_entrypoint_novnc.sh b/.docker/ros_entrypoint_novnc.sh index fb4e09d..039a60b 100644 --- a/.docker/ros_entrypoint_novnc.sh +++ b/.docker/ros_entrypoint_novnc.sh @@ -15,5 +15,5 @@ set -e # setup ros environment # export ROS_LOCALHOST_ONLY=1 source "/opt/ros/$ROS_DISTRO/setup.bash" -source "/ros2_dev/scara_tutorial_ros2/install/setup.bash" +source "/ros2_dev/ecat_ros2_workshop/install/setup.bash" exec "$@" \ No newline at end of file diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml new file mode 100644 index 0000000..a728d76 --- /dev/null +++ b/.github/workflows/deploy-docs.yml @@ -0,0 +1,59 @@ +name: Documentation + +# Controls when the workflow will run +on: + # Triggers the workflow on push events but only for the main branch + push: + branches: [ main ] + + # Triggers the workflow on pull request events + pull_request: + branches: [ main ] + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +# A workflow run is made up of one or more jobs that can run sequentially or in parallel +jobs: + # This workflow contains a single job called "build_documentation" + build_documentation: + # The type of runner that the job will run on + runs-on: ubuntu-latest + + # Steps represent a sequence of tasks that will be executed as part of the job + steps: + # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it + - uses: actions/checkout@v4 + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: '3.x' + + - name: Install dependencies + run: | + pip install --upgrade pip + pip install -r docs/requirements.txt + + - name: Build documentation + run: | + mkdocs build --config-file docs/mkdocs.yml + + - name: Create commit + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + run: | + git clone https://github.com/ICube-Robotics/ecat_ros2_workshop.git --branch gh-pages --single-branch gh-pages + cp -r docs/site/* gh-pages/ + cd gh-pages + git config --local user.email "action@github.com" + git config --local user.name "GitHub Action" + git add . + git commit -m "Update documentation" -a || true + + - name: Push changes + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + uses: ad-m/github-push-action@master + with: + branch: gh-pages + directory: gh-pages + github_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/README.md b/README.md index 372bd04..b39ae60 100644 --- a/README.md +++ b/README.md @@ -1,50 +1,112 @@ -# Scara tutorial ROS2 -The [ros2_control](https://control.ros.org) framework is a realtime control framework designed for general robotics applications that gains more and more attention both for research and industrial purposes. An overview of the ros2_control framework can be found [here](resources/r2c_overview.md). - -This tutorial is made to understand the basic concepts of controlling a robot using ros2_control. In particular, it describes how to : -- [Write a URDF description of a simple SCARA manipulator](resources/urdf_tutorial.md) -- [Launch and interact with the SCARA robot](resources/launch_tutorial.md) -- [Write a custom hardware interface for the SCARA robot](resources/hardware_tutorial.md) -- [Write a custom controller for the SCARA robot](resources/controller_tutorial.md) -- [Set up the SCARA manipulator to run with ros2_control and Gazebo](resources/gazebo_tutorial.md) - -![scara model](resources/scara_model.png) - -## Getting Started -***Required setup : Ubuntu 24.04 LTS and ROS2 Jazzy*** - -1. Install `ros2` packages. The current development is based on `ros2 jazzy`. Installation steps are described [here](https://docs.ros.org/en/jazzy/Installation.html). -2. Source your `ros2` environment: - ```shell - source /opt/ros/jazzy/setup.bash - ``` - **NOTE**: The ros2 environment needs to be sourced in every used terminal. If only one distribution of ros2 is used, it can be added to the `~/.bashrc` file. -3. Install `colcon` and its extensions : - ```shell - sudo apt install python3-colcon-common-extensions - ``` -3. Create a new ros2 workspace: - ```shell - mkdir ~/ros2_ws/src - ``` -4. Pull relevant packages, install dependencies, compile, and source the workspace by using: - ```shell - cd ~/ros2_ws - git clone https://github.com/ICube-Robotics/scara_tutorial_ros2.git src/scara_tutorial_ros2 - rosdep install --ignore-src --from-paths . -y -r - colcon build --cmake-args -DCMAKE_BUILD_TYPE=Release --symlink-install - source install/setup.bash - ``` - -## Docker Setup (Alternative) -For a containerized setup, see the [Docker README](.docker/README.md) for instructions on building and running the tutorial in a Docker container with ROS2 Jazzy. - -## Acknowledgments -This tutorial is partially inspired from [pac48](https://github.com/pac48/ros2_control_demos/tree/full-example-tutorial)'s tutorial and the official [ros2_control website](https://control.ros.org). - -## Contacts ## -![icube](https://icube.unistra.fr/fileadmin/templates/DUN/icube/images/logo.png) - -[ICube Laboratory](https://icube.unistra.fr), [University of Strasbourg](https://www.unistra.fr/), France - -__Maciej Bednarczyk:__ [mcbed.robotics@gmail.com](mailto:mcbed.robotics@gmail.com), @github: [mcbed](https://github.com/mcbed) \ No newline at end of file +# SCARA ROS2 EtherCAT Workshop + +[![Documentation](https://img.shields.io/badge/docs-mkdocs-blue)](https://icube-robotics.github.io/ecat_ros2_workshop/) +[![ROS2](https://img.shields.io/badge/ROS2-Jazzy-blue)](https://docs.ros.org/en/jazzy/) +[![License](https://img.shields.io/github/license/ICube-Robotics/ecat_ros2_workshop)](LICENSE) + +The [ros2_control](https://control.ros.org) framework is a real-time control framework designed for general robotics applications that is gaining increasing attention for both research and industrial purposes. + +This comprehensive tutorial teaches the basic concepts of controlling a robot using ros2_control and EtherCAT. + +## πŸ“š Documentation + +**Full documentation is available at: [https://icube-robotics.github.io/ecat_ros2_workshop/](https://icube-robotics.github.io/ecat_ros2_workshop/)** + +## πŸ“– Tutorial Contents + +- [ros2_control Overview](https://icube-robotics.github.io/ecat_ros2_workshop/tutorials/r2c_overview/) +- [Write a URDF description of a SCARA manipulator](https://icube-robotics.github.io/ecat_ros2_workshop/tutorials/urdf_tutorial/) +- [Launch and interact with the SCARA robot](https://icube-robotics.github.io/ecat_ros2_workshop/tutorials/launch_tutorial/) +- [Write a custom hardware interface](https://icube-robotics.github.io/ecat_ros2_workshop/tutorials/hardware_tutorial/) +- [Write a custom controller](https://icube-robotics.github.io/ecat_ros2_workshop/tutorials/controller_tutorial/) +- [Set up Gazebo simulation](https://icube-robotics.github.io/ecat_ros2_workshop/tutorials/gazebo_tutorial/) +- [Control EtherCAT motor drives](https://icube-robotics.github.io/ecat_ros2_workshop/tutorials/ethercat_tutorial/) + +![SCARA Model](resources/scara_model.png) + + +## πŸš€ Quick Start + +**_Required setup: Ubuntu 24.04 LTS and ROS2 Jazzy_** + +### Installation + +1. Install ROS2 Jazzy ([installation guide](https://icube-robotics.github.io/ecat_ros2_workshop/getting-started/installation/)) + +2. Source your ROS2 environment: + ```bash + source /opt/ros/jazzy/setup.bash + ``` + +3. Install colcon: + ```bash + sudo apt install python3-colcon-common-extensions + ``` + +4. Create and build workspace: + ```bash + mkdir -p ~/ros2_ws/src + cd ~/ros2_ws + git clone https://github.com/ICube-Robotics/ecat_ros2_workshop.git src/ecat_ros2_workshop + rosdep install --ignore-src --from-paths . -y -r + colcon build --cmake-args -DCMAKE_BUILD_TYPE=Release --symlink-install + source install/setup.bash + ``` + +### Docker Setup (Alternative) + +For a containerized environment, see the [Docker setup guide](https://icube-robotics.github.io/ecat_ros2_workshop/getting-started/docker/). + +```bash +docker build --tag ecat_ros2_workshop:jazzy --file .docker/Dockerfile . +docker run ecat_ros2_workshop:jazzy ros2 launch scara_bringup scara.launch.py +``` + +## πŸ“¦ Repository Structure + +``` +ecat_ros2_workshop/ +β”œβ”€β”€ scara_description/ # URDF and robot description files +β”œβ”€β”€ scara_hardware/ # Custom hardware interface implementation +β”œβ”€β”€ scara_controllers/ # Custom controller implementation +β”œβ”€β”€ scara_bringup/ # Launch files and configurations +β”œβ”€β”€ scara_nodes/ # Additional ROS2 nodes +β”œβ”€β”€ docs/ # MkDocs documentation source +└── resources/ # Tutorial resources and images +``` + +## 🀝 Contributing + +Contributions are welcome! Please feel free to submit a Pull Request. + +For bugs and feature requests, please open an issue on [GitHub](https://github.com/ICube-Robotics/ecat_ros2_workshop/issues). + +## πŸ“„ License + +This project is open source. See the [LICENSE](LICENSE) file for details. + + +## πŸ‘₯ Contacts & Maintainers + +### [ICube Laboratory](https://icube.unistra.fr) +[University of Strasbourg](https://www.unistra.fr/), France + +**Manuel Yguel** - [yguel@unistra.fr](mailto:yguel@unistra.fr) | [@yguel](https://github.com/yguel) + +### [Asterion Robotics](https://asterion-robotics.com) + +**Maciej Bednarczyk** - [m.bednarczyk@asterion-robotics.com](mailto:m.bednarczyk@asterion-robotics.com) | [@mcbed](https://github.com/mcbed) + +For more information, see the [full contacts page](https://icube-robotics.github.io/ecat_ros2_workshop/about/contacts/). + +--- + +
+ +[![ICube](https://icube.unistra.fr/fileadmin/templates/DUN/icube/images/logo.png)](https://icube.unistra.fr) +     +[![Asterion](https://raw.githubusercontent.com/Asterion-Robotics/assets/refs/heads/main/asterion-logo.png)](https://asterion-robotics.com) + +**Made with ❀️ by ICube Laboratory and Asterion Robotics** + +
diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..5341b7a --- /dev/null +++ b/docs/README.md @@ -0,0 +1,122 @@ +# Documentation Development + +This directory contains the source files for the MkDocs-based documentation site. + +## Local Development + +### Prerequisites + +Install the required Python packages: + +```bash +pip install -r requirements.txt +``` + +### Preview the Documentation + +To preview the documentation locally: + +```bash +mkdocs serve +``` + +Then open your browser to `http://127.0.0.1:8000/` + +The site will automatically reload when you make changes to the documentation files. + +### Build the Site + +To build the static site: + +```bash +mkdocs build +``` + +The built site will be in the `site/` directory. + +## Structure + +``` +docs/ +β”œβ”€β”€ index.md # Home page +β”œβ”€β”€ getting-started/ +β”‚ β”œβ”€β”€ installation.md # Installation guide +β”‚ └── docker.md # Docker setup +β”œβ”€β”€ tutorials/ +β”‚ β”œβ”€β”€ r2c_overview.md # ros2_control overview +β”‚ β”œβ”€β”€ urdf_tutorial.md # URDF description +β”‚ β”œβ”€β”€ launch_tutorial.md # Launch & interaction +β”‚ β”œβ”€β”€ hardware_tutorial.md # Hardware interface +β”‚ β”œβ”€β”€ controller_tutorial.md # Controller development +β”‚ β”œβ”€β”€ gazebo_tutorial.md # Gazebo simulation +β”‚ └── ethercat_tutorial.md # EtherCAT integration +β”œβ”€β”€ about/ +β”‚ └── contacts.md # Contact information +└── images/ # Image assets +``` + +## Adding Content + +### Adding a New Page + +1. Create a new Markdown file in the appropriate directory +2. Add the page to the navigation in `mkdocs.yml` +3. Preview your changes with `mkdocs serve` + +### Adding Images + +Place images in `docs/images/` and reference them using relative paths: + +```markdown +![Alt text](../images/your-image.png) +``` + +### Using Admonitions + +```markdown +!!! note "Optional Title" + This is a note admonition. + +!!! tip + This is a tip. + +!!! warning + This is a warning. + +!!! info + This is an info box. +``` + +### Code Blocks + +Use fenced code blocks with language specification: + +````markdown +```python +def hello_world(): + print("Hello, World!") +``` +```` + +## Deployment + +The documentation is automatically deployed to GitHub Pages when changes are pushed to the `main` branch. + +The deployment is handled by the GitHub Actions workflow in `.github/workflows/deploy-docs.yml`. + +## Theme + +The documentation uses the [Material for MkDocs](https://squidfunk.github.io/mkdocs-material/) theme with: + +- Dark/light mode toggle +- Blue primary and accent colors +- Navigation tabs and sections +- Search functionality +- Code syntax highlighting +- Responsive design + +## Useful Links + +- [MkDocs Documentation](https://www.mkdocs.org/) +- [Material for MkDocs](https://squidfunk.github.io/mkdocs-material/) +- [Markdown Guide](https://www.markdownguide.org/) diff --git a/docs/about/contacts.md b/docs/about/contacts.md new file mode 100644 index 0000000..1fdbf3c --- /dev/null +++ b/docs/about/contacts.md @@ -0,0 +1,123 @@ +# Contacts + +## Maintainers + +This project is maintained by the ICube Laboratory at the University of Strasbourg and Asterion Robotics. + +--- + +## ICube Laboratory + +
+ +[![ICube Logo](https://icube.unistra.fr/fileadmin/templates/DUN/icube/images/logo.png){ width="300" }](https://icube.unistra.fr) + +**[ICube Laboratory](https://icube.unistra.fr)** +[University of Strasbourg](https://www.unistra.fr/), France + +The ICube laboratory (ICube - Laboratoire des sciences de l'ingΓ©nieur, de l'informatique et de l'imagerie) is a research unit of the University of Strasbourg and CNRS (UMR 7357). + +### Team Member + +**Manuel Yguel** + +- **Email**: [yguel@unistra.fr](mailto:yguel@unistra.fr) +- **GitHub**: [@yguel](https://github.com/yguel) +- **Role**: Research Engineer + +
+ +--- + +## Asterion Robotics + +
+ +[![Asterion Logo](https://raw.githubusercontent.com/Asterion-Robotics/assets/refs/heads/main/asterion-logo.png){ width="300" }](https://asterion-robotics.com) + +**[Asterion Robotics](https://asterion-robotics.com)** +Industrial Robotics Solutions + +Asterion Robotics provides cutting-edge robotics solutions for industrial applications, with a focus on advanced control systems and EtherCAT integration. + +### Team Member + +**Maciej Bednarczyk** + +- **Email**: [m.bednarczyk@asterion-robotics.com](mailto:m.bednarczyk@asterion-robotics.com) +- **GitHub**: [@mcbed](https://github.com/mcbed) +- **Role**: Robotics Engineer + +
+ +--- + +## Contributing + +We welcome contributions to this workshop! If you'd like to contribute: + +1. **Fork the repository** on [GitHub](https://github.com/ICube-Robotics/ecat_ros2_workshop) +2. **Create a feature branch**: `git checkout -b feature/my-feature` +3. **Commit your changes**: `git commit -am 'Add my feature'` +4. **Push to the branch**: `git push origin feature/my-feature` +5. **Submit a pull request** + +### Reporting Issues + +Found a bug or have a suggestion? Please open an issue on our [GitHub repository](https://github.com/ICube-Robotics/ecat_ros2_workshop/issues). + +When reporting issues, please include: + +- Your operating system and ROS2 version +- Steps to reproduce the issue +- Expected vs actual behavior +- Any relevant error messages or logs + +--- + +## Support + +### Getting Help + +If you need help with the workshop: + +1. **Check the documentation** - Most common questions are answered in the tutorials +2. **Search existing issues** on [GitHub](https://github.com/ICube-Robotics/ecat_ros2_workshop/issues) +3. **Ask on ROS Discourse** - [discourse.ros.org](https://discourse.ros.org/) +4. **Contact the maintainers** using the emails above + +### Community + +Join the ROS2 community: + +- **ROS Discourse**: [discourse.ros.org](https://discourse.ros.org/) +- **ROS Answers**: [answers.ros.org](https://answers.ros.org/) +- **ros2_control GitHub**: [github.com/ros-controls](https://github.com/ros-controls) + +--- + +## Acknowledgments + +This workshop is based on the excellent work of the ROS2 and ros2_control communities. We'd like to thank: + +- The [ros2_control](https://control.ros.org) team for the framework +- The [ethercat_driver_ros2](https://github.com/ICube-Robotics/ethercat_driver_ros2) contributors +- The ROS2 community for continuous support and feedback + +--- + +## License + +This project is open source. Check the [LICENSE](https://github.com/ICube-Robotics/ecat_ros2_workshop/blob/main/LICENSE) file in the repository for details. + +--- + +
+ +
+### Questions? + +Feel free to reach out to any of the maintainers above! +
+ +
diff --git a/docs/getting-started/docker.md b/docs/getting-started/docker.md new file mode 100644 index 0000000..72c6204 --- /dev/null +++ b/docs/getting-started/docker.md @@ -0,0 +1,281 @@ +# Docker Setup + +Docker provides an isolated, pre-configured environment for running the SCARA ROS2 EtherCAT Workshop without modifying your host system. + +## Prerequisites + +!!! info "Requirements" + - [Docker](https://docs.docker.com/get-docker/) installed on your system + - At least 8 GB of disk space + - (Optional) [rocker](https://github.com/osrf/rocker) for GUI support + +## Quick Start + +### Basic Docker Setup + +1. **Clone the repository** (if you haven't already): + +```bash +git clone https://github.com/ICube-Robotics/ecat_ros2_workshop.git +cd ecat_ros2_workshop +``` + +2. **Build the Docker image**: + +```bash +docker build --tag ecat_ros2_workshop:jazzy --file .docker/Dockerfile . +``` + +!!! note "Build Time" + The first build may take 10-20 minutes depending on your internet connection and system performance. + +3. **Run the container**: + +```bash +docker run ecat_ros2_workshop:jazzy ros2 launch scara_bringup scara.launch.py +``` + +## Running with GUI + +### Option 1: Using Rocker (Recommended) + +[Rocker](https://github.com/osrf/rocker) provides seamless X11 forwarding and device access. + +1. **Install rocker**: + +```bash +sudo apt update +sudo apt install python3-rocker +``` + +2. **Run with GUI support**: + +```bash +rocker --net=host --x11 --devices /dev/dri --user ecat_ros2_workshop:jazzy ros2 launch scara_bringup scara.launch.py +``` + +!!! info "Rocker Options" + - `--net=host`: Uses host networking for ROS2 communication + - `--x11`: Enables X11 forwarding for GUI applications + - `--devices /dev/dri`: Mounts Direct Rendering Infrastructure for hardware-accelerated graphics (Intel GPUs) + - `--user`: Runs as non-root user for better security + +### Option 2: Using noVNC (Web-Based) + +noVNC provides a web-based VNC client accessible through your browser - no X11 configuration needed! + +1. **Build the noVNC image**: + +```bash +# First ensure the base image is built +docker build --tag ecat_ros2_workshop:jazzy --file .docker/Dockerfile . + +# Build the noVNC image +docker build --tag ecat_ros2_workshop:jazzy_novnc --file .docker/Dockerfile_novnc . +``` + +2. **Run the noVNC container**: + +```bash +docker run --rm -p 6080:6080 -it ecat_ros2_workshop:jazzy_novnc +``` + +3. **Access the desktop**: + +Open your web browser and navigate to: +``` +http://localhost:6080/vnc.html +``` + +4. **Inside the noVNC session**, open a terminal and run: + +```bash +cd ros2_dev/ecat_ros2_workshop/ +source install/setup.bash +ros2 launch scara_bringup scara.launch.py +``` + +!!! tip "Browser Compatibility" + noVNC works best with modern browsers (Chrome, Firefox, Edge). For optimal performance, use a desktop browser rather than mobile. + +## Interactive Shell + +To explore and interact with the environment: + +```bash +docker run -it ecat_ros2_workshop:jazzy +``` + +Inside the container: + +```bash +cd ros2_dev/ecat_ros2_workshop/ +source install/setup.bash + +# Now you can run any ROS2 commands +ros2 launch scara_bringup scara.launch.py +ros2 topic list +ros2 control list_controllers +``` + +## Development Workflow + +### Mounting Local Code + +To develop code on your host and run it in the container: + +```bash +docker run -it \ + -v $(pwd):/workspace \ + -w /workspace \ + ecat_ros2_workshop:jazzy \ + bash +``` + +Inside the container: + +```bash +# Rebuild after changes +colcon build +source install/setup.bash +ros2 launch scara_bringup scara.launch.py +``` + +### Persistent Container + +Create a named container to preserve your changes: + +```bash +# Create and start the container +docker run -it --name scara_dev ecat_ros2_workshop:jazzy bash + +# Later, restart and attach to the same container +docker start scara_dev +docker attach scara_dev +``` + +## Hardware Acceleration + +### NVIDIA GPU Support + +For NVIDIA GPU support, use the NVIDIA Container Toolkit: + +```bash +# Install NVIDIA Container Toolkit +distribution=$(. /etc/os-release;echo $ID$VERSION_ID) +curl -s -L https://nvidia.github.io/nvidia-docker/gpgkey | sudo apt-key add - +curl -s -L https://nvidia.github.io/nvidia-docker/$distribution/nvidia-docker.list | \ + sudo tee /etc/apt/sources.list.d/nvidia-docker.list + +sudo apt update +sudo apt install -y nvidia-container-toolkit +sudo systemctl restart docker + +# Run with GPU support +docker run --gpus all -it ecat_ros2_workshop:jazzy +``` + +### Intel Integrated Graphics + +For Intel integrated graphics (already included in rocker command): + +```bash +rocker --devices /dev/dri ecat_ros2_workshop:jazzy +``` + +## Docker Compose (Advanced) + +For more complex setups, create a `docker-compose.yml`: + +```yaml +version: '3.8' + +services: + scara_workshop: + image: ecat_ros2_workshop:jazzy + container_name: scara_workshop + stdin_open: true + tty: true + network_mode: host + environment: + - DISPLAY=${DISPLAY} + - QT_X11_NO_MITSHM=1 + volumes: + - /tmp/.X11-unix:/tmp/.X11-unix:rw + - ./:/workspace + devices: + - /dev/dri:/dev/dri + command: bash +``` + +Run with: + +```bash +xhost +local:docker +docker-compose up +``` + +## Troubleshooting + +### Display Issues + +If you encounter display errors with rocker: + +```bash +# Allow Docker to connect to X server +xhost +local:docker + +# Run rocker again +rocker --x11 ecat_ros2_workshop:jazzy +``` + +### Network Issues + +If ROS2 nodes can't communicate: + +```bash +# Use host networking +docker run --net=host -it ecat_ros2_workshop:jazzy +``` + +### Permission Denied + +If you get permission errors: + +```bash +# Add your user to docker group +sudo usermod -aG docker $USER + +# Log out and back in for changes to take effect +``` + +### Container Size + +To check Docker disk usage: + +```bash +docker system df + +# Clean up unused containers and images +docker system prune -a +``` + +## Comparison: Docker Methods + +| Method | Pros | Cons | Best For | +|--------|------|------|----------| +| **Basic** | Simple, fast | No GUI | Headless testing | +| **Rocker** | Native performance, hardware acceleration | Requires X11 setup | Local development | +| **noVNC** | Works anywhere, no X11 needed | Slight latency | Remote access, web-based | + +## Next Steps + +Now that you have Docker set up: + +- Continue with the [ros2_control Overview](../tutorials/r2c_overview.md) +- Try running the tutorials inside Docker +- Explore the [URDF Tutorial](../tutorials/urdf_tutorial.md) + +--- + +**Tip**: Docker is great for trying things out, but for serious development, consider a [native installation](installation.md) for better performance. diff --git a/docs/getting-started/installation.md b/docs/getting-started/installation.md new file mode 100644 index 0000000..fa31551 --- /dev/null +++ b/docs/getting-started/installation.md @@ -0,0 +1,188 @@ +# Installation Guide + +This guide will walk you through setting up the SCARA ROS2 EtherCAT Workshop on your system. + +## System Requirements + +!!! info "Required Setup" + - **Operating System**: Ubuntu 24.04 LTS + - **ROS Distribution**: ROS2 Jazzy + - **Disk Space**: ~5 GB free space + - **Memory**: 4 GB RAM minimum (8 GB recommended) + +## Step-by-Step Installation + +### 1. Install ROS2 Jazzy + +Follow the official ROS2 installation instructions for Ubuntu 24.04: + +```bash +# Add ROS2 apt repository +sudo apt install software-properties-common +sudo add-apt-repository universe +sudo apt update && sudo apt install curl -y + +sudo curl -sSL https://raw.githubusercontent.com/ros/rosdistro/master/ros.key -o /usr/share/keyrings/ros-archive-keyring.gpg + +echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/ros-archive-keyring.gpg] http://packages.ros.org/ros2/ubuntu $(. /etc/os-release && echo $UBUNTU_CODENAME) main" | sudo tee /etc/apt/sources.list.d/ros2.list > /dev/null + +# Install ROS2 Jazzy Desktop +sudo apt update +sudo apt install ros-jazzy-desktop -y +``` + +For detailed instructions, visit the [official ROS2 documentation](https://docs.ros.org/en/jazzy/Installation.html). + +### 2. Source ROS2 Environment + +After installation, source your ROS2 environment: + +```bash +source /opt/ros/jazzy/setup.bash +``` + +!!! tip "Auto-sourcing" + If you're only using ROS2 Jazzy, add the source command to your `~/.bashrc` file: + ```bash + echo "source /opt/ros/jazzy/setup.bash" >> ~/.bashrc + ``` + +### 3. Install Colcon + +Colcon is the build tool for ROS2 workspaces: + +```bash +sudo apt install python3-colcon-common-extensions +``` + +### 4. Create a ROS2 Workspace + +Create a workspace directory for the tutorial: + +```bash +mkdir -p ~/ros2_ws/src +cd ~/ros2_ws +``` + +### 5. Clone the Repository + +Clone the workshop repository into your workspace: + +```bash +cd ~/ros2_ws/src +git clone https://github.com/ICube-Robotics/ecat_ros2_workshop.git +``` + +### 6. Install Dependencies + +Use `rosdep` to install all required dependencies: + +```bash +cd ~/ros2_ws +sudo apt update +rosdep update +rosdep install --from-paths src --ignore-src -y -r +``` + +!!! note + If this is your first time using `rosdep`, you may need to initialize it: + ```bash + sudo rosdep init + rosdep update + ``` + +### 7. Build the Workspace + +Build the workspace using colcon: + +```bash +cd ~/ros2_ws +colcon build --cmake-args -DCMAKE_BUILD_TYPE=Release --symlink-install +``` + +!!! tip "Build Options" + - `--cmake-args -DCMAKE_BUILD_TYPE=Release`: Optimizes the build for performance + - `--symlink-install`: Creates symbolic links instead of copying files (useful during development) + +### 8. Source the Workspace + +After building, source the workspace: + +```bash +source ~/ros2_ws/install/setup.bash +``` + +!!! tip "Auto-sourcing Workspace" + Add this to your `~/.bashrc` for automatic sourcing: + ```bash + echo "source ~/ros2_ws/install/setup.bash" >> ~/.bashrc + ``` + +## Verify Installation + +Test that everything is installed correctly: + +```bash +# Check if packages are available +ros2 pkg list | grep scara + +# Launch the SCARA robot (should open RViz2) +ros2 launch scara_bringup scara.launch.py +``` + +If RViz2 opens with the SCARA robot displayed, your installation is successful! πŸŽ‰ + +## Troubleshooting + +### Common Issues + +#### Missing Dependencies + +If you encounter missing dependencies during build: + +```bash +cd ~/ros2_ws +rosdep install --from-paths src --ignore-src -y -r --rosdistro jazzy +``` + +#### Build Errors + +If the build fails, try cleaning the workspace and rebuilding: + +```bash +cd ~/ros2_ws +rm -rf build install log +colcon build --cmake-args -DCMAKE_BUILD_TYPE=Release --symlink-install +``` + +#### RViz2 Not Opening + +If RViz2 doesn't open or displays errors: + +```bash +# Install RViz2 if missing +sudo apt install ros-jazzy-rviz2 + +# Check OpenGL support +glxinfo | grep "OpenGL" +``` + +### Getting Help + +If you're still experiencing issues: + +1. Check the [GitHub Issues](https://github.com/ICube-Robotics/ecat_ros2_workshop/issues) +2. Review the [ROS2 troubleshooting guide](https://docs.ros.org/en/jazzy/Troubleshooting.html) +3. Contact the maintainers (see [Contacts](../about/contacts.md)) + +## Next Steps + +Now that your environment is set up, you can: + +- Try the [Docker setup](docker.md) as an alternative installation method +- Start with the [ros2_control Overview](../tutorials/r2c_overview.md) +- Jump into the [URDF Tutorial](../tutorials/urdf_tutorial.md) + +--- + +Ready to start? [Begin with the ros2_control Overview :material-arrow-right:](../tutorials/r2c_overview.md){ .md-button .md-button--primary } diff --git a/docs/images/r2c_architecture.png b/docs/images/r2c_architecture.png new file mode 100644 index 0000000..b701226 Binary files /dev/null and b/docs/images/r2c_architecture.png differ diff --git a/docs/images/r2c_cm.png b/docs/images/r2c_cm.png new file mode 100644 index 0000000..aa71063 Binary files /dev/null and b/docs/images/r2c_cm.png differ diff --git a/docs/images/r2c_overview.png b/docs/images/r2c_overview.png new file mode 100644 index 0000000..aee4eb4 Binary files /dev/null and b/docs/images/r2c_overview.png differ diff --git a/docs/images/scara_gazebo.png b/docs/images/scara_gazebo.png new file mode 100644 index 0000000..05619b4 Binary files /dev/null and b/docs/images/scara_gazebo.png differ diff --git a/docs/images/scara_model.png b/docs/images/scara_model.png new file mode 100644 index 0000000..bbccb98 Binary files /dev/null and b/docs/images/scara_model.png differ diff --git a/docs/images/scara_rviz.png b/docs/images/scara_rviz.png new file mode 100644 index 0000000..971e9ee Binary files /dev/null and b/docs/images/scara_rviz.png differ diff --git a/docs/images/scara_rviz_empty.png b/docs/images/scara_rviz_empty.png new file mode 100644 index 0000000..c48aa98 Binary files /dev/null and b/docs/images/scara_rviz_empty.png differ diff --git a/docs/images/scara_tf.png b/docs/images/scara_tf.png new file mode 100644 index 0000000..4e2015e Binary files /dev/null and b/docs/images/scara_tf.png differ diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..98ee465 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,134 @@ +# SCARA ROS2 EtherCAT Workshop + +Welcome to the **SCARA ROS2 EtherCAT Workshop**! This comprehensive tutorial will guide you through the fundamentals of controlling robotic systems using the [ros2_control](https://control.ros.org) framework and EtherCAT communication. + +![SCARA Model](images/scara_model.png) + +## Overview + +The [ros2_control](https://control.ros.org) framework is a real-time control framework designed for general robotics applications that is gaining increasing attention in both research and industrial contexts. This tutorial is designed to help you understand the basic concepts of controlling a robot using ros2_control and EtherCAT. + +## What You'll Learn + +This workshop covers the following topics: + +
+ +- :material-cube-outline: **URDF Description** + + --- + + Learn how to write a complete URDF description for a simple SCARA manipulator, including geometry, dynamics, and ros2_control interfaces. + + [:octicons-arrow-right-24: URDF Tutorial](tutorials/urdf_tutorial.md) + +- :material-rocket-launch: **Launch & Interact** + + --- + + Discover how to launch your robot system and interact with controllers through the Controller Manager. + + [:octicons-arrow-right-24: Launch Tutorial](tutorials/launch_tutorial.md) + +- :material-chip: **Hardware Interface** + + --- + + Develop custom hardware interfaces to communicate with your robot's physical hardware or simulation. + + [:octicons-arrow-right-24: Hardware Tutorial](tutorials/hardware_tutorial.md) + +- :material-cog: **Controller Development** + + --- + + Write custom controllers tailored to your specific application requirements. + + [:octicons-arrow-right-24: Controller Tutorial](tutorials/controller_tutorial.md) + +- :material-cube-scan: **Gazebo Simulation** + + --- + + Set up your SCARA manipulator to run with ros2_control and Gazebo for realistic physics simulation. + + [:octicons-arrow-right-24: Gazebo Tutorial](tutorials/gazebo_tutorial.md) + +- :material-ethernet: **EtherCAT Integration** + + --- + + Configure and control CIA 402 compliant EtherCAT motor drives using the ethercat_driver_ros2 stack. + + [:octicons-arrow-right-24: EtherCAT Tutorial](tutorials/ethercat_tutorial.md) + +
+ +## Prerequisites + +!!! info "System Requirements" + - **Operating System**: Ubuntu 24.04 LTS + - **ROS Distribution**: ROS2 Jazzy + - **Basic Knowledge**: Familiarity with ROS2, C++, and Python + +## Quick Start + +Ready to get started? Follow our installation guide: + +[Get Started :material-arrow-right:](getting-started/installation.md){ .md-button .md-button--primary } + +## The SCARA Robot + +Throughout this tutorial, we'll work with a SCARA (Selective Compliance Assembly Robot Arm) manipulator. This robot type is commonly used in industrial applications for pick-and-place operations, assembly tasks, and more. + +### Robot Structure + +The SCARA robot consists of: + +- **3 Degrees of Freedom**: Two revolute joints for horizontal movement and one prismatic joint for vertical movement +- **Joint 1 & 2**: Revolute joints controlling the arm position in the XY plane +- **Joint 3**: Prismatic joint controlling the vertical (Z-axis) position + +## ros2_control Framework + +Before diving into the tutorials, it's helpful to understand the ros2_control framework architecture: + +- **Controller Manager**: Orchestrates the control loop and manages controller lifecycle +- **Hardware Interface**: Abstracts physical hardware or simulation +- **Controllers**: Implement control algorithms for your robot +- **Resource Manager**: Manages hardware resources and interfaces + +For a detailed overview, see the [ros2_control Overview](tutorials/r2c_overview.md). + +## Support & Contribution + +!!! question "Need Help?" + If you encounter any issues or have questions: + + - Check the tutorial documentation + - Open an issue on [GitHub](https://github.com/ICube-Robotics/ecat_ros2_workshop/issues) + - Contact the maintainers (see [Contacts](about/contacts.md)) + +## License + +This project is open source. Check the repository for license information. + +--- + +
+ +
+### ICube Laboratory +[![ICube](https://icube.unistra.fr/fileadmin/templates/DUN/icube/images/logo.png){ width="200" }](https://icube.unistra.fr) + +[University of Strasbourg](https://www.unistra.fr/), France +
+ +
+### Asterion Robotics +[![Asterion](https://raw.githubusercontent.com/Asterion-Robotics/assets/refs/heads/main/asterion-logo.png){ width="200" }](https://asterion-robotics.com) + +Industrial robotics solutions +
+ +
diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml new file mode 100644 index 0000000..b97c400 --- /dev/null +++ b/docs/mkdocs.yml @@ -0,0 +1,96 @@ +site_name: SCARA ROS2 EtherCAT Workshop +site_description: Tutorial for controlling robots using ros2_control and EtherCAT +site_author: ICube Laboratory & Asterion Robotics +site_url: https://ICube-Robotics.github.io/ecat_ros2_workshop +docs_dir: . +site_dir: ../site + +repo_name: ICube-Robotics/ecat_ros2_workshop +repo_url: https://github.com/ICube-Robotics/ecat_ros2_workshop +edit_uri: edit/main/docs/ + +theme: + name: material + palette: + # Palette toggle for light mode + - scheme: default + primary: blue + accent: blue + toggle: + icon: material/brightness-7 + name: Switch to dark mode + # Palette toggle for dark mode + - scheme: slate + primary: blue + accent: blue + toggle: + icon: material/brightness-4 + name: Switch to light mode + features: + - navigation.tabs + - navigation.sections + - navigation.expand + - navigation.top + - navigation.tracking + - navigation.indexes + - search.suggest + - search.highlight + - content.tabs.link + - content.code.annotation + - content.code.copy + - toc.follow + language: en + icon: + repo: fontawesome/brands/github + +plugins: + - search + - glightbox + +markdown_extensions: + - pymdownx.highlight: + anchor_linenums: true + line_spans: __span + pygments_lang_class: true + - pymdownx.inlinehilite + - pymdownx.snippets + - pymdownx.superfences + - pymdownx.tabbed: + alternate_style: true + - pymdownx.details + - admonition + - tables + - attr_list + - md_in_html + - def_list + - pymdownx.tasklist: + custom_checkbox: true + - pymdownx.emoji: + emoji_index: !!python/name:material.extensions.emoji.twemoji + emoji_generator: !!python/name:material.extensions.emoji.to_svg + +extra: + social: + - icon: fontawesome/brands/github + link: https://github.com/ICube-Robotics + - icon: fontawesome/solid/globe + link: https://icube.unistra.fr + generator: false + +copyright: Copyright © 2025 ICube Laboratory & Asterion Robotics + +nav: + - Home: index.md + - Getting Started: + - Installation: getting-started/installation.md + - Docker Setup: getting-started/docker.md + - Tutorials: + - ros2_control Overview: tutorials/r2c_overview.md + - URDF Description: tutorials/urdf_tutorial.md + - Launch & Interaction: tutorials/launch_tutorial.md + - Hardware Interface: tutorials/hardware_tutorial.md + - Controller Development: tutorials/controller_tutorial.md + - Gazebo Simulation: tutorials/gazebo_tutorial.md + - EtherCAT Integration: tutorials/ethercat_tutorial.md + - About: + - Contacts: about/contacts.md diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 0000000..aef7a7a --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1,4 @@ +mkdocs>=1.5.0 +mkdocs-material>=9.5.0 +mkdocs-glightbox>=0.3.5 +pymdown-extensions>=10.7 diff --git a/docs/tutorials/controller_tutorial.md b/docs/tutorials/controller_tutorial.md new file mode 100644 index 0000000..aa7c33e --- /dev/null +++ b/docs/tutorials/controller_tutorial.md @@ -0,0 +1,277 @@ +# Writing of a Controller + +In ros2_control, controllers are implemented as plugins that conforms to the `ControllerInterface` public interface. Unlike hardware interfaces, controllers are [managed node](https://design.ros2.org/articles/node_lifecycle.html), which means that they work as state-machines and thus have a finite set of states, which are: + +1. Unconfigured +2. Inactive +3. Active +4. Finalized + +In order to properly manage controllers, certain interface methods are called when transitions between these states occur. During the main control loop, the controller needs to be in the active state. +In the following development, we will focus on the requirements for writing a new controller interface. For the purpose of this tutorial, the developed controller will be a joint velocity controller taking as input an array of velocities and applying them to each position controlled joint. + +The controller plugin for the tutorial robot is called `ScaraJointVelocityController` that inherits from `controller_interface::ControllerInterface`. The `ScaraJointVelocityController` must implement nine public methods. The latter 6 are [managed node](https://design.ros2.org/articles/node_lifecycle.html) transition callbacks. +1. `command_interface_configuration` +2. `state_interface_configuration` +3. `update` +4. `on_configure` +5. `on_activate` +6. `on_deactivate` +7. `on_cleanup` +8. `on_error` +9. `on_shutdown` + +These methods are defined in the [scara_joint_velocity_controller.hpp](../scara_controllers/scara_joint_velocity_controller/include/scara_joint_velocity_controller/scara_joint_velocity_controller.hpp) header file as follows: +```c++ +class ScaraJointVelocityController : public controller_interface::ControllerInterface { + public: + controller_interface::InterfaceConfiguration command_interface_configuration() const override; + controller_interface::InterfaceConfiguration state_interface_configuration() const override; + controller_interface::return_type update(const rclcpp::Time &time, const rclcpp::Duration &period) override; + CallbackReturn on_init() override; + CallbackReturn on_configure(const rclcpp_lifecycle::State &previous_state) override; + CallbackReturn on_activate(const rclcpp_lifecycle::State &previous_state) override; + CallbackReturn on_deactivate(const rclcpp_lifecycle::State &previous_state) override; + CallbackReturn on_cleanup(const rclcpp_lifecycle::State &previous_state) override; + CallbackReturn on_error(const rclcpp_lifecycle::State &previous_state) override; + CallbackReturn on_shutdown(const rclcpp_lifecycle::State &previous_state) override; +// private members +// ... +} +``` +## Initializing the Controller +The `on_init` method is called immediately after the controller plugin is dynamically loaded. The method is called only once during the lifetime for the controller, hence memory that exists for the lifetime of the controller should be allocated. Additionally, the parameter values for `joints`, `command_interfaces` and `state_interfaces` should be declared and accessed. Those parameter values are required for the next two methods. + +In this tutorial, in the `on_init` method the `joints` parameter is declared as follows: +```c++ +CallbackReturn ScaraJointVelocityController::on_init(){ + // declare and get parameters needed for controller initialization + // allocate memory that will exist for the life of the controller + // ... + auto_declare>("joints", std::vector()); + return CallbackReturn::SUCCESS; +} +``` +This parameter allows to specify the joints that will be controlled with this controller. + +## Configuring the Controller +The `on_configure` method is called immediately after the controller is set to the inactive state. This state occurs when the controller is started for the first time, but also when it is restarted. Reconfigurable parameters should be read in this method. Additionally, publishers and subscribers should be created. + +In this tutorial, in this method the names of the controlled joints are queried and the subscription to the `~/joint_velocity` topic is made as follows: +```c++ +CallbackReturn ScaraJointVelocityController::on_configure(const rclcpp_lifecycle::State &previous_state){ + // declare and get parameters needed for controller operations + // setup realtime buffers, ROS publishers, and ROS subscribers + + joint_names_ = get_node()->get_parameter("joints").as_string_array(); + + // the desired velocity is queried from the joint_velocity topic + // and passed to update via a rt pipe + joints_command_subscriber_ = get_node()->create_subscription( + "~/joint_velocity", rclcpp::SystemDefaultsQoS(), + [this](const CmdType::SharedPtr msg) {rt_command_ptr_.writeFromNonRT(msg);}); + return CallbackReturn::SUCCESS; +} +``` +Notice here that the subscriber callback uses the `rt_command_ptr_` object to pass the received message. This step is very important as without it the subscriber would block the control loop that needs to be realtime. + +The `command_interface_configuration` method is called after `on_configure`. The method returns a list of `InterfaceConfiguration` objects to indicate which command interfaces the controller needs to operate. The command interfaces are uniquely identified by their name and interface type. If a requested interface is not offered by a loaded hardware interface, then the controller will fail. + +In our case, as we want the method to send commands to the position of the joints, the method is defined as follows: +```c++ +controller_interface::InterfaceConfiguration ScaraJointVelocityController::command_interface_configuration(){ + controller_interface::InterfaceConfiguration conf; + // add required command interface to `conf` by specifying their names and interface types. + conf.names.reserve(joint_names_.size()); + for (const auto & joint_name : joint_names_) { + conf.names.push_back(joint_name + "/" + hardware_interface::HW_IF_POSITION); + } + return conf; +} +``` + +The `state_interface_configuration` method is then called, which is similar to the last method. The difference is that a list of `InterfaceConfiguration` objects representing the required state interfaces to operate is returned. + +Here again, for the purpose of this tutorial we only need the position state. The method is defined as follows: +```c++ +controller_interface::InterfaceConfiguration ScaraJointVelocityController::state_interface_configuration() { + controller_interface::InterfaceConfiguration conf; + // add required state interface to `conf` by specifying their names and interface types. + conf.type = controller_interface::interface_configuration_type::INDIVIDUAL; + conf.names.reserve(joint_names_.size()); + for (const auto & joint_name : joint_names_) { + conf.names.push_back(joint_name + "/" + hardware_interface::HW_IF_POSITION); + } + return conf; +} +``` +## Activating the Controller +The `on_activate` is called once when the controller is activated. This method should handle controller restarts, such as setting the resetting reference to safe values. It should also perform controller specific safety checks. The `command_interface_configuration` and `state_interface_configuration` are also called again when the controller is activated. + +In this tutorial, this method is used to order the command interfaces to fit the joint name order of the controller `joints` parameter. This is done by defining: +```c++ +CallbackReturn ScaraJointVelocityController::on_activate(const rclcpp_lifecycle::State &previous_state){ + // Handle controller restarts and dynamic parameter updating + std::vector> ordered_interfaces; + get_ordered_interfaces( + command_interfaces_, + joint_names_, + hardware_interface::HW_IF_POSITION, + ordered_interfaces) + return CallbackReturn::SUCCESS; +} +``` + +## Running the Controller + +The `update` method is part of the main control loop. Since the method is part of the realtime control loop, the realtime constraint must be enforced. The controller should read from the state interfaces, the reference and compute the command. Normally, the reference is access via a ROS2 subscriber. Since the subscriber runs on the non-realtime thread, a realtime buffer is used to a transfer the message to the realtime thread. The realtime buffer is eventually a pointer to a ROS message with a mutex that guarantees thread safety and that the realtime thread is never blocked. The calculated control command should then be written to the command interface, which will be passed to the hardware. + +In this tutorial, the `update` method is defined as follows: +```c++ +controller_interface::return_type ScaraJointVelocityController::update(const rclcpp::Time &time, const rclcpp::Duration &period){ + // Read controller inputs values from state interfaces + // Calculate controller output values and write them to command interfaces + + // getting the data from the subscriber using the rt pipe + auto joint_velocity = rt_command_ptr_.readFromRT(); + + // no command received yet + if (!joint_velocity || !(*joint_velocity)) { + return controller_interface::return_type::OK; + } + + // the states are given in the same order as defines in state_interface_configuration + for(auto j = 0ul; j < joint_names_.size(); j++) + { + double q = state_interfaces_[j].get_value(); + double vq = (*joint_velocity)->data[j]; + + double command = q + vq*(period.nanoseconds()*1e-9); + + command_interfaces_[j].set_value(command); + } + + return controller_interface::return_type::OK; +} +``` +In this method, at first the data is queried from the subscriber using the `rt_command_ptr_` object. Then we check if a new command was received. Next, the controller iterates over all commanded joints and computes the commanded position to be applied and finally updates the `command_interfaces_` with the new position that needs to be passed to the hardware. + +## Additional methods + The `on_deactivate` is called when a controller stops running. It is important to release the claimed command interface in this method, so other controllers can use them if needed. This is down with the `release_interfaces` function. + + In our case, to keep things simple this method is empty: +```c++ +CallbackReturn on_deactivate(const rclcpp_lifecycle::State &previous_state){ + release_interfaces(); + // The controller should be properly shutdown during this + // ... + return CallbackReturn::SUCCESS; +} +``` +The `on_cleanup` and `on_shutdown` are called when the controller's lifecycle node is transitioning to shutting down. Freeing any allocated memory and general cleanup should be done in these methods. +In our case, to keep things simple this methods are also empty: +```c++ +CallbackReturn on_cleanup(const rclcpp_lifecycle::State &previous_state){ + // Callback function for cleanup transition + // ... + return CallbackReturn::SUCCESS; +} +``` +```c++ +CallbackReturn on_shutdown(const rclcpp_lifecycle::State &previous_state){ + // Callback function for shutdown transition + // ... + return CallbackReturn::SUCCESS; +} +``` + +The `on_error` method is called if the managed node fails a state transition. This should generally never happen. + +In our case, to keep things simple this method is empty: +```c++ +CallbackReturn on_error(const rclcpp_lifecycle::State &previous_state){ + // Callback function for erroneous transition + // ... + return CallbackReturn::SUCCESS; +} +``` +## Building the Controller plugin +Building the Controller plugin is done with the same steps as for the Hardware Interface: +* Adding C++ export macro +* Creating the plugin description file +* Exporting the CMake library + +### Adding C++ export macro +In order to reference the previously defined controller as a ros2_control plugin, we need to add the following two lines of code at the end of the [scara_joint_velocity_controller.cpp](../scara_controllers/scara_joint_velocity_controller/src/scara_joint_velocity_controller.cpp) file containing our method definitions: + +```c++ +#include "pluginlib/class_list_macros.hpp" + +PLUGINLIB_EXPORT_CLASS(scara_joint_velocity_controller::ScaraJointVelocityController, controller_interface::ControllerInterface) +``` +The `PLUGINLIB_EXPORT_CLASS` is a c++ macro that creates a plugin library using `pluginlib`. More information about it can be found [here](https://docs.ros.org/en/humble/Tutorials/Beginner-Client-Libraries/Pluginlib.html). + +### Creating the plugin description file +The plugin description file is again required for the controller, since it is exported as a library. The controller plugin description file is formatted as follows: + +```xml + + + + {Human readable description} + + + +``` + See [here](../scara_controllers/scara_joint_velocity_controller/controller_plugin.xml) for the complete XML file. + +### Exporting the CMake library +The plugin must be specified in the CMake file that builds the controller plugin. + +```cmake +add_library( + scara_joint_velocity_controller + SHARED + src/scara_joint_velocity_controller.cpp +) + +# include and link dependencies +# ... + +# Causes the visibility macros to use dllexport rather than dllimport, which is appropriate when building the dll but not consuming it. +target_compile_definitions(scara_joint_velocity_controller PRIVATE "CONTROLLER_PLUGIN_DLL") +# export plugin +pluginlib_export_plugin_description_file(scara_joint_velocity_controller controller_plugin.xml) +# install libraries +# ... +``` + +See [here](../scara_controllers/scara_joint_velocity_controller/CMakeLists.txt) for the complete `CMakeLists.txt` file. + +Now that the controller is ready to be used, let's add it to the [scara_controllers.yaml](../scara_description/config/scara_controllers.yaml) file and run it on the scara robot! + +To do so, in the [scara_controllers.yaml](../scara_description/config/scara_controllers.yaml) file add : +``` yaml +controller_manager: + ros__parameters: + update_rate: 100 # Hz + + # other controllers + + scara_joint_velocity_controller: + type: scara_joint_velocity_controller/ScaraJointVelocityController + +scara_joint_velocity_controller: + ros__parameters: + joints: + - joint1 + - joint2 + - joint3 + +# other controllers +``` + +You can now load and interact with the controller as explained in the previous [section](launch_tutorial.md). \ No newline at end of file diff --git a/docs/tutorials/ethercat_tutorial.md b/docs/tutorials/ethercat_tutorial.md new file mode 100644 index 0000000..9463f96 --- /dev/null +++ b/docs/tutorials/ethercat_tutorial.md @@ -0,0 +1,368 @@ +# EtherCAT Tutorial: Using ethercat_driver_ros2 with CIA 402 Drives + +This tutorial provides a comprehensive guide to using the `ethercat_driver_ros2` stack to integrate EtherCAT-based CIA 402 drives with ROS 2 and ros2_control. + +!!! info "What You'll Learn" + - How to install and configure ethercat_driver_ros2 + - Understanding CIA 402 drive protocol and state machine + - Configuring EtherCAT slaves with YAML files + - Integrating drives into URDF with ros2_control + - Controlling real EtherCAT hardware + +## Introduction + +### What is ethercat_driver_ros2? + +The `ethercat_driver_ros2` stack provides a bridge between EtherCAT devices and ROS 2's ros2_control framework. It enables: + +- **Real-time communication** with EtherCAT modules +- **Generic plugin architecture** for different device types +- **Seamless integration** with ros2_control Hardware Interfaces +- **Support for CIA 402 compliant motor drives** + +!!! tip "Resources" + - **Repository**: [ICube-Robotics/ethercat_driver_ros2](https://github.com/ICube-Robotics/ethercat_driver_ros2/tree/jazzy) + - **Documentation**: [https://icube-robotics.github.io/ethercat_driver_ros2/](https://icube-robotics.github.io/ethercat_driver_ros2/) + +### What is CIA 402? + +CIA 402 (CANopen device profile for drives and motion control) is a standardized protocol for controlling motor drives. The `EcCiA402Drive` plugin implements this standard for EtherCAT-based drives, providing: + +- **Automatic state machine management** (Switched On, Operation Enabled, etc.) +- **Multiple modes of operation**: Position (8), Velocity (9), Effort/Torque (10), Homing (6) +- **Fault handling** with automatic or manual reset +- **Safe default behavior** to prevent unwanted movements + +## Prerequisites + +### System Requirements + +- **ROS 2** (Jazzy or compatible distribution) +- **Linux with PREEMPT_RT kernel** (recommended for real-time performance) +- **IgH EtherCAT Master** installed and configured +- **Root privileges** or proper user permissions for EtherCAT access + +### Knowledge Requirements + +- Basic understanding of ROS 2 and ros2_control +- Familiarity with URDF and YAML configuration files +- Understanding of EtherCAT concepts (PDO, SDO, etc.) + +## Installation + +### 1. Install IgH EtherCAT Master + +First, install the IgH EtherCAT Master: + +```bash +# Clone the repository +git clone https://gitlab.com/etherlab.org/ethercat.git +cd ethercat + +# Configure and build +./bootstrap +./configure --prefix=/opt/etherlab --disable-8139too --enable-generic +make +sudo make install + +# Configure the system +sudo ln -s /opt/etherlab/etc/init.d/ethercat /etc/init.d/ethercat +sudo mkdir -p /etc/sysconfig +sudo cp /opt/etherlab/etc/sysconfig/ethercat /etc/sysconfig/ +``` + +Edit `/etc/sysconfig/ethercat` to configure your network interface: + +```bash +sudo nano /etc/sysconfig/ethercat +``` + +Set the `MASTER0_DEVICE` to your Ethernet MAC address. + +### 2. Install ethercat_driver_ros2 + +Create a ROS 2 workspace and clone the repository: + +```bash +# Create workspace +mkdir -p ~/ros2_ethercat_ws/src +cd ~/ros2_ethercat_ws/src + +# Clone the repository (jazzy branch) +git clone -b jazzy https://github.com/ICube-Robotics/ethercat_driver_ros2.git + +# Install dependencies +cd ~/ros2_ethercat_ws +rosdep install --from-paths src --ignore-src -r -y + +# Build the workspace +colcon build --cmake-args -DCMAKE_BUILD_TYPE=Release + +# Source the workspace +source install/setup.bash +``` + +### 3. Configure User Permissions + +Add your user to the ethercat group: + +```bash +sudo usermod -a -G ethercat $USER +``` + +!!! warning + Log out and log back in for the changes to take effect. + +## Understanding CIA 402 Drives + +### State Machine + +CIA 402 drives follow a standardized state machine: + +```mermaid +stateDiagram-v2 + [*] --> SwitchOnDisabled + SwitchOnDisabled --> ReadyToSwitchOn + ReadyToSwitchOn --> SwitchedOn + SwitchedOn --> OperationEnabled + OperationEnabled --> [*] +``` + +The `EcCiA402Drive` plugin automatically manages these transitions, bringing the drive to `OPERATION_ENABLED` state by default. + +### Modes of Operation + +| Mode | Value | Description | +|------|-------|-------------| +| **Cyclic Sync Position** | 8 | Real-time position control (most common) | +| **Cyclic Sync Velocity** | 9 | Real-time velocity control | +| **Cyclic Sync Torque** | 10 | Real-time torque control | +| **Homing** | 6 | Automatic homing procedure | + +## Configuration + +### Step 1: Create a Slave Configuration File + +Create a YAML file to configure your EtherCAT drive. Example for Maxon EPOS3: + +```yaml title="config/maxon_epos3.yaml" +# Configuration file for Maxon EPOS3 drive +vendor_id: 0x000000fb +product_id: 0x64400000 +assign_activate: 0x0300 # DC Synch register +auto_fault_reset: false # Manual fault reset + +# SDO configuration at startup +sdo: + - {index: 0x60C2, sub_index: 1, type: int8, value: 10} # Interpolation time: 10 ms + - {index: 0x60C2, sub_index: 2, type: int8, value: -3} # Time base: 10^-3s + +# RxPDO Mapping (Master β†’ Slave) +rpdo: + - index: 0x1603 + channels: + - {index: 0x6040, sub_index: 0, type: uint16, default: 0} # Control word + - {index: 0x607a, sub_index: 0, type: int32, command_interface: position, default: .nan} + - {index: 0x60ff, sub_index: 0, type: int32, default: 0} # Target velocity + - {index: 0x6071, sub_index: 0, type: int16, default: 0} # Target torque + - {index: 0x6060, sub_index: 0, type: int8, default: 8} # Mode: Cyclic Sync Position + +# TxPDO Mapping (Slave β†’ Master) +tpdo: + - index: 0x1a03 + channels: + - {index: 0x6041, sub_index: 0, type: uint16} # Status word + - {index: 0x6064, sub_index: 0, type: int32, state_interface: position} + - {index: 0x606c, sub_index: 0, type: int32, state_interface: velocity} + - {index: 0x6077, sub_index: 0, type: int16, state_interface: effort} + - {index: 0x6061, sub_index: 0, type: int8} # Mode display +``` + +### Key Configuration Elements + +#### vendor_id and product_id + +Find these using: + +```bash +ethercat slaves +``` + +#### PDO Mapping + +**RxPDO (Master β†’ Slave)**: Commands sent to the drive + +- `command_interface`: Maps to ros2_control command interfaces +- `default`: Default value when not actively controlled + +**TxPDO (Slave β†’ Master)**: Feedback from the drive + +- `state_interface`: Maps to ros2_control state interfaces + +## URDF Integration + +### Basic Configuration + +Integrate the EtherCAT drive into your robot's URDF: + +```xml title="ros2_control/robot.ros2_control.urdf" + + + + + + ethercat_driver/EthercatDriver + 0 + 100 + + + + + + + + + + + + ethercat_generic_plugins/EcCiA402Drive + 0 + 0 + 8 + /path/to/maxon_epos3.yaml + + + + + + +``` + +### Multi-Joint Configuration + +For robots with multiple joints, add more joint definitions with incremented positions: + +```xml + + + + ethercat_generic_plugins/EcCiA402Drive + 0 + 1 + 8 + /path/to/drive_config.yaml + + +``` + +## Launch and Usage + +### Start EtherCAT Master + +```bash +sudo systemctl start ethercat +``` + +Verify slaves are detected: + +```bash +ethercat slaves +``` + +Expected output: +``` +0 0:0 PREOP + MAXON EPOS3 70/10 EtherCAT +``` + +### Launch Your Robot + +Create a launch file similar to the SCARA tutorial, but ensure you run with real-time priority: + +```python +control_node = Node( + package="controller_manager", + executable="ros2_control_node", + parameters=[robot_description, robot_controllers], + output="both", + # Run with real-time priority + prefix=['sudo -E env "PATH=$PATH" chrt -f 95'], +) +``` + +Launch your robot: + +```bash +ros2 launch my_robot_bringup robot_ethercat.launch.py +``` + +### Send Commands + +Use standard ros2_control interfaces to command your robot: + +```bash +# Check controller status +ros2 control list_controllers + +# Send position command +ros2 topic pub /joint_trajectory_controller/joint_trajectory \ + trajectory_msgs/msg/JointTrajectory \ + "{joint_names: ['joint_1'], points: [{positions: [0.5], time_from_start: {sec: 2}}]}" \ + --once +``` + +## Advanced Topics + +### Fault Handling + +#### Manual Fault Reset + +When `auto_fault_reset: false`: + +```bash +# Trigger fault reset via rising edge +ros2 service call /controller_manager/set_hardware_component_state \ + controller_manager_msgs/srv/SetHardwareComponentState \ + "{name: 'ethercat_system', target_state: {id: 3, label: 'active'}}" +``` + +### Mode Switching + +Switch between position, velocity, and torque control dynamically by: + +1. Defining multiple command interfaces in URDF +2. Using mode-switching controllers +3. Sending `NaN` to unused interfaces when switching + +### Real-Time Performance + +For optimal real-time performance: + +1. **Install PREEMPT_RT kernel** +2. **Set real-time priorities** in launch file +3. **Optimize control frequency** (100-1000 Hz) +4. **Isolate CPU cores** for real-time tasks + +## Troubleshooting + +| Issue | Solution | +|-------|----------| +| Slave not detected | Check `ethercat config` and network interface | +| Permission denied | Add user to ethercat group: `sudo usermod -aG ethercat $USER` | +| Drive stays in fault | Check wiring, power, and config file | +| Position drift | Set `default: .nan` for target position in config | + +## Next Steps + +- Experiment with different control modes +- Optimize configuration for your hardware +- Explore the [developer guide](https://icube-robotics.github.io/ethercat_driver_ros2/developer_guide/new_plugin.html) + +## Additional Resources + +- [Generic EtherCAT Slave Configuration](https://icube-robotics.github.io/ethercat_driver_ros2/user_guide/config_generic_slave.html) +- [CANopen over EtherCAT](https://icube-robotics.github.io/ethercat_driver_ros2/developer_guide/coe.html) +- [API Reference](https://icube-robotics.github.io/ethercat_driver_ros2/api/) + +--- + +Happy controlling! πŸ€– diff --git a/docs/tutorials/gazebo_tutorial.md b/docs/tutorials/gazebo_tutorial.md new file mode 100644 index 0000000..fb1d1bd --- /dev/null +++ b/docs/tutorials/gazebo_tutorial.md @@ -0,0 +1,96 @@ +# Setting up the SCARA manipulator to run with ros2_control and Gazebo +Whereas using simulated hardware is very important to test the kinematic properties of the robot, in many application the use of robot dynamics and interactions with the environment is required. The [Gazebo](https://gazebosim.org/home) simulator is often used in ROS2 projects as it easy to combine ROS2 with Gazebo. Also, ros2_control has specific Hardware Interfaces that allow to simulate a hardware inside Gazebo while using controllers in the ros2_control framework. + +In this section of the tutorial, we will focus on how to simulate the SCARA robot in Gazebo with ros2_control and how to interact with it. + +## Adding the Gazebo description to the URDF +In order to use ros2_control with Gazebo, some additional setup steps are required in the robot description. In fact, Gazebo allows to implement Gazebo plugins to interact with the simulation, what is used to create a bridge between Gazebo and ros2_control. The `gazebo_ros2_control` plugin is used for this reason. In contrary to the previous steps where the Controller Manager was acting as a standalone ROS2 node, in this case the `gazebo_ros2_control` plugin is responsible of running an instance of the Controller Manager inside the Gazebo node. + +This can be done by adding a [`scara.gazebo.xacro`](../scara_description/gazebo/scara.gazebo.xacro) configuration file in the `scara_description` package, that contains: + +```xml + + + + + + $(find scara_description)/config/scara_controllers.yaml + robot_state_publisher + + + + + true + + + + + Gazebo/Blue + + + + + +``` +In this description file, the `gazebo` tags are used to specify parameters to be used by Gazebo and in particular the `plugin` that needs to be loaded. The plugin here is the `gazebo_ros2_control` plugin contained in the `libgazebo_ros2_control.so` library. This plugin takes 2 parameters in the same way as the ControllerManager before, which are the [`scara_controllers.yaml`](../scara_description/config/scara_controllers.yaml) configuration file and the name of the node that published the `robot_description` topic, which is by default done by the `robot_state_publisher` node. The `gazebo` tags are also used to add additional gazebo parameters to already defined objects. Here we use them to fix the robot to a static world frame and to add a color to each link. + +This description file can then be added to the [`scara.config.xacro`](../scara_description/config/scara.config.xacro) by adding: +``` xml + + +``` + +## Adding the Gazebo ros2_control Hardware Interface +Now that the ros2_control plugin was added to the description, we also need to specify the ros2_control Hardware Interface that needs to be used to link the Gazebo hardware with ros2_control. To do so we just need to specify the `GazeboSystem` hardware interface plugin in the ros2_control urdf description [file](../scara_description/ros2_control/scara.ros2_control.urdf) as follows: +```xml + + + + + gazebo_ros2_control/GazeboSystem + + + + +``` +## Launching Gazebo with ros2_control +Once the description is complete, we need to modify the launch file so that it can launch Gazebo and spawn the robot from the URDF description. + +The Gazebo node has its own launch file that can be included in our launch file as follows: +```python +gazebo = IncludeLaunchDescription( + PythonLaunchDescriptionSource( + [PathJoinSubstitution( + [FindPackageShare('gazebo_ros'), + 'launch', 'gazebo.launch.py'] + )] + ), + launch_arguments={'verbose': 'false'}.items(), +) +``` + +To spawn the robot from its description to Gazebo a dedicated spawner node is available and can be called as follows: +```python +spawn_entity = Node( + package='gazebo_ros', + executable='spawn_entity.py', + arguments=['-topic', robot_description, '-entity', 'scara'], + output='screen', +) +``` +The arguments that are given here are: +* The `topic` parameter, specifying the topic name in which the robot description is published. This topic is usually published by the `robot_state_publisher` node. +* The `entity` parameter, specifying the name of the robot to be spawned as given in the URDF description. + +See the [`scara_gazebo.launch.py`](../scara_bringup/launch/scara_gazebo.launch.py) file for the complete launch file. + +Notice that because the Controller Manager is already running inside the Gazebo node, it will not be launched here. + +You can now run the launch file using: +```shell +$ ros2 launch scara_bringup scara_gazebo.launch.py +``` +You should have Gazebo displaying the scara robot running as a Gazebo ros2_control hardware +![scara gazebo](../images/scara_gazebo.png) + +You can now load and interact with it as explained in the previous [section](launch_tutorial.md). \ No newline at end of file diff --git a/docs/tutorials/hardware_tutorial.md b/docs/tutorials/hardware_tutorial.md new file mode 100644 index 0000000..570eb07 --- /dev/null +++ b/docs/tutorials/hardware_tutorial.md @@ -0,0 +1,211 @@ +# Writing of a Hardware Interface + +In the case if the hardware interface is not available or not suited for the desired application, it can be developed in a custom way, what is the topic of the next section. + +In ros2_control, hardware system components are integrated via user defined driver plugins that conform to the `HarwareInterface` public interface. Hardware plugins specified in the URDF are dynamically loaded during initialization using the `pluginlib` interface. More information about creating and using plugins can be found [here](https://docs.ros.org/en/humble/Tutorials/Beginner-Client-Libraries/Pluginlib.html). + +For the purpose of this tutorial, let's create the custom interface plugin `ScaraRobot` that will be used to simulate a scara robot. We want the simulated system to be controlled in joint position and provide information about its current position and velocity, as described in the ros2_control description [file](../scara_description/ros2_control/scara.ros2_control.urdf). + +To do so, in the `scara_hardware` package, let's first define the hardware plugin called `ScaraRobot` that inherits from `hardware_interface::SystemInterface`. The `SystemInterface` is one of the offered hardware interfaces designed for a complete robot system. For example, The UR5 uses this interface. The `ScaraRobot` must implement five public methods: +1. `on_init` +2. `export_state_interfaces` +3. `export_command_interfaces` +4. `read` +5. `write` + +These methods are defined in the [scara_robot.hpp](../scara_hardware/include/scara_hardware/scara_robot.hpp) header file as follows: + +```c++ +using CallbackReturn = rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn; +#include "hardware_interface/types/hardware_interface_return_values.hpp" + +class HARDWARE_INTERFACE_PUBLIC ScaraRobot : public hardware_interface::SystemInterface { + public: + CallbackReturn on_init(const hardware_interface::HardwareInfo &info) override; + std::vector export_state_interfaces() override; + std::vector export_command_interfaces() override; + return_type read(const rclcpp::Time &time, const rclcpp::Duration &period) override; + return_type write(const rclcpp::Time & /*time*/, const rclcpp::Duration & /*period*/) override; + // private members + // ... +} +``` + +## Initializing the hardware +Let's first have a look at the initialization `on_init` method. The `on_init` method is called once during ros2_control initialization if the `ScaraRobot` was specified in the URDF. This method should: +- Check the validity of the requested `command_interfaces` and `state_interfaces` w.r.t. the loaded driver +- Instantiate the communication with the robot hardware +- Allocate memory + +Since in this tutorial the robot is simulated, no communication need to be established. Instead, vectors will be initialized that represent the state all the hardware using the initial values from the description file. The definition of this method is as follows: + +```c++ +CallbackReturn ScaraRobot::on_init(const hardware_interface::HardwareInfo &info) { + if (hardware_interface::SystemInterface::on_init(info) != CallbackReturn::SUCCESS) { + return CallbackReturn::ERROR; + } + + // allocate memory + hw_states_position_.resize(info_.joints.size() std::numeric_limits::quiet_NaN()); + //... + // check the validity of the description + for (const hardware_interface::ComponentInfo & joint : info_.joints) { + if (joint.command_interfaces[0].name != hardware_interface::HW_IF_POSITION) + { + return CallbackReturn::ERROR; + } + } + // ... + // initialize states + for (uint i = 0; i < info_.joints.size(); i++) { + hw_states_position_[i] = std::stod(info_.joints[i].state_interfaces[0].initial_value); + } + // ... + return CallbackReturn::SUCCESS; +} +``` +Note that the behavior of `on_init` is expected to vary depending on the URDF description file. The `SystemInterface::on_init(info)` call fills out the `info` object with specifics from the URDF. The `info` object has fields for joints, sensors, gpios, and more. This allows to check if the interface that is called is compatible with the description and use to parameters from the description file to set up the hardware. + +## Exporting interfaces + +Next, `export_state_interfaces` and `export_command_interfaces` methods are called in succession. Their purpose is to create a handle to link the internal state/command variable with the ros2_control framework so that it can be accessed from any method. The `export_state_interfaces` method returns a vector of `StateInterface` describing the `state_interfaces` for each joint. The `StateInterface` objects are read only data handles that contain the interface name, interface type, and a pointer to a double data value of the internal state variable. For the `ScaraRobot`, the `export_state_interfaces` references `hw_states_position_` vector with the position state interface and is defined as follows: +```c++ +std::vector ScaraRobot::export_state_interfaces() { + std::vector state_interfaces; + for (uint i = 0; i < info_.joints.size(); i++) { + state_interfaces.emplace_back( + hardware_interface::StateInterface( + info_.joints[i].name, hardware_interface::HW_IF_POSITION, &hw_states_position_[i])); + } + // ... + return state_interfaces; +} +``` +The `export_command_interfaces` method is nearly identical to the previous one. The difference is that a vector of `CommandInterface` is returned. The vector contains objects describing the `command_interfaces` for each joint. For the `ScaraRobot`, the `export_command_interfaces` references the `hw_commands_position_` vector with the position command interface and is defined as follows: +```c++ +std::vector ScaraRobot::export_command_interfaces() { + std::vector command_interfaces; + for (uint i = 0; i < info_.joints.size(); i++) { + command_interfaces.emplace_back( + hardware_interface::CommandInterface( + info_.joints[i].name, hardware_interface::HW_IF_POSITION, &hw_commands_position_[i])); + } + return command_interfaces; +} +``` +Now that the Hardware Interface is initialized, connected to the robot and that the internal variables are connected to the ros2_control framework, let's focus on the main control loop. + +## Reading and writing from the hardware +In ros2_control the main control loop consists in successive calls of the hardware `read` method, followed by the controller `update` method, followed by the hardware `write` method. In the read phase of the main loop, ros2_control loops over all hardware components that where loaded to call their `read` method. It is executed on the realtime thread, hence the method must obey by realtime constraints. The `read` method is responsible for accessing the robot current state and updating the data values of the `state_interfaces`. +In this tutorial, as we only want to simulate the robot, we compute its current velocity as follows: +```c++ +hardware_interface::return_type ScaraRobot::read(const rclcpp::Time & time, const rclcpp::Duration &period) { + // read hardware values for state interfaces, e.g joint encoders and sensor readings + for (uint i = 0; i < info_.joints.size(); i++) { + hw_states_velocity_[i] = (hw_states_position_[i] - hw_states_previous_position_[i])/(period.nanoseconds()*1e-9); + + hw_states_previous_position_[i] = hw_states_position_[i]; + } + return hardware_interface::return_type::OK; +} +``` +In the same way, during the write phase of the main loop, the `write` method of all loaded hardware components is called after the controller `update` in the realtime loop. For this reason, the `write` method must also obey by realtime constraints. The `write` method is responsible for updating the data values of the `command_interfaces`. +In the case of our scara robot, the methods is defined as follows: +```c++ +hardware_interface::return_type write(const rclcpp::Time & time, const rclcpp::Duration & period) { + // send command interface values to hardware, e.g joint set joint velocity + bool isNan = false; + for (auto i = 0ul; i < hw_commands_position_.size(); i++) { + if (hw_commands_position_[i] != hw_commands_position_[i]) isNan = true; + } + + if (!isNan) { + for (uint i = 0; i < info_.joints.size(); i++) { + double min_position = std::stod(info_.joints[i].state_interfaces[0].min); + double max_position = std::stod(info_.joints[i].state_interfaces[0].max); + + hw_states_position_[i] = hw_commands_position_[i]; + + if(hw_states_position_[i] > max_position) hw_states_position_[i] = max_position; + if(hw_states_position_[i] < min_position) hw_states_position_[i] = min_position; + } + } + + return hardware_interface::return_type::OK; +} +``` +Notice here that in the first part of the methods we check if the value command is valid (i.e. not NAN), that means that a command was received through the `command_interface`. Also, notice that we can use some information from the ros2_control description file such as position `min` and `max` parameters to prevent the robot to go outside of the its limits. + +Now that we have defined our hardware interface, let's focus on building it in the next section. + +## Building the Hardware Interface plugin +Building the Hardware Interface plugin is done with the following steps: +* Adding C++ export macro +* Creating the plugin description file +* Exporting the CMake library + +### Adding C++ export macro +In order to reference the previously defined hardware interface as a ros2_control plugin, we need to add the following two lines of code at the end of the [scara_robot.cpp](../scara_hardware/src/scara_robot.cpp) file containing our method definitions: + +```c++ +#include "pluginlib/class_list_macros.hpp" + +PLUGINLIB_EXPORT_CLASS(scara_hardware::ScaraRobot, hardware_interface::SystemInterface) +``` +The `PLUGINLIB_EXPORT_CLASS` is a c++ macro that creates a plugin library using `pluginlib`. More information about it can be found [here](https://docs.ros.org/en/humble/Tutorials/Beginner-Client-Libraries/Pluginlib.html). + +### Creating the plugin description file +The plugin description file is a required XML file that describes the plugin's library name, class type, namespace, description, and interface type. This file allows ROS2 to automatically discover and load plugins. It is formatted as follows: + +```xml + + + + {Human readable description} + + + +``` + +The `path` attribute of the `library` tags refers to the cmake library name of the user defined hardware plugin. See [here](../scara_hardware/scara_hardware_plugin.xml) for the complete XML file used for this tutorial. + +### Exporting the CMake library +The general CMake template to make a hardware plugin available in ros2_control is shown below: +```cmake +add_library( + scara_hardware + SHARED + src/scara_robot.cpp +) + +# include and link dependencies +# ... + +# Causes the visibility macros to use dllexport rather than dllimport, which is appropriate when building the dll but not consuming it. +target_compile_definitions(scara_hardware PRIVATE "HARDWARE_PLUGIN_DLL") +# export plugin +pluginlib_export_plugin_description_file(scara_hardware scara_hardware_plugin.xml) +# install libraries +# ... +``` +Notice that a library is created using the plugin source code just like any other cmake library. In addition, an extra compile definition and cmake export macro (`pluginlib_export_plugin_description_file`) need to be added. See [here](../scara_hardware/CMakeLists.txt) for the complete `CMakeLists.txt` file used for this tutorial. + +Now that our scara robot's hardware is ready to be loaded as a plugin let's run our scara robot! + +To do so you just need to specify your hardware plugin in the ros2_control urdf description [file](../scara_description/ros2_control/scara.ros2_control.urdf) as follows: +```xml + + + + + scara_hardware/ScaraRobot + + + + +``` + +Now you can test your hardware as explained in the previous [section on launching and interacting with the hardware](launch_tutorial.md), or got further and see the [section on how to develop a custom controller](controller_tutorial.md). \ No newline at end of file diff --git a/docs/tutorials/launch_tutorial.md b/docs/tutorials/launch_tutorial.md new file mode 100644 index 0000000..ccdbf6f --- /dev/null +++ b/docs/tutorials/launch_tutorial.md @@ -0,0 +1,243 @@ +# Launching and interacting with the Scara robot +In ros2_control, there is one main node responsible for running the framework, which is the `ControllerManager`. In this section, we will focus on how to set up and run this node and how to interact with it. + +## Configuring the Controller Manager +The `ControllerManager` node requires in addition to the robot description a configuration file with additional parameters, such as the control loop update rate, as well as a list of the desired controllers and their parameters. Such a configuration file is usually formatted as follows: +```yaml +controller_manager: + ros__parameters: + update_rate: 100 # Hz + + {controller_name}: + type: {namespace}/{class_name} + +{controller_name}: + ros__parameters: + # controller parameters +``` +In the controller configuration file, the `update_rate` parameter allows to set the update rate of the `ControllerManager` node. In addition, the desired controllers that we plan to run need to be referenced and set up. + +In the example of the scara robot, the configuration [file](../scara_description/config/scara_controllers.yaml) is the following: +```yaml +controller_manager: + ros__parameters: + update_rate: 100 # Hz + + joint_state_broadcaster: + type: joint_state_broadcaster/JointStateBroadcaster + + scara_position_controller: + type: position_controllers/JointGroupPositionController + +scara_position_controller: + ros__parameters: + joints: + - joint1 + - joint2 + - joint3 +``` +In this configuration the `update_rate` is set at 100Hz and 2 controllers are referenced: +* The `joint_state_broadcaster`, which is of type `JointStateBroadcaster`, is a general purpose controller available in the [`ros2_controllers`](https://github.com/ros-controls/ros2_controllers) package. This controller is a broadcaster, which means that it does not command the robot but only publishes its state to make it available to other ros2 components. +* The `scara_position_controller`, which is of type `JointGroupPositionController`, is also a general purpose controller available in the [`ros2_controllers`](https://github.com/ros-controls/ros2_controllers) package. The purpose of this controller is to command the robot joints using the position interface. + +Notice here, that in contrary to the `joint_state_broadcaster` that streams by default all states of all joints, the `scara_position_controller` requires additional parameters that specify the targeted joint names. + +For more information about available controllers and their usage refer to the [`ros2_controllers`](https://github.com/ros-controls/ros2_controllers) package. + +Now that we have the robot URDF description and the configuration for the Controller Manager node, let's create a launch file to run the scara robot. + +## Creating a launch file + +In the [`scara.launch.py`](../scara_bringup/launch/scara.launch.py) file, we first need to load the robot description from URDF. As we use XACRO, the global description file [`scara.config.xacro`](../scara_description/config/scara.config.xacro) of the robot needs to be evaluated first, what can be achieved as follows: +```python +robot_description_content = Command([ + PathJoinSubstitution([FindExecutable(name='xacro')]), + ' ', + PathJoinSubstitution( + [FindPackageShare('scara_description'), 'config', 'scara.config.xacro'] + ), +]) +robot_description = {'robot_description': robot_description_content} +``` + +In addition we need to load the previously defined configuration file for the Controller Manager. This can be done as follows: + +```python +robot_controllers = PathJoinSubstitution( + [ + FindPackageShare('scara_description'), + 'config', + 'scara_controllers.yaml', + ] +) +``` +With this done, we can now create a node running the ros2_control Controller Manager as follows: +```python +control_node = Node( + package='controller_manager', + executable='ros2_control_node', + parameters=[robot_description, robot_controllers], + output='both', + ) +``` +For the purpose of this tutorial you will also need to launch an `rviz2` node as well as `robot_state_publisher`, which are required to have a visual of the scara robot. + +See [here](../scara_bringup/launch/scara.launch.py) for the complete launch file used for this tutorial. + +## Running and interacting with the scara robot + +After building your workspace, you can run the launch file using: +```shell +$ ros2 launch scara_bringup scara.launch.py +``` +A RViz2 window should open and display the following: +![scara model](../images/scara_rviz_empty.png) + +This output indicates that the `robot_state_publisher` node does not have any information about the robot current state. This is not an error in the configuration of your robot and is due to the fact that by default, the `controller_manager` node does not load any controllers, including the `joint_state_broadcaster` responsible for sharing the state data with the ROS2 environment. + +The ros2_control framework comes with some [command line functionalities](https://control.ros.org/master/doc/ros2_control/ros2controlcli/doc/userdoc.html) that allow you to interact with Controller Manager. For example, to see what are the hardware interfaces that are currently running, run in a new terminal: +```shell +$ ros2 control list_hardware_interfaces +``` +which should produce the following output: +```shell +command interfaces + joint1/position [available] [unclaimed] + joint2/position [available] [unclaimed] + joint3/position [available] [unclaimed] +state interfaces + joint1/position + joint1/velocity + joint2/position + joint2/velocity + joint3/position + joint3/velocity +``` +This shows, as expected from the robot description, that a position command interface is available for all joints and that for each joint there is a position and velocity state interface. Notice also the `unclaimed` flag next to the command interfaces. This flag indicates that no controller was loaded to claim this particular command interface. In fact, if you now run: +``` shell +$ ros2 control list_controllers +``` +It will give you and empty output. In this case, let's load the `joint_state_broadcaster`. To do so, run in your terminal: +```shell +$ ros2 control load_controller joint_state_broadcaster --set-state active +``` +what should return; +```shell +Successfully loaded controller joint_state_broadcaster into state active +``` +Now have a look at your RViz2 window. It should finally display the expected output: +![scara model](../images/scara_rviz.png) + +Also, if you run again: +``` shell +$ ros2 control list_controllers +``` +it will output: +```shell +joint_state_broadcaster[joint_state_broadcaster/JointStateBroadcaster] active +``` +This means that only the `joint_state_broadcaster` is currently running. This particular controller is responsible for reading the states from the hardware and publishing them in the `\joint_states` topic, so that it can be interpreted by the robot state publisher node and displayed in RViz2. You can also read the current state of the robot by listening directly to the `\joint_states` topic by running: +```shell +$ ros2 topic echo /joint_states +``` +Even though the `joint_state_broadcaster` is a controller, it does not command any interface of the robot. In the next section, let's focus on running another controller that this time will move the robot. + +## Controlling joints with controllers + +Let's now focus on another controller that was set up in the [`scara_controllers.yaml`](../scara_description/config/scara_controllers.yaml) configuration file. In order to give position commands to the scara robot, load the `scara_position_controller` by running: +```shell +$ ros2 control load_controller scara_position_controller --set-state active +``` +Now if you run: +``` shell +$ ros2 control list_controllers +``` +that should give you: +``` shell +joint_state_broadcaster[joint_state_broadcaster/JointStateBroadcaster] active +scara_position_controller[position_controllers/JointGroupPositionController] active +``` +Your `scara_position_controller` is now ready to receive position commands. In fact, if you run +```shell +$ ros2 topic list +``` +you will see that a new topic `/scara_position_controller/commands` appeared. Let's inspect this topic by running : +```shell +$ ros2 topic info /scara_position_controller/commands +``` +which will output: +```shell +Type: std_msgs/msg/Float64MultiArray +Publisher count: 0 +Subscription count: 1 +``` +This shows you that the expected command message format is of type `Float64MultiArray`. Let's check how this message type is defined by running : +``` shell +$ ros2 interface show std_msgs/msg/Float64MultiArray +``` +which will return : +```shell +# Please look at the MultiArrayLayout message definition for +# documentation on all multiarrays. + +MultiArrayLayout layout # specification of data layout +float64[] data # array of data +``` +The expected message is an array of `float64`. Let's now publish a set of position commands on that topic: +```shell +ros2 topic pub --once /scara_position_controller/commands std_msgs/msg/Float64MultiArray "{data: [0.5,-1.5,0.3]}" +``` +Your robot moved to the desired position! +Notice here that the motion to the desired position was done in one shot, what on a real robot would require excessive torques. In the case of a real robot, it would be more suited to use another controller that is able of interpolating the robot motion such as the [`joint_trajectory_controller`](https://control.ros.org/master/doc/ros2_controllers/joint_trajectory_controller/doc/userdoc.html). After adding the new controller to the configuration [file](../scara_description/config/scara_controllers.yaml) as the `scara_trajectory_controller`, let's see how to switch from one controller to the other. We consider that the application was not stopped and that the `scara_position_controller` is still running. + +At first, the new controller needs to be loaded. If we run the previous command: +```shell +$ ros2 control load_controller scara_trajectory_controller --set-state active +``` +it will return: +```shell +[ERROR] [1663347965.049772820] [controller_manager]: Resource conflict for controller 'scara_trajectory_controller'. Command interface 'joint1/position' is already claimed. +``` +As explained in the [overview section](../images/r2c_overview.md), the command interfaces are exclusively accessed to avoid this particular case where two controllers claim the same interface. To deal with it, the `scara_position_controller` needs to release the interface before the `scara_trajectory_controller` can claim it. One way of doing it is to first deactivate the `scara_position_controller` by running: +```shell +$ ros2 control set_controller_state scara_position_controller inactive +``` +and then activate the other controller by running: +```shell +$ ros2 control set_controller_state scara_trajectory_controller active +``` +As a result, the controllers are switched what can be checked running once again: +``` shell +$ ros2 control list_controllers +``` +that should give you: +``` shell +joint_state_broadcaster[joint_state_broadcaster/JointStateBroadcaster] active +scara_position_controller[position_controllers/JointGroupPositionController] inactive +scara_trajectory_controller[joint_trajectory_controller/JointTrajectoryController] active +``` + +Alternatively, in order to avoid the period where teh hardware is not controlled, both actions can be done simultaneously by running: +```shell +$ ros2 control switch_controllers --deactivate scara_position_controller --activate scara_trajectory_controller +``` + +Notice that in order to be able to activate the `scara_trajectory_controller`, its state needs to be `inactive` meaning that is was already configured. In order to configure the controller without activating it, you can run: +```shell +ros2 control load_controller scara_trajectory_controller --set-state configured +``` + + +## Additional comments on controllers +In ros2_control, controllers can be loaded, unloaded and switched on runtime without stopping the hardware. This allows to address the need of applications that have multiple different operating phases. More information can be found [here](https://control.ros.org/master/index.html). + +Also, in most applications the controller to be loaded is known from start and therefore it can be loaded directly at startup in the [launch](../scara_bringup/launch/scara.launch.py) file by calling the `spawner` node: +```python +controller_spawner = Node( + package='controller_manager', + executable='spawner', + arguments=[''], + ) +``` + +Some additional information about available controllers can be found [here](https://control.ros.org/master/doc/ros2_controllers/doc/controllers_index.html). If, however, for the purpose of your applications you need a custom controller, you can go to the [section on how to develop one](controller_tutorial.md). \ No newline at end of file diff --git a/docs/tutorials/r2c_overview.md b/docs/tutorials/r2c_overview.md new file mode 100644 index 0000000..850735c --- /dev/null +++ b/docs/tutorials/r2c_overview.md @@ -0,0 +1,65 @@ +# Overview of the ros2_control framework + +The [ros2_control](https://control.ros.org) framework is a realtime control framework designed for general robotics applications. It is an abstraction layer for simple integration of hardware and controllers. The ros2_control framework comes with standard interfaces to enhance code modularity and robot agnostic design. Application specific details, such as the controller to be used or the robot description, are easily specified via configuration files. Finally, the ros2_control framework can easily be deployed via ROS2 launch a file. + +All these features of ros2_control allow to focus on the design of complex application by taking advantage of the possibility to use and reuse as often as possible existing controllers, hardware drivers and freely switch between simulation and execution on a real robotic system. Also, the effort spent on the development of custom controllers or hardware interfaces can easily be reused in many other projects. Finally, using standard components allows to easily integrate 3rd party ROS2 packages into the developed applications. + +![r2c overview](../images/r2c_overview.png) + +In order to better understand how the framework works, let's focus at the components it consists of. + +## Controller Manager + +The Controller Manager is the main component in the ros2_control framework, which connects the controllers and hardware-abstraction sides of the framework. It also serves as the entry-point for users through ROS services. The Controller Manager implements a node without an executor so it can be integrated into a custom setup. Still, for a standard user, it is recommended to use the default node-setup implemented in `ros2_control_node` file from the `controller_manager` package. + +On the one side, Controller Manager manages (e.g., loading, activating, deactivating, unloading) controllers and the interfaces they require. On the other side, it has access to the hardware components through the Resource Manager. The Controller Manager matches required and provided interfaces, gives controllers access to hardware when activated, or reports an error if there is an access conflict. + +The execution of the different components of the ros2_control framework in the control-loop is managed by the Controller Manager. At each loop iteration, it reads data from the hardware components, updates outputs of all active controllers, and writes the result to the components. + +The Controller Manager's control loop update can be summarized as follows: +![r2c cm](../images/r2c_cm.png) + +## Resource Manager + +The Resource Manager abstracts physical hardware and its drivers (called hardware components) for the ros2_control framework. The Resource Manager loads the components as plugins. It also manages the components' lifecycle and as well as state and command interfaces. This abstraction provided by the Resource Manager enables re-usability of implemented hardware components, e.g., robot and gripper, without any implementation and flexible hardware application for state and command interfaces, e.g., separate hardware/communication libraries for motor control and encoder reading. + +In the control loop execution managed by the Controller Manager, the Resource Manager’s `read()` and `write()` methods are called and deal with communication to the hardware components. + +## Controllers + +The controllers in the ros2_control framework have the same functionality as defined in the control theory. They compare the reference value with the measured output and, based on this error, calculate a system’s input. The controllers are loaded as plugins making their development independent of the framework. In ros2_control, controllers are [managed node](https://design.ros2.org/articles/node_lifecycle.html), which means that they work as state-machines and thus have a finite set of states, which are: + +1. Unconfigured +2. Inactive +3. Active +4. Finalized + +This configuration has several advantages: +- It allows to have greater control over the current state of the controller and ensure that it has been correctly instantiated before being executed. +- It allows to load, restart and replace controllers on-line. + +Notice here also, that in order to be executed by the Controller Manager and command the hardware, the controller needs to be in `active` state. + +When executing the control-loop, the Controller Manager calls the `update()` method of all controllers. This method can access the latest hardware states and enable the controller to send commands to the hardware interfaces. + +## Hardware Abstraction + +The hardware abstraction layer is done using ros2_control hardware components that realize the communication to physical hardware and represent its abstraction in the ros2_control framework. The components have to be exported as plugins. The Resource Manager dynamically loads those plugins and manages their lifecycle. + +There are three basic types of components: + +1. __System__: Complex (multi-DOF) robotic hardware like industrial robots. The main difference between the Actuator component is the possibility to use complex transmissions like needed for humanoid robot’s hands. This component has reading and writing capabilities. It is used when the is only one logical communication channel to the hardware, such as when using a robot driver and SDK. + +2. __Sensor__: Hardware used for sensing some environment parameters. This component type has only reading capabilities. + +3. __Actuator__: Simple (1 DOF) robotic hardware like motors, valves, and similar. An actuator implementation is related to only one joint. This component type has reading and writing capabilities. Reading is not mandatory if not possible (e.g., DC motor control with Arduino board). The actuator type can also be used with a multi-DOF robot if its hardware enables modular design, e.g., CAN-communication with each motor independently. + +## State and Command Interfaces + +Finally, the ros2_control framework introduces `state_interfaces` and `command_interfaces` to abstract hardware interfacing. The `state_interfaces` are read only data handles that generally represent sensors readings, e.g. joint encoder. The `command_interfaces` are read and write data handles that are used to pass commands to the hardware. The `command_interfaces` are exclusively accessed, meaning if a controller has "claimed" an interface, it cannot be used by any other controller until it is released. This guarantees that two controllers will never be able to send commands to the same hardware in the same time, what could have a dangerous outcome. Both `state_interfaces` and `command_interfaces` are set up together with the hardware using configuration files. + +The overall architecture of the ros2_control framework can be summarized as follows: +![r2c architecture](../images/r2c_architecture.png) + + + diff --git a/docs/tutorials/urdf_tutorial.md b/docs/tutorials/urdf_tutorial.md new file mode 100644 index 0000000..5e9b2ad --- /dev/null +++ b/docs/tutorials/urdf_tutorial.md @@ -0,0 +1,166 @@ +# Writing the URDF description of the SCARA robot +The URDF file is a standard XML based file used to describe characteristic of a robot. It can represent any robot with a tree structure, except those with cycles. Each link must have only one parent. For ros2_control, there are three primary tags: `link`, `joint`, and `ros2_control`. The `joint` tag define the robot's kinematic structure, while the `link` tag defines the dynamic properties and 3D geometry. The `ros2_control` defines the hardware and controller configuration. + +A good practice in ROS2 is to specify the description of the used robot in a dedicated package. In this tutorial, the package is named in a standard way `scara_description`. In this package you can find different folders containing the configuration of the used system for different ROS2 components. + +## Global URDF description using Xacro +In order to simplify the setup of the robot we often build the robot URDF description using `xacro`. Xacro (XML Macros) is an XML macro language. With xacro, you can construct shorter and more readable XML files by using macros that expand to larger XML expressions. Using xacro allows to include smaller segments of the system description for better readability. For example, in the case of the scara robot, the global URDF is defined using the [scara.config.xacro](../scara_description/config/scara.config.xacro) file, formatted as follows: +```xml + + + + + + + + + + + + + + +``` +In this xml description: +* The `robot` tag encloses all contents of the URDF file. It has a name attribute which must be specified. +* The `xacro:include` tag is used to import the geometric description, the materials file and the ros2_control description. + +In the next sections, let's focus more in details on the included description files. + +## Geometry and Dynamics + +In this section, let's focus on the [`scara.urdf`](../scara_description/urdf/scara.urdf) description file. The URDF file describes in details the geometry of the robot as well as some additional parameters such as its visual and collision meshes, dynamics and others. + +Let's create a robot description for a scara robot with the following structure: + +![scara_tf](../images/scara_tf.png) + +The resulting URDF description file is generally formatted as follows: + +``` xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +``` +In this xml description: +* The `robot` tag encloses all contents of the URDF file. It has a name attribute which must be specified. +* The `link` tag defines the robot's geometry and inertia properties. It has a name attribute which will be referred to by the `joint` tags. +* The `visual` tag specifies the rotation and translation of the visual shapes. The shapes require to set the `origin` tag to fit the desired link shape. +* The `geometry`, `box` and `cylinder` tags specify the geometry of the robot link. Alternatively, you can also use the `mesh` tag to specify the location of the 3D mesh file relative to a specified ROS 2 package. +* The `collision` tag is equivalent to the `visual` tag, except the specified mesh is used for collision checking in some applications. +* The `inertial` tag specifies mass and inertia for the link. The origin tag specifies the link's center of mass. These values are used to calculate forward and inverse dynamics. Since our application does not use dynamics, uniform arbitrary values are used. +* The `` comments indicates that many consecutive `link` tags will be defined, one for each link. +* The `` and `` elements are not required. However, it is convention to set the link at the tip of the robot to tool0 and to define the robot's base link relative to a world frame. +* The `joint` tag specifies the kinematic structure of the robot. It two required attributes: name and type. The type specifies the viable motion between the two connected links. The subsequent `parent` and `child` links specify which two links are joined by the joint. +* The `axis` tag species the joint's axis of rotation. If the meshes were process as described previously, then the axis value is always `"0 0 1"`. +* The `limits` tag specifies kinematic and dynamic limits for the joint. +* The `dynamics` tag specifies some dynamics properties of the joint such as its damping or friction coefficients. + +## Hardware Interface setup for ros2_control + +In this section, let's focus on the [`scara.ros2_control.urdf`](../scara_description/ros2_control/scara.ros2_control.urdf) description file. This description file is used to set up the ros2_control hardware that will be used to specify the `command_interface` and `state_interface` for each `joint`, `sensor` and/or `gpio`. + +The ros2_control description is generally formatted as follows: + +```xml + + + + + + + mock_components/GenericSystem + + + + + + 0.0 + -1.57 + 1.57 + + + 0.0 + + + + + + + + +``` +In this xml description: +* The `robot` tag encloses all contents of the URDF file. It has a name attribute which must be specified. +* The `hardware` and `plugin` tags instruct the ros2_control framework to dynamically load a hardware driver conforming to `HardwareInterface` as a plugin. The plugin is specified as `{Name_Space}/{Class_Name}`. In this case we use the `GenericSystem` hardware which is a general purpose simulation hardware available in the [`ros2_control`](https://github.com/ros-controls/ros2_control) package. +* The`joint` tag specifies the state and command interfaces that the loaded plugin will offer. The joint is specified with the name attribute. The `command_interface` and `state_interface` tags specify the interface type, usually position, velocity, acceleration, or effort. Additionally, for each interface additional parameters such as `min`, `max` and `initial_value` can be set. + +The hardware interface that can be loaded here as a plugin is dependant of the type of robot that is controlled and its control mode. It is an interface between ros2_control and the robot driver. For robots that support ros2_control, the interface is often given either by the manufacturer or the community. A non exhaustive list of available hardware interfaces can be found [here](https://control.ros.org/master/doc/supported_robots/supported_robots.html). + +At this point you can either go further to the next [section on how to launch and interact with the current system](launch_tutorial.md). In the case where the hardware interface that you want to use is not already available, you can go to the [section on how to develop a custom one](hardware_tutorial.md). \ No newline at end of file diff --git a/resources/ethercat_tutorial.md b/resources/ethercat_tutorial.md new file mode 100644 index 0000000..e69de29