Skip to content

Add motion_primitives_forward_controller for interfacing motion primitive messages with hardware interfaces #1636

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 59 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
98cd2e3
added COLCON_IGNORE to every controller folder to not build it every …
mathias31415 Apr 8, 2025
783d129
added motion_primitive_forward_controller from https://github.com/mat…
mathias31415 Apr 8, 2025
6ed1181
renamed motion_primitives_controller_pkg to motion_primitives_forward…
mathias31415 Apr 8, 2025
9074829
added functionallity to execute multiple primitives as a motion sequence
mathias31415 Apr 9, 2025
2cf7b09
Added new state interface ready_for_new_primitive to indicate whether…
mathias31415 Apr 9, 2025
c09d1fd
removed case 33 block with hardcoded motion sequence (was only for te…
mathias31415 Apr 9, 2025
f4abcbd
removed mail
mathias31415 Apr 10, 2025
79e47be
added command_mutex_
mathias31415 Apr 10, 2025
316ad54
edited readme
mathias31415 Apr 10, 2025
28adf54
changed image in readme to white background
mathias31415 Apr 11, 2025
e28554d
edited Related packages/ repos in readme
mathias31415 Apr 11, 2025
c451fb4
added/ modified copyright headers
mathias31415 Apr 11, 2025
188f8b5
removed LICENSE file
mathias31415 Apr 24, 2025
93cc787
removed repos files
mathias31415 Apr 24, 2025
1122a31
changed license from BSD-3-Clause to Apache License 2.0
mathias31415 Apr 24, 2025
af63eab
added email to package.xml
mathias31415 Apr 24, 2025
3f3881f
changed version to fit the rest of the repo, added maintainers and ur…
mathias31415 Apr 24, 2025
ce8b937
removed .gitignore (was not supposed to be pushed to the repo)
mathias31415 Apr 24, 2025
92ee810
removed visibility control
mathias31415 Apr 24, 2025
bce1031
removed/ changed license headers from template files
mathias31415 Apr 30, 2025
82786cb
Merge branch 'master' into motion_primitive_forward_controller
bmagyar May 2, 2025
ae083e7
removed license header from motion_primitives_forward_controller.yaml
mathias31415 May 5, 2025
487e88a
changed license in readme to apache 2.0
mathias31415 May 5, 2025
e0d87c6
uncommented test_motion_primitives_forward_controller
mathias31415 May 5, 2025
9fa58b4
changed msg_queue_ form private to protected to be accessible in tests
mathias31415 May 5, 2025
3eb44c2
changed test_motion_primitives_forward_controller implementation from…
mathias31415 May 5, 2025
d9d4c67
uncommented test_motion_primitives_forward_controller_preceeding impl…
mathias31415 May 5, 2025
e76a8d0
changed test_motion_primitives_forward_controller_preceeding implemen…
mathias31415 May 5, 2025
11bec4d
moved reset_controller_reference_msg into motion_primitives_forward_c…
mathias31415 May 5, 2025
6c98434
changed get_value() to get_optional()
mathias31415 May 5, 2025
d2ef095
cleanup on comments and commented code
mathias31415 May 5, 2025
7c4f167
clarified motion primitive publishing to ~/reference topic
mathias31415 May 5, 2025
59ecb5b
pre-commit fixes
mathias31415 May 6, 2025
a2eaaea
added ExecutionState STOPPED and MotionType RESET_STOP
mathias31415 May 8, 2025
558de38
discard new commands while robot is stopped
mathias31415 May 12, 2025
29356d5
added missing test_depend's to package.xml
mathias31415 May 21, 2025
48dd4f1
moved test dependencies to if(BUILD_TESTING) block in CMakeLists.txt
mathias31415 May 21, 2025
c96da97
changed reference topic to action
mathias31415 Jun 5, 2025
53147b9
modified tests for action based controller
mathias31415 Jun 5, 2025
7a8bcb2
updated readme
mathias31415 Jun 5, 2025
72fa44f
Update motion_primitives_forward_controller/CMakeLists.txt
mathias31415 Jun 26, 2025
e670fdd
removed ament_target_dependencies from CMakeLists.txt
mathias31415 Jun 26, 2025
a49cbb5
reference the final package destinations in redme and added shematic …
mathias31415 Jun 26, 2025
51b7b0a
Removed STATE_MY_ITFS and CMD_MY_ITFS. References to command and stat…
mathias31415 Jun 26, 2025
123cdb1
removed static constexpr rmw_qos_profile_t rmw_qos_profile_services_h…
mathias31415 Jun 26, 2025
cb2e1d6
changed ExecutionState, MotionType and ReadyForNewPrimitive to enum
mathias31415 Jun 26, 2025
f19e43e
removed validate_motion_primitives_forward_controller_parameters.hpp
mathias31415 Jun 30, 2025
06ba279
Changed some RCLCPP_INFO prints to _DEBUG to reduce output
mathias31415 Jun 30, 2025
cd95fde
renamed robot_stopped_ to robot_stop_requested_ and changed it from b…
mathias31415 Jun 30, 2025
14adc0e
removed queue_size_ parameter
mathias31415 Jun 30, 2025
c47834c
wrapped moprim_queue_.push with start and end marker into a lambda
mathias31415 Jun 30, 2025
1e2691a
Moved functionallity to cancle movement from goal_cancelled_callback(…
mathias31415 Jun 30, 2025
5e95340
removed execute_goal() in extra thread. Integrated it into goal_accep…
mathias31415 Jun 30, 2025
55a59a3
updated action draw.io
mathias31415 Jun 30, 2025
d1b772c
Merge branch 'ros-controls:master' into motion_primitive_forward_cont…
mathias31415 Jun 30, 2025
bf3d2f6
changed version to match the other controllers
mathias31415 Jun 30, 2025
fddc824
removed name parameter for the moprim controller
mathias31415 Jun 30, 2025
f366f46
Merge branch 'ros-controls:master' into motion_primitive_forward_cont…
mathias31415 Jul 2, 2025
632ca1a
added std::atomic<bool> moprim_queue_write_enabled_
mathias31415 Jul 3, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 120 additions & 0 deletions motion_primitives_forward_controller/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
cmake_minimum_required(VERSION 3.8)
project(motion_primitives_forward_controller)

if(CMAKE_CXX_COMPILER_ID MATCHES "(GNU|Clang)")
add_compile_options(-Wall -Wextra -Werror=conversion -Werror=unused-but-set-variable -Werror=return-type -Werror=shadow)
endif()

set(THIS_PACKAGE_INCLUDE_DEPENDS
control_msgs
controller_interface
hardware_interface
pluginlib
rclcpp
rclcpp_lifecycle
realtime_tools
std_srvs
industrial_robot_motion_interfaces
)

find_package(ament_cmake REQUIRED)
find_package(generate_parameter_library REQUIRED)
foreach(Dependency IN ITEMS ${THIS_PACKAGE_INCLUDE_DEPENDS})
find_package(${Dependency} REQUIRED)
endforeach()

generate_parameter_library(motion_primitives_forward_controller_parameters
src/motion_primitives_forward_controller.yaml
)
add_library(
motion_primitives_forward_controller
SHARED
src/motion_primitives_forward_controller.cpp
)
target_include_directories(motion_primitives_forward_controller PUBLIC
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include/${PROJECT_NAME}>
)
target_link_libraries(motion_primitives_forward_controller PUBLIC
motion_primitives_forward_controller_parameters
controller_interface::controller_interface
hardware_interface::hardware_interface
pluginlib::pluginlib
rclcpp::rclcpp
rclcpp_lifecycle::rclcpp_lifecycle
realtime_tools::realtime_tools
${control_msgs_TARGETS}
${std_srvs_TARGETS}
${industrial_robot_motion_interfaces_TARGETS}
)

target_compile_definitions(motion_primitives_forward_controller PRIVATE "MOTION_PRIMITIVES_FORWARD_CONTROLLER_BUILDING_DLL")

pluginlib_export_plugin_description_file(
controller_interface motion_primitives_forward_controller.xml)

install(
TARGETS
motion_primitives_forward_controller
RUNTIME DESTINATION bin
ARCHIVE DESTINATION lib
LIBRARY DESTINATION lib
)

install(
DIRECTORY include/
DESTINATION include/${PROJECT_NAME}
)

if(BUILD_TESTING)
find_package(ament_cmake_gmock REQUIRED)
find_package(controller_manager REQUIRED)
find_package(hardware_interface REQUIRED)
find_package(ros2_control_test_assets REQUIRED)

ament_add_gmock(
test_load_motion_primitives_forward_controller
test/test_load_motion_primitives_forward_controller.cpp
)
target_link_libraries(test_load_motion_primitives_forward_controller
motion_primitives_forward_controller
controller_manager::controller_manager
ros2_control_test_assets::ros2_control_test_assets
)

add_rostest_with_parameters_gmock(
test_motion_primitives_forward_controller
test/test_motion_primitives_forward_controller.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test/motion_primitives_forward_controller_params.yaml
)
target_link_libraries(test_motion_primitives_forward_controller
motion_primitives_forward_controller
controller_interface::controller_interface
hardware_interface::hardware_interface
ros2_control_test_assets::ros2_control_test_assets
)

add_rostest_with_parameters_gmock(
test_motion_primitives_forward_controller_preceeding
test/test_motion_primitives_forward_controller_preceeding.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test/motion_primitives_forward_controller_preceeding_params.yaml
)
target_link_libraries(test_motion_primitives_forward_controller_preceeding
motion_primitives_forward_controller
controller_interface::controller_interface
hardware_interface::hardware_interface
ros2_control_test_assets::ros2_control_test_assets
)
endif()

ament_export_include_directories(
include
)
ament_export_dependencies(
${THIS_PACKAGE_INCLUDE_DEPENDS}
)
ament_export_libraries(
motion_primitives_forward_controller
)

ament_package()
35 changes: 35 additions & 0 deletions motion_primitives_forward_controller/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
motion_primitives_forward_controller
==========================================

Package to control robots using motion primitives like LINEAR_JOINT (PTP/ MOVEJ), LINEAR_CARTESIAN (LIN/ MOVEL) and CIRCULAR_CARTESIAN (CIRC/ MOVEC)

[![Licence](https://img.shields.io/badge/License-Apache-2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)


This project provides an interface for sending motion primitives to an industrial robot controller using the `ExecuteMotion.action` action from the [industrial_robot_motion_interfaces](https://github.com/UniversalRobots/industrial_robot_motion_interfaces) package. The controller receives the primitives via the action interface and forwards them through command interfaces to the robot-specific hardware interface. Currently, hardware interfaces for [Universal Robots](https://github.com/UniversalRobots/Universal_Robots_ROS2_Driver) and [KUKA Robots](https://github.com/b-robotized-forks/kuka_experimental/tree/motion_primitive_kuka_driver) are implemented.

- Supported motion primitives:
- `LINEAR_JOINT`
- `LINEAR_CARTESIAN`
- `CIRCULAR_CARTESIAN`

If multiple motion primitives are passed to the controller via the action, the controller forwards them to the hardware interface as a sequence. To do this, it first sends `MOTION_SEQUENCE_START`, followed by each individual primitive, and finally `MOTION_SEQUENCE_END`. All primitives between these two markers will be executed as a single, continuous sequence. This allows seamless transitions (blending) between primitives.

The action interface also allows stopping the current execution of motion primitives. When a stop request is received, the controller sends `STOP_MOTION` to the hardware interface, which then halts the robot's movement. Once the controller receives confirmation that the robot has stopped, it sends `RESET_STOP` to the hardware interface. After that, new commands can be sent.

![Action Image](doc/Moprim_Controller_ExecuteMotion_Action.drawio.png)

This can be done, for example, via a Python script as demonstrated in the [`example python script`](https://github.com/UniversalRobots/Universal_Robots_ROS2_Driver/blob/main/ur_robot_driver/examples/send_dummy_motion_primitives_hka_ur10e.py) in the `Universal_Robots_ROS2_Driver` package.

## Architecture Overview
The following diagram shows the architecture for a UR robot.
For this setup, the [motion primitives mode of the `Universal_Robots_ROS2_Driver`](https://github.com/UniversalRobots/Universal_Robots_ROS2_Driver) is used.

![UR Robot Architecture](doc/ros2_control_motion_primitives_ur_integrated.drawio.png)


# TODOs/ improvements
- Use references for command and state interfaces to improve code readability and less error-prone.
- Extend the tests
- Test for a sequence of multiple primitives
- Test for canceling movement
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
// Copyright (c) 2025, b»robotized
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Authors: Mathias Fuhrer

#ifndef MOTION_PRIMITIVES_FORWARD_CONTROLLER__MOTION_PRIMITIVES_FORWARD_CONTROLLER_HPP_
#define MOTION_PRIMITIVES_FORWARD_CONTROLLER__MOTION_PRIMITIVES_FORWARD_CONTROLLER_HPP_

#include <memory>
#include <queue>
#include <string>
#include <vector>

#include <motion_primitives_forward_controller/motion_primitives_forward_controller_parameters.hpp>
#include "controller_interface/controller_interface.hpp"
#include "rclcpp_lifecycle/node_interfaces/lifecycle_node_interface.hpp"
#include "rclcpp_lifecycle/state.hpp"
#include "realtime_tools/realtime_buffer.hpp"
#include "realtime_tools/realtime_publisher.hpp"

#include "industrial_robot_motion_interfaces/action/execute_motion.hpp"
#include "industrial_robot_motion_interfaces/msg/motion_primitive.hpp"
#include "rclcpp_action/rclcpp_action.hpp"

namespace motion_primitives_forward_controller
{
enum class ExecutionState : uint8_t
{
IDLE = 0,
EXECUTING = 1,
SUCCESS = 2,
ERROR = 3,
STOPPED = 4
};

enum class MotionType : uint8_t
{
LINEAR_JOINT = 10,
LINEAR_CARTESIAN = 50,
CIRCULAR_CARTESIAN = 51,

STOP_MOTION = 66,
RESET_STOP = 67,

MOTION_SEQUENCE_START = 100,
MOTION_SEQUENCE_END = 101
};

enum class ReadyForNewPrimitive : uint8_t
{
NOT_READY = 0,
READY = 1
};

class MotionPrimitivesForwardController : public controller_interface::ControllerInterface
{
public:
MotionPrimitivesForwardController();

controller_interface::CallbackReturn on_init() override;

controller_interface::InterfaceConfiguration command_interface_configuration() const override;

controller_interface::InterfaceConfiguration state_interface_configuration() const override;

controller_interface::CallbackReturn on_configure(
const rclcpp_lifecycle::State & previous_state) override;

controller_interface::CallbackReturn on_activate(
const rclcpp_lifecycle::State & previous_state) override;

controller_interface::CallbackReturn on_deactivate(
const rclcpp_lifecycle::State & previous_state) override;

controller_interface::return_type update(
const rclcpp::Time & time, const rclcpp::Duration & period) override;

protected:
std::shared_ptr<motion_primitives_forward_controller::ParamListener> param_listener_;
motion_primitives_forward_controller::Params params_;

using MotionPrimitive = industrial_robot_motion_interfaces::msg::MotionPrimitive;
std::queue<std::shared_ptr<MotionPrimitive>> moprim_queue_;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With that queue you are creating a couple of potential issues:

  • pushing and popping from this in the real-time thread will create new (shared pointer) objects and therefore allocate memory in the real-time thread. One way to circumvent this would be to use an std::vector<MotionPrimitive> container and instead of popping items increase an index variable to get references to the respective data stored at the index.
  • You'll need to do thread synchronization as you did with the moprim_queue_write_enabled_ member. That definitively does work that way, but seems a bit fragile because it doesn't really protect the queue as a mutex would.

You could potentially use the realtime_tools::LockFreeQueue to avoid both problems.


using ExecuteMotion = industrial_robot_motion_interfaces::action::ExecuteMotion;
rclcpp_action::Server<ExecuteMotion>::SharedPtr action_server_;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

May I suggest using the realtime_tools::RealtimeServerGoalHandle for this? This way you will gain

  • Proper thread synchronization between the accepted callback and the update method
  • non-blocking calls to set the result in the update method.

I am not sure how severe things are with the latter point, but it seems like the right choice nevertheless. The JTC and also our passthrough trajectory controller use that.

rclcpp_action::GoalResponse goal_received_callback(
const rclcpp_action::GoalUUID & uuid, std::shared_ptr<const ExecuteMotion::Goal> goal);
rclcpp_action::CancelResponse goal_cancelled_callback(
const std::shared_ptr<rclcpp_action::ServerGoalHandle<ExecuteMotion>> goal_handle);
void goal_accepted_callback(
const std::shared_ptr<rclcpp_action::ServerGoalHandle<ExecuteMotion>> goal_handle);
std::shared_ptr<rclcpp_action::ServerGoalHandle<ExecuteMotion>> pending_action_goal_;

void reset_command_interfaces();
bool set_command_interfaces();

bool print_error_once_ = true;
// cancel requested by the action server
std::atomic<bool> cancel_requested_ = false;
// robot stop command sent to the hardware interface
std::atomic<bool> robot_stop_requested_ = false;
bool was_executing_ = false;
ExecutionState execution_status_;
ReadyForNewPrimitive ready_for_new_primitive_;

std::atomic<bool> moprim_queue_write_enabled_ = false;
};

} // namespace motion_primitives_forward_controller

#endif // MOTION_PRIMITIVES_FORWARD_CONTROLLER__MOTION_PRIMITIVES_FORWARD_CONTROLLER_HPP_
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<library path="motion_primitives_forward_controller">
<class name="motion_primitives_forward_controller/MotionPrimitivesForwardController"
type="motion_primitives_forward_controller::MotionPrimitivesForwardController" base_class_type="controller_interface::ControllerInterface">
<description>
MotionPrimitivesForwardController ros2_control controller.
</description>
</class>
</library>
43 changes: 43 additions & 0 deletions motion_primitives_forward_controller/package.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?xml version="1.0"?>
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="3">
<name>motion_primitives_forward_controller</name>
<version>5.2.0</version>
<description>Package to control robots using motion primitives like PTP, LIN and CIRC</description>

<maintainer email="[email protected]">Bence Magyar</maintainer>
<maintainer email="[email protected]">Denis Štogl</maintainer>
<maintainer email="[email protected]">Christoph Froehlich</maintainer>
<maintainer email="[email protected]">Sai Kishor Kothakota</maintainer>

<license>Apache License 2.0</license>

<url type="website">https://control.ros.org</url>
<url type="bugtracker">https://github.com/ros-controls/ros2_controllers/issues</url>
<url type="repository">https://github.com/ros-controls/ros2_controllers/</url>

<author email="[email protected]">Mathias Fuhrer</author>

<buildtool_depend>ament_cmake</buildtool_depend>

<build_depend>generate_parameter_library</build_depend>

<depend>control_msgs</depend>
<depend>controller_interface</depend>
<depend>hardware_interface</depend>
<depend>pluginlib</depend>
<depend>industrial_robot_motion_interfaces</depend>
<depend>rclcpp</depend>
<depend>rclcpp_lifecycle</depend>
<depend>realtime_tools</depend>
<depend>std_srvs</depend>

<test_depend>ament_cmake_gmock</test_depend>
<test_depend>controller_manager</test_depend>
<test_depend>hardware_interface</test_depend>
<test_depend>ros2_control_test_assets</test_depend>

<export>
<build_type>ament_cmake</build_type>
</export>
</package>
Loading