Nav2 Navigation Stack - jazzy  jazzy
ROS 2 Navigation Stack
bt_utils.hpp
1 // Copyright (c) 2018 Intel Corporation
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 #ifndef NAV2_BEHAVIOR_TREE__BT_UTILS_HPP_
16 #define NAV2_BEHAVIOR_TREE__BT_UTILS_HPP_
17 
18 #include <string>
19 #include <set>
20 #include <vector>
21 
22 #include "rclcpp/time.hpp"
23 #include "rclcpp/node.hpp"
24 #include "behaviortree_cpp/behavior_tree.h"
25 #include "geometry_msgs/msg/point.hpp"
26 #include "geometry_msgs/msg/quaternion.hpp"
27 #include "geometry_msgs/msg/pose_stamped.hpp"
28 #include "nav_msgs/msg/path.hpp"
29 
30 namespace BT
31 {
32 
33 // The follow templates are required when using these types as parameters
34 // in our BT XML files. They parse the strings in the XML into their corresponding
35 // data type.
36 
42 template<>
43 inline geometry_msgs::msg::Point convertFromString(const StringView key)
44 {
45  // if string starts with "json:{", try to parse it as json
46  if (StartWith(key, "json:")) {
47  auto new_key = key;
48  new_key.remove_prefix(5);
49  return convertFromJSON<geometry_msgs::msg::Point>(new_key);
50  }
51 
52  // three real numbers separated by semicolons
53  auto parts = BT::splitString(key, ';');
54  if (parts.size() != 3) {
55  throw std::runtime_error("invalid number of fields for point attribute)");
56  } else {
57  geometry_msgs::msg::Point position;
58  position.x = BT::convertFromString<double>(parts[0]);
59  position.y = BT::convertFromString<double>(parts[1]);
60  position.z = BT::convertFromString<double>(parts[2]);
61  return position;
62  }
63 }
64 
70 template<>
71 inline geometry_msgs::msg::Quaternion convertFromString(const StringView key)
72 {
73  // if string starts with "json:{", try to parse it as json
74  if (StartWith(key, "json:")) {
75  auto new_key = key;
76  new_key.remove_prefix(5);
77  return convertFromJSON<geometry_msgs::msg::Quaternion>(new_key);
78  }
79 
80  // four real numbers separated by semicolons
81  auto parts = BT::splitString(key, ';');
82  if (parts.size() != 4) {
83  throw std::runtime_error("invalid number of fields for orientation attribute)");
84  } else {
85  geometry_msgs::msg::Quaternion orientation;
86  orientation.x = BT::convertFromString<double>(parts[0]);
87  orientation.y = BT::convertFromString<double>(parts[1]);
88  orientation.z = BT::convertFromString<double>(parts[2]);
89  orientation.w = BT::convertFromString<double>(parts[3]);
90  return orientation;
91  }
92 }
93 
99 template<>
100 inline geometry_msgs::msg::PoseStamped convertFromString(const StringView key)
101 {
102  // if string starts with "json:{", try to parse it as json
103  if (StartWith(key, "json:")) {
104  auto new_key = key;
105  new_key.remove_prefix(5);
106  return convertFromJSON<geometry_msgs::msg::PoseStamped>(new_key);
107  }
108 
109  // 7 real numbers separated by semicolons
110  auto parts = BT::splitString(key, ';');
111  if (parts.size() != 9) {
112  throw std::runtime_error("invalid number of fields for PoseStamped attribute)");
113  } else {
114  geometry_msgs::msg::PoseStamped pose_stamped;
115  pose_stamped.header.stamp = rclcpp::Time(BT::convertFromString<int64_t>(parts[0]));
116  pose_stamped.header.frame_id = BT::convertFromString<std::string>(parts[1]);
117  pose_stamped.pose.position.x = BT::convertFromString<double>(parts[2]);
118  pose_stamped.pose.position.y = BT::convertFromString<double>(parts[3]);
119  pose_stamped.pose.position.z = BT::convertFromString<double>(parts[4]);
120  pose_stamped.pose.orientation.x = BT::convertFromString<double>(parts[5]);
121  pose_stamped.pose.orientation.y = BT::convertFromString<double>(parts[6]);
122  pose_stamped.pose.orientation.z = BT::convertFromString<double>(parts[7]);
123  pose_stamped.pose.orientation.w = BT::convertFromString<double>(parts[8]);
124  return pose_stamped;
125  }
126 }
127 
133 template<>
134 inline std::vector<geometry_msgs::msg::PoseStamped> convertFromString(const StringView key)
135 {
136  // 9 real numbers separated by semicolons
137  auto parts = BT::splitString(key, ';');
138  if (parts.size() % 9 != 0) {
139  throw std::runtime_error("invalid number of fields for std::vector<PoseStamped> attribute)");
140  } else {
141  std::vector<geometry_msgs::msg::PoseStamped> poses;
142  for (size_t i = 0; i < parts.size(); i += 9) {
143  geometry_msgs::msg::PoseStamped pose_stamped;
144  pose_stamped.header.stamp = rclcpp::Time(BT::convertFromString<int64_t>(parts[i]));
145  pose_stamped.header.frame_id = BT::convertFromString<std::string>(parts[i + 1]);
146  pose_stamped.pose.position.x = BT::convertFromString<double>(parts[i + 2]);
147  pose_stamped.pose.position.y = BT::convertFromString<double>(parts[i + 3]);
148  pose_stamped.pose.position.z = BT::convertFromString<double>(parts[i + 4]);
149  pose_stamped.pose.orientation.x = BT::convertFromString<double>(parts[i + 5]);
150  pose_stamped.pose.orientation.y = BT::convertFromString<double>(parts[i + 6]);
151  pose_stamped.pose.orientation.z = BT::convertFromString<double>(parts[i + 7]);
152  pose_stamped.pose.orientation.w = BT::convertFromString<double>(parts[i + 8]);
153  poses.push_back(pose_stamped);
154  }
155  return poses;
156  }
157 }
158 
164 template<>
165 inline nav_msgs::msg::Path convertFromString(const StringView key)
166 {
167  // if string starts with "json:{", try to parse it as json
168  if (StartWith(key, "json:")) {
169  auto new_key = key;
170  new_key.remove_prefix(5);
171  return convertFromJSON<nav_msgs::msg::Path>(new_key);
172  }
173 
174  // 9 real numbers separated by semicolons
175  auto parts = BT::splitString(key, ';');
176  if ((parts.size() - 2) % 9 != 0) {
177  throw std::runtime_error("invalid number of fields for Path attribute)");
178  } else {
179  nav_msgs::msg::Path path;
180  path.header.stamp = rclcpp::Time(BT::convertFromString<int64_t>(parts[0]));
181  path.header.frame_id = BT::convertFromString<std::string>(parts[1]);
182  for (size_t i = 2; i < parts.size(); i += 9) {
183  geometry_msgs::msg::PoseStamped pose_stamped;
184  path.header.stamp = rclcpp::Time(BT::convertFromString<int64_t>(parts[i]));
185  pose_stamped.header.frame_id = BT::convertFromString<std::string>(parts[i + 1]);
186  pose_stamped.pose.position.x = BT::convertFromString<double>(parts[i + 2]);
187  pose_stamped.pose.position.y = BT::convertFromString<double>(parts[i + 3]);
188  pose_stamped.pose.position.z = BT::convertFromString<double>(parts[i + 4]);
189  pose_stamped.pose.orientation.x = BT::convertFromString<double>(parts[i + 5]);
190  pose_stamped.pose.orientation.y = BT::convertFromString<double>(parts[i + 6]);
191  pose_stamped.pose.orientation.z = BT::convertFromString<double>(parts[i + 7]);
192  pose_stamped.pose.orientation.w = BT::convertFromString<double>(parts[i + 8]);
193  path.poses.push_back(pose_stamped);
194  }
195  return path;
196  }
197 }
198 
204 template<>
205 inline std::chrono::milliseconds convertFromString<std::chrono::milliseconds>(const StringView key)
206 {
207  // if string starts with "json:{", try to parse it as json
208  if (StartWith(key, "json:")) {
209  auto new_key = key;
210  new_key.remove_prefix(5);
211  return convertFromJSON<std::chrono::milliseconds>(new_key);
212  }
213  return std::chrono::milliseconds(std::stoul(key.data()));
214 }
215 
221 template<>
222 inline std::set<int> convertFromString(StringView key)
223 {
224  // Real numbers separated by semicolons
225  auto parts = splitString(key, ';');
226 
227  std::set<int> set;
228  for (const auto part : parts) {
229  set.insert(convertFromString<int>(part));
230  }
231  return set;
232 }
233 
241 template<typename T1, typename T2 = BT::TreeNode>
242 T1 deconflictPortAndParamFrame(
243  rclcpp::Node::SharedPtr node,
244  std::string param_name,
245  const T2 * behavior_tree_node)
246 {
247  T1 param_value;
248  bool param_from_input = behavior_tree_node->getInput(param_name, param_value).has_value();
249 
250  if constexpr (std::is_same_v<T1, std::string>) {
251  // not valid if port doesn't exist or it is an empty string
252  param_from_input &= !param_value.empty();
253  }
254 
255  if (!param_from_input) {
256  RCLCPP_DEBUG(
257  node->get_logger(),
258  "Parameter '%s' not provided by behavior tree xml file, "
259  "using parameter from ros2 parameter file",
260  param_name.c_str());
261  node->get_parameter(param_name, param_value);
262  return param_value;
263  } else {
264  RCLCPP_DEBUG(
265  node->get_logger(),
266  "Parameter '%s' provided by behavior tree xml file",
267  param_name.c_str());
268  return param_value;
269  }
270 }
271 
283 template<typename T> inline
284 bool getInputPortOrBlackboard(
285  const BT::TreeNode & bt_node,
286  const BT::Blackboard & blackboard,
287  const std::string & param_name,
288  T & value)
289 {
290  if (bt_node.getInput<T>(param_name, value)) {
291  return true;
292  }
293  if (blackboard.get<T>(param_name, value)) {
294  return true;
295  }
296  return false;
297 }
298 
299 // Macro to remove boiler plate when using getInputPortOrBlackboard
300 #define getInputOrBlackboard(name, value) \
301  getInputPortOrBlackboard(*this, *(this->config().blackboard), name, value);
302 
303 } // namespace BT
304 
305 #endif // NAV2_BEHAVIOR_TREE__BT_UTILS_HPP_