Skip to content

Commit 1b3aea6

Browse files
authored
Merge branch 'main' into pr-fix-servo-after-unpause
2 parents 069436f + dbf07b1 commit 1b3aea6

File tree

4 files changed

+282
-18
lines changed

4 files changed

+282
-18
lines changed

moveit_plugins/moveit_ros_control_interface/CMakeLists.txt

+4
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,10 @@ target_include_directories(moveit_ros_control_interface_empty_plugin
6767
ament_target_dependencies(moveit_ros_control_interface_empty_plugin
6868
${THIS_PACKAGE_INCLUDE_DEPENDS} Boost)
6969

70+
if(BUILD_TESTING)
71+
add_subdirectory(test)
72+
endif()
73+
7074
# ##############################################################################
7175
# Install ##
7276
# ##############################################################################

moveit_plugins/moveit_ros_control_interface/src/controller_manager_plugin.cpp

+91-18
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,44 @@ std::string parseJointNameFromResource(const std::string& claimed_interface)
8383
return claimed_interface.substr(0, index);
8484
}
8585

86+
/**
87+
* \brief Modifies controller activation/deactivation lists to conform to ROS 2 control expectations.
88+
* \detail Activation/deactivation is expected to be disjoint. For example, if controller B is a dependency of A (A
89+
* chains to B) but controller B is also a dependency of C (B chains to B), then the switch from A->B to C->B would
90+
* cause B to be in both the activation and deactivate list. This causes ROS 2 control to throw an error and reject the
91+
* switch. This function adds the logic needed to avoid this from happening.
92+
* @param[in] activate_controllers controllers to activate
93+
* @param[in] deactivate_controllers controllers to deactivate
94+
*/
95+
void deconflictControllerActivationLists(std::vector<std::string>& activate_controllers,
96+
std::vector<std::string>& deactivate_controllers)
97+
{
98+
// Convert vectors to sets for uniqueness
99+
std::unordered_set activate_set(activate_controllers.begin(), activate_controllers.end());
100+
std::unordered_set deactivate_set(deactivate_controllers.begin(), deactivate_controllers.end());
101+
102+
// Find common elements
103+
std::unordered_set<std::string> common_controllers;
104+
for (const auto& str : activate_set)
105+
{
106+
if (deactivate_set.count(str))
107+
{
108+
common_controllers.insert(str);
109+
}
110+
}
111+
112+
// Remove common elements from both sets
113+
for (const auto& controller_name : common_controllers)
114+
{
115+
activate_set.erase(controller_name);
116+
deactivate_set.erase(controller_name);
117+
}
118+
119+
// Convert sets back to vectors
120+
activate_controllers.assign(activate_set.begin(), activate_set.end());
121+
deactivate_controllers.assign(deactivate_set.begin(), deactivate_set.end());
122+
}
123+
86124
MOVEIT_CLASS_FORWARD(Ros2ControlManager); // Defines Ros2ControlManagerPtr, ConstPtr, WeakPtr... etc
87125

88126
/**
@@ -119,8 +157,11 @@ class Ros2ControlManager : public moveit_controller_manager::MoveItControllerMan
119157
rclcpp::Client<controller_manager_msgs::srv::ListControllers>::SharedPtr list_controllers_service_;
120158
rclcpp::Client<controller_manager_msgs::srv::SwitchController>::SharedPtr switch_controller_service_;
121159

122-
// Chained controllers have dependencies (other controllers which must be running)
160+
// Chained controllers have dependent controllers (other controllers which must be started if the chained controller is started)
123161
std::unordered_map<std::string /* controller name */, std::vector<std::string> /* dependencies */> dependency_map_;
162+
// Controllers may have preceding chained controllers (other chained controllers which must be shutdown if the controller is shutdown)
163+
std::unordered_map<std::string /* controller name */, std::vector<std::string> /* reverse dependencies */>
164+
dependency_map_reverse_;
124165

