Skip to content

Commit 4683ee6

Browse files
authored
Update scaled JTC (#1753)
This commits updates the scaled JTC to come closer to what we have available from Jazzy on. Added tests for ScaledJointTrajectoryController
1 parent 1e10cb6 commit 4683ee6

6 files changed

Lines changed: 433 additions & 44 deletions

ur_controllers/CMakeLists.txt

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,31 @@ if(BUILD_TESTING)
184184
controller_manager
185185
ros2_control_test_assets
186186
)
187+
ament_add_gmock(test_load_scaled_joint_trajectory_controller
188+
test/test_load_scaled_joint_trajectory_controller.cpp
189+
)
190+
target_link_libraries(test_load_scaled_joint_trajectory_controller
191+
${PROJECT_NAME}
192+
)
193+
ament_target_dependencies(test_load_scaled_joint_trajectory_controller
194+
controller_manager
195+
ros2_control_test_assets
196+
)
197+
ament_add_gmock(test_scaled_joint_trajectory_controller_sampling
198+
test/test_scaled_joint_trajectory_controller_sampling.cpp
199+
)
200+
target_link_libraries(test_scaled_joint_trajectory_controller_sampling
201+
${PROJECT_NAME}
202+
)
203+
ament_target_dependencies(test_scaled_joint_trajectory_controller_sampling
204+
controller_interface
205+
hardware_interface
206+
joint_trajectory_controller
207+
lifecycle_msgs
208+
rclcpp
209+
rclcpp_lifecycle
210+
trajectory_msgs
211+
)
187212
ament_add_gmock(test_load_friction_model_controller
188213
test/test_load_friction_model_controller.cpp
189214
)

ur_controllers/include/ur_controllers/scaled_joint_trajectory_controller.hpp

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -57,26 +57,23 @@ class ScaledJointTrajectoryController : public joint_trajectory_controller::Join
5757

5858
controller_interface::InterfaceConfiguration state_interface_configuration() const override;
5959

60+
controller_interface::CallbackReturn on_configure(const rclcpp_lifecycle::State& state) override;
61+
6062
controller_interface::CallbackReturn on_activate(const rclcpp_lifecycle::State& state) override;
6163

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

6466
CallbackReturn on_init() override;
6567

66-
protected:
67-
struct TimeData
68-
{
69-
TimeData() : time(0.0), period(rclcpp::Duration::from_nanoseconds(0.0)), uptime(0.0)
70-
{
71-
}
72-
rclcpp::Time time;
73-
rclcpp::Duration period;
74-
rclcpp::Time uptime;
75-
};
76-
7768
private:
7869
double scaling_factor_{ 1.0 };
79-
realtime_tools::RealtimeBuffer<TimeData> time_data_;
70+
void update_pids();
71+
rclcpp::Duration update_period_{ 0, 0 };
72+
73+
trajectory_msgs::msg::JointTrajectoryPoint command_next_;
74+
75+
rclcpp::Time last_commanded_time_;
76+
rclcpp::Time traj_time_;
8077

8178
std::optional<std::reference_wrapper<hardware_interface::LoanedStateInterface>> scaling_state_interface_ =
8279
std::nullopt;

ur_controllers/src/scaled_joint_trajectory_controller.cpp

Lines changed: 61 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -72,14 +72,15 @@ controller_interface::InterfaceConfiguration ScaledJointTrajectoryController::st
7272
return conf;
7373
}
7474

75-
controller_interface::CallbackReturn ScaledJointTrajectoryController::on_activate(const rclcpp_lifecycle::State& state)
75+
controller_interface::CallbackReturn ScaledJointTrajectoryController::on_configure(const rclcpp_lifecycle::State& state)
7676
{
77-
TimeData time_data;
78-
time_data.time = get_node()->now();
79-
time_data.period = rclcpp::Duration::from_nanoseconds(0);
80-
time_data.uptime = get_node()->now();
81-
time_data_.initRT(time_data);
77+
update_period_ = rclcpp::Duration(0.0, static_cast<uint32_t>(1.0e9 / static_cast<double>(get_update_rate())));
78+
79+
return JointTrajectoryController::on_configure(state);
80+
}
8281

