How to create a configuration package

In order to use the ros2_canopen stack for your robot, you need to create a configuration package, that holds the configuration of the bus as well as you launch script. The following sections detail the steps to create such a package.

Package creation

When you create your package, you have to first make some decisions. You will need to choose a package name, decide which bus configurations you need to have (usually one per CAN interface) and which slaves you have.

package_name: Name of the package
bus_config_name: Name of the the bus configuration (you can have multiple)

You can use ros2 pkg create command to create your package.

$ ros2 pkg create --dependencies canopen lely_core_libraries --build-type ament_cmake {package_name}
$ cd {package_name}
$ rm -rf src
$ rm -rf include
$ mkdir -p launch
$ mkdir -p config

Now your package directory should look like this.

{package_name}
├── config
├── launch
├── CMakeLists.txt
└── package.xml

Bus configuration creation

  1. Bus configuration decisions

    Decide how many bus configurations you need. Add one subfolder for each bus configuration in your config-folder.

    $ mkdir -p {bus_config_name}
    
  2. Add device information to bus configurations

    Once you created the folders for your bus configurations add the .eds files for the devices that are in the bus configuration to the respective folder.

    Now your package directory should now look like this.

    {package_name}
    ├── config
       ├── {bus_config_name_1}
       |   ├── {device1}.eds
       |   ├── {device...}.eds
       |   └── {slave_n}.eds
       └── {bus_config_name_2}
           ├── {device1}.eds
           ├── {device...}.eds
           └── {slave_n}.eds
    ├── CMakeLists.txt
    └── package.xml
    
  3. Create the bus configuration specifications

    To specify the bus configuration ros2_canopen uses the a YAML-file called bus.yml. Create the file in the respective bus configuration folder.

    $ touch bus.yml
    
    {package_name}
    ├── config
       ├── {bus_config_name_1}
       |   ├── bus.yml
       |   ├── {device1}.eds
       |   ├── {device...}.eds
       |   └── {slave_n}.eds
       └── {bus_config_name_2}
           ├── bus.yml
           ├── {device1}.eds
           ├── {device...}.eds
           └── {slave_n}.eds
    ├── CMakeLists.txt
    └── package.xml
    
  4. Edit the bus configuration specifications

    You need to modify each bus.yml file according to your needs. First you need to define where these files and generated files will be found at runtime. This is usually the following if you use colcon to build from source.

    options:
      dcf_path: install/{package_name}/share/{package_name}/config/{bus_config_name}
    

    Then you need to define your master.

    master:
      node_id: [node id]
      package: [ros2 package where to find the master driver (usually canopen_core)]
      driver: [component type of the driver (ros2_canopen::MasterDriver or ros2_canopen::LifecycleMasterDriver)]
    

    Make sure, that you specify a lifecycle master if you use the lifecycled version of ros2_canopen. And add other configuration data as necessary. A documentation of configuration options available can be found in the Configuration Package documentation.

    Once you have defined the configuration of your master, add your slaves. The following describes the mandatory data per slave. Further configuration options can be found in the Configuration Package documentation. The slave name is the node name that will be assigned to the driver.

    nodes:
      - [unique slave name]:
        node_id: [node id]
        package: [ros2 package where to find the driver]
        driver: [qualified name of the driver]
    

    Make sure you use a lifecycle slave if you use the lifecycled version of ros2_canopen.

Launch configuration creation

Create a launch folder in your package directory and a launch file.

mkdir launch
touch {...}.launch.py

Add the following code:

def generate_launch_description():
      """Generate launch description with multiple components."""
      path_file = os.path.dirname(__file__)

      ld = launch.LaunchDescription()


      device_container = IncludeLaunchDescription(
          PythonLaunchDescriptionSource(
              [
                  os.path.join(get_package_share_directory("canopen_core"), "launch"),
                  "/canopen.launch.py",
              ]
          ),
          launch_arguments={
              "master_config": os.path.join(
                  get_package_share_directory("{package_name}"),
                  "config",
                  "{bus_config_name}",
                  "master.dcf",
              ),
              "master_bin": os.path.join(
                  get_package_share_directory("{package_name}"),
                  "config",
                  "{bus_config_name}",
                  "master.bin",
              ),
              "bus_config": os.path.join(
                  get_package_share_directory("{package_name}"),
                  "config",
                  "{bus_config_name}",
                  "bus.yml",
              ),
              "can_interface_name": "{can_interface_name i.e. can0}",
          }.items(),

      )

      ld.add_action(device_container)

      return ld

CMAKE Configuration creation

Finally we need to adjust the CMakeLists.txt file to pick everything up correctly.

cmake_minimum_required(VERSION 3.8)
project({package_name})

if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
  add_compile_options(-Wall -Wextra -Wpedantic)
endif()

# find dependencies
find_package(ament_cmake REQUIRED)
find_package(canopen_core REQUIRED)
find_package(canopen_interfaces REQUIRED)
find_package(canopen_base_driver REQUIRED)
find_package(canopen_proxy_driver REQUIRED)
find_package(lely_core_libraries REQUIRED)


cogen_dcf({bus_config_name})

install(DIRECTORY
  launch/
  DESTINATION share/${PROJECT_NAME}/launch/
)

install(DIRECTORY
  launch_tests/
  DESTINATION share/${PROJECT_NAME}/launch_tests/
)


if(BUILD_TESTING)
  find_package(ament_lint_auto REQUIRED)
  # the following line skips the linter which checks for copyrights
  # comment the line when a copyright and license is added to all source files
  set(ament_cmake_copyright_FOUND TRUE)
  # the following line skips cpplint (only works in a git repo)
  # comment the line when this package is in a git repo and when
  # a copyright and license is added to all source files
  set(ament_cmake_cpplint_FOUND TRUE)
  ament_lint_auto_find_test_dependencies()
endif()

ament_package()