Skip to content

Commit e9df2fd

Browse files
saikishormergify[bot]
authored andcommitted
[PID Controllers] Set first set point to current measurement (#2205)
(cherry picked from commit bb214dc) # Conflicts: # doc/release_notes.rst
1 parent 880702c commit e9df2fd

File tree

6 files changed

+140
-13
lines changed

6 files changed

+140
-13
lines changed

doc/release_notes.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,15 @@ joint_trajectory_controller
3939
up to the first waypoint will use the same interpolation as the rest of the trajectory. (`#2043
4040
<https://github.com/ros-controls/ros2_controllers/pull/2043>`_)
4141

42+
<<<<<<< HEAD
4243
omni_wheel_drive_controller
4344
*********************************
4445
* 🚀 The omni_wheel_drive_controller was added 🎉 (`#1535 <https://github.com/ros-controls/ros2_controllers/pull/1535>`_).
46+
=======
47+
pid_controller
48+
**************
49+
* Added parameter ``set_current_state_as_first_setpoint`` (default: true) to set the current state as the first setpoint when the controller is activated, helping to avoid large initial errors and sudden jumps in control output.
50+
>>>>>>> bb214dc ([PID Controllers] Set first set point to current measurement (#2205))
4551

4652
pid_controller
4753
*******************************

pid_controller/src/pid_controller.cpp

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -411,11 +411,49 @@ controller_interface::CallbackReturn PidController::on_activate(
411411
measured_state_.try_set(current_state_);
412412
}
413413

414-
reference_interfaces_.assign(
415-
reference_interfaces_.size(), std::numeric_limits<double>::quiet_NaN());
416414
measured_state_values_.assign(
417415
measured_state_values_.size(), std::numeric_limits<double>::quiet_NaN());
418416

417+
// Initialize reference interfaces from current state so initial reference equals current state
418+
reference_interfaces_.assign(
419+
reference_interfaces_.size(), std::numeric_limits<double>::quiet_NaN());
420+
421+
if (params_.set_current_state_as_first_setpoint)
422+
{
423+
if (params_.use_external_measured_states)
424+
{
425+
auto measured_state_opt = measured_state_.try_get();
426+
if (measured_state_opt.has_value())
427+
{
428+
const auto & state = measured_state_opt.value();
429+
for (size_t i = 0; i < dof_; ++i)
430+
{
431+
if (i < state.values.size() && !std::isnan(state.values[i]))
432+
{
433+
reference_interfaces_[i] = state.values[i];
434+
}
435+
if (
436+
reference_interfaces_.size() == 2 * dof_ && i < state.values_dot.size() &&
437+
!std::isnan(state.values_dot[i]))
438+
{
439+
reference_interfaces_[dof_ + i] = state.values_dot[i];
440+
}
441+
}
442+
}
443+
}
444+
else
445+
{
446+
for (size_t i = 0; i < measured_state_values_.size(); ++i)
447+
{
448+
const auto state_interface_value_op = state_interfaces_[i].get_optional();
449+
if (state_interface_value_op.has_value())
450+
{
451+
reference_interfaces_[i] = state_interface_value_op.value();
452+
}
453+
}
454+
}
455+
}
456+
419457
// prefixed save_i_term parameter is read from ROS parameters
420458
for (auto & pid : pids_)
421459
{

pid_controller/src/pid_controller.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,12 @@ pid_controller:
3838
size_lt<>: 3,
3939
}
4040
}
41+
set_current_state_as_first_setpoint: {
42+
type: bool,
43+
default_value: true,
44+
read_only: true,
45+
description: "When true, the controller will set the current state as the first setpoint when the controller is activated. This can help to avoid large initial errors and sudden jumps in the control output when the controller starts."
46+
}
4147
use_external_measured_states: {
4248
type: bool,
4349
default_value: false,

pid_controller/test/pid_controller_params.yaml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ test_pid_controller:
1414

1515
reference_and_state_interfaces: ["position"]
1616

17+
set_current_state_as_first_setpoint: true
18+
1719
gains:
1820
joint1: {p: 1.0, i: 2.0, d: 3.0, u_clamp_max: 5.0, u_clamp_min: -5.0}
1921

@@ -26,6 +28,8 @@ test_pid_controller_unlimited:
2628

2729
reference_and_state_interfaces: ["position"]
2830

31+
set_current_state_as_first_setpoint: true
32+
2933
gains:
3034
joint1: {p: 1.0, i: 2.0, d: 3.0}
3135

@@ -38,6 +42,8 @@ test_pid_controller_angle_wraparound_on:
3842

3943
reference_and_state_interfaces: ["position"]
4044

45+
set_current_state_as_first_setpoint: true
46+
4147
gains:
4248
joint1: {p: 1.0, i: 2.0, d: 3.0, angle_wraparound: true}
4349

@@ -50,6 +56,8 @@ test_pid_controller_with_feedforward_gain:
5056

5157
reference_and_state_interfaces: ["position"]
5258

59+
set_current_state_as_first_setpoint: true
60+
5361
gains:
5462
joint1: {p: 0.5, i: 0.0, d: 0.0, feedforward_gain: 1.0}
5563

@@ -63,6 +71,8 @@ test_pid_controller_with_feedforward_gain_dual_interface:
6371

6472
reference_and_state_interfaces: ["position", "velocity"]
6573

74+
set_current_state_as_first_setpoint: true
75+
6676
gains:
6777
joint1: {p: 0.5, i: 0.3, d: 0.4, feedforward_gain: 1.0}
6878
joint2: {p: 0.5, i: 0.3, d: 0.4, feedforward_gain: 1.0}
@@ -76,6 +86,8 @@ test_save_i_term_off:
7686

7787
reference_and_state_interfaces: ["position"]
7888

89+
set_current_state_as_first_setpoint: true
90+
7991
gains:
8092
joint1: {p: 1.0, i: 2.0, d: 3.0, save_i_term: false}
8193

@@ -88,5 +100,21 @@ test_save_i_term_on:
88100

89101
reference_and_state_interfaces: ["position"]
90102

103+
set_current_state_as_first_setpoint: true
104+
91105
gains:
92106
joint1: {p: 1.0, i: 2.0, d: 3.0, save_i_term: true}
107+
108+
test_pid_controller_no_first_setpoint:
109+
ros__parameters:
110+
dof_names:
111+
- joint1
112+
113+
command_interface: position
114+
115+
reference_and_state_interfaces: ["position"]
116+
117+
set_current_state_as_first_setpoint: false
118+
119+
gains:
120+
joint1: {p: 1.0, i: 2.0, d: 3.0, u_clamp_max: 5.0, u_clamp_min: -5.0}

pid_controller/test/test_pid_controller.cpp

Lines changed: 58 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -142,10 +142,12 @@ TEST_F(PidControllerTest, activate_success)
142142
EXPECT_TRUE(std::isnan(cmd));
143143
}
144144

145+
// With set_current_state_as_first_setpoint=true (default), reference interfaces are initialized
146+
// to the current state values on activation
145147
EXPECT_EQ(controller_->reference_interfaces_.size(), dof_state_values_.size());
146-
for (const auto & interface : controller_->reference_interfaces_)
148+
for (size_t i = 0; i < controller_->reference_interfaces_.size(); ++i)
147149
{
148-
EXPECT_TRUE(std::isnan(interface));
150+
EXPECT_EQ(controller_->reference_interfaces_[i], dof_state_values_[i]);
149151
}
150152
}
151153

@@ -175,16 +177,18 @@ TEST_F(PidControllerTest, reactivate_success)
175177
SetUpController();
176178

177179
ASSERT_EQ(controller_->on_configure(rclcpp_lifecycle::State()), NODE_SUCCESS);
180+
// With set_current_state_as_first_setpoint=true (default), reference interfaces are set to
181+
// current state values (dof_state_values_[0] = 1.1) on each activation
178182
ASSERT_EQ(controller_->on_activate(rclcpp_lifecycle::State()), NODE_SUCCESS);
179-
ASSERT_TRUE(std::isnan(controller_->reference_interfaces_[0]));
183+
ASSERT_EQ(controller_->reference_interfaces_[0], dof_state_values_[0]);
180184
ASSERT_TRUE(std::isnan(controller_->measured_state_values_[0]));
181185
ASSERT_EQ(controller_->command_interfaces_[0].get_optional().value(), 101.101);
182186
ASSERT_EQ(controller_->on_deactivate(rclcpp_lifecycle::State()), NODE_SUCCESS);
183-
ASSERT_TRUE(std::isnan(controller_->reference_interfaces_[0]));
187+
ASSERT_EQ(controller_->reference_interfaces_[0], dof_state_values_[0]);
184188
ASSERT_TRUE(std::isnan(controller_->measured_state_values_[0]));
185189
ASSERT_EQ(controller_->command_interfaces_[0].get_optional().value(), 101.101);
186190
ASSERT_EQ(controller_->on_activate(rclcpp_lifecycle::State()), NODE_SUCCESS);
187-
ASSERT_TRUE(std::isnan(controller_->reference_interfaces_[0]));
191+
ASSERT_EQ(controller_->reference_interfaces_[0], dof_state_values_[0]);
188192
ASSERT_TRUE(std::isnan(controller_->measured_state_values_[0]));
189193
ASSERT_EQ(controller_->command_interfaces_[0].get_optional().value(), 101.101);
190194

@@ -211,7 +215,7 @@ TEST_F(PidControllerTest, test_update_logic_zero_feedforward_gain)
211215
EXPECT_TRUE(std::isnan(controller_->input_ref_.get().values[0]));
212216
for (const auto & interface : controller_->reference_interfaces_)
213217
{
214-
EXPECT_TRUE(std::isnan(interface));
218+
EXPECT_TRUE(std::isfinite(interface));
215219
}
216220

217221
controller_->set_reference(dof_command_values_);
@@ -220,7 +224,7 @@ TEST_F(PidControllerTest, test_update_logic_zero_feedforward_gain)
220224
{
221225
EXPECT_FALSE(std::isnan(controller_->input_ref_.get().values[i]));
222226
EXPECT_EQ(controller_->input_ref_.get().values[i], dof_command_values_[i]);
223-
EXPECT_TRUE(std::isnan(controller_->reference_interfaces_[i]));
227+
EXPECT_TRUE(std::isfinite(controller_->reference_interfaces_[i]));
224228
}
225229

226230
ASSERT_EQ(
@@ -389,7 +393,7 @@ TEST_F(PidControllerTest, subscribe_and_get_messages_success)
389393
for (size_t i = 0; i < dof_names_.size(); ++i)
390394
{
391395
ASSERT_EQ(msg.dof_states[i].name, dof_names_[i]);
392-
EXPECT_TRUE(std::isnan(msg.dof_states[i].reference));
396+
EXPECT_TRUE(std::isfinite(msg.dof_states[i].reference));
393397
ASSERT_EQ(msg.dof_states[i].output, dof_command_values_[i]);
394398
}
395399
}
@@ -414,21 +418,21 @@ TEST_F(PidControllerTest, receive_message_and_publish_updated_status)
414418
for (size_t i = 0; i < dof_names_.size(); ++i)
415419
{
416420
ASSERT_EQ(msg.dof_states[i].name, dof_names_[i]);
417-
EXPECT_TRUE(std::isnan(msg.dof_states[i].reference));
421+
EXPECT_TRUE(std::isfinite(msg.dof_states[i].reference));
418422
ASSERT_EQ(msg.dof_states[i].output, dof_command_values_[i]);
419423
}
420424

421425
for (size_t i = 0; i < controller_->reference_interfaces_.size(); ++i)
422426
{
423-
EXPECT_TRUE(std::isnan(controller_->reference_interfaces_[i]));
427+
EXPECT_TRUE(std::isfinite(controller_->reference_interfaces_[i]));
424428
}
425429

426430
publish_commands();
427431
controller_->wait_for_commands(executor);
428432

429433
for (size_t i = 0; i < controller_->reference_interfaces_.size(); ++i)
430434
{
431-
EXPECT_TRUE(std::isnan(controller_->reference_interfaces_[i]));
435+
EXPECT_TRUE(std::isfinite(controller_->reference_interfaces_[i]));
432436
}
433437

434438
ASSERT_EQ(
@@ -690,6 +694,49 @@ TEST_F(PidControllerTest, test_save_i_term_on)
690694
EXPECT_NEAR(actual_value, 2.00002, 1e-5); // i_term from above
691695
}
692696

697+
/**
698+
* @brief Test that reference interfaces are initialized to current state on activation when
699+
* set_current_state_as_first_setpoint is true (default).
700+
*/
701+
TEST_F(PidControllerTest, test_activate_set_current_state_as_first_setpoint_true)
702+
{
703+
SetUpController(); // uses test_pid_controller: set_current_state_as_first_setpoint defaults to
704+
// true
705+
706+
ASSERT_EQ(controller_->on_configure(rclcpp_lifecycle::State()), NODE_SUCCESS);
707+
ASSERT_TRUE(controller_->params_.set_current_state_as_first_setpoint);
708+
709+
ASSERT_EQ(controller_->on_activate(rclcpp_lifecycle::State()), NODE_SUCCESS);
710+
711+
// reference interfaces must be initialized to the current state values (dof_state_values_)
712+
ASSERT_EQ(controller_->reference_interfaces_.size(), dof_state_values_.size());
713+
for (size_t i = 0; i < controller_->reference_interfaces_.size(); ++i)
714+
{
715+
EXPECT_EQ(controller_->reference_interfaces_[i], dof_state_values_[i]);
716+
}
717+
}
718+
719+
/**
720+
* @brief Test that reference interfaces remain NaN on activation when
721+
* set_current_state_as_first_setpoint is false.
722+
*/
723+
TEST_F(PidControllerTest, test_activate_set_current_state_as_first_setpoint_false)
724+
{
725+
SetUpController("test_pid_controller_no_first_setpoint");
726+
727+
ASSERT_EQ(controller_->on_configure(rclcpp_lifecycle::State()), NODE_SUCCESS);
728+
ASSERT_FALSE(controller_->params_.set_current_state_as_first_setpoint);
729+
730+
ASSERT_EQ(controller_->on_activate(rclcpp_lifecycle::State()), NODE_SUCCESS);
731+
732+
// reference interfaces must remain NaN since set_current_state_as_first_setpoint is false
733+
ASSERT_EQ(controller_->reference_interfaces_.size(), dof_state_values_.size());
734+
for (const auto & interface : controller_->reference_interfaces_)
735+
{
736+
EXPECT_TRUE(std::isnan(interface));
737+
}
738+
}
739+
693740
int main(int argc, char ** argv)
694741
{
695742
::testing::InitGoogleTest(&argc, argv);

pid_controller/test/test_pid_controller.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ class TestablePidController : public pid_controller::PidController
6464
FRIEND_TEST(PidControllerDualInterfaceTest, test_chained_feedforward_with_gain_dual_interface);
6565
FRIEND_TEST(PidControllerTest, test_save_i_term_on);
6666
FRIEND_TEST(PidControllerTest, test_save_i_term_off);
67+
FRIEND_TEST(PidControllerTest, test_activate_set_current_state_as_first_setpoint_true);
68+
FRIEND_TEST(PidControllerTest, test_activate_set_current_state_as_first_setpoint_false);
6769

6870
public:
6971
controller_interface::CallbackReturn on_configure(

0 commit comments

Comments
 (0)