Skip to content

Commit cb4b1f6

Browse files
committed
refactored RosTopicSubNode
1 parent 674f3c3 commit cb4b1f6

File tree

4 files changed

+160
-42
lines changed

4 files changed

+160
-42
lines changed

behaviortree_ros2/include/behaviortree_ros2/bt_topic_sub_node.hpp

Lines changed: 88 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,12 @@
2222
#include "behaviortree_cpp/bt_factory.h"
2323

2424
#include "behaviortree_ros2/ros_node_params.hpp"
25+
#include <boost/signals2.hpp>
2526

2627
namespace BT
2728
{
2829

30+
2931
/**
3032
* @brief Abstract class to wrap a Topic subscriber.
3133
* Considering the example in the tutorial:
@@ -43,11 +45,44 @@ namespace BT
4345
template<class TopicT>
4446
class RosTopicSubNode : public BT::ConditionNode
4547
{
46-
47-
public:
48+
public:
4849
// Type definitions
4950
using Subscriber = typename rclcpp::Subscription<TopicT>;
5051

52+
protected:
53+
struct SubscriberInstance
54+
{
55+
std::shared_ptr<Subscriber> subscriber;
56+
rclcpp::CallbackGroup::SharedPtr callback_group;
57+
rclcpp::executors::SingleThreadedExecutor callback_group_executor;
58+
boost::signals2::signal<void (const std::shared_ptr<TopicT>&)> broadcaster;
59+
};
60+
61+
static std::mutex& registryMutex()
62+
{
63+
static std::mutex sub_mutex;
64+
return sub_mutex;
65+
}
66+
67+
// contains the fully-qualified name of the node and the name of the topic
68+
static SubscriberInstance& getRegisteredInstance(const std::string& key)
69+
{
70+
static std::unordered_map<std::string, SubscriberInstance> subscribers_registry;
71+
return subscribers_registry[key];
72+
}
73+
74+
std::shared_ptr<rclcpp::Node> node_;
75+
SubscriberInstance* sub_instance_ = nullptr;
76+
std::shared_ptr<TopicT> last_msg_;
77+
std::string topic_name_;
78+
79+
rclcpp::Logger logger()
80+
{
81+
return node_->get_logger();
82+
}
83+
84+
public:
85+
5186
/** You are not supposed to instantiate this class directly, the factory will do it.
5287
* To register this class into the factory, use:
5388
*
@@ -87,32 +122,17 @@ class RosTopicSubNode : public BT::ConditionNode
87122

88123
NodeStatus tick() override final;
89124

90-
/** topicCallback is the callback invoked when a message is received.
91-
*/
92-
virtual void topicCallback(const std::shared_ptr<TopicT> msg);
93-
94125
/** Callback invoked in the tick. You must return either SUCCESS of FAILURE
95126
*
96-
* @param last_msg the latest message received since the last tick.
97-
* it might be empty.
127+
* @param last_msg the latest message received, since the last tick.
128+
* Will be empty if no new message received.
129+
*
98130
* @return the new status of the Node, based on last_msg
99131
*/
100-
virtual BT::NodeStatus onTick(const typename TopicT::SharedPtr& last_msg) = 0;
101-
102-
protected:
103-
104-
std::shared_ptr<rclcpp::Node> node_;
105-
std::string prev_topic_name_;
106-
bool topic_name_may_change_ = false;
132+
virtual NodeStatus onTick(const std::shared_ptr<TopicT>& last_msg) = 0;
107133

108134
private:
109135

110-
std::shared_ptr<Subscriber> subscriber_;
111-
typename TopicT::SharedPtr last_msg_;
112-
bool is_message_received_;
113-
rclcpp::CallbackGroup::SharedPtr callback_group_;
114-
rclcpp::executors::SingleThreadedExecutor callback_group_executor_;
115-
116136
bool createSubscriber(const std::string& topic_name);
117137
};
118138