82+
controller_interface::CallbackReturn ScaledJointTrajectoryController::on_activate(const rclcpp_lifecycle::State& state)
83+
{
8384
// Set scaling interfaces
8485
if (!scaled_params_.speed_scaling_interface_name.empty()) {
8586
auto it = std::find_if(state_interfaces_.begin(), state_interfaces_.end(), [&](auto& interface) {
@@ -91,6 +92,7 @@ controller_interface::CallbackReturn ScaledJointTrajectoryController::on_activat
9192
RCLCPP_ERROR(get_node()->get_logger(), "Did not find speed scaling interface in state interfaces.");
9293
}
9394
}
95+
last_commanded_time_ = rclcpp::Time();
9496

9597
return JointTrajectoryController::on_activate(state);
9698
}
@@ -101,7 +103,6 @@ controller_interface::return_type ScaledJointTrajectoryController::update(const
101103
if (scaling_state_interface_.has_value()) {
102104
scaling_factor_ = scaling_state_interface_->get().get_value();
103105
}
104-
105106
if (get_state().id() == lifecycle_msgs::msg::State::PRIMARY_STATE_INACTIVE) {
106107
return controller_interface::return_type::OK;
107108
}
@@ -110,9 +111,14 @@ controller_interface::return_type ScaledJointTrajectoryController::update(const
110111
if (param_listener_->is_old(params_)) {
111112
params_ = param_listener_->get_params();
112113
default_tolerances_ = get_segment_tolerances(logger, params_);
114+
// update the PID gains
115+
// variable use_closed_loop_pid_adapter_ is updated in on_configure only
116+
if (use_closed_loop_pid_adapter_) {
117+
update_pids();
118+
}
113119
}
114120

115-
auto compute_error_for_joint = [&](JointTrajectoryPoint& error, int index, const JointTrajectoryPoint& current,
121+
auto compute_error_for_joint = [&](JointTrajectoryPoint& error, size_t index, const JointTrajectoryPoint& current,
116122
const JointTrajectoryPoint& desired) {
117123
// error defined as the difference between current and desired
118124
if (joints_angle_wraparound_[index]) {
@@ -153,38 +159,36 @@ controller_interface::return_type ScaledJointTrajectoryController::update(const
153159
};
154160

155161
// current state update
156-
state_current_.time_from_start.set__sec(0);
162+
state_current_.time_from_start.sec = 0;
163+
state_current_.time_from_start.nanosec = 0;
157164
read_state_from_state_interfaces(state_current_);
158165

159166
// currently carrying out a trajectory
160167
if (has_active_trajectory()) {
161-
// Main Speed scaling difference...
162-
// Adjust time with scaling factor
163-
TimeData time_data;
164-
time_data.time = time;
165-
rcl_duration_value_t t_period = (time_data.time - time_data_.readFromRT()->time).nanoseconds();
166-
time_data.period = rclcpp::Duration::from_nanoseconds(scaling_factor_ * t_period);
167-
time_data.uptime = time_data_.readFromRT()->uptime + time_data.period;
168-
rclcpp::Time traj_time = time_data_.readFromRT()->uptime + rclcpp::Duration::from_nanoseconds(t_period);
169-
time_data_.reset();
170-
time_data_.initRT(time_data);
171-
172168
bool first_sample = false;
173169
// if sampling the first time, set the point before you sample
174170
if (!traj_external_point_ptr_->is_sampled_already()) {
175171
first_sample = true;
176172
if (params_.open_loop_control) {
177-
traj_external_point_ptr_->set_point_before_trajectory_msg(traj_time, last_commanded_state_,
173+
traj_external_point_ptr_->set_point_before_trajectory_msg(last_commanded_time_, last_commanded_state_,
178174
joints_angle_wraparound_);
179175
} else {
180-
traj_external_point_ptr_->set_point_before_trajectory_msg(traj_time, state_current_, joints_angle_wraparound_);
176+
traj_external_point_ptr_->set_point_before_trajectory_msg(time, state_current_, joints_angle_wraparound_);
181177
}
178+
traj_time_ = time;
179+
} else {
180+
traj_time_ += period * scaling_factor_;
182181
}
182+
joint_trajectory_controller::TrajectoryPointConstIter start_segment_itr, end_segment_itr;
183+
// Sample expected state from the trajectory
184+
traj_external_point_ptr_->sample(traj_time_, interpolation_method_, state_desired_, start_segment_itr,
185+
end_segment_itr);
186+
state_desired_.time_from_start = traj_time_ - traj_external_point_ptr_->time_from_start();
183187

184188
// find segment for current timestamp
185-
joint_trajectory_controller::TrajectoryPointConstIter start_segment_itr, end_segment_itr;
186-
const bool valid_point = traj_external_point_ptr_->sample(traj_time, interpolation_method_, state_desired_,
187-
start_segment_itr, end_segment_itr);
189+
const bool valid_point = traj_external_point_ptr_->sample(traj_time_ + update_period_, interpolation_method_,
190+
command_next_, start_segment_itr, end_segment_itr);
191+
state_current_.time_from_start = time - traj_external_point_ptr_->time_from_start();
188192

189193
if (valid_point) {
190194
const rclcpp::Time traj_start = traj_external_point_ptr_->time_from_start();
@@ -195,7 +199,7 @@ controller_interface::return_type ScaledJointTrajectoryController::update(const
195199
// time_difference is
196200
// - negative until first point is reached
197201
// - counting from zero to time_from_start of next point
198-
const double time_difference = time_data.uptime.seconds() - segment_time_from_start.seconds();
202+
double time_difference = traj_time_.seconds() - segment_time_from_start.seconds();
199203
bool tolerance_violated_while_moving = false;
200204
bool outside_goal_tolerance = false;
201205
bool within_goal_time = true;
@@ -246,32 +250,33 @@ controller_interface::return_type ScaledJointTrajectoryController::update(const
246250
if (use_closed_loop_pid_adapter_) {
247251
// Update PIDs
248252
for (auto i = 0ul; i < dof_; ++i) {
249-
tmp_command_[i] = (state_desired_.velocities[i] * ff_velocity_scale_[i]) +
253+
tmp_command_[i] = (command_next_.velocities[i] * ff_velocity_scale_[i]) +
250254
pids_[i]->computeCommand(state_error_.positions[i], state_error_.velocities[i],
251255
(uint64_t)period.nanoseconds());
252256
}
253257
}
254258

255259
// set values for next hardware write()
256260
if (has_position_command_interface_) {
257-
assign_interface_from_point(joint_command_interface_[0], state_desired_.positions);
261+
assign_interface_from_point(joint_command_interface_[0], command_next_.positions);
258262
}
259263
if (has_velocity_command_interface_) {
260264
if (use_closed_loop_pid_adapter_) {
261265
assign_interface_from_point(joint_command_interface_[1], tmp_command_);
262266
} else {
263-
assign_interface_from_point(joint_command_interface_[1], state_desired_.velocities);
267+
assign_interface_from_point(joint_command_interface_[1], command_next_.velocities);
264268
}
265269
}
266270
if (has_acceleration_command_interface_) {
267-
assign_interface_from_point(joint_command_interface_[2], state_desired_.accelerations);
271+
assign_interface_from_point(joint_command_interface_[2], command_next_.accelerations);
268272
}
269273
if (has_effort_command_interface_) {
270274
assign_interface_from_point(joint_command_interface_[3], tmp_command_);
271275
}
272276

273277
// store the previous command. Used in open-loop control mode
274-
last_commanded_state_ = state_desired_;
278+
last_commanded_state_ = command_next_;
279+
last_commanded_time_ = time;
275280
}
276281

277282
if (active_goal) {
@@ -329,7 +334,7 @@ controller_interface::return_type ScaledJointTrajectoryController::update(const
329334
rt_active_goal_.writeFromNonRT(RealtimeGoalHandlePtr());
330335
rt_has_pending_goal_ = false;
331336

332-
RCLCPP_WARN(logger, error_string.c_str());
337+
RCLCPP_WARN(logger, "%s", error_string.c_str());
333338

334339
traj_msg_external_point_ptr_.reset();
335340
traj_msg_external_point_ptr_.initRT(set_hold_position());
@@ -357,6 +362,30 @@ controller_interface::return_type ScaledJointTrajectoryController::update(const
357362
return controller_interface::return_type::OK;
358363
}
359364

365+
void ScaledJointTrajectoryController::update_pids()
366+
{
367+
for (size_t i = 0; i < dof_; ++i) {
368+
const auto& gains = params_.gains.joints_map.at(params_.joints[i]);
369+
if (pids_[i]) {
370+
// update PIDs with gains from ROS parameters
371+
pids_[i]->setGains(gains.p, gains.i, gains.d, gains.i_clamp, -gains.i_clamp);
372+
} else {
373+
// Init PIDs with gains from ROS parameters
374+
pids_[i] = std::make_shared<control_toolbox::Pid>(gains.p, gains.i, gains.d, gains.i_clamp, -gains.i_clamp);
375+
}
376+
// Check deprecated style for "ff_velocity_scale" parameter definition.
377+
if (gains.ff_velocity_scale == 0.0) {
378+
RCLCPP_WARN(get_node()->get_logger(), "'ff_velocity_scale' parameters is not defined under 'gains.<joint_name>.' "
379+
"structure. "
380+
"Maybe you are using deprecated format 'ff_velocity_scale/<joint_name>'!");
381+
382+
ff_velocity_scale_[i] = auto_declare<double>("ff_velocity_scale/" + params_.joints[i], 0.0);
383+
} else {
384+
ff_velocity_scale_[i] = gains.ff_velocity_scale;
385+
}
386+
}
387+
}
388+
360389
} // namespace ur_controllers
361390

362391
#include "pluginlib/class_list_macros.hpp"
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
---
2+
scaled_joint_trajectory_controller:
3+
ros__parameters:
4+
joints:
5+
- joint1
6+
command_interfaces:
7+
- position
8+
state_interfaces:
9+
- position
10+
- velocity
11+
state_publish_rate: 50.0
12+
action_monitor_rate: 20.0
13+
allow_partial_joints_goal: false
14+
speed_scaling_interface_name: ""
15+
constraints:
16+
stopped_velocity_tolerance: 0.2
17+
goal_time: 0.0
18+
joint1: { trajectory: 0.2, goal: 0.1 }
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
// Copyright 2026, 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+
#include <gmock/gmock.h>
30+
#include "controller_manager/controller_manager.hpp"
31+
#include "rclcpp/executor.hpp"
32+
#include "rclcpp/executors/single_threaded_executor.hpp"
33+
#include "rclcpp/utilities.hpp"
34+
#include "ros2_control_test_assets/descriptions.hpp"
35+
36+
TEST(TestLoadScaledJointTrajectoryController, load_controller)
37+
{
38+
std::shared_ptr<rclcpp::Executor> executor = std::make_shared<rclcpp::executors::SingleThreadedExecutor>();
39+
40+
controller_manager::ControllerManager cm(
41+
std::make_unique<hardware_interface::ResourceManager>(ros2_control_test_assets::minimal_robot_urdf), executor,
42+
"test_controller_manager");
43+
44+
const std::string test_file_path = std::string{ TEST_FILES_DIRECTORY } + "/scaled_joint_trajectory_controller_params."
45+
"yaml";
46+
cm.set_parameter({ "test_scaled_joint_trajectory_controller.params_file", test_file_path });
47+
48+
cm.set_parameter({ "test_scaled_joint_trajectory_controller.type", "ur_controllers/"
49+
"ScaledJointTrajectoryController" });
50+
51+
ASSERT_NE(cm.load_controller("test_scaled_joint_trajectory_controller"), nullptr);
52+
}
53+
54+
int main(int argc, char* argv[])
55+
{
56+
::testing::InitGoogleMock(&argc, argv);
57+
rclcpp::init(argc, argv);
58+
59+
int result = RUN_ALL_TESTS();
60+
rclcpp::shutdown();
61+
62+
return result;
63+
}

0 commit comments

Comments
 (0)