125166
/**
126167
* \brief Check if given controller is active
@@ -373,32 +414,59 @@ class Ros2ControlManager : public moveit_controller_manager::MoveItControllerMan
373414
/**
374415
* \brief Filter lists for managed controller and computes switching set.
375416
* Stopped list might be extended by unsupported controllers that claim needed resources
376-
* @param activate vector of controllers to be activated
377-
* @param deactivate vector of controllers to be deactivated
417+
* @param activate_base vector of controllers to be activated
418+
* @param deactivate_base vector of controllers to be deactivated
378419
* @return true if switching succeeded
379420
*/
380421
bool switchControllers(const std::vector<std::string>& activate_base,
381422
const std::vector<std::string>& deactivate_base) override
382423
{
383-
// add controller dependencies
384-
std::vector<std::string> activate = activate_base;
385-
std::vector<std::string> deactivate = deactivate_base;
386-
for (auto controllers : { &activate, &deactivate })
387-
{
388-
auto queue = *controllers;
424+
// add_all_dependencies traverses the provided dependency map and appends the results to the controllers vector.
425+
auto add_all_dependencies = [](const std::unordered_map<std::string, std::vector<std::string>>& dependencies,
426+
const std::function<bool(const std::string&)>& should_include,
427+
std::vector<std::string>& controllers) {
428+
auto queue = controllers;
389429
while (!queue.empty())
390430
{
391431
auto controller = queue.back();
392-
controller.erase(0, 1);
393432
queue.pop_back();
394-
for (const auto& dependency : dependency_map_[controller])
433+
if (controller.size() > 1 && controller[0] == '/')
434+
{
435+
// Remove leading / from controller name
436+
controller.erase(0, 1);
437+
}
438+
if (dependencies.find(controller) == dependencies.end())
439+
{
440+
continue;
441+
}
442+
for (const auto& dependency : dependencies.at(controller))
395443
{
396444
queue.push_back(dependency);
397-
controllers->push_back("/" + dependency);
445+
if (should_include(dependency))
446+
{
447+
controllers.push_back("/" + dependency);
448+
}
398449
}
399450
}
400-
}
401-
// activation dependencies must be started first, but they are processed last, so the order needs to be flipped
451+
};
452+
453+
std::vector<std::string> activate = activate_base;
454+
add_all_dependencies(
455+
dependency_map_,
456+
[this](const std::string& dependency) {
457+
return active_controllers_.find(dependency) == active_controllers_.end();
458+
},
459+
activate);
460+
std::vector<std::string> deactivate = deactivate_base;
461+
add_all_dependencies(
462+
dependency_map_reverse_,
463+
[this](const std::string& dependency) {
464+
return active_controllers_.find(dependency) != active_controllers_.end();
465+
},
466+
deactivate);
467+
468+
// The dependencies for chained controller activation must be started first, but they are processed last, so the
469+
// order needs to be flipped
402470
std::reverse(activate.begin(), activate.end());
403471

404472
std::scoped_lock<std::mutex> lock(controllers_mutex_);
@@ -471,6 +539,11 @@ class Ros2ControlManager : public moveit_controller_manager::MoveItControllerMan
471539
}
472540
}
473541