@@ -151,7 +171,7 @@ template<class T> inline
151171
createSubscriber(bb_topic_name);
152172
}
153173
else {
154-
topic_name_may_change_ = true;
174+
// do nothing
155175
// createSubscriber will be invoked in the first tick().
156176
}
157177
}
@@ -173,48 +193,74 @@ template<class T> inline
173193
{
174194
throw RuntimeError("topic_name is empty");
175195
}
176-
177-
callback_group_ = node_->create_callback_group(rclcpp::CallbackGroupType::MutuallyExclusive, false);
178-
callback_group_executor_.add_callback_group(callback_group_, node_->get_node_base_interface());
179-
rclcpp::SubscriptionOptions sub_option;
180-
sub_option.callback_group = callback_group_;
181-
auto callback = std::bind(&RosTopicSubNode::topicCallback, this, std::placeholders::_1);
182-
subscriber_ = node_->create_subscription<T>(topic_name, 1, callback, sub_option);
183-
prev_topic_name_ = topic_name;
196+
if(sub_instance_)
197+
{
198+
throw RuntimeError("Can't call createSubscriber more than once");
199+
}
200+
201+
// find SubscriberInstance in the registry
202+
std::unique_lock lk(registryMutex());
203+
const auto key = topic_name + "@" + node_->get_fully_qualified_name();
204+
sub_instance_ = &(getRegisteredInstance(key));
205+
206+
// just created (subscriber is not initialized)
207+
if(!sub_instance_->subscriber)
208+
{
209+
// create a callback group for this particular instance
210+
sub_instance_->callback_group =
211+
node_->create_callback_group(rclcpp::CallbackGroupType::MutuallyExclusive, false);
212+
sub_instance_->callback_group_executor.add_callback_group(
213+
sub_instance_->callback_group, node_->get_node_base_interface());
214+
215+
rclcpp::SubscriptionOptions sub_option;
216+
sub_option.callback_group = sub_instance_->callback_group;
217+
218+
// The callback will broadcast to all the instances of RosTopicSubNode<T>
219+
auto callback = [this](const std::shared_ptr<T> msg) {
220+
sub_instance_->broadcaster(msg);
221+
};
222+
sub_instance_->subscriber =
223+
node_->create_subscription<T>(topic_name, 1, callback, sub_option);
224+
225+
RCLCPP_INFO(logger(),
226+
"Node [%s] created Subscriber to topic [%s]",
227+
name().c_str(), topic_name.c_str() );
228+
}
229+
230+
// add "this" as received of the broadcaster
231+
sub_instance_->broadcaster.connect([this](const std::shared_ptr<T> msg)
232+
{
233+
last_msg_ = msg;
234+
});
235+
236+
topic_name_ = topic_name;
184237
return true;
185238
}
186239

187-
template<class T> inline
188-
void RosTopicSubNode<T>::topicCallback(const std::shared_ptr<T> msg)
189-
{
190-
last_msg_ = msg;
191-
}
192240

