Skip to content

Commit 4c7d984

Browse files
committed
New async pub and sub nodes
1 parent 7a61db8 commit 4c7d984

File tree

2 files changed

+573
-0
lines changed

2 files changed

+573
-0
lines changed
Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
1+
// Copyright (c) 2023 Davide Faconti, Unmanned Life
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#pragma once
16+
17+
#include <memory>
18+
#include <string>
19+
#include <rclcpp/executors.hpp>
20+
#include <rclcpp/allocator/allocator_common.hpp>
21+
#include "behaviortree_cpp/action_node.h"
22+
#include "behaviortree_cpp/bt_factory.h"
23+
#include "behaviortree_ros2/ros_node_params.hpp"
24+
25+
namespace BT
26+
{
27+
28+
/**
29+
* @brief Abstract class to wrap a ROS publisher
30+
*
31+
*/
32+
template <class TopicT>
33+
class AsyncRosTopicPubNode : public BT::ActionNodeBase
34+
{
35+
public:
36+
// Type definitions
37+
using Publisher = typename rclcpp::Publisher<TopicT>;
38+
39+
/** You are not supposed to instantiate this class directly, the factory will do it.
40+
* To register this class into the factory, use:
41+
*
42+
* RegisterRosAction<DerivedClass>(factory, params)
43+
*
44+
* Note that if the external_action_client is not set, the constructor will build its own.
45+
* */
46+
explicit AsyncRosTopicPubNode(const std::string& instance_name, const BT::NodeConfig& conf,
47+
const RosNodeParams& params);
48+
49+
virtual ~AsyncRosTopicPubNode() = default;
50+
51+
/**
52+
* @brief Any subclass of AsyncRosTopicPubNode that has additinal ports must provide a
53+
* providedPorts method and call providedBasicPorts in it.
54+
*
55+
* @param addition Additional ports to add to BT port list
56+
* @return PortsList Containing basic ports along with node-specific ports
57+
*/
58+
static PortsList providedBasicPorts(PortsList addition)
59+
{
60+
PortsList basic = {
61+
InputPort<std::string>("topic_name", "__default__placeholder__", "Topic name"),
62+
InputPort<float>("frequency", 10.0, "Frequency of the published message (Hz)"),
63+
InputPort<float>("duration", 1.0, "Duration to publish for (secs)")
64+
};
65+
basic.insert(addition.begin(), addition.end());
66+
return basic;
67+
}
68+
69+
/**
70+
* @brief Creates list of BT ports
71+
* @return PortsList Containing basic ports along with node-specific ports
72+
*/
73+
static PortsList providedPorts()
74+
{
75+
return providedBasicPorts({});
76+
}
77+
78+
NodeStatus tick() override final;
79+
80+
/**
81+
* @brief setMessage is a callback invoked in tick to allow the user to pass
82+
* the message to be published.
83+
*
84+
* @param msg the message.
85+
* @return return false if anything is wrong and we must not send the message.
86+
* the Condition will return FAILURE.
87+
*/
88+
virtual bool setMessage(TopicT& msg) = 0;
89+
90+
virtual void halt() override
91+
{
92+
start_time_ = 0.0;
93+
last_pub_time_ = 0.0;
94+
resetStatus();
95+
}
96+
97+
protected:
98+
std::shared_ptr<rclcpp::Node> node_;
99+
std::string prev_topic_name_;
100+
bool topic_name_may_change_ = false;
101+
102+
double now()
103+
{
104+
return node_->get_clock()->now().seconds();
105+
}
106+
107+
private:
108+
std::shared_ptr<Publisher> publisher_;
109+
double start_time_;
110+
double last_pub_time_;
111+
112+
bool createPublisher(const std::string& topic_name);
113+
};
114+
115+
//----------------------------------------------------------------
116+
//---------------------- DEFINITIONS -----------------------------
117+
//----------------------------------------------------------------
118+
119+
template <class T>
120+
inline AsyncRosTopicPubNode<T>::AsyncRosTopicPubNode(const std::string& instance_name,
121+
const NodeConfig& conf,
122+
const RosNodeParams& params)
123+
: BT::ActionNodeBase(instance_name, conf), node_(params.nh),
124+
start_time_(0.0), last_pub_time_(0.0)
125+
{
126+
// check port remapping
127+
auto portIt = config().input_ports.find("topic_name");
128+
if(portIt != config().input_ports.end())
129+
{
130+
const std::string& bb_topic_name = portIt->second;
131+
132+
if(bb_topic_name.empty() || bb_topic_name == "__default__placeholder__")
133+
{
134+
if(params.default_port_value.empty())
135+
{
136+
throw std::logic_error("Both [topic_name] in the InputPort and the RosNodeParams "
137+
"are empty.");
138+
}
139+
else
140+
{
141+
createPublisher(params.default_port_value);
142+
}
143+
}
144+
else if(!isBlackboardPointer(bb_topic_name))
145+
{
146+
// If the content of the port "topic_name" is not
147+
// a pointer to the blackboard, but a static string, we can
148+
// create the client in the constructor.
149+
createPublisher(bb_topic_name);
150+
}
151+
else
152+
{
153+
topic_name_may_change_ = true;
154+
// createPublisher will be invoked in the first tick().
155+
}
156+
}
157+
else
158+
{
159+
if(params.default_port_value.empty())
160+
{
161+
throw std::logic_error("Both [topic_name] in the InputPort and the RosNodeParams "
162+
"are empty.");
163+
}
164+
else
165+
{
166+
createPublisher(params.default_port_value);
167+
}
168+
}
169+
}
170+
171+
template <class T>
172+
inline bool AsyncRosTopicPubNode<T>::createPublisher(const std::string& topic_name)
173+
{
174+
if(topic_name.empty())
175+
{
176+
throw RuntimeError("topic_name is empty");
177+
}
178+
179+
publisher_ = node_->create_publisher<T>(topic_name, 1);
180+
prev_topic_name_ = topic_name;
181+
return true;
182+
}
183+
184+
template <class T>
185+
inline NodeStatus AsyncRosTopicPubNode<T>::tick()
186+
{
187+
// First, check if the subscriber_ is valid and that the name of the
188+
// topic_name in the port didn't change.
189+
// otherwise, create a new subscriber
190+
if(!publisher_ || (status() == NodeStatus::IDLE && topic_name_may_change_))
191+
{
192+
std::string topic_name;
193+
getInput("topic_name", topic_name);
194+
if(prev_topic_name_ != topic_name)
195+
{
196+
createPublisher(topic_name);
197+
}
198+
}
199+
200+
// Get publish period and duration
201+
float period = 1 / getInput<float>("frequency").value();
202+
float duration = getInput<float>("duration").value();
203+
if (duration < 0)
204+
{
205+
duration = 1e10;
206+
}
207+
208+
T msg;
209+
if(!setMessage(msg))
210+
{
211+
return NodeStatus::FAILURE;
212+
}
213+
214+
if (abs(start_time_) < 1e-5)
215+
{
216+
start_time_ = now();
217+
}
218+
219+
if (abs(last_pub_time_) < 1e-5)
220+
{
221+
last_pub_time_ = now();
222+
}
223+
224+
double time_now = now();
225+
if (duration > 0 && (time_now - start_time_) > duration)
226+
{
227+
return NodeStatus::SUCCESS;
228+
}
229+
230+
if ((time_now - last_pub_time_) > period)
231+
{
232+
publisher_->publish(msg);
233+
last_pub_time_ = time_now;
234+
}
235+
236+
return NodeStatus::RUNNING;
237+
}
238+
239+
} // namespace BT

0 commit comments

Comments
 (0)