Skip to content

Commit ca5fb3e

Browse files
URJalaurfeex
andauthored
Add trajectory passthrough controller (#944)
This adds a controller that allows sending a complete trajectory to the robot for execution. --------- Co-authored-by: Felix Exner <[email protected]>
1 parent 660e363 commit ca5fb3e

26 files changed

+1758
-45
lines changed

ur_controllers/CMakeLists.txt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ find_package(std_srvs REQUIRED)
1919
find_package(ur_dashboard_msgs REQUIRED)
2020
find_package(ur_msgs REQUIRED)
2121
find_package(generate_parameter_library REQUIRED)
22+
find_package(trajectory_msgs REQUIRED)
23+
find_package(control_msgs REQUIRED)
24+
find_package(action_msgs REQUIRED)
25+
2226

2327
set(THIS_PACKAGE_INCLUDE_DEPENDS
2428
angles
@@ -34,6 +38,9 @@ set(THIS_PACKAGE_INCLUDE_DEPENDS
3438
ur_dashboard_msgs
3539
ur_msgs
3640
generate_parameter_library
41+
control_msgs
42+
trajectory_msgs
43+
action_msgs
3744
)
3845

3946
include_directories(include)
@@ -54,6 +61,11 @@ generate_parameter_library(
5461
src/scaled_joint_trajectory_controller_parameters.yaml
5562
)
5663

64+
generate_parameter_library(
65+
passthrough_trajectory_controller_parameters
66+
src/passthrough_trajectory_controller_parameters.yaml
67+
)
68+
5769
generate_parameter_library(
5870
ur_configuration_controller_parameters
5971
src/ur_configuration_controller_parameters.yaml
@@ -63,6 +75,7 @@ add_library(${PROJECT_NAME} SHARED
6375
src/scaled_joint_trajectory_controller.cpp
6476
src/speed_scaling_state_broadcaster.cpp
6577
src/gpio_controller.cpp
78+
src/passthrough_trajectory_controller.cpp
6679
src/ur_configuration_controller.cpp)
6780

6881
target_include_directories(${PROJECT_NAME} PRIVATE
@@ -72,6 +85,7 @@ target_link_libraries(${PROJECT_NAME}
7285
gpio_controller_parameters
7386
speed_scaling_state_broadcaster_parameters
7487
scaled_joint_trajectory_controller_parameters
88+
passthrough_trajectory_controller_parameters
7589
ur_configuration_controller_parameters
7690
)
7791
ament_target_dependencies(${PROJECT_NAME}

ur_controllers/controller_plugins.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@
1414
This controller publishes the Tool IO.
1515
</description>
1616
</class>
17+
<class name="ur_controllers/PassthroughTrajectoryController" type="ur_controllers::PassthroughTrajectoryController" base_class_type="controller_interface::ControllerInterface">
18+
<description>
19+
This controller forwards a joint-based trajectory to the robot controller for interpolation.
20+
</description>
21+
</class>
1722
<class name="ur_controllers/URConfigurationController" type="ur_controllers::URConfigurationController" base_class_type="controller_interface::ControllerInterface">
1823
<description>
1924
Controller used to get and change the configuration of the robot

ur_controllers/doc/index.rst

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,3 +135,132 @@ Advertised services
135135
* ``~/set_speed_slider [ur_msgs/srv/SetSpeedSliderFraction]``: Set the value of the speed slider.
136136
* ``~/zero_ftsensor [std_srvs/srv/Trigger]``: Zeroes the reported wrench of the force torque
137137
sensor.
138+
139+
.. _passthrough_trajectory_controller:
140+
141+
ur_controllers/PassthroughTrajectoryController
142+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
143+
144+
This controller uses a ``control_msgs/FollowJointTrajectory`` action but instead of interpolating
145+
the trajectory on the ROS pc it forwards the complete trajectory to the robot controller for
146+
interpolation and execution. This way, the realtime requirements for the control PC can be
147+
massively decreased, since the robot always "knows" what to do next. That means that you should be
148+
able to run a stable driver connection also without a real-time patched kernel.
149+
150+
Interpolation depends on the robot controller's implementation, but in conjunction with the
151+
ur_robot_driver it defaults to mimicking ros2_control's spline interpolation. So, any trajectory
152+
planned e.g. with MoveIt! will be executed following the trajectory exactly.
153+
154+
A trajectory sent to the controller's action server will be forwarded to the robot controller and
155+
executed there. Once all setpoints are transferred to the robot, the controller goes into a waiting
156+
state where it waits for the trajectory to be finished. While waiting, the controller tracks the
157+
time spent on the trajectory to ensure the robot isn't stuck during execution.
158+
159+
This controller also supports **speed scaling** such that and scaling down of the trajectory done
160+
by the robot, for example due to safety settings on the robot or simply because a slower execution
161+
is configured on the teach pendant. This will be considered, during execution monitoring, so the
162+
controller basically tracks the scaled time instead of the real time.
163+
164+
.. note::
165+
166+
When using this controller with the URSim simulator execution times can be slightly larger than
167+
the expected time depending on the simulation host's resources. This effect will not be present
168+
when using a real UR arm.
169+
170+
.. note::
171+
172+
This controller can currently only be used with URSim or a real UR robot. Neither mock hardware
173+
nor gazebo support this type of trajectory interfaces at the time being.
174+
175+
Tolerances
176+
""""""""""
177+
178+
Currently, the trajectory passthrough controller only supports goal tolerances and goal time
179+
tolerances passed in the action directly. Please make sure that the tolerances are completely
180+
filled with all joint names.
181+
182+
A **goal time tolerance** of ``0.0`` means that no goal time tolerance is set and the action will
183+
not fail when execution takes too long.
184+
185+
Action interface / usage
186+
""""""""""""""""""""""""
187+
188+
To use this controller, publish a goal to the ``~/follow_joint_trajectory`` action interface
189+
similar to the `joint_trajectory_controller <https://control.ros.org/master/doc/ros2_controllers/joint_trajectory_controller/doc/userdoc.html>`_.
190+
191+
Currently, the controller doesn't support replacing a running trajectory action. While a trajectory
192+
is being executed, goals will be rejected until the action has finished. If you want to replace it,
193+
first cancel the running action and then send a new one.
194+
195+
Parameters
196+
""""""""""
197+
198+
The trajectory passthrough controller uses the following parameters:
199+
200+
+----------------------------------+--------------+----------------------------------------+------------------------------------------------------------------------------------------------------------------+
201+
| Parameter name | Type | Default value | Description |
202+
| | | | |
203+
+----------------------------------+--------------+----------------------------------------+------------------------------------------------------------------------------------------------------------------+
204+
| ``joints`` (required) | string_array | <empty> | Joint names to listen to |
205+
+----------------------------------+--------------+----------------------------------------+------------------------------------------------------------------------------------------------------------------+
206+
| ``state_interfaces`` (required) | string_array | <empty> | State interfaces provided by the hardware for all joints. Subset of ``["position", "velocity", "acceleration"]`` |
207+
+----------------------------------+--------------+----------------------------------------+------------------------------------------------------------------------------------------------------------------+
208+
| ``speed_scaling_interface_name`` | string | ``speed_scaling/speed_scaling_factor`` | Fully qualified name of the speed scaling interface name. |
209+
+----------------------------------+--------------+----------------------------------------+------------------------------------------------------------------------------------------------------------------+
210+
| ``tf_prefix`` | string | <empty> | Urdf prefix of the corresponding arm |
211+
+----------------------------------+--------------+----------------------------------------+------------------------------------------------------------------------------------------------------------------+
212+
213+
Interfaces
214+
""""""""""
215+
216+
In order to use this, the hardware has to export a command interface for passthrough operations for each joint. It always has
217+
to export position, velocity and acceleration interfaces in order to be able to project the full
218+
JointTrajectory definition. This is why there are separate fields used, as for passthrough mode
219+
accelerations might be relevant also for robots that don't support commanding accelerations
220+
directly to their joints.
221+
222+
.. code:: xml
223+
224+
<gpio name="${tf_prefix}trajectory_passthrough">
225+
<command_interface name="setpoint_positions_0"/>
226+
<command_interface name="setpoint_positions_1"/>
227+
<command_interface name="setpoint_positions_2"/>
228+
<command_interface name="setpoint_positions_3"/>
229+
<command_interface name="setpoint_positions_4"/>
230+
<command_interface name="setpoint_positions_5"/>
231+
<command_interface name="setpoint_velocities_0"/>
232+
<command_interface name="setpoint_velocities_1"/>
233+
<command_interface name="setpoint_velocities_2"/>
234+
<command_interface name="setpoint_velocities_3"/>
235+
<command_interface name="setpoint_velocities_4"/>
236+
<command_interface name="setpoint_velocities_5"/>
237+
<command_interface name="setpoint_accelerations_0"/>
238+
<command_interface name="setpoint_accelerations_1"/>
239+
<command_interface name="setpoint_accelerations_2"/>
240+
<command_interface name="setpoint_accelerations_3"/>
241+
<command_interface name="setpoint_accelerations_4"/>
242+
<command_interface name="setpoint_accelerations_5"/>
243+
<command_interface name="transfer_state"/>
244+
<command_interface name="time_from_start"/>
245+
<command_interface name="abort"/>
246+
</gpio>
247+
248+
.. note::
249+
250+
The hardware component has to take care that the passthrough command interfaces cannot be
251+
activated in parallel to the streaming command interfaces.
252+
253+
Implementation details / dataflow
254+
"""""""""""""""""""""""""""""""""
255+
256+
* A trajectory passed to the controller will be sent to the hardware component one by one.
257+
* The controller will send one setpoint and then wait for the hardware to acknowledge that it can
258+
take a new setpoint.
259+
* This happens until all setpoints have been transferred to the hardware. Then, the controller goes
260+
into a waiting state where it monitors execution time and waits for the hardware to finish
261+
execution.
262+
* If execution takes longer than anticipated, a warning will be printed.
263+
* If execution finished taking longer than expected (plus the goal time tolerance), the action will fail.
264+
* When the hardware reports that execution has been aborted (The ``passthrough_trajectory_abort``
265+
command interface), the action will be aborted.
266+
* When the action is preempted, execution on the hardware is preempted.
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
// Copyright 2024, Universal Robots A/S
2+
//
3+
// Redistribution and use in source and binary forms, with or without
4+
// modification, are permitted provided that the following conditions are met:
5+
//
6+
// * Redistributions of source code must retain the above copyright
7+
// notice, this list of conditions and the following disclaimer.
8+
//
9+
// * Redistributions in binary form must reproduce the above copyright
10+
// notice, this list of conditions and the following disclaimer in the
11+
// documentation and/or other materials provided with the distribution.
12+
//
13+
// * Neither the name of the {copyright_holder} nor the names of its
14+
// contributors may be used to endorse or promote products derived from
15+
// this software without specific prior written permission.
16+
//
17+
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18+
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19+
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20+
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
21+
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22+
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23+
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24+
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25+
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26+
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27+
// POSSIBILITY OF SUCH DAMAGE.
28+
29+
//----------------------------------------------------------------------
30+
/*!\file
31+
*
32+
* \author Jacob Larsen [email protected]
33+
* \date 2024-03-11
34+
*
35+
*
36+
*
37+
*
38+
*/
39+
//----------------------------------------------------------------------
40+
41+
#ifndef UR_CONTROLLERS__PASSTHROUGH_TRAJECTORY_CONTROLLER_HPP_
42+
#define UR_CONTROLLERS__PASSTHROUGH_TRAJECTORY_CONTROLLER_HPP_
43+
44+
#include <stdint.h>
45+
46+
#include <realtime_tools/realtime_buffer.h>
47+
#include <realtime_tools/realtime_server_goal_handle.h>
48+
49+
#include <functional>
50+
#include <limits>
51+
#include <memory>
52+
#include <string>
53+
#include <unordered_map>
54+
#include <vector>
55+
56+
#include <controller_interface/controller_interface.hpp>
57+
#include <rclcpp_action/server.hpp>
58+
#include <rclcpp_action/create_server.hpp>
59+
#include <rclcpp/rclcpp.hpp>
60+
#include <rclcpp_action/server_goal_handle.hpp>
61+
#include <rclcpp/time.hpp>
62+
#include <rclcpp/duration.hpp>
63+
#include <rclcpp/clock.hpp>
64+
65+
#include <trajectory_msgs/msg/joint_trajectory.hpp>
66+
#include <control_msgs/action/follow_joint_trajectory.hpp>
67+
68+
#include "passthrough_trajectory_controller_parameters.hpp"
69+
70+
namespace ur_controllers
71+
{
72+
73+
/*
74+
* 0.0: No trajectory to forward, the controller is idling and ready to receive a new trajectory.
75+
* 1.0: The controller has received and accepted a new trajectory. When the state is 1.0, the controller will write a
76+
* point to the hardware interface.
77+
* 2.0: The hardware interface will read the point written from the controller. The state will switch between 1.0
78+
* and 2.0 until all points have been read by the hardware interface.
79+
* 3.0: The hardware interface has read all the points, and will now write all the points to the physical robot
80+
* controller.
81+
* 4.0: The robot is moving through the trajectory.
82+
* 5.0: The robot finished executing the trajectory.
83+
*/
84+
const double TRANSFER_STATE_IDLE = 0.0;
85+
const double TRANSFER_STATE_WAITING_FOR_POINT = 1.0;
86+
const double TRANSFER_STATE_TRANSFERRING = 2.0;
87+
const double TRANSFER_STATE_TRANSFER_DONE = 3.0;
88+
const double TRANSFER_STATE_IN_MOTION = 4.0;
89+
const double TRANSFER_STATE_DONE = 5.0;
90+
91+
using namespace std::chrono_literals; // NOLINT
92+
93+
class PassthroughTrajectoryController : public controller_interface::ControllerInterface
94+
{
95+
public:
96+
PassthroughTrajectoryController() = default;
97+
~PassthroughTrajectoryController() override = default;
98+
99+
controller_interface::InterfaceConfiguration state_interface_configuration() const override;
100+
101+
controller_interface::InterfaceConfiguration command_interface_configuration() const override;
102+
103+
controller_interface::CallbackReturn on_init() override;
104+
105+
controller_interface::CallbackReturn on_configure(const rclcpp_lifecycle::State& previous_state) override;
106+
107+
controller_interface::CallbackReturn on_activate(const rclcpp_lifecycle::State& state) override;
108+
109+
controller_interface::CallbackReturn on_deactivate(const rclcpp_lifecycle::State& state) override;
110+
111+
controller_interface::return_type update(const rclcpp::Time& time, const rclcpp::Duration& period) override;
112+
113+
private:
114+
using FollowJTrajAction = control_msgs::action::FollowJointTrajectory;
115+
using RealtimeGoalHandle = realtime_tools::RealtimeServerGoalHandle<FollowJTrajAction>;
116+
using RealtimeGoalHandlePtr = std::shared_ptr<RealtimeGoalHandle>;
117+
using RealtimeGoalHandleBuffer = realtime_tools::RealtimeBuffer<RealtimeGoalHandlePtr>;
118+
119+
RealtimeGoalHandleBuffer rt_active_goal_; ///< Currently active action goal, if any.
120+
rclcpp::TimerBase::SharedPtr goal_handle_timer_; ///< Timer to frequently check on the running goal
121+
realtime_tools::RealtimeBuffer<std::unordered_map<std::string, size_t>> joint_trajectory_mapping_;
122+
123+
rclcpp::Duration action_monitor_period_ = rclcpp::Duration(50ms);
124+
125+
/* Start an action server with an action called: /passthrough_trajectory_controller/forward_joint_trajectory. */
126+
void start_action_server(void);
127+
128+
void end_goal();
129+
130+
bool check_goal_tolerance();
131+
132+
// Get a mapping between the trajectory's joint order and the internal one
133+
std::unordered_map<std::string, size_t> create_joint_mapping(const std::vector<std::string>& joint_names) const;
134+
135+
std::shared_ptr<passthrough_trajectory_controller::ParamListener> passthrough_param_listener_;
136+
passthrough_trajectory_controller::Params passthrough_params_;
137+
138+
rclcpp_action::Server<control_msgs::action::FollowJointTrajectory>::SharedPtr send_trajectory_action_server_;
139+
140+
rclcpp_action::GoalResponse
141+
goal_received_callback(const rclcpp_action::GoalUUID& uuid,
142+
std::shared_ptr<const control_msgs::action::FollowJointTrajectory::Goal> goal);
143+
144+
rclcpp_action::CancelResponse goal_cancelled_callback(
145+
const std::shared_ptr<rclcpp_action::ServerGoalHandle<control_msgs::action::FollowJointTrajectory>> goal_handle);
146+
147+
void goal_accepted_callback(
148+
std::shared_ptr<rclcpp_action::ServerGoalHandle<control_msgs::action::FollowJointTrajectory>> goal_handle);
149+
150+
realtime_tools::RealtimeBuffer<std::vector<std::string>> joint_names_;
151+
std::vector<std::string> state_interface_types_;
152+
153+
std::vector<std::string> joint_state_interface_names_;
154+
std::vector<std::reference_wrapper<hardware_interface::LoanedStateInterface>> joint_position_state_interface_;
155+
std::vector<std::reference_wrapper<hardware_interface::LoanedStateInterface>> joint_velocity_state_interface_;
156+
std::vector<std::reference_wrapper<hardware_interface::LoanedStateInterface>> joint_acceleration_state_interface_;
157+
158+
bool check_goal_tolerances(std::shared_ptr<const control_msgs::action::FollowJointTrajectory::Goal> goal);
159+
bool check_goal_positions(std::shared_ptr<const control_msgs::action::FollowJointTrajectory::Goal> goal);
160+
bool check_goal_velocities(std::shared_ptr<const control_msgs::action::FollowJointTrajectory::Goal> goal);
161+
bool check_goal_accelerations(std::shared_ptr<const control_msgs::action::FollowJointTrajectory::Goal> goal);
162+
163+
trajectory_msgs::msg::JointTrajectory active_joint_traj_;
164+
// std::vector<control_msgs::msg::JointTolerance> path_tolerance_;
165+
realtime_tools::RealtimeBuffer<std::vector<control_msgs::msg::JointTolerance>> goal_tolerance_;
166+
realtime_tools::RealtimeBuffer<rclcpp::Duration> goal_time_tolerance_{ rclcpp::Duration(0, 0) };
167+
168+
std::atomic<size_t> current_index_;
169+
std::atomic<bool> trajectory_active_;
170+
rclcpp::Duration active_trajectory_elapsed_time_ = rclcpp::Duration::from_nanoseconds(0);
171+
rclcpp::Duration max_trajectory_time_ = rclcpp::Duration::from_nanoseconds(0);
172+
double scaling_factor_;
173+
std::atomic<size_t> number_of_joints_;
174+
static constexpr double NO_VAL = std::numeric_limits<double>::quiet_NaN();
175+
176+
std::optional<std::reference_wrapper<hardware_interface::LoanedStateInterface>> scaling_state_interface_;
177+
std::optional<std::reference_wrapper<hardware_interface::LoanedCommandInterface>> abort_command_interface_;
178+
std::optional<std::reference_wrapper<hardware_interface::LoanedCommandInterface>> transfer_command_interface_;
179+
std::optional<std::reference_wrapper<hardware_interface::LoanedCommandInterface>> time_from_start_command_interface_;
180+
181+
rclcpp::Clock::SharedPtr clock_;
182+
};
183+
} // namespace ur_controllers
184+
#endif // UR_CONTROLLERS__PASSTHROUGH_TRAJECTORY_CONTROLLER_HPP_

ur_controllers/package.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@
3333
<depend>std_srvs</depend>
3434
<depend>ur_dashboard_msgs</depend>
3535
<depend>ur_msgs</depend>
36+
<depend>control_msgs</depend>
37+
<depend>trajectory_msgs</depend>
38+
<depend>action_msgs</depend>
39+
3640

3741
<export>
3842
<build_type>ament_cmake</build_type>

0 commit comments

Comments
 (0)