193241
template<class T> inline
194242
NodeStatus RosTopicSubNode<T>::tick()
195243
{
196244
// First, check if the subscriber_ is valid and that the name of the
197245
// topic_name in the port didn't change.
198246
// otherwise, create a new subscriber
199-
if(!subscriber_ || (status() == NodeStatus::IDLE && topic_name_may_change_))
247+
if(!sub_instance_)
200248
{
201249
std::string topic_name;
202250
getInput("topic_name", topic_name);
203-
if(prev_topic_name_ != topic_name)
204-
{
205-
createSubscriber(topic_name);
206-
}
251+
createSubscriber(topic_name);
207252
}
208253

209254
auto CheckStatus = [](NodeStatus status)
210255
{
211256
if( !isStatusCompleted(status) )
212257
{
213-
throw std::logic_error("RosTopicSubNode: the callback must return either SUCCESS or FAILURE");
258+
throw std::logic_error("RosTopicSubNode: the callback must return"
259+
"either SUCCESS or FAILURE");
214260
}
215261
return status;
216262
};
217-
callback_group_executor_.spin_some();
263+
sub_instance_->callback_group_executor.spin_some();
218264
auto status = CheckStatus (onTick(last_msg_));
219265
last_msg_ = nullptr;
220266

btcpp_ros2_samples/CMakeLists.txt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,12 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
77

88
find_package(ament_cmake REQUIRED)
99
find_package(behaviortree_ros2 REQUIRED)
10+
find_package(std_msgs REQUIRED)
1011
find_package(btcpp_ros2_interfaces REQUIRED)
1112

1213
set(THIS_PACKAGE_DEPS
1314
behaviortree_ros2
15+
std_msgs
1416
btcpp_ros2_interfaces )
1517

1618
######################################################
@@ -38,6 +40,11 @@ ament_target_dependencies(sleep_client_dyn ${THIS_PACKAGE_DEPS})
3840
add_executable(sleep_server src/sleep_server.cpp)
3941
ament_target_dependencies(sleep_server ${THIS_PACKAGE_DEPS})
4042

43+
######################################################
44+
# Build subscriber_test
45+
add_executable(subscriber_test src/subscriber_test.cpp)
46+
ament_target_dependencies(subscriber_test ${THIS_PACKAGE_DEPS})
47+
4148
######################################################
4249
# INSTALL
4350

@@ -46,6 +53,7 @@ install(TARGETS
4653
sleep_client_dyn
4754
sleep_server
4855
sleep_plugin
56+
subscriber_test
4957
DESTINATION lib/${PROJECT_NAME}
5058
)
5159

btcpp_ros2_samples/package.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
<buildtool_depend>ament_cmake</buildtool_depend>
1313

1414
<depend>behaviortree_ros2</depend>
15+
<depend>std_msgs</depend>
1516
<depend>btcpp_ros2_interfaces</depend>
1617

1718
<export>
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
#include "behaviortree_ros2/bt_topic_sub_node.hpp"
2+
#include <std_msgs/msg/string.hpp>
3+
4+
using namespace BT;
5+
6+
class ReceiveString: public RosTopicSubNode<std_msgs::msg::String>
7+
{
8+
public:
9+
ReceiveString(const std::string& name,
10+
const NodeConfig& conf,
11+
const RosNodeParams& params)
12+
: RosTopicSubNode<std_msgs::msg::String>(name, conf, params)
13+
{}
14+
15+
static BT::PortsList providedPorts()
16+
{
17+
return {};
18+
}
19+
20+
NodeStatus onTick(const std::shared_ptr<std_msgs::msg::String>& last_msg) override
21+
{
22+
if(last_msg) // empty if no new message received, since the last tick
23+
{
24+
RCLCPP_INFO(logger(), "New message: %s", last_msg->data.c_str());
25+
}
26+
return NodeStatus::SUCCESS;
27+
}
28+
};
29+
30+
// Simple tree, used to execute once each action.
31+
static const char* xml_text = R"(
32+
<root BTCPP_format="4">
33+
<BehaviorTree>
34+
<Sequence>
35+
<ReceiveString/>
36+
<ReceiveString/>
37+
<ReceiveString/>
38+
</Sequence>
39+
</BehaviorTree>
40+
</root>
41+
)";
42+
43+
int main(int argc, char **argv)
44+
{
45+
rclcpp::init(argc, argv);
46+
auto nh = std::make_shared<rclcpp::Node>("subscriber_test");
47+
48+
BehaviorTreeFactory factory;
49+
50+
RosNodeParams params;
51+
params.nh = nh;
52+
params.default_port_value = "btcpp_string";
53+
factory.registerNodeType<ReceiveString>("ReceiveString", params);
54+
55+
auto tree = factory.createTreeFromText(xml_text);
56+
57+
while(rclcpp::ok())
58+
{
59+
tree.tickWhileRunning();
60+
}
61+
62+
return 0;
63+
}

0 commit comments

Comments
 (0)