Nav2 Navigation Stack - kilted  kilted
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/pose_stamped.hpp"
27 #include "geometry_msgs/msg/quaternion.hpp"
28 #include "nav_msgs/msg/path.hpp"
29 #include "nav_msgs/msg/goals.hpp"
30 #include "nav2_msgs/msg/waypoint_status.hpp"
31 
32 namespace BT
33 {
34 
35 // The follow templates are required when using these types as parameters
36 // in our BT XML files. They parse the strings in the XML into their corresponding
37 // data type.
38 
44 template<>
45 inline geometry_msgs::msg::Point convertFromString(const StringView key)
46 {
47  // if string starts with "json:{", try to parse it as json
48  if (StartWith(key, "json:")) {
49  auto new_key = key;
50  new_key.remove_prefix(5);
51  return convertFromJSON<geometry_msgs::msg::Point>(new_key);
52  }
53 
54  // three real numbers separated by semicolons
55  auto parts = BT::splitString(key, ';');
56  if (parts.size() != 3) {
57  throw std::runtime_error("invalid number of fields for point attribute)");
58  } else {
59  geometry_msgs::msg::Point position;
60  position.x = BT::convertFromString<double>(parts[0]);
61  position.y = BT::convertFromString<double>(parts[1]);
62  position.z = BT::convertFromString<double>(parts[2]);
63  return position;
64  }
65 }
66 
72 template<>
73 inline geometry_msgs::msg::Quaternion convertFromString(const StringView key)
74 {
75  // if string starts with "json:{", try to parse it as json
76  if (StartWith(key, "json:")) {
77  auto new_key = key;
78  new_key.remove_prefix(5);
79  return convertFromJSON<geometry_msgs::msg::Quaternion>(new_key);
80  }
81 
82  // four real numbers separated by semicolons
83  auto parts = BT::splitString(key, ';');
84  if (parts.size() != 4) {
85  throw std::runtime_error("invalid number of fields for orientation attribute)");
86  } else {
87  geometry_msgs::msg::Quaternion orientation;
88  orientation.x = BT::convertFromString<double>(parts[0]);
89  orientation.y = BT::convertFromString<double>(parts[1]);
90  orientation.z = BT::convertFromString<double>(parts[2]);
91  orientation.w = BT::convertFromString<double>(parts[3]);
92  return orientation;
93  }
94 }
95 
101 template<>
102 inline geometry_msgs::msg::PoseStamped convertFromString(const StringView key)
103 {
104  // if string starts with "json:{", try to parse it as json
105  if (StartWith(key, "json:")) {
106  auto new_key = key;
107  new_key.remove_prefix(5);
108  return convertFromJSON<geometry_msgs::msg::PoseStamped>(new_key);
109  }
110 
111  // 7 real numbers separated by semicolons
112  auto parts = BT::splitString(key, ';');
113  if (parts.size() != 9) {
114  throw std::runtime_error("invalid number of fields for PoseStamped attribute)");
115  } else {
116  geometry_msgs::msg::PoseStamped pose_stamped;
117  pose_stamped.header.stamp = rclcpp::Time(BT::convertFromString<int64_t>(parts[0]));
118  pose_stamped.header.frame_id = BT::convertFromString<std::string>(parts[1]);
119  pose_stamped.pose.position.x = BT::convertFromString<double>(parts[2]);
120  pose_stamped.pose.position.y = BT::convertFromString<double>(parts[3]);
121  pose_stamped.pose.position.z = BT::convertFromString<double>(parts[4]);
122  pose_stamped.pose.orientation.x = BT::convertFromString<double>(parts[5]);
123  pose_stamped.pose.orientation.y = BT::convertFromString<double>(parts[6]);
124  pose_stamped.pose.orientation.z = BT::convertFromString<double>(parts[7]);
125  pose_stamped.pose.orientation.w = BT::convertFromString<double>(parts[8]);
126  return pose_stamped;
127  }
128 }
129 
135 template<>
136 inline std::vector<geometry_msgs::msg::PoseStamped> convertFromString(const StringView key)
137 {
138  // if string starts with "json:{", try to parse it as json
139  if (StartWith(key, "json:")) {
140  auto new_key = key;
141  new_key.remove_prefix(5);
142  return convertFromJSON<std::vector<geometry_msgs::msg::PoseStamped>>(new_key);
143  }
144 
145  auto parts = BT::splitString(key, ';');
146  if (parts.size() % 9 != 0) {
147  throw std::runtime_error("invalid number of fields for std::vector<PoseStamped> attribute)");
148  } else {
149  std::vector<geometry_msgs::msg::PoseStamped> poses;
150  for (size_t i = 0; i < parts.size(); i += 9) {
151  geometry_msgs::msg::PoseStamped pose_stamped;
152  pose_stamped.header.stamp = rclcpp::Time(BT::convertFromString<int64_t>(parts[i]));
153  pose_stamped.header.frame_id = BT::convertFromString<std::string>(parts[i + 1]);
154  pose_stamped.pose.position.x = BT::convertFromString<double>(parts[i + 2]);
155  pose_stamped.pose.position.y = BT::convertFromString<double>(parts[i + 3]);
156  pose_stamped.pose.position.z = BT::convertFromString<double>(parts[i + 4]);
157  pose_stamped.pose.orientation.x = BT::convertFromString<double>(parts[i + 5]);
158  pose_stamped.pose.orientation.y = BT::convertFromString<double>(parts[i + 6]);
159  pose_stamped.pose.orientation.z = BT::convertFromString<double>(parts[i + 7]);
160  pose_stamped.pose.orientation.w = BT::convertFromString<double>(parts[i + 8]);
161  poses.push_back(pose_stamped);
162  }
163  return poses;
164  }
165 }
166 
172 template<>
173 inline nav_msgs::msg::Goals convertFromString(const StringView key)
174 {
175  // if string starts with "json:{", try to parse it as json
176  if (StartWith(key, "json:")) {
177  auto new_key = key;
178  new_key.remove_prefix(5);
179  return convertFromJSON<nav_msgs::msg::Goals>(new_key);
180  }
181 
182  auto parts = BT::splitString(key, ';');
183  if ((parts.size() - 2) % 9 != 0) {
184  throw std::runtime_error("invalid number of fields for Goals attribute)");
185  } else {
186  nav_msgs::msg::Goals goals_array;
187  goals_array.header.stamp = rclcpp::Time(BT::convertFromString<int64_t>(parts[0]));
188  goals_array.header.frame_id = BT::convertFromString<std::string>(parts[1]);
189  for (size_t i = 2; i < parts.size(); i += 9) {
190  geometry_msgs::msg::PoseStamped pose_stamped;
191  pose_stamped.header.stamp = rclcpp::Time(BT::convertFromString<int64_t>(parts[i]));
192  pose_stamped.header.frame_id = BT::convertFromString<std::string>(parts[i + 1]);
193  pose_stamped.pose.position.x = BT::convertFromString<double>(parts[i + 2]);
194  pose_stamped.pose.position.y = BT::convertFromString<double>(parts[i + 3]);
195  pose_stamped.pose.position.z = BT::convertFromString<double>(parts[i + 4]);
196  pose_stamped.pose.orientation.x = BT::convertFromString<double>(parts[i + 5]);
197  pose_stamped.pose.orientation.y = BT::convertFromString<double>(parts[i + 6]);
198  pose_stamped.pose.orientation.z = BT::convertFromString<double>(parts[i + 7]);
199  pose_stamped.pose.orientation.w = BT::convertFromString<double>(parts[i + 8]);
200  goals_array.goals.push_back(pose_stamped);
201  }
202  return goals_array;
203  }
204 }
205 
211 template<>
212 inline nav_msgs::msg::Path convertFromString(const StringView key)
213 {
214  // if string starts with "json:{", try to parse it as json
215  if (StartWith(key, "json:")) {
216  auto new_key = key;
217  new_key.remove_prefix(5);
218  return convertFromJSON<nav_msgs::msg::Path>(new_key);
219  }
220 
221  auto parts = BT::splitString(key, ';');
222  if ((parts.size() - 2) % 9 != 0) {
223  throw std::runtime_error("invalid number of fields for Path attribute)");
224  } else {
225  nav_msgs::msg::Path path;
226  path.header.stamp = rclcpp::Time(BT::convertFromString<int64_t>(parts[0]));
227  path.header.frame_id = BT::convertFromString<std::string>(parts[1]);
228  for (size_t i = 2; i < parts.size(); i += 9) {
229  geometry_msgs::msg::PoseStamped pose_stamped;
230  path.header.stamp = rclcpp::Time(BT::convertFromString<int64_t>(parts[i]));
231  pose_stamped.header.frame_id = BT::convertFromString<std::string>(parts[i + 1]);
232  pose_stamped.pose.position.x = BT::convertFromString<double>(parts[i + 2]);
233  pose_stamped.pose.position.y = BT::convertFromString<double>(parts[i + 3]);
234  pose_stamped.pose.position.z = BT::convertFromString<double>(parts[i + 4]);
235  pose_stamped.pose.orientation.x = BT::convertFromString<double>(parts[i + 5]);
236  pose_stamped.pose.orientation.y = BT::convertFromString<double>(parts[i + 6]);
237  pose_stamped.pose.orientation.z = BT::convertFromString<double>(parts[i + 7]);
238  pose_stamped.pose.orientation.w = BT::convertFromString<double>(parts[i + 8]);
239  path.poses.push_back(pose_stamped);
240  }
241  return path;
242  }
243 }
244 
250 template<>
251 inline nav2_msgs::msg::WaypointStatus convertFromString(const StringView key)
252 {
253  // if string starts with "json:{", try to parse it as json
254  if (StartWith(key, "json:")) {
255  auto new_key = key;
256  new_key.remove_prefix(5);
257  return convertFromJSON<nav2_msgs::msg::WaypointStatus>(new_key);
258  }
259 
260  auto parts = BT::splitString(key, ';');
261  if (parts.size() != 13) {
262  throw std::runtime_error("invalid number of fields for WaypointStatus attribute)");
263  } else {
264  nav2_msgs::msg::WaypointStatus waypoint_status;
265  waypoint_status.waypoint_status = BT::convertFromString<uint8_t>(parts[0]);
266  waypoint_status.waypoint_index = BT::convertFromString<uint32_t>(parts[1]);
267  waypoint_status.waypoint_pose.header.stamp =
268  rclcpp::Time(BT::convertFromString<int64_t>(parts[2]));
269  waypoint_status.waypoint_pose.header.frame_id = BT::convertFromString<std::string>(parts[3]);
270  waypoint_status.waypoint_pose.pose.position.x = BT::convertFromString<double>(parts[4]);
271  waypoint_status.waypoint_pose.pose.position.y = BT::convertFromString<double>(parts[5]);
272  waypoint_status.waypoint_pose.pose.position.z = BT::convertFromString<double>(parts[6]);
273  waypoint_status.waypoint_pose.pose.orientation.x = BT::convertFromString<double>(parts[7]);
274  waypoint_status.waypoint_pose.pose.orientation.y = BT::convertFromString<double>(parts[8]);
275  waypoint_status.waypoint_pose.pose.orientation.z = BT::convertFromString<double>(parts[9]);
276  waypoint_status.waypoint_pose.pose.orientation.w = BT::convertFromString<double>(parts[10]);
277  waypoint_status.error_code = BT::convertFromString<uint16_t>(parts[11]);
278  waypoint_status.error_msg = BT::convertFromString<std::string>(parts[12]);
279  return waypoint_status;
280  }
281 }
282 
288 template<>
289 inline std::vector<nav2_msgs::msg::WaypointStatus> convertFromString(const StringView key)
290 {
291  // if string starts with "json:{", try to parse it as json
292  if (StartWith(key, "json:")) {
293  auto new_key = key;
294  new_key.remove_prefix(5);
295  return convertFromJSON<std::vector<nav2_msgs::msg::WaypointStatus>>(new_key);
296  }
297 
298  auto parts = BT::splitString(key, ';');
299  if (parts.size() % 13 != 0) {
300  throw std::runtime_error("invalid number of fields for std::vector<WaypointStatus> attribute)");
301  } else {
302  std::vector<nav2_msgs::msg::WaypointStatus> wp_status_vector;
303  for (size_t i = 0; i < parts.size(); i += 13) {
304  nav2_msgs::msg::WaypointStatus wp_status;
305  wp_status.waypoint_status = BT::convertFromString<uint8_t>(parts[i]);
306  wp_status.waypoint_index = BT::convertFromString<uint32_t>(parts[i + 1]);
307  wp_status.waypoint_pose.header.stamp =
308  rclcpp::Time(BT::convertFromString<int64_t>(parts[i + 2]));
309  wp_status.waypoint_pose.header.frame_id = BT::convertFromString<std::string>(parts[i + 3]);
310  wp_status.waypoint_pose.pose.position.x = BT::convertFromString<double>(parts[i + 4]);
311  wp_status.waypoint_pose.pose.position.y = BT::convertFromString<double>(parts[i + 5]);
312  wp_status.waypoint_pose.pose.position.z = BT::convertFromString<double>(parts[i + 6]);
313  wp_status.waypoint_pose.pose.orientation.x = BT::convertFromString<double>(parts[i + 7]);
314  wp_status.waypoint_pose.pose.orientation.y = BT::convertFromString<double>(parts[i + 8]);
315  wp_status.waypoint_pose.pose.orientation.z = BT::convertFromString<double>(parts[i + 9]);
316  wp_status.waypoint_pose.pose.orientation.w = BT::convertFromString<double>(parts[i + 10]);
317  wp_status.error_code = BT::convertFromString<uint16_t>(parts[i + 11]);
318  wp_status.error_msg = BT::convertFromString<std::string>(parts[i + 12]);
319  wp_status_vector.push_back(wp_status);
320  }
321  return wp_status_vector;
322  }
323 }
324 
330 template<>
331 inline std::chrono::milliseconds convertFromString<std::chrono::milliseconds>(const StringView key)
332 {
333  // if string starts with "json:{", try to parse it as json
334  if (StartWith(key, "json:")) {
335  auto new_key = key;
336  new_key.remove_prefix(5);
337  return convertFromJSON<std::chrono::milliseconds>(new_key);
338  }
339 
340  return std::chrono::milliseconds(std::stoul(key.data()));
341 }
342 
350 template<typename T1, typename T2 = BT::TreeNode>
351 T1 deconflictPortAndParamFrame(
352  rclcpp::Node::SharedPtr node,
353  std::string param_name,
354  const T2 * behavior_tree_node)
355 {
356  T1 param_value;
357  bool param_from_input = behavior_tree_node->getInput(param_name, param_value).has_value();
358 
359  if constexpr (std::is_same_v<T1, std::string>) {
360  // not valid if port doesn't exist or it is an empty string
361  param_from_input &= !param_value.empty();
362  }
363 
364  if (!param_from_input) {
365  RCLCPP_DEBUG(
366  node->get_logger(),
367  "Parameter '%s' not provided by behavior tree xml file, "
368  "using parameter from ros2 parameter file",
369  param_name.c_str());
370  node->get_parameter(param_name, param_value);
371  return param_value;
372  } else {
373  RCLCPP_DEBUG(
374  node->get_logger(),
375  "Parameter '%s' provided by behavior tree xml file",
376  param_name.c_str());
377  return param_value;
378  }
379 }
380 
392 template<typename T> inline
393 bool getInputPortOrBlackboard(
394  const BT::TreeNode & bt_node,
395  const BT::Blackboard & blackboard,
396  const std::string & param_name,
397  T & value)
398 {
399  if (bt_node.getInput<T>(param_name, value)) {
400  return true;
401  }
402  if (blackboard.get<T>(param_name, value)) {
403  return true;
404  }
405  return false;
406 }
407 
408 // Macro to remove boiler plate when using getInputPortOrBlackboard
409 #define getInputOrBlackboard(name, value) \
410  getInputPortOrBlackboard(*this, *(this->config().blackboard), name, value);
411 
412 } // namespace BT
413 
414 #endif // NAV2_BEHAVIOR_TREE__BT_UTILS_HPP_