20#include "rclcpp/executors.hpp"
21#include "behaviortree_cpp/action_node.h"
42 return "SERVICE_UNREACHABLE";
44 return "SERVICE_TIMEOUT";
46 return "INVALID_REQUEST";
48 return "SERVICE_ABORTED";
71template <
class ServiceT>
74 using ServiceClient =
typename rclcpp::Client<ServiceT>;
75 using ServiceClientPtr = std::shared_ptr<ServiceClient>;
77 struct ServiceClientInstance
79 ServiceClientInstance(std::shared_ptr<rclcpp::Node> node,
const std::string& service_name);
81 ServiceClientPtr service_client;
82 rclcpp::CallbackGroup::SharedPtr callback_group;
83 rclcpp::executors::SingleThreadedExecutor callback_executor;
86 using ClientsRegistry = std::unordered_map<std::string, std::weak_ptr<ServiceClientInstance>>;
90 using Request =
typename ServiceT::Request;
108 BT::PortsList basic = { BT::InputPort<std::string>(
"service_name",
"",
"Service name") };
109 basic.insert(addition.begin(), addition.end());
122 BT::NodeStatus
tick()
override;
125 void halt()
override;
135 virtual bool setRequest(
typename Request::SharedPtr& request);
140 virtual BT::NodeStatus
onResponseReceived(
const typename Response::SharedPtr& response);
164 std::string service_name_;
165 bool service_name_should_be_checked_ =
false;
166 std::shared_ptr<ServiceClientInstance> srv_instance_;
167 std::shared_future<typename Response::SharedPtr> future_response_;
168 rclcpp::Time time_request_sent_;
169 BT::NodeStatus on_feedback_state_change_;
170 bool response_received_;
171 typename Response::SharedPtr response_;
173 bool createClient(
const std::string& service_name);
180template <
class ServiceT>
182 const std::string& service_name)
184 callback_group = node->create_callback_group(rclcpp::CallbackGroupType::MutuallyExclusive,
false);
185 callback_executor.add_callback_group(callback_group, node->get_node_base_interface());
187 service_client = node->create_client<ServiceT>(service_name, rmw_qos_profile_services_default, callback_group);
190template <
class ServiceT>
193 : BT::ActionNodeBase(instance_name, config),
logger_(context.getLogger()), context_(context)
196 auto portIt = config.input_ports.find(
"service_name");
197 if (portIt != config.input_ports.end())
199 const std::string& bb_service_name = portIt->second;
201 if (isBlackboardPointer(bb_service_name))
204 service_name_should_be_checked_ = true;
206 else if (!bb_service_name.empty())
209 createClient(bb_service_name);
213 if (!srv_instance_ && !context.default_port_name.empty())
215 createClient(context.default_port_name);
219template <
class ServiceT>
220inline bool RosServiceNode<ServiceT>::createClient(
const std::string& service_name)
222 if (service_name.empty())
224 throw exceptions::RosNodeError(getFullName() +
" - Argument service_name is empty when trying to create a client.");
227 std::unique_lock lk(getMutex());
228 auto node = getRosContext().nh.lock();
231 throw exceptions::RosNodeError(getFullName() +
232 " - The shared pointer to the ROS node went out of scope. The tree node doesn't "
233 "take the ownership of the node.");
235 auto client_key = std::string(node->get_fully_qualified_name()) +
"/" + service_name;
237 auto& registry = getRegistry();
238 auto it = registry.find(client_key);
239 if (it == registry.end() || it->second.expired())
241 srv_instance_ = std::make_shared<ServiceClientInstance>(node, service_name);
242 registry.insert({ client_key, srv_instance_ });
243 RCLCPP_DEBUG(logger_,
"%s - Created client for service '%s'.", getFullName().c_str(), service_name.c_str());
247 srv_instance_ = it->second.lock();
249 service_name_ = service_name;
251 bool found = srv_instance_->service_client->wait_for_service(getRosContext().wait_for_server_timeout);
254 RCLCPP_WARN(logger_,
"%s - Service with name '%s' is not reachable.", getFullName().c_str(), service_name_.c_str());
259template <
class ServiceT>
265 return BT::NodeStatus::FAILURE;
271 if (!srv_instance_ || (status() == BT::NodeStatus::IDLE && service_name_should_be_checked_))
273 std::string service_name;
274 getInput(
"service_name", service_name);
275 if (service_name_ != service_name)
277 createClient(service_name);
284 " - You must specify a service name either by using a default value or by "
285 "passing a value to the corresponding dynamic input port.");
288 auto check_status = [
this](BT::NodeStatus status) {
289 if (!isStatusCompleted(status))
297 if (status() == BT::NodeStatus::IDLE)
299 setStatus(BT::NodeStatus::RUNNING);
301 response_received_ =
false;
302 future_response_ = {};
303 on_feedback_state_change_ = BT::NodeStatus::RUNNING;
306 typename Request::SharedPtr request = std::make_shared<Request>();
308 if (!setRequest(request))
314 if (!srv_instance_->service_client->service_is_ready())
319 future_response_ = srv_instance_->service_client->async_send_request(request).share();
320 time_request_sent_ = getRosContext().getCurrentTime();
322 return BT::NodeStatus::RUNNING;
325 if (status() == BT::NodeStatus::RUNNING)
327 srv_instance_->callback_executor.spin_some();
330 if (!response_received_)
332 auto ret = srv_instance_->callback_executor.spin_until_future_complete(future_response_, std::chrono::seconds(0));
334 if (ret != rclcpp::FutureReturnCode::SUCCESS)
336 if ((getRosContext().getCurrentTime() - time_request_sent_) > getRosContext().request_timeout)
342 return BT::NodeStatus::RUNNING;
347 response_received_ =
true;
348 response_ = future_response_.get();
349 future_response_ = {};
359 return check_status(onResponseReceived(response_));
361 return BT::NodeStatus::RUNNING;
364template <
class ServiceT>
367 if (status() == BT::NodeStatus::RUNNING)
373template <
class ServiceT>
379template <
class ServiceT>
382 return BT::NodeStatus::SUCCESS;
385template <
class ServiceT>
388 RCLCPP_ERROR(logger_,
"%s - Error %d: %s", getFullName().c_str(), error,
toStr(error));
389 return BT::NodeStatus::FAILURE;
392template <
class ServiceT>
397 if (registrationName().empty())
399 if (this->name() == this->registrationName())
401 return this->name() +
" (Type: " + this->registrationName() +
")";
404template <
class ServiceT>
410template <
class ServiceT>
413 static std::mutex action_client_mutex;
414 return action_client_mutex;
417template <
class ServiceT>
420 service_name_ = service_name;
421 createClient(service_name);
424template <
class ServiceT>
427 static ClientsRegistry clients_registry;
428 return clients_registry;
Abstract class use to wrap rclcpp::Client<>
RosServiceNode(const std::string &instance_name, const Config &config, const Context &context)
std::string getFullName() const
virtual bool setRequest(typename Request::SharedPtr &request)
static ClientsRegistry & getRegistry()
void setServiceName(const std::string &service_name)
virtual ~RosServiceNode()=default
static std::mutex & getMutex()
void halt() override
The default halt() implementation.
virtual BT::NodeStatus onResponseReceived(const typename Response::SharedPtr &response)
const Context & getRosContext()
static BT::PortsList providedBasicPorts(BT::PortsList addition)
Any subclass of RosServiceNode that has ports must implement a providedPorts method and call provided...
typename ServiceT::Response Response
virtual BT::NodeStatus onFailure(ServiceNodeErrorCode error)
typename ServiceT::Request Request
BT::NodeStatus tick() override
static BT::PortsList providedPorts()
Creates list of BT ports.
const rclcpp::Logger logger_
std::string toStr(TreeExecutor::ExecutionState state)