542+
// ROS2 Control expects simplified controller activation/deactivation.
543+
// E.g. a controller should not be stopped and started at the same time, rather it should be removed from both the
544+
// activation and deactivation request.
545+
deconflictControllerActivationLists(request->activate_controllers, request->deactivate_controllers);
546+
474547
// Setting level to STRICT means that the controller switch will only be committed if all controllers are
475548
// successfully activated or deactivated.
476549
request->strictness = controller_manager_msgs::srv::SwitchController::Request::STRICT;
@@ -502,6 +575,9 @@ class Ros2ControlManager : public moveit_controller_manager::MoveItControllerMan
502575
{
503576
controller_name_map[result->controller[i].name] = i;
504577
}
578+
579+
dependency_map_.clear();
580+
dependency_map_reverse_.clear();
505581
for (auto& controller : result->controller)
506582
{
507583
if (controller.chain_connections.size() > 1)
@@ -511,14 +587,11 @@ class Ros2ControlManager : public moveit_controller_manager::MoveItControllerMan
511587
"one controller is not supported.");
512588
return false;
513589
}
514-
dependency_map_[controller.name].clear();
515590
for (const auto& chained_controller : controller.chain_connections)
516591
{
517592
auto ind = controller_name_map[chained_controller.name];
518593
dependency_map_[controller.name].push_back(chained_controller.name);
519-
std::copy(result->controller[ind].required_command_interfaces.begin(),
520-
result->controller[ind].required_command_interfaces.end(),
521-
std::back_inserter(controller.required_command_interfaces));
594+
dependency_map_reverse_[chained_controller.name].push_back(controller.name);
522595
std::copy(result->controller[ind].reference_interfaces.begin(),
523596
result->controller[ind].reference_interfaces.end(),
524597
std::back_inserter(controller.required_command_interfaces));
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
find_package(ament_cmake_gtest REQUIRED)
2+
find_package(pluginlib REQUIRED)
3+
ament_add_gtest(test_controller_manager_plugin
4+
test_controller_manager_plugin.cpp TIMEOUT 20)
5+
target_link_libraries(test_controller_manager_plugin
6+
moveit_ros_control_interface_plugin)
7+
target_include_directories(test_controller_manager_plugin
8+
PRIVATE ${CMAKE_SOURCE_DIR}/include)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
/*********************************************************************
2+
* Software License Agreement (BSD License)
3+
*
4+
* Copyright (c) 2013, Willow Garage, Inc.
5+
* All rights reserved.
6+
*
7+
* Redistribution and use in source and binary forms, with or without
8+
* modification, are permitted provided that the following conditions
9+
* are met:
10+
*
11+
* * Redistributions of source code must retain the above copyright
12+
* notice, this list of conditions and the following disclaimer.
13+
* * Redistributions in binary form must reproduce the above
14+
* copyright notice, this list of conditions and the following
15+
* disclaimer in the documentation and/or other materials provided
16+
* with the distribution.
17+
* * Neither the name of Willow Garage nor the names of its
18+
* contributors may be used to endorse or promote products derived
19+
* from this software without specific prior written permission.
20+
*
21+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22+
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23+
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24+
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
25+
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26+
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
27+
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28+
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
29+
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30+
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
31+
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32+
* POSSIBILITY OF SUCH DAMAGE.
33+
*********************************************************************/
34+
35+
#include <vector>
36+
37+
#include <gtest/gtest.h>
38+
39+
#include <controller_manager_msgs/srv/detail/list_controllers__struct.hpp>
40+
#include <controller_manager_msgs/srv/detail/switch_controller__struct.hpp>
41+
#include <eigen3/Eigen/Eigen>
42+
#include <moveit/controller_manager/controller_manager.hpp>
43+
#include <pluginlib/class_loader.hpp>
44+
45+
class MockControllersManagerService final : public rclcpp::Node
46+
{
47+
public:
48+
MockControllersManagerService() : Node("list_controllers_service")
49+
{
50+
list_controller_service_ = create_service<controller_manager_msgs::srv::ListControllers>(
51+
"controller_manager/list_controllers",
52+
[this](const std::shared_ptr<controller_manager_msgs::srv::ListControllers::Request>& request,
53+
const std::shared_ptr<controller_manager_msgs::srv::ListControllers::Response>& response) {
54+
handleListControllersService(request, response);
55+
});
56+
switch_controller_service_ = create_service<controller_manager_msgs::srv::SwitchController>(
57+
"controller_manager/switch_controller",
58+
[this](const std::shared_ptr<controller_manager_msgs::srv::SwitchController::Request>& request,
59+
const std::shared_ptr<controller_manager_msgs::srv::SwitchController::Response>& response) {
60+
handleSwitchControllerService(request, response);
61+
});
62+
}
63+
64+
controller_manager_msgs::srv::SwitchController::Request last_request;
65+
66+
private:
67+
void handleListControllersService(
68+
const std::shared_ptr<controller_manager_msgs::srv::ListControllers::Request>& /*request*/,
69+
const std::shared_ptr<controller_manager_msgs::srv::ListControllers::Response>& response)
70+
{
71+
controller_manager_msgs::msg::ChainConnection chain_connection_a;
72+
chain_connection_a.name = "B";
73+
chain_connection_a.reference_interfaces = { "ref_1", "ref_2", "ref_3" };
74+
controller_manager_msgs::msg::ControllerState controller_a;
75+
controller_a.chain_connections.push_back(chain_connection_a);
76+
controller_a.name = "A";
77+
controller_a.is_chained = true;
78+
controller_a.required_command_interfaces = { "jnt_1", "jnt_2", "jnt_3" };
79+
controller_a.type = "joint_trajectory_controller/JointTrajectoryController";
80+
controller_a.state = activate_set_.find(controller_a.name) != activate_set_.end() ? "active" : "inactive";
81+
82+
controller_manager_msgs::msg::ControllerState controller_b;
83+
controller_b.name = "B";
84+
controller_b.required_command_interfaces = { "jnt_4", "jnt_5" };
85+
controller_b.reference_interfaces = { "ref_1", "ref_2", "ref_3" };
86+
controller_b.type = "joint_trajectory_controller/JointTrajectoryController";
87+
controller_b.state = activate_set_.find(controller_b.name) != activate_set_.end() ? "active" : "inactive";
88+
89+
response->controller = { controller_a, controller_b };
90+
}
91+
92+
void handleSwitchControllerService(
93+
const std::shared_ptr<controller_manager_msgs::srv::SwitchController::Request>& request,
94+
const std::shared_ptr<controller_manager_msgs::srv::SwitchController::Response>& response)
95+
{
96+
last_request = *request;
97+
for (const auto& deactivate : request->deactivate_controllers)
98+
{
99+
activate_set_.erase(deactivate);
100+
}
101+
for (const auto& activate : request->activate_controllers)
102+
{
103+
activate_set_.insert(activate);
104+
}
105+
response->ok = true;
106+
}
107+
108+
std::unordered_set<std::string> activate_set_;
109+
rclcpp::Service<controller_manager_msgs::srv::ListControllers>::SharedPtr list_controller_service_;
110+
rclcpp::Service<controller_manager_msgs::srv::SwitchController>::SharedPtr switch_controller_service_;
111+
};
112+
113+
TEST(ControllerManagerPlugin, SwitchControllers)
114+
{
115+
// GIVEN a ClassLoader for MoveItControllerManager
116+
pluginlib::ClassLoader<moveit_controller_manager::MoveItControllerManager> loader(
117+
"moveit_core", "moveit_controller_manager::MoveItControllerManager");
118+
119+
// WHEN we load the custom plugin defined in this package
120+
// THEN loading succeeds
121+
auto instance = loader.createUniqueInstance("moveit_ros_control_interface/Ros2ControlManager");
122+
123+
const auto mock_service = std::make_shared<MockControllersManagerService>();
124+
rclcpp::executors::SingleThreadedExecutor executor;
125+
std::thread ros_thread([&executor, &mock_service]() {
126+
executor.add_node(mock_service);
127+
executor.spin();
128+
});
129+
instance->initialize(mock_service);
130+
131+
// A and B should start
132+
instance->switchControllers({ "/A" }, {});
133+
EXPECT_EQ(mock_service->last_request.activate_controllers, std::vector<std::string>({ "A", "B" }));
134+
EXPECT_EQ(mock_service->last_request.deactivate_controllers, std::vector<std::string>({}));
135+
// A and B should stop
136+
instance->switchControllers({}, { "/B" });
137+
EXPECT_EQ(mock_service->last_request.activate_controllers, std::vector<std::string>({}));
138+
EXPECT_EQ(mock_service->last_request.deactivate_controllers, std::vector<std::string>({ "A", "B" }));
139+
// Only B should start
140+
instance->switchControllers({ "/B" }, {});
141+
EXPECT_EQ(mock_service->last_request.activate_controllers, std::vector<std::string>({ "B" }));
142+
EXPECT_EQ(mock_service->last_request.deactivate_controllers, std::vector<std::string>({}));
143+
// Only B should stop
144+
instance->switchControllers({}, { "/B" });
145+
EXPECT_EQ(mock_service->last_request.activate_controllers, std::vector<std::string>({}));
146+
EXPECT_EQ(mock_service->last_request.deactivate_controllers, std::vector<std::string>({ "B" }));
147+
// Multiple activations results in only 1
148+
instance->switchControllers({ "/B", "/B", "/B", "/B" }, {});
149+
EXPECT_EQ(mock_service->last_request.activate_controllers, std::vector<std::string>({ "B" }));
150+
EXPECT_EQ(mock_service->last_request.deactivate_controllers, std::vector<std::string>({}));
151+
instance->switchControllers({}, { "/B" });
152+
// Multiple activation and deactivate of same controller results in empty list
153+
instance->switchControllers({ "/B", "/A" }, { "/A", "/A", "/A" });
154+
EXPECT_EQ(mock_service->last_request.activate_controllers, std::vector<std::string>({ "B" }));
155+
EXPECT_EQ(mock_service->last_request.deactivate_controllers, std::vector<std::string>({}));
156+
157+
executor.cancel();
158+
ros_thread.join();
159+
}
160+
161+
TEST(ControllerManagerPlugin, LoadMoveItControllerManagerPlugin)
162+
{
163+
// GIVEN a ClassLoader for MoveItControllerManager
164+
pluginlib::ClassLoader<moveit_controller_manager::MoveItControllerManager> loader(
165+
"moveit_core", "moveit_controller_manager::MoveItControllerManager");
166+
167+
// WHEN we load the custom plugin defined in this package
168+
// THEN loading succeeds
169+
EXPECT_NO_THROW(loader.createUniqueInstance("moveit_ros_control_interface/Ros2ControlManager"));
170+
}
171+
172+
int main(int argc, char** argv)
173+
{
174+
::testing::InitGoogleTest(&argc, argv);
175+
rclcpp::init(argc, argv);
176+
const int result = RUN_ALL_TESTS();
177+
rclcpp::shutdown();
178+
return result;
179+
}

0 commit comments

Comments
 (0)