add humble-navigation2

This commit is contained in:
X-lanni
2025-05-27 19:03:40 +08:00
parent 974abb5e1e
commit e74ec539c2
1280 changed files with 204114 additions and 0 deletions
View File
+63
View File
@@ -0,0 +1,63 @@
cmake_minimum_required(VERSION 3.5)
project(nav2_util)
find_package(ament_cmake REQUIRED)
find_package(nav2_common REQUIRED)
find_package(nav2_msgs REQUIRED)
find_package(tf2_ros REQUIRED)
find_package(tf2 REQUIRED)
find_package(tf2_geometry_msgs REQUIRED)
find_package(geometry_msgs REQUIRED)
find_package(nav_msgs REQUIRED)
find_package(rclcpp REQUIRED)
find_package(lifecycle_msgs REQUIRED)
find_package(rclcpp_action REQUIRED)
find_package(rclcpp_lifecycle REQUIRED)
find_package(tf2_geometry_msgs REQUIRED)
find_package(bondcpp REQUIRED)
find_package(bond REQUIRED)
find_package(action_msgs REQUIRED)
set(dependencies
nav2_msgs
tf2_ros
tf2
tf2_geometry_msgs
geometry_msgs
nav_msgs
rclcpp
lifecycle_msgs
rclcpp_action
rclcpp_lifecycle
bondcpp
bond
action_msgs
rcl_interfaces
)
nav2_package()
include_directories(include)
set(library_name ${PROJECT_NAME}_core)
add_subdirectory(src)
install(DIRECTORY include/
DESTINATION include/
)
if(BUILD_TESTING)
find_package(ament_lint_auto REQUIRED)
find_package(ament_cmake_pytest REQUIRED)
find_package(launch_testing_ament_cmake REQUIRED)
ament_lint_auto_find_test_dependencies()
find_package(ament_cmake_gtest REQUIRED)
add_subdirectory(test)
endif()
ament_export_include_directories(include)
ament_export_libraries(${library_name})
ament_export_dependencies(${dependencies})
ament_package()
+12
View File
@@ -0,0 +1,12 @@
# Nav2 Util
The `nav2_util` package contains utilities abstracted from individual packages which may find use in other uses. Some examples of things you'll find here:
- Geometry utilities for computing distances and values in paths
- A Nav2 specific lifecycle node wrapper for boilerplate code and useful common utilities like `declare_parameter_if_not_declared()`
- Simplified service clients
- Simplified action servers
- Transformation and robot pose helpers
The long-term aim is for these utilities to find more permanent homes in other packages (within and outside of Nav2) or migrate to the raw tools made available in ROS 2.
@@ -0,0 +1,49 @@
// Copyright (c) 2019 Intel Corporation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef NAV2_UTIL__CLEAR_ENTIRELY_COSTMAP_SERVICE_CLIENT_HPP_
#define NAV2_UTIL__CLEAR_ENTIRELY_COSTMAP_SERVICE_CLIENT_HPP_
#include <string>
#include "nav2_util/service_client.hpp"
#include "std_srvs/srv/empty.hpp"
#include "nav2_msgs/srv/clear_entire_costmap.hpp"
namespace nav2_util
{
/**
* @class nav2_util::ClearEntirelyCostmapServiceClient
* @brief A service client to clear costmaps entirely
*/
class ClearEntirelyCostmapServiceClient
: public nav2_util::ServiceClient<nav2_msgs::srv::ClearEntireCostmap>
{
public:
/**
* @brief A constructor for nav2_util::ClearEntirelyCostmapServiceClient
*/
explicit ClearEntirelyCostmapServiceClient(const std::string & service_name)
: nav2_util::ServiceClient<nav2_msgs::srv::ClearEntireCostmap>(service_name)
{
}
using clearEntirelyCostmapServiceRequest =
nav2_util::ServiceClient<nav2_msgs::srv::ClearEntireCostmap>::RequestType;
using clearEntirelyCostmapServiceResponse =
nav2_util::ServiceClient<nav2_msgs::srv::ClearEntireCostmap>::ResponseType;
};
} // namespace nav2_util
#endif // NAV2_UTIL__CLEAR_ENTIRELY_COSTMAP_SERVICE_CLIENT_HPP_
@@ -0,0 +1,140 @@
// Copyright (c) 2018 Intel Corporation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef NAV2_UTIL__COSTMAP_HPP_
#define NAV2_UTIL__COSTMAP_HPP_
#include <vector>
#include <cstdint>
#include "rclcpp/rclcpp.hpp"
#include "nav2_msgs/msg/costmap.hpp"
#include "nav2_msgs/msg/costmap_meta_data.hpp"
#include "nav_msgs/msg/occupancy_grid.hpp"
namespace nav2_util
{
enum class TestCostmap
{
open_space,
bounded,
bottom_left_obstacle,
top_left_obstacle,
maze1,
maze2
};
/**
* @class nav2_util::Costmap
* @brief Class for a single layered costmap initialized from an
* occupancy grid representing the map.
*/
class Costmap
{
public:
typedef uint8_t CostValue;
/**
* @brief A constructor for nav2_util::Costmap
* @param node Ptr to a node
* @param trinary_costmap Whether the costmap should be trinary
* @param track_unknown_space Whether to track unknown space in costmap
* @param lethal_threshold The lethal space cost threshold to use
* @param unknown_cost_value Internal costmap cell value for unknown space
*/
Costmap(
rclcpp::Node * node, bool trinary_costmap = true, bool track_unknown_space = true,
int lethal_threshold = 100, int unknown_cost_value = -1);
Costmap() = delete;
~Costmap();
/**
* @brief Set the static map of this costmap
* @param occupancy_grid Occupancy grid to populate this costmap with
*/
void set_static_map(const nav_msgs::msg::OccupancyGrid & occupancy_grid);
/**
* @brief Set the test costmap type of this costmap
* @param testCostmapType Type of stored costmap to use
*/
void set_test_costmap(const TestCostmap & testCostmapType);
/**
* @brief Get a costmap message from this object
* @param specifications Parameters of costmap
* @return Costmap msg of this costmap
*/
nav2_msgs::msg::Costmap get_costmap(const nav2_msgs::msg::CostmapMetaData & specifications);
/**
* @brief Get a metadata message from this object
* @return Costmap metadata of this costmap
*/
nav2_msgs::msg::CostmapMetaData get_properties() {return costmap_properties_;}
/**
* @brief Get whether some coordinates are free
* @return bool if free
*/
bool is_free(const unsigned int x_coordinate, const unsigned int y_coordinate) const;
/**
* @brief Get whether some index in the costmap is free
* @return bool if free
*/
bool is_free(const unsigned int index) const;
// Mapping for often used cost values
static const CostValue no_information;
static const CostValue lethal_obstacle;
static const CostValue inscribed_inflated_obstacle;
static const CostValue medium_cost;
static const CostValue free_space;
private:
/**
* @brief Get data from the test
* @return data
*/
std::vector<uint8_t> get_test_data(const TestCostmap configuration);
/**
* @brief Get the interpreted value in the costmap
* @return uint value
*/
uint8_t interpret_value(const int8_t value) const;
// Costmap isn't itself a node
rclcpp::Node * node_;
// TODO(orduno): For now, only holding costs from static map
nav2_msgs::msg::CostmapMetaData costmap_properties_;
std::vector<uint8_t> costs_;
// Static layer parameters
bool trinary_costmap_;
bool track_unknown_space_;
int lethal_threshold_;
int unknown_cost_value_;
// Flags to determine the origin of the costmap
bool map_provided_;
bool using_test_map_;
};
} // namespace nav2_util
#endif // NAV2_UTIL__COSTMAP_HPP_
@@ -0,0 +1,52 @@
// Copyright (c) 2018 Intel Corporation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef NAV2_UTIL__EXECUTION_TIMER_HPP_
#define NAV2_UTIL__EXECUTION_TIMER_HPP_
#include <chrono>
namespace nav2_util
{
/// @brief Measures execution time of code between calls to start and end
class ExecutionTimer
{
public:
using Clock = std::chrono::high_resolution_clock;
using nanoseconds = std::chrono::nanoseconds;
/// @brief Call just prior to code you want to measure
void start() {start_ = Clock::now();}
/// @brief Call just after the code you want to measure
void end() {end_ = Clock::now();}
/// @brief Extract the measured time as an integral std::chrono::duration object
nanoseconds elapsed_time() {return end_ - start_;}
/// @brief Extract the measured time as a floating point number of seconds.
double elapsed_time_in_seconds()
{
return std::chrono::duration<double>(end_ - start_).count();
}
protected:
Clock::time_point start_;
Clock::time_point end_;
};
} // namespace nav2_util
#endif // NAV2_UTIL__EXECUTION_TIMER_HPP_
@@ -0,0 +1,185 @@
// Copyright (c) 2019 Intel Corporation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef NAV2_UTIL__GEOMETRY_UTILS_HPP_
#define NAV2_UTIL__GEOMETRY_UTILS_HPP_
#include <cmath>
#include "geometry_msgs/msg/pose.hpp"
#include "geometry_msgs/msg/pose_stamped.hpp"
#include "geometry_msgs/msg/pose2_d.hpp"
#include "geometry_msgs/msg/point.hpp"
#include "geometry_msgs/msg/quaternion.hpp"
#include "tf2_geometry_msgs/tf2_geometry_msgs.hpp"
#include "nav_msgs/msg/path.hpp"
namespace nav2_util
{
namespace geometry_utils
{
/**
* @brief Get a geometry_msgs Quaternion from a yaw angle
* @param angle Yaw angle to generate a quaternion from
* @return geometry_msgs Quaternion
*/
inline geometry_msgs::msg::Quaternion orientationAroundZAxis(double angle)
{
tf2::Quaternion q;
q.setRPY(0, 0, angle); // void returning function
return tf2::toMsg(q);
}
/**
* @brief Get the euclidean distance between 2 geometry_msgs::Points
* @param pos1 First point
* @param pos1 Second point
* @param is_3d True if a true L2 distance is desired (default false)
* @return double L2 distance
*/
inline double euclidean_distance(
const geometry_msgs::msg::Point & pos1,
const geometry_msgs::msg::Point & pos2,
const bool is_3d = false)
{
double dx = pos1.x - pos2.x;
double dy = pos1.y - pos2.y;
if (is_3d) {
double dz = pos1.z - pos2.z;
return std::hypot(dx, dy, dz);
}
return std::hypot(dx, dy);
}
/**
* @brief Get the L2 distance between 2 geometry_msgs::Poses
* @param pos1 First pose
* @param pos1 Second pose
* @param is_3d True if a true L2 distance is desired (default false)
* @return double euclidean distance
*/
inline double euclidean_distance(
const geometry_msgs::msg::Pose & pos1,
const geometry_msgs::msg::Pose & pos2,
const bool is_3d = false)
{
double dx = pos1.position.x - pos2.position.x;
double dy = pos1.position.y - pos2.position.y;
if (is_3d) {
double dz = pos1.position.z - pos2.position.z;
return std::hypot(dx, dy, dz);
}
return std::hypot(dx, dy);
}
/**
* @brief Get the L2 distance between 2 geometry_msgs::PoseStamped
* @param pos1 First pose
* @param pos1 Second pose
* @param is_3d True if a true L2 distance is desired (default false)
* @return double L2 distance
*/
inline double euclidean_distance(
const geometry_msgs::msg::PoseStamped & pos1,
const geometry_msgs::msg::PoseStamped & pos2,
const bool is_3d = false)
{
return euclidean_distance(pos1.pose, pos2.pose, is_3d);
}
/**
* @brief Get the L2 distance between 2 geometry_msgs::Pose2D
* @param pos1 First pose
* @param pos1 Second pose
* @return double L2 distance
*/
inline double euclidean_distance(
const geometry_msgs::msg::Pose2D & pos1,
const geometry_msgs::msg::Pose2D & pos2)
{
double dx = pos1.x - pos2.x;
double dy = pos1.y - pos2.y;
return std::hypot(dx, dy);
}
/**
* Find element in iterator with the minimum calculated value
*/
template<typename Iter, typename Getter>
inline Iter min_by(Iter begin, Iter end, Getter getCompareVal)
{
if (begin == end) {
return end;
}
auto lowest = getCompareVal(*begin);
Iter lowest_it = begin;
for (Iter it = ++begin; it != end; ++it) {
auto comp = getCompareVal(*it);
if (comp < lowest) {
lowest = comp;
lowest_it = it;
}
}
return lowest_it;
}
/**
* Find first element in iterator that is greater integrated distance than comparevalue
*/
template<typename Iter, typename Getter>
inline Iter first_after_integrated_distance(Iter begin, Iter end, Getter getCompareVal)
{
if (begin == end) {
return end;
}
Getter dist = 0.0;
for (Iter it = begin; it != end - 1; it++) {
dist += euclidean_distance(*it, *(it + 1));
if (dist > getCompareVal) {
return it + 1;
}
}
return end;
}
/**
* @brief Calculate the length of the provided path, starting at the provided index
* @param path Path containing the poses that are planned
* @param start_index Optional argument specifying the starting index for
* the calculation of path length. Provide this if you want to calculate length of a
* subset of the path.
* @return double Path length
*/
inline double calculate_path_length(const nav_msgs::msg::Path & path, size_t start_index = 0)
{
if (start_index + 1 >= path.poses.size()) {
return 0.0;
}
double path_length = 0.0;
for (size_t idx = start_index; idx < path.poses.size() - 1; ++idx) {
path_length += euclidean_distance(path.poses[idx].pose, path.poses[idx + 1].pose);
}
return path_length;
}
} // namespace geometry_utils
} // namespace nav2_util
#endif // NAV2_UTIL__GEOMETRY_UTILS_HPP_
@@ -0,0 +1,212 @@
// Copyright (c) 2019 Intel Corporation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef NAV2_UTIL__LIFECYCLE_NODE_HPP_
#define NAV2_UTIL__LIFECYCLE_NODE_HPP_
#include <memory>
#include <string>
#include <thread>
#include "nav2_util/node_thread.hpp"
#include "rclcpp_lifecycle/lifecycle_node.hpp"
#include "rclcpp/rclcpp.hpp"
#include "bondcpp/bond.hpp"
#include "bond/msg/constants.hpp"
namespace nav2_util
{
using CallbackReturn = rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn;
/**
* @class nav2_util::LifecycleNode
* @brief A lifecycle node wrapper to enable common Nav2 needs such as manipulating parameters
*/
class LifecycleNode : public rclcpp_lifecycle::LifecycleNode
{
public:
/**
* @brief A lifecycle node constructor
* @param node_name Name for the node
* @param namespace Namespace for the node, if any
* @param options Node options
*/
LifecycleNode(
const std::string & node_name,
const std::string & ns = "",
const rclcpp::NodeOptions & options = rclcpp::NodeOptions());
virtual ~LifecycleNode();
typedef struct
{
double from_value;
double to_value;
double step;
} floating_point_range;
typedef struct
{
int from_value;
int to_value;
int step;
} integer_range;
/**
* @brief Declare a parameter that has no integer or floating point range constraints
* @param node_name Name of parameter
* @param default_value Default node value to add
* @param description Node description
* @param additional_constraints Any additional constraints on the parameters to list
* @param read_only Whether this param should be considered read only
*/
void add_parameter(
const std::string & name, const rclcpp::ParameterValue & default_value,
const std::string & description = "", const std::string & additional_constraints = "",
bool read_only = false)
{
auto descriptor = rcl_interfaces::msg::ParameterDescriptor();
descriptor.name = name;
descriptor.description = description;
descriptor.additional_constraints = additional_constraints;
descriptor.read_only = read_only;
declare_parameter(descriptor.name, default_value, descriptor);
}
/**
* @brief Declare a parameter that has a floating point range constraint
* @param node_name Name of parameter
* @param default_value Default node value to add
* @param fp_range floating point range
* @param description Node description
* @param additional_constraints Any additional constraints on the parameters to list
* @param read_only Whether this param should be considered read only
*/
void add_parameter(
const std::string & name, const rclcpp::ParameterValue & default_value,
const floating_point_range fp_range,
const std::string & description = "", const std::string & additional_constraints = "",
bool read_only = false)
{
auto descriptor = rcl_interfaces::msg::ParameterDescriptor();
descriptor.name = name;
descriptor.description = description;
descriptor.additional_constraints = additional_constraints;
descriptor.read_only = read_only;
descriptor.floating_point_range.resize(1);
descriptor.floating_point_range[0].from_value = fp_range.from_value;
descriptor.floating_point_range[0].to_value = fp_range.to_value;
descriptor.floating_point_range[0].step = fp_range.step;
declare_parameter(descriptor.name, default_value, descriptor);
}
/**
* @brief Declare a parameter that has an integer range constraint
* @param node_name Name of parameter
* @param default_value Default node value to add
* @param integer_range Integer range
* @param description Node description
* @param additional_constraints Any additional constraints on the parameters to list
* @param read_only Whether this param should be considered read only
*/
void add_parameter(
const std::string & name, const rclcpp::ParameterValue & default_value,
const integer_range int_range,
const std::string & description = "", const std::string & additional_constraints = "",
bool read_only = false)
{
auto descriptor = rcl_interfaces::msg::ParameterDescriptor();
descriptor.name = name;
descriptor.description = description;
descriptor.additional_constraints = additional_constraints;
descriptor.read_only = read_only;
descriptor.integer_range.resize(1);
descriptor.integer_range[0].from_value = int_range.from_value;
descriptor.integer_range[0].to_value = int_range.to_value;
descriptor.integer_range[0].step = int_range.step;
declare_parameter(descriptor.name, default_value, descriptor);
}
/**
* @brief Get a shared pointer of this
*/
std::shared_ptr<nav2_util::LifecycleNode> shared_from_this()
{
return std::static_pointer_cast<nav2_util::LifecycleNode>(
rclcpp_lifecycle::LifecycleNode::shared_from_this());
}
/**
* @brief Abstracted on_error state transition callback, since unimplemented as of 2020
* in the managed ROS2 node state machine
* @param state State prior to error transition
* @return Return type for success or failed transition to error state
*/
nav2_util::CallbackReturn on_error(const rclcpp_lifecycle::State & /*state*/)
{
RCLCPP_FATAL(
get_logger(),
"Lifecycle node %s does not have error state implemented", get_name());
return nav2_util::CallbackReturn::SUCCESS;
}
/**
* @brief Perform preshutdown activities before our Context is shutdown.
* Note that this is related to our Context's shutdown sequence, not the
* lifecycle node state machine.
*/
virtual void on_rcl_preshutdown();
/**
* @brief Create bond connection to lifecycle manager
*/
void createBond();
/**
* @brief Destroy bond connection to lifecycle manager
*/
void destroyBond();
protected:
/**
* @brief Print notifications for lifecycle node
*/
void printLifecycleNodeNotification();
/**
* Register our preshutdown callback for this Node's rcl Context.
* The callback fires before this Node's Context is shutdown.
* Note this is not directly related to the lifecycle state machine.
*/
void register_rcl_preshutdown_callback();
std::unique_ptr<rclcpp::PreShutdownCallbackHandle> rcl_preshutdown_cb_handle_{nullptr};
/**
* Run some common cleanup steps shared between rcl preshutdown and destruction.
*/
void runCleanups();
// Connection to tell that server is still up
std::unique_ptr<bond::Bond> bond_{nullptr};
};
} // namespace nav2_util
#endif // NAV2_UTIL__LIFECYCLE_NODE_HPP_
@@ -0,0 +1,64 @@
// Copyright (c) 2019 Intel Corporation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef NAV2_UTIL__LIFECYCLE_SERVICE_CLIENT_HPP_
#define NAV2_UTIL__LIFECYCLE_SERVICE_CLIENT_HPP_
#include <chrono>
#include <memory>
#include <string>
#include "lifecycle_msgs/srv/change_state.hpp"
#include "lifecycle_msgs/srv/get_state.hpp"
#include "nav2_util/service_client.hpp"
#include "nav2_util/node_utils.hpp"
namespace nav2_util
{
/// Helper functions to interact with a lifecycle node.
class LifecycleServiceClient
{
public:
explicit LifecycleServiceClient(const std::string & lifecycle_node_name);
LifecycleServiceClient(
const std::string & lifecycle_node_name,
rclcpp::Node::SharedPtr parent_node);
/// Trigger a state change
/**
* Throws std::runtime_error on failure
*/
bool change_state(
const uint8_t transition, // takes a lifecycle_msgs::msg::Transition id
const std::chrono::seconds timeout);
/// Trigger a state change, returning result
bool change_state(std::uint8_t transition);
/// Get the current state as a lifecycle_msgs::msg::State id value
/**
* Throws std::runtime_error on failure
*/
uint8_t get_state(const std::chrono::seconds timeout = std::chrono::seconds(2));
protected:
rclcpp::Node::SharedPtr node_;
ServiceClient<lifecycle_msgs::srv::ChangeState> change_state_;
ServiceClient<lifecycle_msgs::srv::GetState> get_state_;
};
} // namespace nav2_util
#endif // NAV2_UTIL__LIFECYCLE_SERVICE_CLIENT_HPP_
@@ -0,0 +1,82 @@
// Copyright (c) 2018 Intel Corporation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef NAV2_UTIL__LIFECYCLE_UTILS_HPP_
#define NAV2_UTIL__LIFECYCLE_UTILS_HPP_
#include <vector>
#include <string>
#include <chrono>
#include "nav2_util/string_utils.hpp"
namespace nav2_util
{
/// Transition the given lifecycle nodes to the ACTIVATED state in order
/** At this time, service calls frequently hang for unknown reasons. The only
* way to combat that is to timeout the service call and retry it. To use this
* function, estimate how long your nodes should take to at each transition and
* set your timeout accordingly.
* \param[in] node_names A vector of the fully qualified node names to startup.
* \param[in] service_call_timeout The maximum amount of time to wait for a
* service call.
* \param[in] retries The number of times to try a state transition service call
*/
void startup_lifecycle_nodes(
const std::vector<std::string> & node_names,
const std::chrono::seconds service_call_timeout = std::chrono::seconds::max(),
const int retries = 3);
/// Transition the given lifecycle nodes to the ACTIVATED state in order.
/**
* \param[in] nodes A ':' seperated list of node names. eg. "/node1:/node2"
*/
void startup_lifecycle_nodes(
const std::string & nodes,
const std::chrono::seconds service_call_timeout = std::chrono::seconds::max(),
const int retries = 3)
{
startup_lifecycle_nodes(split(nodes, ':'), service_call_timeout, retries);
}
/// Transition the given lifecycle nodes to the UNCONFIGURED state in order
/** At this time, service calls frequently hang for unknown reasons. The only
* way to combat that is to timeout the service call and retry it. To use this
* function, estimate how long your nodes should take to at each transition and
* set your timeout accordingly.
* \param[in] node_names A vector of the fully qualified node names to reset.
* \param[in] service_call_timeout The maximum amount of time to wait for a
* service call.
* \param[in] retries The number of times to try a state transition service call
*/
void reset_lifecycle_nodes(
const std::vector<std::string> & node_names,
const std::chrono::seconds service_call_timeout = std::chrono::seconds::max(),
const int retries = 3);
/// Transition the given lifecycle nodes to the UNCONFIGURED state in order.
/**
* \param[in] nodes A ':' seperated list of node names. eg. "/node1:/node2"
*/
void reset_lifecycle_nodes(
const std::string & nodes,
const std::chrono::seconds service_call_timeout = std::chrono::seconds::max(),
const int retries = 3)
{
reset_lifecycle_nodes(split(nodes, ':'), service_call_timeout, retries);
}
} // namespace nav2_util
#endif // NAV2_UTIL__LIFECYCLE_UTILS_HPP_
@@ -0,0 +1,200 @@
// Copyright (c) 2012, Willow Garage, Inc.
// All rights reserved.
//
// Software License Agreement (BSD License 2.0)
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following
// disclaimer in the documentation and/or other materials provided
// with the distribution.
// * Neither the name of the Willow Garage, Inc. nor the names of its
// contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
// COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
#ifndef NAV2_UTIL__LINE_ITERATOR_HPP_
#define NAV2_UTIL__LINE_ITERATOR_HPP_
#include <stdlib.h>
namespace nav2_util
{
/**
* @class nav2_util::LineIterator
* @brief An iterator implementing Bresenham Ray-Tracing.
*/
class LineIterator
{
public:
/**
* @brief A constructor for LineIterator
* @param x0 Starting x
* @param y0 Starting y
* @param x1 Ending x
* @param y1 Ending y
*/
LineIterator(int x0, int y0, int x1, int y1)
: x0_(x0),
y0_(y0),
x1_(x1),
y1_(y1),
x_(x0), // X and Y start of at first endpoint.
y_(y0),
deltax_(abs(x1 - x0)),
deltay_(abs(y1 - y0)),
curpixel_(0)
{
if (x1_ >= x0_) { // The x-values are increasing
xinc1_ = 1;
xinc2_ = 1;
} else { // The x-values are decreasing
xinc1_ = -1;
xinc2_ = -1;
}
if (y1_ >= y0_) { // The y-values are increasing
yinc1_ = 1;
yinc2_ = 1;
} else { // The y-values are decreasing
yinc1_ = -1;
yinc2_ = -1;
}
if (deltax_ >= deltay_) { // There is at least one x-value for every y-value
xinc1_ = 0; // Don't change the x when numerator >= denominator
yinc2_ = 0; // Don't change the y for every iteration
den_ = deltax_;
num_ = deltax_ / 2;
numadd_ = deltay_;
numpixels_ = deltax_; // There are more x-values than y-values
} else { // There is at least one y-value for every x-value
xinc2_ = 0; // Don't change the x for every iteration
yinc1_ = 0; // Don't change the y when numerator >= denominator
den_ = deltay_;
num_ = deltay_ / 2;
numadd_ = deltax_;
numpixels_ = deltay_; // There are more y-values than x-values
}
}
/**
* @brief If the iterator is valid
* @return bool If valid
*/
bool isValid() const
{
return curpixel_ <= numpixels_;
}
/**
* @brief Advance iteration along the line
*/
void advance()
{
num_ += numadd_; // Increase the numerator by the top of the fraction
if (num_ >= den_) { // Check if numerator >= denominator
num_ -= den_; // Calculate the new numerator value
x_ += xinc1_; // Change the x as appropriate
y_ += yinc1_; // Change the y as appropriate
}
x_ += xinc2_; // Change the x as appropriate
y_ += yinc2_; // Change the y as appropriate
curpixel_++;
}
/**
* @brief Get current X value
* @return X
*/
int getX() const
{
return x_;
}
/**
* @brief Get current Y value
* @return Y
*/
int getY() const
{
return y_;
}
/**
* @brief Get initial X value
* @return X
*/
int getX0() const
{
return x0_;
}
/**
* @brief Get initial Y value
* @return Y
*/
int getY0() const
{
return y0_;
}
/**
* @brief Get terminal X value
* @return X
*/
int getX1() const
{
return x1_;
}
/**
* @brief Get terminal Y value
* @return Y
*/
int getY1() const
{
return y1_;
}
private:
int x0_; ///< X coordinate of first end point.
int y0_; ///< Y coordinate of first end point.
int x1_; ///< X coordinate of second end point.
int y1_; ///< Y coordinate of second end point.
int x_; ///< X coordinate of current point.
int y_; ///< Y coordinate of current point.
int deltax_; ///< Difference between Xs of endpoints.
int deltay_; ///< Difference between Ys of endpoints.
int curpixel_; ///< index of current point in line loop.
int xinc1_, xinc2_, yinc1_, yinc2_;
int den_, num_, numadd_, numpixels_;
};
} // end namespace nav2_util
#endif // NAV2_UTIL__LINE_ITERATOR_HPP_
@@ -0,0 +1,65 @@
// Copyright (c) 2019 Intel Corporation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef NAV2_UTIL__NODE_THREAD_HPP_
#define NAV2_UTIL__NODE_THREAD_HPP_
#include <memory>
#include "rclcpp/rclcpp.hpp"
namespace nav2_util
{
/**
* @class nav2_util::NodeThread
* @brief A background thread to process node/executor callbacks
*/
class NodeThread
{
public:
/**
* @brief A background thread to process node callbacks constructor
* @param node_base Interface to Node to spin in thread
*/
explicit NodeThread(rclcpp::node_interfaces::NodeBaseInterface::SharedPtr node_base);
/**
* @brief A background thread to process executor's callbacks constructor
* @param executor Interface to executor to spin in thread
*/
explicit NodeThread(rclcpp::executors::SingleThreadedExecutor::SharedPtr executor);
/**
* @brief A background thread to process node callbacks constructor
* @param node Node pointer to spin in thread
*/
template<typename NodeT>
explicit NodeThread(NodeT node)
: NodeThread(node->get_node_base_interface())
{}
/**
* @brief A destructor
*/
~NodeThread();
protected:
rclcpp::node_interfaces::NodeBaseInterface::SharedPtr node_;
std::unique_ptr<std::thread> thread_;
rclcpp::Executor::SharedPtr executor_;
};
} // namespace nav2_util
#endif // NAV2_UTIL__NODE_THREAD_HPP_
@@ -0,0 +1,176 @@
// Copyright (c) 2019 Intel Corporation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef NAV2_UTIL__NODE_UTILS_HPP_
#define NAV2_UTIL__NODE_UTILS_HPP_
#include <vector>
#include <string>
#include "rclcpp/rclcpp.hpp"
#include "rcl_interfaces/srv/list_parameters.hpp"
namespace nav2_util
{
/// Replace invalid characters in a potential node name
/**
* There is frequently a need to create internal nodes. They must have a name,
* and commonly the name is based on some parameter related to the node's
* purpose. However, only alphanumeric characters and '_' are allowed in node
* names. This function replaces any invalid character with a '_'
*
* \param[in] potential_node_name Potential name but possibly with invalid charaters.
* \return A copy of the input string but with non-alphanumeric characters replaced with '_'
*/
std::string sanitize_node_name(const std::string & potential_node_name);
/// Concatenate two namespaces to produce an absolute namespace
/**
* \param[in] top_ns The namespace to place first
* \param[in] sub_ns The namespace to place after top_ns
* \return An absolute namespace starting with "/"
*/
std::string add_namespaces(const std::string & top_ns, const std::string & sub_ns = "");
/// Add some random characters to a node name to ensure it is unique in the system
/**
* There are utility classes that create an internal private node to interact
* with the system. These private nodes are given a generated name. If multiple
* clients end up using the same service, there is the potential for node name
* conflicts. To ensure node names are globally unique, this appends some random
* numbers to the end of the prefix.
*
* \param[in] prefix A string to help understand the purpose of the node.
* \return A copy of the prefix + '_' + 8 random digits. eg. prefix_12345678
*/
std::string generate_internal_node_name(const std::string & prefix = "");
/// Creates a node with a name as generated by generate_internal_node_name
/**
* Creates a node with the following settings:
* - name generated by generate_internal_node_name
* - no parameter services
* - no parameter event publisher
*
* \param[in] prefix A string to help understand the purpose of the node.
* \return A shared_ptr to the node.
*/
rclcpp::Node::SharedPtr generate_internal_node(const std::string & prefix = "");
/// Generates a pseudo random string of digits.
/**
* Generates pseudo random digits by converting the current system time to a
* string. This means that any length more than 8 or so digits will just get
* padded with zeros and doesn't add any additional randomness.
*
* \param[in] len Length of the output string
* \return A string containing random digits
*/
std::string time_to_string(size_t len);
/// Declares static ROS2 parameter and sets it to a given value if it was not already declared
/* Declares static ROS2 parameter and sets it to a given value
* if it was not already declared.
*
* \param[in] node A node in which given parameter to be declared
* \param[in] param_name The name of parameter
* \param[in] default_value Parameter value to initialize with
* \param[in] parameter_descriptor Parameter descriptor (optional)
*/
template<typename NodeT>
void declare_parameter_if_not_declared(
NodeT node,
const std::string & param_name,
const rclcpp::ParameterValue & default_value,
const rcl_interfaces::msg::ParameterDescriptor & parameter_descriptor =
rcl_interfaces::msg::ParameterDescriptor())
{
if (!node->has_parameter(param_name)) {
node->declare_parameter(param_name, default_value, parameter_descriptor);
}
}
/// Declares static ROS2 parameter with given type if it was not already declared
/* Declares static ROS2 parameter with given type if it was not already declared.
*
* \param[in] node A node in which given parameter to be declared
* \param[in] param_type The type of parameter
* \param[in] default_value Parameter value to initialize with
* \param[in] parameter_descriptor Parameter descriptor (optional)
*/
template<typename NodeT>
void declare_parameter_if_not_declared(
NodeT node,
const std::string & param_name,
const rclcpp::ParameterType & param_type,
const rcl_interfaces::msg::ParameterDescriptor & parameter_descriptor =
rcl_interfaces::msg::ParameterDescriptor())
{
if (!node->has_parameter(param_name)) {
node->declare_parameter(param_name, param_type, parameter_descriptor);
}
}
/// Gets the type of plugin for the selected node and its plugin
/**
* Gets the type of plugin for the selected node and its plugin.
* Actually seeks for the value of "<plugin_name>.plugin" parameter.
*
* \param[in] node Selected node
* \param[in] plugin_name The name of plugin the type of which is being searched for
* \return A string containing the type of plugin (the value of "<plugin_name>.plugin" parameter)
*/
template<typename NodeT>
std::string get_plugin_type_param(
NodeT node,
const std::string & plugin_name)
{
declare_parameter_if_not_declared(node, plugin_name + ".plugin", rclcpp::PARAMETER_STRING);
std::string plugin_type;
try {
if (!node->get_parameter(plugin_name + ".plugin", plugin_type)) {
RCLCPP_FATAL(
node->get_logger(), "Can not get 'plugin' param value for %s", plugin_name.c_str());
exit(-1);
}
} catch (rclcpp::exceptions::ParameterUninitializedException & ex) {
RCLCPP_FATAL(node->get_logger(), "'plugin' param not defined for %s", plugin_name.c_str());
exit(-1);
}
return plugin_type;
}
/**
* @brief A method to copy all parameters from one node (parent) to another (child).
* May throw parameter exceptions in error conditions
* @param parent Node to copy parameters from
* @param child Node to copy parameters to
*/
template<typename NodeT1, typename NodeT2>
void copy_all_parameters(const NodeT1 & parent, const NodeT2 & child)
{
using Parameters = std::vector<rclcpp::Parameter>;
std::vector<std::string> param_names = parent->list_parameters({}, 0).names;
Parameters params = parent->get_parameters(param_names);
for (Parameters::const_iterator iter = params.begin(); iter != params.end(); ++iter) {
if (!child->has_parameter(iter->get_name())) {
child->declare_parameter(iter->get_name(), iter->get_parameter_value());
}
}
}
} // namespace nav2_util
#endif // NAV2_UTIL__NODE_UTILS_HPP_
@@ -0,0 +1,50 @@
// Copyright (c) 2020 Samsung Research Russia
// All rights reserved.
//
// Software License Agreement (BSD License 2.0)
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following
// disclaimer in the documentation and/or other materials provided
// with the distribution.
// * Neither the name of the <ORGANIZATION> nor the names of its
// contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
// COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//
// Author: Alexey Merzlyakov
#ifndef NAV2_UTIL__OCC_GRID_VALUES_HPP_
#define NAV2_UTIL__OCC_GRID_VALUES_HPP_
namespace nav2_util
{
/**
* @brief OccupancyGrid data constants
*/
static constexpr int8_t OCC_GRID_UNKNOWN = -1;
static constexpr int8_t OCC_GRID_FREE = 0;
static constexpr int8_t OCC_GRID_OCCUPIED = 100;
} // namespace nav2_util
#endif // NAV2_UTIL__OCC_GRID_VALUES_HPP_
@@ -0,0 +1,102 @@
// Copyright (c) 2018 Intel Corporation
// Copyright (c) 2020 Sarthak Mittal
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef NAV2_UTIL__ODOMETRY_UTILS_HPP_
#define NAV2_UTIL__ODOMETRY_UTILS_HPP_
#include <cmath>
#include <chrono>
#include <memory>
#include <mutex>
#include <string>
#include <deque>
#include "geometry_msgs/msg/twist.hpp"
#include "geometry_msgs/msg/twist_stamped.hpp"
#include "nav_msgs/msg/odometry.hpp"
#include "nav2_util/lifecycle_node.hpp"
#include "rclcpp/rclcpp.hpp"
#include "nav2_util/node_utils.hpp"
namespace nav2_util
{
/**
* @class OdomSmoother
* Wrapper for getting smooth odometry readings using a simple moving avergae.
* Subscribes to the topic with a mutex.
*/
class OdomSmoother
{
public:
/**
* @brief Constructor that subscribes to an Odometry topic
* @param parent NodeHandle for creating subscriber
* @param filter_duration Duration for odom history (seconds)
* @param odom_topic Topic on which odometry should be received
*/
explicit OdomSmoother(
const rclcpp::Node::WeakPtr & parent,
double filter_duration = 0.3,
const std::string & odom_topic = "odom");
/**
* @brief Overloadded Constructor for nav_util::LifecycleNode parent
* that subscribes to an Odometry topic
* @param parent NodeHandle for creating subscriber
* @param filter_duration Duration for odom history (seconds)
* @param odom_topic Topic on which odometry should be received
*/
explicit OdomSmoother(
const nav2_util::LifecycleNode::WeakPtr & parent,
double filter_duration = 0.3,
const std::string & odom_topic = "odom");
/**
* @brief Get twist msg from smoother
* @return twist Twist msg
*/
inline geometry_msgs::msg::Twist getTwist() {return vel_smooth_.twist;}
/**
* @brief Get twist stamped msg from smoother
* @return twist TwistStamped msg
*/
inline geometry_msgs::msg::TwistStamped getTwistStamped() {return vel_smooth_;}
protected:
/**
* @brief Callback of odometry subscriber to process
* @param msg Odometry msg to smooth
*/
void odomCallback(nav_msgs::msg::Odometry::SharedPtr msg);
/**
* @brief Update internal state of the smoother after getting new data
*/
void updateState();
rclcpp::Subscription<nav_msgs::msg::Odometry>::SharedPtr odom_sub_;
nav_msgs::msg::Odometry odom_cumulate_;
geometry_msgs::msg::TwistStamped vel_smooth_;
std::mutex odom_mutex_;
rclcpp::Duration odom_history_duration_;
std::deque<nav_msgs::msg::Odometry> odom_history_;
};
} // namespace nav2_util
#endif // NAV2_UTIL__ODOMETRY_UTILS_HPP_
@@ -0,0 +1,121 @@
// Copyright (c) 2018 Intel Corporation
// Copyright (c) 2019 Steven Macenski
// Copyright (c) 2019 Samsung Research America
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef NAV2_UTIL__ROBOT_UTILS_HPP_
#define NAV2_UTIL__ROBOT_UTILS_HPP_
#include <string>
#include "geometry_msgs/msg/pose_stamped.hpp"
#include "geometry_msgs/msg/twist.hpp"
#include "tf2/time.h"
#include "tf2_ros/buffer.h"
#include "tf2_geometry_msgs/tf2_geometry_msgs.hpp"
#include "rclcpp/rclcpp.hpp"
namespace nav2_util
{
/**
* @brief get the current pose of the robot
* @param global_pose Pose to transform
* @param tf_buffer TF buffer to use for the transformation
* @param global_frame Frame to transform into
* @param robot_frame Frame to transform from
* @param transform_timeout TF Timeout to use for transformation
* @return bool Whether it could be transformed successfully
*/
bool getCurrentPose(
geometry_msgs::msg::PoseStamped & global_pose,
tf2_ros::Buffer & tf_buffer, const std::string global_frame = "map",
const std::string robot_frame = "base_link", const double transform_timeout = 0.1,
const rclcpp::Time stamp = rclcpp::Time());
/**
* @brief get an arbitrary pose in a target frame
* @param input_pose Pose to transform
* @param transformed_pose Output transformation
* @param tf_buffer TF buffer to use for the transformation
* @param target_frame Frame to transform into
* @param transform_timeout TF Timeout to use for transformation
* @return bool Whether it could be transformed successfully
*/
bool transformPoseInTargetFrame(
const geometry_msgs::msg::PoseStamped & input_pose,
geometry_msgs::msg::PoseStamped & transformed_pose,
tf2_ros::Buffer & tf_buffer, const std::string target_frame,
const double transform_timeout = 0.1);
/**
* @brief Obtains a transform from source_frame_id at source_time ->
* to target_frame_id at target_time time
* @param source_frame_id Source frame ID to convert from
* @param source_time Source timestamp to convert from
* @param target_frame_id Target frame ID to convert to
* @param target_time Target time to interpolate to
* @param transform_tolerance Transform tolerance
* @param tf_transform Output source->target transform
* @return True if got correct transform, otherwise false
*/
/**
* @brief Obtains a transform from source_frame_id -> to target_frame_id
* @param source_frame_id Source frame ID to convert from
* @param target_frame_id Target frame ID to convert to
* @param transform_tolerance Transform tolerance
* @param tf_buffer TF buffer to use for the transformation
* @param tf_transform Output source->target transform
* @return True if got correct transform, otherwise false
*/
bool getTransform(
const std::string & source_frame_id,
const std::string & target_frame_id,
const tf2::Duration & transform_tolerance,
const std::shared_ptr<tf2_ros::Buffer> tf_buffer,
tf2::Transform & tf2_transform);
/**
* @brief Obtains a transform from source_frame_id at source_time ->
* to target_frame_id at target_time time
* @param source_frame_id Source frame ID to convert from
* @param source_time Source timestamp to convert from
* @param target_frame_id Target frame ID to convert to
* @param target_time Current node time to interpolate to
* @param fixed_frame_id The frame in which to assume the transform is constant in time
* @param transform_tolerance Transform tolerance
* @param tf_buffer TF buffer to use for the transformation
* @param tf_transform Output source->target transform
* @return True if got correct transform, otherwise false
*/
bool getTransform(
const std::string & source_frame_id,
const rclcpp::Time & source_time,
const std::string & target_frame_id,
const rclcpp::Time & target_time,
const std::string & fixed_frame_id,
const tf2::Duration & transform_tolerance,
const std::shared_ptr<tf2_ros::Buffer> tf_buffer,
tf2::Transform & tf2_transform);
/**
* @brief Validates a twist message contains no nans or infs
* @param msg Twist message to validate
* @return True if valid, false if contains unactionable values
*/
bool validateTwist(const geometry_msgs::msg::Twist & msg);
} // end namespace nav2_util
#endif // NAV2_UTIL__ROBOT_UTILS_HPP_
@@ -0,0 +1,158 @@
// Copyright (c) 2018 Intel Corporation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef NAV2_UTIL__SERVICE_CLIENT_HPP_
#define NAV2_UTIL__SERVICE_CLIENT_HPP_
#include <string>
#include "rclcpp/rclcpp.hpp"
namespace nav2_util
{
/**
* @class nav2_util::ServiceClient
* @brief A simple wrapper on ROS2 services for invoke() and block-style calling
*/
template<class ServiceT>
class ServiceClient
{
public:
/**
* @brief A constructor
* @param service_name name of the service to call
* @param provided_node Node to create the service client off of
*/
explicit ServiceClient(
const std::string & service_name,
const rclcpp::Node::SharedPtr & provided_node)
: service_name_(service_name), node_(provided_node)
{
callback_group_ = node_->create_callback_group(
rclcpp::CallbackGroupType::MutuallyExclusive,
false);
callback_group_executor_.add_callback_group(callback_group_, node_->get_node_base_interface());
client_ = node_->create_client<ServiceT>(
service_name,
rclcpp::ServicesQoS().get_rmw_qos_profile(),
callback_group_);
}
using RequestType = typename ServiceT::Request;
using ResponseType = typename ServiceT::Response;
/**
* @brief Invoke the service and block until completed or timed out
* @param request The request object to call the service using
* @param timeout Maximum timeout to wait for, default infinite
* @return Response A pointer to the service response from the request
*/
typename ResponseType::SharedPtr invoke(
typename RequestType::SharedPtr & request,
const std::chrono::nanoseconds timeout = std::chrono::nanoseconds(-1))
{
while (!client_->wait_for_service(std::chrono::seconds(1))) {
if (!rclcpp::ok()) {
throw std::runtime_error(
service_name_ + " service client: interrupted while waiting for service");
}
RCLCPP_INFO(
node_->get_logger(), "%s service client: waiting for service to appear...",
service_name_.c_str());
}
RCLCPP_DEBUG(
node_->get_logger(), "%s service client: send async request",
service_name_.c_str());
auto future_result = client_->async_send_request(request);
if (callback_group_executor_.spin_until_future_complete(future_result, timeout) !=
rclcpp::FutureReturnCode::SUCCESS)
{
// Pending request must be manually cleaned up if execution is interrupted or timed out
client_->remove_pending_request(future_result);
throw std::runtime_error(service_name_ + " service client: async_send_request failed");
}
return future_result.get();
}
/**
* @brief Invoke the service and block until completed
* @param request The request object to call the service using
* @param Response A pointer to the service response from the request
* @return bool Whether it was successfully called
*/
bool invoke(
typename RequestType::SharedPtr & request,
typename ResponseType::SharedPtr & response)
{
while (!client_->wait_for_service(std::chrono::seconds(1))) {
if (!rclcpp::ok()) {
throw std::runtime_error(
service_name_ + " service client: interrupted while waiting for service");
}
RCLCPP_INFO(
node_->get_logger(), "%s service client: waiting for service to appear...",
service_name_.c_str());
}
RCLCPP_DEBUG(
node_->get_logger(), "%s service client: send async request",
service_name_.c_str());
auto future_result = client_->async_send_request(request);
if (callback_group_executor_.spin_until_future_complete(future_result) !=
rclcpp::FutureReturnCode::SUCCESS)
{
// Pending request must be manually cleaned up if execution is interrupted or timed out
client_->remove_pending_request(future_result);
return false;
}
response = future_result.get();
return response.get();
}
/**
* @brief Block until a service is available or timeout
* @param timeout Maximum timeout to wait for, default infinite
* @return bool true if service is available
*/
bool wait_for_service(const std::chrono::nanoseconds timeout = std::chrono::nanoseconds::max())
{
return client_->wait_for_service(timeout);
}
/**
* @brief Gets the service name
* @return string Service name
*/
std::string getServiceName()
{
return service_name_;
}
protected:
std::string service_name_;
rclcpp::Node::SharedPtr node_;
rclcpp::CallbackGroup::SharedPtr callback_group_;
rclcpp::executors::SingleThreadedExecutor callback_group_executor_;
typename rclcpp::Client<ServiceT>::SharedPtr client_;
};
} // namespace nav2_util
#endif // NAV2_UTIL__SERVICE_CLIENT_HPP_
@@ -0,0 +1,613 @@
// Copyright (c) 2019 Intel Corporation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef NAV2_UTIL__SIMPLE_ACTION_SERVER_HPP_
#define NAV2_UTIL__SIMPLE_ACTION_SERVER_HPP_
#include <memory>
#include <mutex>
#include <string>
#include <thread>
#include <future>
#include <chrono>
#include "rclcpp/rclcpp.hpp"
#include "rclcpp_action/rclcpp_action.hpp"
#include "nav2_util/node_thread.hpp"
namespace nav2_util
{
/**
* @class nav2_util::SimpleActionServer
* @brief An action server wrapper to make applications simpler using Actions
*/
template<typename ActionT>
class SimpleActionServer
{
public:
// Callback function to complete main work. This should itself deal with its
// own exceptions, but if for some reason one is thrown, it will be caught
// in SimpleActionServer and terminate the action itself.
typedef std::function<void ()> ExecuteCallback;
// Callback function to notify the user that an exception was thrown that
// the simple action server caught (or another failure) and the action was
// terminated. To avoid using, catch exceptions in your application such that
// the SimpleActionServer will never need to terminate based on failed action
// ExecuteCallback.
typedef std::function<void ()> CompletionCallback;
/**
* @brief An constructor for SimpleActionServer
* @param node Ptr to node to make actions
* @param action_name Name of the action to call
* @param execute_callback Execution callback function of Action
* @param server_timeout Timeout to to react to stop or preemption requests
* @param spin_thread Whether to spin with a dedicated thread internally
* @param options Options to pass to the underlying rcl_action_server_t
*/
template<typename NodeT>
explicit SimpleActionServer(
NodeT node,
const std::string & action_name,
ExecuteCallback execute_callback,
CompletionCallback completion_callback = nullptr,
std::chrono::milliseconds server_timeout = std::chrono::milliseconds(500),
bool spin_thread = false,
const rcl_action_server_options_t & options = rcl_action_server_get_default_options())
: SimpleActionServer(
node->get_node_base_interface(),
node->get_node_clock_interface(),
node->get_node_logging_interface(),
node->get_node_waitables_interface(),
action_name, execute_callback, completion_callback, server_timeout, spin_thread, options)
{}
/**
* @brief An constructor for SimpleActionServer
* @param <node interfaces> Abstract node interfaces to make actions
* @param action_name Name of the action to call
* @param execute_callback Execution callback function of Action
* @param server_timeout Timeout to to react to stop or preemption requests
* @param spin_thread Whether to spin with a dedicated thread internally
* @param options Options to pass to the underlying rcl_action_server_t
*/
explicit SimpleActionServer(
rclcpp::node_interfaces::NodeBaseInterface::SharedPtr node_base_interface,
rclcpp::node_interfaces::NodeClockInterface::SharedPtr node_clock_interface,
rclcpp::node_interfaces::NodeLoggingInterface::SharedPtr node_logging_interface,
rclcpp::node_interfaces::NodeWaitablesInterface::SharedPtr node_waitables_interface,
const std::string & action_name,
ExecuteCallback execute_callback,
CompletionCallback completion_callback = nullptr,
std::chrono::milliseconds server_timeout = std::chrono::milliseconds(500),
bool spin_thread = false,
const rcl_action_server_options_t & options = rcl_action_server_get_default_options())
: node_base_interface_(node_base_interface),
node_clock_interface_(node_clock_interface),
node_logging_interface_(node_logging_interface),
node_waitables_interface_(node_waitables_interface),
action_name_(action_name),
execute_callback_(execute_callback),
completion_callback_(completion_callback),
server_timeout_(server_timeout),
spin_thread_(spin_thread)
{
using namespace std::placeholders; // NOLINT
if (spin_thread_) {
callback_group_ = node_base_interface->create_callback_group(
rclcpp::CallbackGroupType::MutuallyExclusive, false);
}
action_server_ = rclcpp_action::create_server<ActionT>(
node_base_interface_,
node_clock_interface_,
node_logging_interface_,
node_waitables_interface_,
action_name_,
std::bind(&SimpleActionServer::handle_goal, this, _1, _2),
std::bind(&SimpleActionServer::handle_cancel, this, _1),
std::bind(&SimpleActionServer::handle_accepted, this, _1),
options,
callback_group_);
if (spin_thread_) {
executor_ = std::make_shared<rclcpp::executors::SingleThreadedExecutor>();
executor_->add_callback_group(callback_group_, node_base_interface_);
executor_thread_ = std::make_unique<nav2_util::NodeThread>(executor_);
}
}
/**
* @brief handle the goal requested: accept or reject. This implementation always accepts.
* @param uuid Goal ID
* @param Goal A shared pointer to the specific goal
* @return GoalResponse response of the goal processed
*/
rclcpp_action::GoalResponse handle_goal(
const rclcpp_action::GoalUUID & /*uuid*/,
std::shared_ptr<const typename ActionT::Goal>/*goal*/)
{
std::lock_guard<std::recursive_mutex> lock(update_mutex_);
if (!server_active_) {
return rclcpp_action::GoalResponse::REJECT;
}
debug_msg("Received request for goal acceptance");
return rclcpp_action::GoalResponse::ACCEPT_AND_EXECUTE;
}
/**
* @brief Accepts cancellation requests of action server.
* @param uuid Goal ID
* @param Goal A server goal handle to cancel
* @return CancelResponse response of the goal cancelled
*/
rclcpp_action::CancelResponse handle_cancel(
const std::shared_ptr<rclcpp_action::ServerGoalHandle<ActionT>> handle)
{
std::lock_guard<std::recursive_mutex> lock(update_mutex_);
if (!handle->is_active()) {
warn_msg(
"Received request for goal cancellation,"
"but the handle is inactive, so reject the request");
return rclcpp_action::CancelResponse::REJECT;
}
debug_msg("Received request for goal cancellation");
return rclcpp_action::CancelResponse::ACCEPT;
}
/**
* @brief Handles accepted goals and adds to preempted queue to switch to
* @param Goal A server goal handle to cancel
*/
void handle_accepted(const std::shared_ptr<rclcpp_action::ServerGoalHandle<ActionT>> handle)
{
std::lock_guard<std::recursive_mutex> lock(update_mutex_);
debug_msg("Receiving a new goal");
if (is_active(current_handle_) || is_running()) {
debug_msg("An older goal is active, moving the new goal to a pending slot.");
if (is_active(pending_handle_)) {
debug_msg(
"The pending slot is occupied."
" The previous pending goal will be terminated and replaced.");
terminate(pending_handle_);
}
pending_handle_ = handle;
preempt_requested_ = true;
} else {
if (is_active(pending_handle_)) {
// Shouldn't reach a state with a pending goal but no current one.
error_msg("Forgot to handle a preemption. Terminating the pending goal.");
terminate(pending_handle_);
preempt_requested_ = false;
}
current_handle_ = handle;
// Return quickly to avoid blocking the executor, so spin up a new thread
debug_msg("Executing goal asynchronously.");
execution_future_ = std::async(std::launch::async, [this]() {work();});
}
}
/**
* @brief Computed background work and processes stop requests
*/
void work()
{
while (rclcpp::ok() && !stop_execution_ && is_active(current_handle_)) {
debug_msg("Executing the goal...");
try {
execute_callback_();
} catch (std::exception & ex) {
RCLCPP_ERROR(
node_logging_interface_->get_logger(),
"Action server failed while executing action callback: \"%s\"", ex.what());
terminate_all();
completion_callback_();
return;
}
debug_msg("Blocking processing of new goal handles.");
std::lock_guard<std::recursive_mutex> lock(update_mutex_);
if (stop_execution_) {
warn_msg("Stopping the thread per request.");
terminate_all();
completion_callback_();
break;
}
if (is_active(current_handle_)) {
warn_msg("Current goal was not completed successfully.");
terminate(current_handle_);
completion_callback_();
}
if (is_active(pending_handle_)) {
debug_msg("Executing a pending handle on the existing thread.");
accept_pending_goal();
} else {
debug_msg("Done processing available goals.");
break;
}
}
debug_msg("Worker thread done.");
}
/**
* @brief Active action server
*/
void activate()
{
std::lock_guard<std::recursive_mutex> lock(update_mutex_);
server_active_ = true;
stop_execution_ = false;
}
/**
* @brief Deactive action server
*/
void deactivate()
{
debug_msg("Deactivating...");
{
std::lock_guard<std::recursive_mutex> lock(update_mutex_);
server_active_ = false;
stop_execution_ = true;
}
if (!execution_future_.valid()) {
return;
}
if (is_running()) {
warn_msg(
"Requested to deactivate server but goal is still executing."
" Should check if action server is running before deactivating.");
}
using namespace std::chrono; //NOLINT
auto start_time = steady_clock::now();
while (execution_future_.wait_for(milliseconds(100)) != std::future_status::ready) {
info_msg("Waiting for async process to finish.");
if (steady_clock::now() - start_time >= server_timeout_) {
terminate_all();
if (completion_callback_) {completion_callback_();}
error_msg("Action callback is still running and missed deadline to stop");
}
}
debug_msg("Deactivation completed.");
}
/**
* @brief Whether the action server is munching on a goal
* @return bool If its running or not
*/
bool is_running()
{
return execution_future_.valid() &&
(execution_future_.wait_for(std::chrono::milliseconds(0)) ==
std::future_status::timeout);
}
/**
* @brief Whether the action server is active or not
* @return bool If its active or not
*/
bool is_server_active()
{
std::lock_guard<std::recursive_mutex> lock(update_mutex_);
return server_active_;
}
/**
* @brief Whether the action server has been asked to be preempted with a new goal
* @return bool If there's a preemption request or not
*/
bool is_preempt_requested() const
{
std::lock_guard<std::recursive_mutex> lock(update_mutex_);
return preempt_requested_;
}
/**
* @brief Accept pending goals
* @return Goal Ptr to the goal that's going to be accepted
*/
const std::shared_ptr<const typename ActionT::Goal> accept_pending_goal()
{
std::lock_guard<std::recursive_mutex> lock(update_mutex_);
if (!pending_handle_ || !pending_handle_->is_active()) {
error_msg("Attempting to get pending goal when not available");
return std::shared_ptr<const typename ActionT::Goal>();
}
if (is_active(current_handle_) && current_handle_ != pending_handle_) {
debug_msg("Cancelling the previous goal");
current_handle_->abort(empty_result());
}
current_handle_ = pending_handle_;
pending_handle_.reset();
preempt_requested_ = false;
debug_msg("Preempted goal");
return current_handle_->get_goal();
}
/**
* @brief Terminate pending goals
*/
void terminate_pending_goal()
{
std::lock_guard<std::recursive_mutex> lock(update_mutex_);
if (!pending_handle_ || !pending_handle_->is_active()) {
error_msg("Attempting to terminate pending goal when not available");
return;
}
terminate(pending_handle_);
preempt_requested_ = false;
debug_msg("Pending goal terminated");
}
/**
* @brief Get the current goal object
* @return Goal Ptr to the goal that's being processed currently
*/
const std::shared_ptr<const typename ActionT::Goal> get_current_goal() const
{
std::lock_guard<std::recursive_mutex> lock(update_mutex_);
if (!is_active(current_handle_)) {
error_msg("A goal is not available or has reached a final state");
return std::shared_ptr<const typename ActionT::Goal>();
}
return current_handle_->get_goal();
}
const rclcpp_action::GoalUUID get_current_goal_id() const
{
std::lock_guard<std::recursive_mutex> lock(update_mutex_);
if (!is_active(current_handle_)) {
error_msg("A goal is not available or has reached a final state");
return rclcpp_action::GoalUUID();
}
return current_handle_->get_goal_id();
}
/**
* @brief Get the pending goal object
* @return Goal Ptr to the goal that's pending
*/
const std::shared_ptr<const typename ActionT::Goal> get_pending_goal() const
{
std::lock_guard<std::recursive_mutex> lock(update_mutex_);
if (!pending_handle_ || !pending_handle_->is_active()) {
error_msg("Attempting to get pending goal when not available");
return std::shared_ptr<const typename ActionT::Goal>();
}
return pending_handle_->get_goal();
}
/**
* @brief Whether or not a cancel command has come in
* @return bool Whether a cancel command has been requested or not
*/
bool is_cancel_requested() const
{
std::lock_guard<std::recursive_mutex> lock(update_mutex_);
// A cancel request is assumed if either handle is canceled by the client.
if (current_handle_ == nullptr) {
error_msg("Checking for cancel but current goal is not available");
return false;
}
if (pending_handle_ != nullptr) {
return pending_handle_->is_canceling();
}
return current_handle_->is_canceling();
}
/**
* @brief Terminate all pending and active actions
* @param result A result object to send to the terminated actions
*/
void terminate_all(
typename std::shared_ptr<typename ActionT::Result> result =
std::make_shared<typename ActionT::Result>())
{
std::lock_guard<std::recursive_mutex> lock(update_mutex_);
terminate(current_handle_, result);
terminate(pending_handle_, result);
preempt_requested_ = false;
}
/**
* @brief Terminate the active action
* @param result A result object to send to the terminated action
*/
void terminate_current(
typename std::shared_ptr<typename ActionT::Result> result =
std::make_shared<typename ActionT::Result>())
{
std::lock_guard<std::recursive_mutex> lock(update_mutex_);
terminate(current_handle_, result);
}
/**
* @brief Return success of the active action
* @param result A result object to send to the terminated actions
*/
void succeeded_current(
typename std::shared_ptr<typename ActionT::Result> result =
std::make_shared<typename ActionT::Result>())
{
std::lock_guard<std::recursive_mutex> lock(update_mutex_);
if (is_active(current_handle_)) {
debug_msg("Setting succeed on current goal.");
current_handle_->succeed(result);
current_handle_.reset();
}
}
/**
* @brief Publish feedback to the action server clients
* @param feedback A feedback object to send to the clients
*/
void publish_feedback(typename std::shared_ptr<typename ActionT::Feedback> feedback)
{
if (!is_active(current_handle_)) {
error_msg("Trying to publish feedback when the current goal handle is not active");
return;
}
current_handle_->publish_feedback(feedback);
}
protected:
// The SimpleActionServer isn't itself a node, so it needs interfaces to one
rclcpp::node_interfaces::NodeBaseInterface::SharedPtr node_base_interface_;
rclcpp::node_interfaces::NodeClockInterface::SharedPtr node_clock_interface_;
rclcpp::node_interfaces::NodeLoggingInterface::SharedPtr node_logging_interface_;
rclcpp::node_interfaces::NodeWaitablesInterface::SharedPtr node_waitables_interface_;
std::string action_name_;
ExecuteCallback execute_callback_;
CompletionCallback completion_callback_;
std::future<void> execution_future_;
bool stop_execution_{false};
mutable std::recursive_mutex update_mutex_;
bool server_active_{false};
bool preempt_requested_{false};
std::chrono::milliseconds server_timeout_;
std::shared_ptr<rclcpp_action::ServerGoalHandle<ActionT>> current_handle_;
std::shared_ptr<rclcpp_action::ServerGoalHandle<ActionT>> pending_handle_;
typename rclcpp_action::Server<ActionT>::SharedPtr action_server_;
bool spin_thread_;
rclcpp::CallbackGroup::SharedPtr callback_group_{nullptr};
rclcpp::executors::SingleThreadedExecutor::SharedPtr executor_;
std::unique_ptr<nav2_util::NodeThread> executor_thread_;
/**
* @brief Generate an empty result object for an action type
*/
constexpr auto empty_result() const
{
return std::make_shared<typename ActionT::Result>();
}
/**
* @brief Whether a given goal handle is currently active
* @param handle Goal handle to check
* @return Whether this goal handle is active
*/
constexpr bool is_active(
const std::shared_ptr<rclcpp_action::ServerGoalHandle<ActionT>> handle) const
{
return handle != nullptr && handle->is_active();
}
/**
* @brief Terminate a particular action with a result
* @param handle goal handle to terminate
* @param the Results object to terminate the action with
*/
void terminate(
std::shared_ptr<rclcpp_action::ServerGoalHandle<ActionT>> & handle,
typename std::shared_ptr<typename ActionT::Result> result =
std::make_shared<typename ActionT::Result>())
{
std::lock_guard<std::recursive_mutex> lock(update_mutex_);
if (is_active(handle)) {
if (handle->is_canceling()) {
info_msg("Client requested to cancel the goal. Cancelling.");
handle->canceled(result);
} else {
warn_msg("Aborting handle.");
handle->abort(result);
}
handle.reset();
}
}
/**
* @brief Info logging
*/
void info_msg(const std::string & msg) const
{
RCLCPP_INFO(
node_logging_interface_->get_logger(),
"[%s] [ActionServer] %s", action_name_.c_str(), msg.c_str());
}
/**
* @brief Debug logging
*/
void debug_msg(const std::string & msg) const
{
RCLCPP_DEBUG(
node_logging_interface_->get_logger(),
"[%s] [ActionServer] %s", action_name_.c_str(), msg.c_str());
}
/**
* @brief Error logging
*/
void error_msg(const std::string & msg) const
{
RCLCPP_ERROR(
node_logging_interface_->get_logger(),
"[%s] [ActionServer] %s", action_name_.c_str(), msg.c_str());
}
/**
* @brief Warn logging
*/
void warn_msg(const std::string & msg) const
{
RCLCPP_WARN(
node_logging_interface_->get_logger(),
"[%s] [ActionServer] %s", action_name_.c_str(), msg.c_str());
}
};
} // namespace nav2_util
#endif // NAV2_UTIL__SIMPLE_ACTION_SERVER_HPP_
@@ -0,0 +1,44 @@
// Copyright (c) 2018 Intel Corporation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef NAV2_UTIL__STRING_UTILS_HPP_
#define NAV2_UTIL__STRING_UTILS_HPP_
#include <string>
#include <vector>
namespace nav2_util
{
typedef std::vector<std::string> Tokens;
/*
* @brief Remove leading slash from a topic name
* @param in String of topic in
* @return String out without slash
*/
std::string strip_leading_slash(const std::string & in);
///
/*
* @brief Split a string at the delimiters
* @param in String to split
* @param Delimiter criteria
* @return Tokens
*/
Tokens split(const std::string & tokenstring, char delimiter);
} // namespace nav2_util
#endif // NAV2_UTIL__STRING_UTILS_HPP_
@@ -0,0 +1,179 @@
// Copyright (c) 2024 GoesM
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef NAV2_UTIL__VALIDATE_MESSAGES_HPP_
#define NAV2_UTIL__VALIDATE_MESSAGES_HPP_
#include <cmath>
#include <iostream>
#include "nav_msgs/msg/occupancy_grid.hpp"
#include "nav_msgs/msg/odometry.hpp"
#include "geometry_msgs/msg/pose_with_covariance_stamped.hpp"
// @brief Validation Check
// Check recieved message is safe or not for the nav2-system
// For each msg-type known in nav2, we could check it as following:
// if(!validateMsg()) RCLCPP_ERROR(,"malformed msg. Rejecting.")
//
// Workflow of validateMsg():
// if here's a sub-msg-type in the recieved msg,
// the content of sub-msg would be checked as sub-msg-type
// then, check the whole recieved msg.
//
// Following conditions are involved in check:
// 1> Value Check: to avoid damaged value like like `nan`, `INF`, empty string and so on
// 2> Logic Check: to avoid value with bad logic,
// like the size of `map` should be equal to `height*width`
// 3> Any other needed condition could be joint here in future
namespace nav2_util
{
bool validateMsg(const double & num)
{
/* @brief double/float value check
* if here'a need to check message validation
* it should be avoid to use double value like `nan`, `inf`
* otherwise, we regard it as an invalid message
*/
if (std::isinf(num)) {return false;}
if (std::isnan(num)) {return false;}
return true;
}
template<size_t N>
bool validateMsg(const std::array<double, N> & msg)
{
/* @brief value check for double-array
* like the field `covariance` used in the msg-type:
* geometry_msgs::msg::PoseWithCovarianceStamped
*/
for (const auto & element : msg) {
if (!validateMsg(element)) {return false;}
}
return true;
}
const int NSEC_PER_SEC = 1e9; // 1 second = 1e9 nanosecond
bool validateMsg(const builtin_interfaces::msg::Time & msg)
{
if (msg.nanosec >= NSEC_PER_SEC) {
return false; // invalid nanosec-stamp
}
return true;
}
bool validateMsg(const std_msgs::msg::Header & msg)
{
// check sub-type
if (!validateMsg(msg.stamp)) {return false;}
/* @brief frame_id check
* if here'a need to check message validation
* it should at least have a non-empty frame_id
* otherwise, we regard it as an invalid message
*/
if (msg.frame_id.empty()) {return false;}
return true;
}
bool validateMsg(const geometry_msgs::msg::Point & msg)
{
// check sub-type
if (!validateMsg(msg.x)) {return false;}
if (!validateMsg(msg.y)) {return false;}
if (!validateMsg(msg.z)) {return false;}
return true;
}
const double epsilon = 1e-4;
bool validateMsg(const geometry_msgs::msg::Quaternion & msg)
{
// check sub-type
if (!validateMsg(msg.x)) {return false;}
if (!validateMsg(msg.y)) {return false;}
if (!validateMsg(msg.z)) {return false;}
if (!validateMsg(msg.w)) {return false;}
if (abs(msg.x * msg.x + msg.y * msg.y + msg.z * msg.z + msg.w * msg.w - 1.0) >= epsilon) {
return false;
}
return true;
}
bool validateMsg(const geometry_msgs::msg::Pose & msg)
{
// check sub-type
if (!validateMsg(msg.position)) {return false;}
if (!validateMsg(msg.orientation)) {return false;}
return true;
}
bool validateMsg(const geometry_msgs::msg::PoseWithCovariance & msg)
{
// check sub-type
if (!validateMsg(msg.pose)) {return false;}
if (!validateMsg(msg.covariance)) {return false;}
return true;
}
bool validateMsg(const geometry_msgs::msg::PoseWithCovarianceStamped & msg)
{
// check sub-type
if (!validateMsg(msg.header)) {return false;}
if (!validateMsg(msg.pose)) {return false;}
return true;
}
// Function to verify map meta information
bool validateMsg(const nav_msgs::msg::MapMetaData & msg)
{
// check sub-type
if (!validateMsg(msg.origin)) {return false;}
if (!validateMsg(msg.resolution)) {return false;}
// logic check
// 1> we don't need an empty map
if (msg.height == 0 || msg.width == 0) {return false;}
return true;
}
// for msg-type like map, costmap and others as `OccupancyGrid`
bool validateMsg(const nav_msgs::msg::OccupancyGrid & msg)
{
// check sub-type
if (!validateMsg(msg.header)) {return false;}
// msg.data : @todo any check for it ?
if (!validateMsg(msg.info)) {return false;}
// check logic
if (msg.data.size() != msg.info.width * msg.info.height) {
return false; // check map-size
}
return true;
}
} // namespace nav2_util
#endif // NAV2_UTIL__VALIDATE_MESSAGES_HPP_
+49
View File
@@ -0,0 +1,49 @@
<?xml version="1.0"?>
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="3">
<name>nav2_util</name>
<version>1.1.18</version>
<description>TODO</description>
<maintainer email="michael.jeronimo@intel.com">Michael Jeronimo</maintainer>
<maintainer email="mohammad.haghighipanah@intel.com">Mohammad Haghighipanah</maintainer>
<license>Apache-2.0</license>
<license>BSD-3-Clause</license>
<buildtool_depend>ament_cmake</buildtool_depend>
<build_depend>libboost-program-options-dev</build_depend>
<depend>nav2_common</depend>
<depend>geometry_msgs</depend>
<depend>rclcpp</depend>
<depend>nav2_msgs</depend>
<depend>nav_msgs</depend>
<depend>tf2</depend>
<depend>tf2_ros</depend>
<depend>tf2_geometry_msgs</depend>
<depend>lifecycle_msgs</depend>
<depend>bondcpp</depend>
<depend>bond</depend>
<depend>rclcpp_action</depend>
<depend>rclcpp_lifecycle</depend>
<depend>launch</depend>
<depend>launch_testing_ament_cmake</depend>
<depend>action_msgs</depend>
<depend>rcl_interfaces</depend>
<exec_depend>libboost-program-options</exec_depend>
<test_depend>ament_lint_common</test_depend>
<test_depend>ament_lint_auto</test_depend>
<test_depend>ament_cmake_gtest</test_depend>
<test_depend>launch</test_depend>
<test_depend>launch_testing_ament_cmake</test_depend>
<test_depend>std_srvs</test_depend>
<test_depend>test_msgs</test_depend>
<test_depend>action_msgs</test_depend>
<test_depend>launch_testing_ros</test_depend>
<test_depend>ament_cmake_pytest</test_depend>
<export>
<build_type>ament_cmake</build_type>
</export>
</package>
+43
View File
@@ -0,0 +1,43 @@
add_library(${library_name} SHARED
costmap.cpp
node_utils.cpp
lifecycle_service_client.cpp
string_utils.cpp
lifecycle_utils.cpp
lifecycle_node.cpp
robot_utils.cpp
node_thread.cpp
odometry_utils.cpp
)
ament_target_dependencies(${library_name}
rclcpp
nav2_msgs
tf2
tf2_ros
nav_msgs
geometry_msgs
lifecycle_msgs
rclcpp_lifecycle
tf2_geometry_msgs
bondcpp
)
add_executable(lifecycle_bringup
lifecycle_bringup_commandline.cpp
)
target_link_libraries(lifecycle_bringup ${library_name})
find_package(Boost REQUIRED COMPONENTS program_options)
install(TARGETS
${library_name}
ARCHIVE DESTINATION lib
LIBRARY DESTINATION lib
RUNTIME DESTINATION bin
)
install(TARGETS
lifecycle_bringup
RUNTIME DESTINATION lib/${PROJECT_NAME}
)
+266
View File
@@ -0,0 +1,266 @@
// Copyright (c) 2018 Intel Corporation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <vector>
#include <algorithm>
#include "nav2_util/costmap.hpp"
#include "tf2/LinearMath/Quaternion.h"
#include "nav2_util/geometry_utils.hpp"
using std::vector;
namespace nav2_util
{
using nav2_util::geometry_utils::orientationAroundZAxis;
const Costmap::CostValue Costmap::no_information = 255;
const Costmap::CostValue Costmap::lethal_obstacle = 254;
const Costmap::CostValue Costmap::inscribed_inflated_obstacle = 253;
const Costmap::CostValue Costmap::medium_cost = 128;
const Costmap::CostValue Costmap::free_space = 0;
// TODO(orduno): Port ROS1 Costmap package
Costmap::Costmap(
rclcpp::Node * node, bool trinary_costmap, bool track_unknown_space,
int lethal_threshold, int unknown_cost_value)
: node_(node), trinary_costmap_(trinary_costmap), track_unknown_space_(track_unknown_space),
lethal_threshold_(lethal_threshold), unknown_cost_value_(unknown_cost_value)
{
if (lethal_threshold_ < 0. || lethal_threshold_ > 100.) {
RCLCPP_WARN(
node_->get_logger(), "Costmap: Lethal threshold set to %d, it should be within"
" bounds 0-100. This could result in potential collisions!", lethal_threshold_);
// lethal_threshold_ = std::max(std::min(lethal_threshold_, 100), 0);
}
}
Costmap::~Costmap()
{
}
void Costmap::set_static_map(const nav_msgs::msg::OccupancyGrid & occupancy_grid)
{
RCLCPP_INFO(node_->get_logger(), "Costmap: Setting static costmap");
costmap_properties_.map_load_time = node_->now();
costmap_properties_.update_time = node_->now();
costmap_properties_.layer = "Master";
// Store the properties of the occupancy grid
costmap_properties_.resolution = occupancy_grid.info.resolution;
costmap_properties_.size_x = occupancy_grid.info.width;
costmap_properties_.size_y = occupancy_grid.info.height;
costmap_properties_.origin = occupancy_grid.info.origin;
uint32_t size_x = costmap_properties_.size_x;
uint32_t size_y = costmap_properties_.size_y;
costs_.resize(size_x * size_y);
// TODO(orduno): for now just doing a direct mapping of values from the original static map
// i.e. no cell inflation, etc.
std::vector<int8_t> static_map_cell_values = occupancy_grid.data;
unsigned int index = 0;
for (unsigned int i = 0; i < size_y; ++i) {
for (unsigned int j = 0; j < size_x; ++j) {
unsigned char value = static_map_cell_values[index];
costs_[index] = interpret_value(value);
++index;
}
}
map_provided_ = true;
}
void Costmap::set_test_costmap(const TestCostmap & testCostmapType)
{
costmap_properties_.map_load_time = node_->now();
costmap_properties_.update_time = node_->now();
costmap_properties_.layer = "master";
costmap_properties_.resolution = 1;
costmap_properties_.size_x = 10;
costmap_properties_.size_y = 10;
costmap_properties_.origin.position.x = 0.0;
costmap_properties_.origin.position.y = 0.0;
costmap_properties_.origin.position.z = 0.0;
// Define map rotation
// Provided as yaw with counterclockwise rotation, with yaw = 0 meaning no rotation
costmap_properties_.origin.orientation = orientationAroundZAxis(0.0);
costs_ = get_test_data(testCostmapType);
using_test_map_ = true;
}
nav2_msgs::msg::Costmap Costmap::get_costmap(
const nav2_msgs::msg::CostmapMetaData & /*specifications*/)
{
if (!map_provided_ && !using_test_map_) {
throw std::runtime_error("Costmap has not been set.");
}
// TODO(orduno): build a costmap given the specifications
// for now using the specs of the static map
nav2_msgs::msg::Costmap costmap;
costmap.header.stamp = node_->now();
costmap.header.frame_id = "map";
costmap.metadata = costmap_properties_;
costmap.data = costs_;
return costmap;
}
vector<uint8_t> Costmap::get_test_data(const TestCostmap testCostmapType)
{
// TODO(orduno): alternatively use a mathematical function
const uint8_t n = no_information;
const uint8_t x = lethal_obstacle;
const uint8_t i = inscribed_inflated_obstacle;
const uint8_t u = medium_cost;
const uint8_t o = free_space;
vector<uint8_t> costmapFree =
// 0 1 2 3 4 5 6 7 8 9
{o, o, o, o, o, o, o, o, o, o, // 0
o, o, o, o, o, o, o, o, o, o, // 1
o, o, o, o, o, o, o, o, o, o, // 2
o, o, o, o, o, o, o, o, o, o, // 3
o, o, o, o, o, o, o, o, o, o, // 4
o, o, o, o, o, o, o, o, o, o, // 5
o, o, o, o, o, o, o, o, o, o, // 6
o, o, o, o, o, o, o, o, o, o, // 7
o, o, o, o, o, o, o, o, o, o, // 8
o, o, o, o, o, o, o, o, o, o}; // 9
vector<uint8_t> costmapBounded =
// 0 1 2 3 4 5 6 7 8 9
{n, n, n, n, n, n, n, n, n, n, // 0
n, o, o, o, o, o, o, o, o, n, // 1
n, o, o, o, o, o, o, o, o, n, // 2
n, o, o, o, o, o, o, o, o, n, // 3
n, o, o, o, o, o, o, o, o, n, // 4
n, o, o, o, o, o, o, o, o, n, // 5
n, o, o, o, o, o, o, o, o, n, // 6
n, o, o, o, o, o, o, o, o, n, // 7
n, o, o, o, o, o, o, o, o, n, // 8
n, n, n, n, n, n, n, n, n, n}; // 9
vector<uint8_t> costmapObstacleBL =
// 0 1 2 3 4 5 6 7 8 9
{n, n, n, n, n, n, n, n, n, n, // 0
n, o, o, o, o, o, o, o, o, n, // 1
n, o, o, o, o, o, o, o, o, n, // 2
n, o, o, o, o, o, o, o, o, n, // 3
n, o, o, o, o, o, o, o, o, n, // 4
n, o, x, x, x, o, o, o, o, n, // 5
n, o, x, x, x, o, o, o, o, n, // 6
n, o, x, x, x, o, o, o, o, n, // 7
n, o, o, o, o, o, o, o, o, n, // 8
n, n, n, n, n, n, n, n, n, n}; // 9
vector<uint8_t> costmapObstacleTL =
// 0 1 2 3 4 5 6 7 8 9
{n, n, n, n, n, n, n, n, n, n, // 0
n, o, o, o, o, o, o, o, o, n, // 1
n, o, x, x, x, o, o, o, o, n, // 2
n, o, x, x, x, o, o, o, o, n, // 3
n, o, x, x, x, o, o, o, o, n, // 4
n, o, o, o, o, o, o, o, o, n, // 5
n, o, o, o, o, o, o, o, o, n, // 6
n, o, o, o, o, o, o, o, o, n, // 7
n, o, o, o, o, o, o, o, o, n, // 8
n, n, n, n, n, n, n, n, n, n}; // 9
vector<uint8_t> costmapMaze =
// 0 1 2 3 4 5 6 7 8 9
{n, n, n, n, n, n, n, n, n, n, // 0
n, o, o, o, o, o, o, o, o, n, // 1
n, x, x, o, x, x, x, o, x, n, // 2
n, o, o, o, o, x, o, o, o, n, // 3
n, o, x, x, o, x, o, x, o, n, // 4
n, o, x, x, o, x, o, x, o, n, // 5
n, o, o, x, o, x, o, x, o, n, // 6
n, x, o, x, o, x, o, x, o, n, // 7
n, o, o, o, o, o, o, x, o, n, // 8
n, n, n, n, n, n, n, n, n, n}; // 9
vector<uint8_t> costmapMaze2 =
// 0 1 2 3 4 5 6 7 8 9
{n, n, n, n, n, n, n, n, n, n, // 0
n, o, o, o, o, o, o, o, o, n, // 1
n, x, x, u, x, x, x, o, x, n, // 2
n, o, o, o, o, o, o, o, u, n, // 3
n, o, x, x, o, x, x, x, u, n, // 4
n, o, x, x, o, o, o, x, u, n, // 5
n, o, o, x, u, x, o, x, u, n, // 6
n, x, o, x, u, x, i, x, u, n, // 7
n, o, o, o, o, o, o, o, o, n, // 8
n, n, n, n, n, n, n, n, n, n}; // 9
switch (testCostmapType) {
case TestCostmap::open_space:
return costmapFree;
case TestCostmap::bounded:
return costmapBounded;
case TestCostmap::bottom_left_obstacle:
return costmapObstacleBL;
case TestCostmap::top_left_obstacle:
return costmapObstacleTL;
case TestCostmap::maze1:
return costmapMaze;
case TestCostmap::maze2:
return costmapMaze2;
default:
return costmapFree;
}
}
uint8_t Costmap::interpret_value(const int8_t value) const
{
if (track_unknown_space_ && value == unknown_cost_value_) {
return no_information;
} else if (!track_unknown_space_ && value == unknown_cost_value_) {
return free_space;
} else if (value >= lethal_threshold_) {
return lethal_obstacle;
} else if (trinary_costmap_) {
return free_space;
}
double scale = static_cast<double>(value / lethal_threshold_);
return static_cast<uint8_t>(scale * lethal_obstacle);
}
bool Costmap::is_free(const unsigned int x_coordinate, const unsigned int y_coordinate) const
{
unsigned int index = y_coordinate * costmap_properties_.size_x + x_coordinate;
return is_free(index);
}
bool Costmap::is_free(const unsigned int index) const
{
if (costs_[index] < Costmap::inscribed_inflated_obstacle) {
return true;
}
return false;
}
} // namespace nav2_util
@@ -0,0 +1,47 @@
// Copyright (c) 2019 Intel Corporation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <vector>
#include <string>
#include <iostream>
#include <cstdlib>
#include <chrono>
#include "rclcpp/rclcpp.hpp"
#include "nav2_util/lifecycle_utils.hpp"
using std::cerr;
using namespace std::chrono_literals;
void usage()
{
cerr << "Invalid command line.\n\n";
cerr << "This command will take a set of unconfigured lifecycle nodes through the\n";
cerr << "CONFIGURED to the ACTIVATED state\n";
cerr << "The nodes are brought up in the order listed on the command line\n\n";
cerr << "Usage:\n";
cerr << " > lifecycle_startup <node name> ...\n";
std::exit(1);
}
int main(int argc, char * argv[])
{
if (argc == 1) {
usage();
}
rclcpp::init(0, nullptr);
nav2_util::startup_lifecycle_nodes(
std::vector<std::string>(argv + 1, argv + argc),
10s);
rclcpp::shutdown();
}
@@ -0,0 +1,129 @@
// Copyright (c) 2019 Intel Corporation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "nav2_util/lifecycle_node.hpp"
#include <memory>
#include <string>
#include <vector>
#include "lifecycle_msgs/msg/state.hpp"
namespace nav2_util
{
LifecycleNode::LifecycleNode(
const std::string & node_name,
const std::string & ns,
const rclcpp::NodeOptions & options)
: rclcpp_lifecycle::LifecycleNode(node_name, ns, options)
{
// server side never times out from lifecycle manager
this->declare_parameter(bond::msg::Constants::DISABLE_HEARTBEAT_TIMEOUT_PARAM, true);
this->set_parameter(
rclcpp::Parameter(
bond::msg::Constants::DISABLE_HEARTBEAT_TIMEOUT_PARAM, true));
printLifecycleNodeNotification();
register_rcl_preshutdown_callback();
}
LifecycleNode::~LifecycleNode()
{
RCLCPP_INFO(get_logger(), "Destroying");
runCleanups();
if (rcl_preshutdown_cb_handle_) {
rclcpp::Context::SharedPtr context = get_node_base_interface()->get_context();
context->remove_pre_shutdown_callback(*(rcl_preshutdown_cb_handle_.get()));
rcl_preshutdown_cb_handle_.reset();
}
}
void LifecycleNode::createBond()
{
RCLCPP_INFO(get_logger(), "Creating bond (%s) to lifecycle manager.", this->get_name());
bond_ = std::make_unique<bond::Bond>(
std::string("bond"),
this->get_name(),
shared_from_this());
bond_->setHeartbeatPeriod(0.10);
bond_->setHeartbeatTimeout(4.0);
bond_->start();
}
void LifecycleNode::runCleanups()
{
/*
* In case this lifecycle node wasn't properly shut down, do it here.
* We will give the user some ability to clean up properly here, but it's
* best effort; i.e. we aren't trying to account for all possible states.
*/
if (get_current_state().id() ==
lifecycle_msgs::msg::State::PRIMARY_STATE_ACTIVE)
{
this->deactivate();
}
if (get_current_state().id() ==
lifecycle_msgs::msg::State::PRIMARY_STATE_INACTIVE)
{
this->cleanup();
}
}
void LifecycleNode::on_rcl_preshutdown()
{
RCLCPP_INFO(
get_logger(), "Running Nav2 LifecycleNode rcl preshutdown (%s)",
this->get_name());
runCleanups();
destroyBond();
}
void LifecycleNode::register_rcl_preshutdown_callback()
{
rclcpp::Context::SharedPtr context = get_node_base_interface()->get_context();
rcl_preshutdown_cb_handle_ = std::make_unique<rclcpp::PreShutdownCallbackHandle>(
context->add_pre_shutdown_callback(
std::bind(&LifecycleNode::on_rcl_preshutdown, this))
);
}
void LifecycleNode::destroyBond()
{
RCLCPP_INFO(get_logger(), "Destroying bond (%s) to lifecycle manager.", this->get_name());
if (bond_) {
bond_.reset();
}
}
void LifecycleNode::printLifecycleNodeNotification()
{
RCLCPP_INFO(
get_logger(),
"\n\t%s lifecycle node launched. \n"
"\tWaiting on external lifecycle transitions to activate\n"
"\tSee https://design.ros2.org/articles/node_lifecycle.html for more information.", get_name());
}
} // namespace nav2_util
@@ -0,0 +1,102 @@
// Copyright (c) 2019 Intel Corporation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "nav2_util/lifecycle_service_client.hpp"
#include <string>
#include <chrono>
#include <memory>
#include "lifecycle_msgs/srv/change_state.hpp"
#include "lifecycle_msgs/srv/get_state.hpp"
using nav2_util::generate_internal_node;
using std::chrono::seconds;
using std::make_shared;
using std::string;
using namespace std::chrono_literals;
namespace nav2_util
{
LifecycleServiceClient::LifecycleServiceClient(const string & lifecycle_node_name)
: node_(generate_internal_node(lifecycle_node_name + "_lifecycle_client")),
change_state_(lifecycle_node_name + "/change_state", node_),
get_state_(lifecycle_node_name + "/get_state", node_)
{
// Block until server is up
rclcpp::Rate r(20);
while (!get_state_.wait_for_service(2s)) {
RCLCPP_INFO(
node_->get_logger(), "Waiting for service %s...", get_state_.getServiceName().c_str());
r.sleep();
}
}
LifecycleServiceClient::LifecycleServiceClient(
const string & lifecycle_node_name,
rclcpp::Node::SharedPtr parent_node)
: node_(parent_node),
change_state_(lifecycle_node_name + "/change_state", node_),
get_state_(lifecycle_node_name + "/get_state", node_)
{
// Block until server is up
rclcpp::Rate r(20);
while (!get_state_.wait_for_service(2s)) {
RCLCPP_INFO(
node_->get_logger(), "Waiting for service %s...", get_state_.getServiceName().c_str());
r.sleep();
}
}
bool LifecycleServiceClient::change_state(
const uint8_t transition,
const seconds timeout)
{
if (!change_state_.wait_for_service(timeout)) {
throw std::runtime_error("change_state service is not available!");
}
auto request = std::make_shared<lifecycle_msgs::srv::ChangeState::Request>();
request->transition.id = transition;
auto response = change_state_.invoke(request, timeout);
return response.get();
}
bool LifecycleServiceClient::change_state(
std::uint8_t transition)
{
if (!change_state_.wait_for_service(5s)) {
throw std::runtime_error("change_state service is not available!");
}
auto request = std::make_shared<lifecycle_msgs::srv::ChangeState::Request>();
auto response = std::make_shared<lifecycle_msgs::srv::ChangeState::Response>();
request->transition.id = transition;
return change_state_.invoke(request, response);
}
uint8_t LifecycleServiceClient::get_state(
const seconds timeout)
{
if (!get_state_.wait_for_service(timeout)) {
throw std::runtime_error("get_state service is not available!");
}
auto request = std::make_shared<lifecycle_msgs::srv::GetState::Request>();
auto result = get_state_.invoke(request, timeout);
return result->current_state.id;
}
} // namespace nav2_util
@@ -0,0 +1,101 @@
// Copyright (c) 2019 Intel Corporation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <chrono>
#include <string>
#include <thread>
#include <vector>
#include "lifecycle_msgs/srv/change_state.hpp"
#include "lifecycle_msgs/srv/get_state.hpp"
#include "nav2_util/lifecycle_service_client.hpp"
using std::string;
using lifecycle_msgs::msg::Transition;
namespace nav2_util
{
#define RETRY(fn, retries) \
{ \
int count = 0; \
while (true) { \
try { \
fn; \
break; \
} catch (std::runtime_error & e) { \
++count; \
if (count > (retries)) { \
throw e;} \
} \
} \
}
static void startupLifecycleNode(
const std::string & node_name,
const std::chrono::seconds service_call_timeout,
const int retries)
{
LifecycleServiceClient sc(node_name);
// Despite waiting for the service to be available and using reliable transport
// service calls still frequently hang. To get reliable startup it's necessary
// to timeout the service call and retry it when that happens.
RETRY(
sc.change_state(Transition::TRANSITION_CONFIGURE, service_call_timeout),
retries);
RETRY(
sc.change_state(Transition::TRANSITION_ACTIVATE, service_call_timeout),
retries);
}
void startup_lifecycle_nodes(
const std::vector<std::string> & node_names,
const std::chrono::seconds service_call_timeout,
const int retries)
{
for (const auto & node_name : node_names) {
startupLifecycleNode(node_name, service_call_timeout, retries);
}
}
static void resetLifecycleNode(
const std::string & node_name,
const std::chrono::seconds service_call_timeout,
const int retries)
{
LifecycleServiceClient sc(node_name);
// Despite waiting for the service to be available and using reliable transport
// service calls still frequently hang. To get reliable reset it's necessary
// to timeout the service call and retry it when that happens.
RETRY(
sc.change_state(Transition::TRANSITION_DEACTIVATE, service_call_timeout),
retries);
RETRY(
sc.change_state(Transition::TRANSITION_CLEANUP, service_call_timeout),
retries);
}
void reset_lifecycle_nodes(
const std::vector<std::string> & node_names,
const std::chrono::seconds service_call_timeout,
const int retries)
{
for (const auto & node_name : node_names) {
resetLifecycleNode(node_name, service_call_timeout, retries);
}
}
} // namespace nav2_util
+47
View File
@@ -0,0 +1,47 @@
// Copyright (c) 2019 Intel Corporation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <memory>
#include "nav2_util/node_thread.hpp"
namespace nav2_util
{
NodeThread::NodeThread(rclcpp::node_interfaces::NodeBaseInterface::SharedPtr node_base)
: node_(node_base)
{
executor_ = std::make_shared<rclcpp::executors::SingleThreadedExecutor>();
thread_ = std::make_unique<std::thread>(
[&]()
{
executor_->add_node(node_);
executor_->spin();
executor_->remove_node(node_);
});
}
NodeThread::NodeThread(rclcpp::executors::SingleThreadedExecutor::SharedPtr executor)
: executor_(executor)
{
thread_ = std::make_unique<std::thread>([&]() {executor_->spin();});
}
NodeThread::~NodeThread()
{
executor_->cancel();
thread_->join();
}
} // namespace nav2_util
+92
View File
@@ -0,0 +1,92 @@
// Copyright (c) 2019 Intel Corporation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "nav2_util/node_utils.hpp"
#include <chrono>
#include <string>
#include <algorithm>
#include <cctype>
using std::chrono::high_resolution_clock;
using std::to_string;
using std::string;
using std::replace_if;
using std::isalnum;
namespace nav2_util
{
string sanitize_node_name(const string & potential_node_name)
{
string node_name(potential_node_name);
// read this as `replace` characters in `node_name` `if` not alphanumeric.
// replace with '_'
replace_if(
begin(node_name), end(node_name),
[](auto c) {return !isalnum(c);},
'_');
return node_name;
}
string add_namespaces(const string & top_ns, const string & sub_ns)
{
if (!top_ns.empty() && top_ns.back() == '/') {
if (top_ns.front() == '/') {
return top_ns + sub_ns;
} else {
return "/" + top_ns + sub_ns;
}
}
return top_ns + "/" + sub_ns;
}
std::string time_to_string(size_t len)
{
string output(len, '0'); // prefill the string with zeros
auto timepoint = high_resolution_clock::now();
auto timecount = timepoint.time_since_epoch().count();
auto timestring = to_string(timecount);
if (timestring.length() >= len) {
// if `timestring` is shorter, put it at the end of `output`
output.replace(
0, len,
timestring,
timestring.length() - len, len);
} else {
// if `output` is shorter, just copy in the end of `timestring`
output.replace(
len - timestring.length(), timestring.length(),
timestring,
0, timestring.length());
}
return output;
}
std::string generate_internal_node_name(const std::string & prefix)
{
return sanitize_node_name(prefix) + "_" + time_to_string(8);
}
rclcpp::Node::SharedPtr generate_internal_node(const std::string & prefix)
{
auto options =
rclcpp::NodeOptions()
.start_parameter_services(false)
.start_parameter_event_publisher(false)
.arguments({"--ros-args", "-r", "__node:=" + generate_internal_node_name(prefix), "--"});
return rclcpp::Node::make_shared("_", options);
}
} // namespace nav2_util
@@ -0,0 +1,121 @@
// Copyright (c) 2018 Intel Corporation
// Copyright (c) 2020 Sarthak Mittal
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <string>
#include "nav2_util/odometry_utils.hpp"
using namespace std::chrono; // NOLINT
using namespace std::chrono_literals; // NOLINT
namespace nav2_util
{
OdomSmoother::OdomSmoother(
const rclcpp::Node::WeakPtr & parent,
double filter_duration,
const std::string & odom_topic)
: odom_history_duration_(rclcpp::Duration::from_seconds(filter_duration))
{
auto node = parent.lock();
odom_sub_ = node->create_subscription<nav_msgs::msg::Odometry>(
odom_topic,
rclcpp::SystemDefaultsQoS(),
std::bind(&OdomSmoother::odomCallback, this, std::placeholders::_1));
odom_cumulate_.twist.twist.linear.x = 0;
odom_cumulate_.twist.twist.linear.y = 0;
odom_cumulate_.twist.twist.linear.z = 0;
odom_cumulate_.twist.twist.angular.x = 0;
odom_cumulate_.twist.twist.angular.y = 0;
odom_cumulate_.twist.twist.angular.z = 0;
}
OdomSmoother::OdomSmoother(
const nav2_util::LifecycleNode::WeakPtr & parent,
double filter_duration,
const std::string & odom_topic)
: odom_history_duration_(rclcpp::Duration::from_seconds(filter_duration))
{
auto node = parent.lock();
odom_sub_ = node->create_subscription<nav_msgs::msg::Odometry>(
odom_topic,
rclcpp::SystemDefaultsQoS(),
std::bind(&OdomSmoother::odomCallback, this, std::placeholders::_1));
odom_cumulate_.twist.twist.linear.x = 0;
odom_cumulate_.twist.twist.linear.y = 0;
odom_cumulate_.twist.twist.linear.z = 0;
odom_cumulate_.twist.twist.angular.x = 0;
odom_cumulate_.twist.twist.angular.y = 0;
odom_cumulate_.twist.twist.angular.z = 0;
}
void OdomSmoother::odomCallback(const nav_msgs::msg::Odometry::SharedPtr msg)
{
std::lock_guard<std::mutex> lock(odom_mutex_);
// update cumulated odom only if history is not empty
if (!odom_history_.empty()) {
// to store current time
auto current_time = rclcpp::Time(msg->header.stamp);
// to store time of the first odom in history
auto front_time = rclcpp::Time(odom_history_.front().header.stamp);
// update cumulated odom when duration has exceeded and pop earliest msg
while (current_time - front_time > odom_history_duration_) {
const auto & odom = odom_history_.front();
odom_cumulate_.twist.twist.linear.x -= odom.twist.twist.linear.x;
odom_cumulate_.twist.twist.linear.y -= odom.twist.twist.linear.y;
odom_cumulate_.twist.twist.linear.z -= odom.twist.twist.linear.z;
odom_cumulate_.twist.twist.angular.x -= odom.twist.twist.angular.x;
odom_cumulate_.twist.twist.angular.y -= odom.twist.twist.angular.y;
odom_cumulate_.twist.twist.angular.z -= odom.twist.twist.angular.z;
odom_history_.pop_front();
if (odom_history_.empty()) {
break;
}
// update with the timestamp of earliest odom message in history
front_time = rclcpp::Time(odom_history_.front().header.stamp);
}
}
odom_history_.push_back(*msg);
updateState();
}
void OdomSmoother::updateState()
{
const auto & odom = odom_history_.back();
odom_cumulate_.twist.twist.linear.x += odom.twist.twist.linear.x;
odom_cumulate_.twist.twist.linear.y += odom.twist.twist.linear.y;
odom_cumulate_.twist.twist.linear.z += odom.twist.twist.linear.z;
odom_cumulate_.twist.twist.angular.x += odom.twist.twist.angular.x;
odom_cumulate_.twist.twist.angular.y += odom.twist.twist.angular.y;
odom_cumulate_.twist.twist.angular.z += odom.twist.twist.angular.z;
vel_smooth_.header = odom.header;
vel_smooth_.twist.linear.x = odom_cumulate_.twist.twist.linear.x / odom_history_.size();
vel_smooth_.twist.linear.y = odom_cumulate_.twist.twist.linear.y / odom_history_.size();
vel_smooth_.twist.linear.z = odom_cumulate_.twist.twist.linear.z / odom_history_.size();
vel_smooth_.twist.angular.x = odom_cumulate_.twist.twist.angular.x / odom_history_.size();
vel_smooth_.twist.angular.y = odom_cumulate_.twist.twist.angular.y / odom_history_.size();
vel_smooth_.twist.angular.z = odom_cumulate_.twist.twist.angular.z / odom_history_.size();
}
} // namespace nav2_util
+174
View File
@@ -0,0 +1,174 @@
// Copyright (c) 2018 Intel Corporation
// Copyright (c) 2019 Steven Macenski
// Copyright (c) 2019 Samsung Research America
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <string>
#include <cmath>
#include <memory>
#include "nav2_util/robot_utils.hpp"
#include "rclcpp/logger.hpp"
namespace nav2_util
{
bool getCurrentPose(
geometry_msgs::msg::PoseStamped & global_pose,
tf2_ros::Buffer & tf_buffer, const std::string global_frame,
const std::string robot_frame, const double transform_timeout,
const rclcpp::Time stamp)
{
tf2::toMsg(tf2::Transform::getIdentity(), global_pose.pose);
global_pose.header.frame_id = robot_frame;
global_pose.header.stamp = stamp;
return transformPoseInTargetFrame(
global_pose, global_pose, tf_buffer, global_frame, transform_timeout);
}
bool transformPoseInTargetFrame(
const geometry_msgs::msg::PoseStamped & input_pose,
geometry_msgs::msg::PoseStamped & transformed_pose,
tf2_ros::Buffer & tf_buffer, const std::string target_frame,
const double transform_timeout)
{
static rclcpp::Logger logger = rclcpp::get_logger("transformPoseInTargetFrame");
try {
transformed_pose = tf_buffer.transform(
input_pose, target_frame,
tf2::durationFromSec(transform_timeout));
return true;
} catch (tf2::LookupException & ex) {
RCLCPP_ERROR(
logger,
"No Transform available Error looking up target frame: %s\n", ex.what());
} catch (tf2::ConnectivityException & ex) {
RCLCPP_ERROR(
logger,
"Connectivity Error looking up target frame: %s\n", ex.what());
} catch (tf2::ExtrapolationException & ex) {
RCLCPP_ERROR(
logger,
"Extrapolation Error looking up target frame: %s\n", ex.what());
} catch (tf2::TimeoutException & ex) {
RCLCPP_ERROR(
logger,
"Transform timeout with tolerance: %.4f", transform_timeout);
} catch (tf2::TransformException & ex) {
RCLCPP_ERROR(
logger, "Failed to transform from %s to %s",
input_pose.header.frame_id.c_str(), target_frame.c_str());
}
return false;
}
bool getTransform(
const std::string & source_frame_id,
const std::string & target_frame_id,
const tf2::Duration & transform_tolerance,
const std::shared_ptr<tf2_ros::Buffer> tf_buffer,
tf2::Transform & tf2_transform)
{
geometry_msgs::msg::TransformStamped transform;
tf2_transform.setIdentity(); // initialize by identical transform
if (source_frame_id == target_frame_id) {
// We are already in required frame
return true;
}
try {
// Obtaining the transform to get data from source to target frame
transform = tf_buffer->lookupTransform(
target_frame_id, source_frame_id,
tf2::TimePointZero, transform_tolerance);
} catch (tf2::TransformException & e) {
RCLCPP_ERROR(
rclcpp::get_logger("getTransform"),
"Failed to get \"%s\"->\"%s\" frame transform: %s",
source_frame_id.c_str(), target_frame_id.c_str(), e.what());
return false;
}
// Convert TransformStamped to TF2 transform
tf2::fromMsg(transform.transform, tf2_transform);
return true;
}
bool getTransform(
const std::string & source_frame_id,
const rclcpp::Time & source_time,
const std::string & target_frame_id,
const rclcpp::Time & target_time,
const std::string & fixed_frame_id,
const tf2::Duration & transform_tolerance,
const std::shared_ptr<tf2_ros::Buffer> tf_buffer,
tf2::Transform & tf2_transform)
{
geometry_msgs::msg::TransformStamped transform;
tf2_transform.setIdentity(); // initialize by identical transform
try {
// Obtaining the transform to get data from source to target frame.
// This also considers the time shift between source and target.
transform = tf_buffer->lookupTransform(
target_frame_id, target_time,
source_frame_id, source_time,
fixed_frame_id, transform_tolerance);
} catch (tf2::TransformException & ex) {
RCLCPP_ERROR(
rclcpp::get_logger("getTransform"),
"Failed to get \"%s\"->\"%s\" frame transform: %s",
source_frame_id.c_str(), target_frame_id.c_str(), ex.what());
return false;
}
// Convert TransformStamped to TF2 transform
tf2::fromMsg(transform.transform, tf2_transform);
return true;
}
bool validateTwist(const geometry_msgs::msg::Twist & msg)
{
if (std::isinf(msg.linear.x) || std::isnan(msg.linear.x)) {
return false;
}
if (std::isinf(msg.linear.y) || std::isnan(msg.linear.y)) {
return false;
}
if (std::isinf(msg.linear.z) || std::isnan(msg.linear.z)) {
return false;
}
if (std::isinf(msg.angular.x) || std::isnan(msg.angular.x)) {
return false;
}
if (std::isinf(msg.angular.y) || std::isnan(msg.angular.y)) {
return false;
}
if (std::isinf(msg.angular.z) || std::isnan(msg.angular.z)) {
return false;
}
return true;
}
} // end namespace nav2_util
@@ -0,0 +1,48 @@
// Copyright (c) 2019 Intel Corporation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "nav2_util/string_utils.hpp"
#include <string>
using std::string;
namespace nav2_util
{
std::string strip_leading_slash(const string & in)
{
string out = in;
if ((!in.empty()) && (in[0] == '/')) {
out.erase(0, 1);
}
return out;
}
Tokens split(const string & tokenstring, char delimiter)
{
Tokens tokens;
size_t current_pos = 0;
size_t pos = 0;
while ((pos = tokenstring.find(delimiter, current_pos)) != string::npos) {
tokens.push_back(tokenstring.substr(current_pos, pos - current_pos));
current_pos = pos + 1;
}
tokens.push_back(tokenstring.substr(current_pos));
return tokens;
}
} // namespace nav2_util
+47
View File
@@ -0,0 +1,47 @@
ament_add_gtest(test_execution_timer test_execution_timer.cpp)
ament_add_gtest(test_node_utils test_node_utils.cpp)
target_link_libraries(test_node_utils ${library_name})
find_package(std_srvs REQUIRED)
find_package(test_msgs REQUIRED)
ament_add_gtest(test_service_client test_service_client.cpp)
ament_target_dependencies(test_service_client std_srvs)
target_link_libraries(test_service_client ${library_name})
ament_add_gtest(test_string_utils test_string_utils.cpp)
target_link_libraries(test_string_utils ${library_name})
find_package(rclcpp_lifecycle REQUIRED)
ament_add_gtest(test_lifecycle_utils test_lifecycle_utils.cpp)
ament_target_dependencies(test_lifecycle_utils rclcpp_lifecycle)
target_link_libraries(test_lifecycle_utils ${library_name})
ament_add_gtest(test_actions test_actions.cpp)
ament_target_dependencies(test_actions rclcpp_action test_msgs)
target_link_libraries(test_actions ${library_name})
ament_add_gtest(test_lifecycle_node test_lifecycle_node.cpp)
ament_target_dependencies(test_lifecycle_node rclcpp_lifecycle)
target_link_libraries(test_lifecycle_node ${library_name})
ament_add_gtest(test_lifecycle_cli_node test_lifecycle_cli_node.cpp)
ament_target_dependencies(test_lifecycle_cli_node rclcpp_lifecycle)
target_link_libraries(test_lifecycle_cli_node ${library_name})
ament_add_gtest(test_geometry_utils test_geometry_utils.cpp)
ament_target_dependencies(test_geometry_utils geometry_msgs)
target_link_libraries(test_geometry_utils ${library_name})
ament_add_gtest(test_odometry_utils test_odometry_utils.cpp)
ament_target_dependencies(test_odometry_utils nav_msgs geometry_msgs)
target_link_libraries(test_odometry_utils ${library_name})
ament_add_gtest(test_robot_utils test_robot_utils.cpp)
ament_target_dependencies(test_robot_utils geometry_msgs)
target_link_libraries(test_robot_utils ${library_name})
ament_add_gtest(test_validation_messages test_validation_messages.cpp)
ament_target_dependencies(test_validation_messages rclcpp_lifecycle)
target_link_libraries(test_validation_messages ${library_name})
+553
View File
@@ -0,0 +1,553 @@
// Copyright (c) 2019 Intel Corporation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <chrono>
#include <memory>
#include <thread>
#include "gtest/gtest.h"
#include "nav2_util/node_utils.hpp"
#include "nav2_util/simple_action_server.hpp"
#include "test_msgs/action/fibonacci.hpp"
#include "std_msgs/msg/empty.hpp"
using Fibonacci = test_msgs::action::Fibonacci;
using GoalHandle = rclcpp_action::ServerGoalHandle<Fibonacci>;
using std::placeholders::_1;
using namespace std::chrono_literals;
class FibonacciServerNode : public rclcpp::Node
{
public:
FibonacciServerNode()
: rclcpp::Node("fibonacci_server_node")
{
}
~FibonacciServerNode()
{
}
void on_init()
{
action_server_ = std::make_shared<nav2_util::SimpleActionServer<Fibonacci>>(
shared_from_this(),
"fibonacci",
std::bind(&FibonacciServerNode::execute, this));
deactivate_subs_ = create_subscription<std_msgs::msg::Empty>(
"deactivate_server",
1,
[this](std_msgs::msg::Empty::UniquePtr /*msg*/) {
RCLCPP_INFO(this->get_logger(), "Deactivating");
action_server_->deactivate();
});
activate_subs_ = create_subscription<std_msgs::msg::Empty>(
"activate_server",
1,
[this](std_msgs::msg::Empty::UniquePtr /*msg*/) {
RCLCPP_INFO(this->get_logger(), "Activating");
action_server_->activate();
});
omit_preempt_subs_ = create_subscription<std_msgs::msg::Empty>(
"omit_preemption",
1,
[this](std_msgs::msg::Empty::UniquePtr /*msg*/) {
RCLCPP_INFO(this->get_logger(), "Ignoring preemptions");
do_premptions_ = false;
});
}
void on_term()
{
// when nothing's running make sure everything's dead.
// const std::shared_ptr<const Fibonacci::Goal> a = action_server_->accept_pending_goal();
// const std::shared_ptr<const Fibonacci::Goal> b = action_server_->get_current_goal();
// assert(a == b);
// assert(action_server_->is_cancel_requested() == false);
// auto feedback = std::make_shared<Fibonacci::Feedback>();
// action_server_->publish_feedback(feedback);
action_server_.reset();
}
void execute()
{
rclcpp::Rate loop_rate(10);
preempted:
// Initialize the goal, feedback, and result
auto goal = action_server_->get_current_goal();
auto feedback = std::make_shared<Fibonacci::Feedback>();
auto result = std::make_shared<Fibonacci::Result>();
// Fibonacci-specific initialization
auto & sequence = feedback->sequence;
sequence.push_back(0);
sequence.push_back(1);
for (int i = 1; (i < goal->order) && rclcpp::ok(); ++i) {
// Should be check periodically if this action has been canceled
// or if the server has been deactivated.
if (action_server_->is_cancel_requested() || !action_server_->is_server_active()) {
result->sequence = sequence;
return;
}
// Check if we've gotten an new goal, pre-empting the current one
if (do_premptions_ && action_server_->is_preempt_requested()) {
action_server_->accept_pending_goal();
goto preempted;
}
// Update the sequence
sequence.push_back(sequence[i] + sequence[i - 1]);
// Publish feedback
action_server_->publish_feedback(feedback);
loop_rate.sleep();
}
// Check if goal is done
if (rclcpp::ok()) {
result->sequence = sequence;
action_server_->succeeded_current(result);
}
}
private:
std::shared_ptr<nav2_util::SimpleActionServer<Fibonacci>> action_server_;
rclcpp::Subscription<std_msgs::msg::Empty>::SharedPtr deactivate_subs_;
rclcpp::Subscription<std_msgs::msg::Empty>::SharedPtr activate_subs_;
rclcpp::Subscription<std_msgs::msg::Empty>::SharedPtr omit_preempt_subs_;
bool do_premptions_{true};
};
class RclCppFixture
{
public:
RclCppFixture()
{
}
void Setup()
{
server_thread_ =
std::make_shared<std::thread>(std::bind(&RclCppFixture::server_thread_func, this));
}
~RclCppFixture()
{
server_thread_->join();
}
void server_thread_func()
{
auto node = std::make_shared<FibonacciServerNode>();
node->on_init();
rclcpp::spin(node->get_node_base_interface());
node->on_term();
node.reset();
}
std::shared_ptr<std::thread> server_thread_;
};
RclCppFixture g_rclcppfixture;
class ActionTestNode : public rclcpp::Node
{
public:
ActionTestNode()
: rclcpp::Node(nav2_util::generate_internal_node_name("action_test_node"))
{
}
void on_init()
{
action_client_ = rclcpp_action::create_client<Fibonacci>(shared_from_this(), "fibonacci");
action_client_->wait_for_action_server();
deactivate_pub_ = this->create_publisher<std_msgs::msg::Empty>("deactivate_server", 1);
activate_pub_ = this->create_publisher<std_msgs::msg::Empty>("activate_server", 1);
omit_prempt_pub_ = this->create_publisher<std_msgs::msg::Empty>("omit_preemption", 1);
}
void on_term()
{
action_client_.reset();
}
void deactivate_server()
{
deactivate_pub_->publish(std_msgs::msg::Empty());
}
void activate_server()
{
activate_pub_->publish(std_msgs::msg::Empty());
}
void omit_server_preemptions()
{
omit_prempt_pub_->publish(std_msgs::msg::Empty());
}
rclcpp_action::Client<Fibonacci>::SharedPtr action_client_;
rclcpp::Publisher<std_msgs::msg::Empty>::SharedPtr deactivate_pub_;
rclcpp::Publisher<std_msgs::msg::Empty>::SharedPtr activate_pub_;
rclcpp::Publisher<std_msgs::msg::Empty>::SharedPtr omit_prempt_pub_;
};
class ActionTest : public ::testing::Test
{
protected:
void SetUp() override
{
node_ = std::make_shared<ActionTestNode>();
node_->on_init();
}
void TearDown() override
{
std::cout << " Teardown" << std::endl;
node_->on_term();
std::cout << " Teardown..." << std::endl;
node_.reset();
std::cout << " Teardown complete" << std::endl;
}
std::shared_ptr<ActionTestNode> node_;
};
TEST_F(ActionTest, test_simple_action)
{
node_->activate_server();
// The goal for this invocation
auto goal = Fibonacci::Goal();
goal.order = 12;
// Send the goal
auto future_goal_handle = node_->action_client_->async_send_goal(goal);
EXPECT_EQ(
rclcpp::spin_until_future_complete(
node_,
future_goal_handle), rclcpp::FutureReturnCode::SUCCESS);
auto goal_handle = future_goal_handle.get();
// Wait for the result
auto future_result = node_->action_client_->async_get_result(goal_handle);
EXPECT_EQ(
rclcpp::spin_until_future_complete(node_, future_result),
rclcpp::FutureReturnCode::SUCCESS);
// The final result
rclcpp_action::ClientGoalHandle<Fibonacci>::WrappedResult result = future_result.get();
EXPECT_EQ(result.code, rclcpp_action::ResultCode::SUCCEEDED);
// Sum all of the values in the requested fibonacci series
int sum = 0;
for (auto number : result.result->sequence) {
sum += number;
}
EXPECT_EQ(sum, 376);
SUCCEED();
}
TEST_F(ActionTest, test_simple_action_with_feedback)
{
int feedback_sum = 0;
// A callback to accumulate the intermediate values
auto feedback_callback = [&feedback_sum](
rclcpp_action::ClientGoalHandle<Fibonacci>::SharedPtr /*goal_handle*/,
const std::shared_ptr<const Fibonacci::Feedback> feedback)
{
feedback_sum += feedback->sequence.back();
};
// The goal for this invocation
auto goal = Fibonacci::Goal();
goal.order = 10;
auto send_goal_options = rclcpp_action::Client<Fibonacci>::SendGoalOptions();
send_goal_options.feedback_callback = feedback_callback;
// Send the goal
auto future_goal_handle = node_->action_client_->async_send_goal(goal, send_goal_options);
EXPECT_EQ(
rclcpp::spin_until_future_complete(
node_,
future_goal_handle), rclcpp::FutureReturnCode::SUCCESS);
auto goal_handle = future_goal_handle.get();
// Wait for the result
auto future_result = node_->action_client_->async_get_result(goal_handle);
EXPECT_EQ(
rclcpp::spin_until_future_complete(
node_,
future_result), rclcpp::FutureReturnCode::SUCCESS);
// The final result
rclcpp_action::ClientGoalHandle<Fibonacci>::WrappedResult result = future_result.get();
EXPECT_EQ(result.code, rclcpp_action::ResultCode::SUCCEEDED);
// Sum all of the values in the requested fibonacci series
int sum = 0;
for (auto number : result.result->sequence) {
sum += number;
}
EXPECT_EQ(sum, 143);
EXPECT_GE(feedback_sum, 0); // We should have received *some* feedback
SUCCEED();
}
TEST_F(ActionTest, test_simple_action_activation_cycling)
{
// The goal for this invocation
auto goal = Fibonacci::Goal();
// Sending a goal that will take a long time to calculate
goal.order = 12'000'000;
// Start by sending goal on an active server
// Send the goal
auto future_goal_handle = node_->action_client_->async_send_goal(goal);
EXPECT_EQ(
rclcpp::spin_until_future_complete(
node_,
future_goal_handle), rclcpp::FutureReturnCode::SUCCESS);
// Deactivate while running
node_->deactivate_server();
auto goal_handle = future_goal_handle.get();
// Wait for the result
auto future_result = node_->action_client_->async_get_result(goal_handle);
EXPECT_EQ(
rclcpp::spin_until_future_complete(node_, future_result),
rclcpp::FutureReturnCode::SUCCESS);
// The action should be reported as aborted.
EXPECT_EQ(future_result.get().code, rclcpp_action::ResultCode::ABORTED);
// Cycle back to active
node_->activate_server();
goal.order = 12;
// Send the goal
future_goal_handle = node_->action_client_->async_send_goal(goal);
std::cout << "Sent goal, spinning til complete..." << std::endl;
EXPECT_EQ(
rclcpp::spin_until_future_complete(
node_,
future_goal_handle), rclcpp::FutureReturnCode::SUCCESS);
goal_handle = future_goal_handle.get();
// Wait for the result
future_result = node_->action_client_->async_get_result(goal_handle);
std::cout << "Getting result, spinning til complete..." << std::endl;
EXPECT_EQ(
rclcpp::spin_until_future_complete(node_, future_result),
rclcpp::FutureReturnCode::SUCCESS);
// Now the action should have been successfully executed.
EXPECT_EQ(future_result.get().code, rclcpp_action::ResultCode::SUCCEEDED);
SUCCEED();
}
TEST_F(ActionTest, test_simple_action_preemption)
{
// The goal for this invocation
auto goal = Fibonacci::Goal();
// Sending a goal that will take a long time to calculate
goal.order = 12'000'000;
// Send the goal
auto future_goal_handle = node_->action_client_->async_send_goal(goal);
std::cout << "Sent goal, spinning til complete..." << std::endl;
EXPECT_EQ(
rclcpp::spin_until_future_complete(
node_,
future_goal_handle), rclcpp::FutureReturnCode::SUCCESS);
// Preempt the goal
auto preemption_goal = Fibonacci::Goal();
preemption_goal.order = 1;
// Send the goal
future_goal_handle = node_->action_client_->async_send_goal(preemption_goal);
std::cout << "Sent goal, spinning til complete..." << std::endl;
EXPECT_EQ(
rclcpp::spin_until_future_complete(
node_,
future_goal_handle), rclcpp::FutureReturnCode::SUCCESS);
auto goal_handle = future_goal_handle.get();
// Wait for the result
auto future_result = node_->action_client_->async_get_result(goal_handle);
std::cout << "Getting result, spinning til complete..." << std::endl;
EXPECT_EQ(
rclcpp::spin_until_future_complete(node_, future_result),
rclcpp::FutureReturnCode::SUCCESS);
// The final result
rclcpp_action::ClientGoalHandle<Fibonacci>::WrappedResult result = future_result.get();
EXPECT_EQ(result.code, rclcpp_action::ResultCode::SUCCEEDED);
// Sum all of the values in the requested fibonacci series
int sum = 0;
for (auto number : result.result->sequence) {
sum += number;
}
EXPECT_EQ(sum, 1);
SUCCEED();
}
TEST_F(ActionTest, test_simple_action_preemption_after_succeeded)
{
// Test race condition between successfully completing an action and receiving a preemption.
auto goal = Fibonacci::Goal();
goal.order = 20;
auto preemption = Fibonacci::Goal();
preemption.order = 1;
// Send the goal
auto future_goal_handle = node_->action_client_->async_send_goal(goal);
EXPECT_EQ(
rclcpp::spin_until_future_complete(
node_,
future_goal_handle), rclcpp::FutureReturnCode::SUCCESS);
node_->omit_server_preemptions();
auto future_preempt_handle = node_->action_client_->async_send_goal(preemption);
EXPECT_EQ(
rclcpp::spin_until_future_complete(
node_,
future_goal_handle), rclcpp::FutureReturnCode::SUCCESS);
// Get the results
auto goal_handle = future_goal_handle.get();
// Wait for the result of initial goal
auto future_result = node_->action_client_->async_get_result(goal_handle);
EXPECT_EQ(
rclcpp::spin_until_future_complete(node_, future_result),
rclcpp::FutureReturnCode::SUCCESS);
// The final result
rclcpp_action::ClientGoalHandle<Fibonacci>::WrappedResult result = future_result.get();
EXPECT_EQ(result.code, rclcpp_action::ResultCode::SUCCEEDED);
// Sum all of the values in the requested fibonacci series
int sum = 0;
for (auto number : result.result->sequence) {
sum += number;
}
EXPECT_EQ(sum, 17710);
// Now get the preemption result
goal_handle = future_preempt_handle.get();
// Wait for the result of initial goal
future_result = node_->action_client_->async_get_result(goal_handle);
ASSERT_EQ(
rclcpp::spin_until_future_complete(node_, future_result),
rclcpp::FutureReturnCode::SUCCESS);
// The final result
result = future_result.get();
EXPECT_EQ(result.code, rclcpp_action::ResultCode::SUCCEEDED);
// Sum all of the values in the requested fibonacci series
sum = 0;
for (auto number : result.result->sequence) {
sum += number;
}
EXPECT_EQ(sum, 1);
SUCCEED();
}
TEST_F(ActionTest, test_handle_goal_deactivated)
{
node_->deactivate_server();
auto goal = Fibonacci::Goal();
goal.order = 12;
// Send the goal
auto future_goal_handle = node_->action_client_->async_send_goal(goal);
EXPECT_EQ(
rclcpp::spin_until_future_complete(
node_,
future_goal_handle), rclcpp::FutureReturnCode::SUCCESS);
node_->activate_server();
SUCCEED();
}
TEST_F(ActionTest, test_handle_cancel)
{
auto goal = Fibonacci::Goal();
goal.order = 14000000;
// Send the goal
auto future_goal_handle = node_->action_client_->async_send_goal(goal);
EXPECT_EQ(
rclcpp::spin_until_future_complete(
node_,
future_goal_handle), rclcpp::FutureReturnCode::SUCCESS);
// Cancel the goal
auto cancel_response = node_->action_client_->async_cancel_goal(future_goal_handle.get());
EXPECT_EQ(
rclcpp::spin_until_future_complete(
node_,
cancel_response), rclcpp::FutureReturnCode::SUCCESS);
// Check cancelled
EXPECT_EQ(future_goal_handle.get()->get_status(), rclcpp_action::GoalStatus::STATUS_CANCELING);
SUCCEED();
}
int main(int argc, char ** argv)
{
rclcpp::init(argc, argv);
g_rclcppfixture.Setup();
::testing::InitGoogleTest(&argc, argv);
auto result = RUN_ALL_TESTS();
rclcpp::shutdown();
rclcpp::Rate(1).sleep();
return result;
}
@@ -0,0 +1,33 @@
// Copyright (c) 2018 Intel Corporation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <chrono>
#include <thread>
#include "nav2_util/execution_timer.hpp"
#include "gtest/gtest.h"
using nav2_util::ExecutionTimer;
using std::this_thread::sleep_for;
using namespace std::chrono_literals;
TEST(ExecutionTimer, BasicDelay)
{
ExecutionTimer t;
t.start();
sleep_for(10ns);
t.end();
ASSERT_GE(t.elapsed_time(), 10ns);
ASSERT_GE(t.elapsed_time_in_seconds(), 1e-8);
}
@@ -0,0 +1,130 @@
// Copyright (c) 2018 Intel Corporation
// Copyright (c) 2020 Sarthak Mittal
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "nav2_util/geometry_utils.hpp"
#include "geometry_msgs/msg/point.hpp"
#include "geometry_msgs/msg/pose.hpp"
#include "nav_msgs/msg/path.hpp"
#include "gtest/gtest.h"
using nav2_util::geometry_utils::euclidean_distance;
using nav2_util::geometry_utils::calculate_path_length;
TEST(GeometryUtils, euclidean_distance_point_3d)
{
geometry_msgs::msg::Point point1;
point1.x = 3.0;
point1.y = 2.0;
point1.z = 1.0;
geometry_msgs::msg::Point point2;
point2.x = 1.0;
point2.y = 2.0;
point2.z = 3.0;
ASSERT_NEAR(euclidean_distance(point1, point2, true), 2.82843, 1e-5);
}
TEST(GeometryUtils, euclidean_distance_point_2d)
{
geometry_msgs::msg::Point point1;
point1.x = 3.0;
point1.y = 2.0;
point1.z = 1.0;
geometry_msgs::msg::Point point2;
point2.x = 1.0;
point2.y = 2.0;
point2.z = 3.0;
ASSERT_NEAR(euclidean_distance(point1, point2), 2.0, 1e-5);
}
TEST(GeometryUtils, euclidean_distance_pose_3d)
{
geometry_msgs::msg::Pose pose1;
pose1.position.x = 7.0;
pose1.position.y = 4.0;
pose1.position.z = 3.0;
geometry_msgs::msg::Pose pose2;
pose2.position.x = 17.0;
pose2.position.y = 6.0;
pose2.position.z = 2.0;
ASSERT_NEAR(euclidean_distance(pose1, pose2, true), 10.24695, 1e-5);
}
TEST(GeometryUtils, euclidean_distance_pose_2d)
{
geometry_msgs::msg::Pose pose1;
pose1.position.x = 7.0;
pose1.position.y = 4.0;
pose1.position.z = 3.0;
geometry_msgs::msg::Pose pose2;
pose2.position.x = 17.0;
pose2.position.y = 6.0;
pose2.position.z = 2.0;
ASSERT_NEAR(euclidean_distance(pose1, pose2), 10.19804, 1e-5);
}
TEST(GeometryUtils, calculate_path_length)
{
nav_msgs::msg::Path straight_line_path;
size_t nb_path_points = 10;
float distance_between_poses = 2.0;
float current_x_loc = 0.0;
for (size_t i = 0; i < nb_path_points; ++i) {
geometry_msgs::msg::PoseStamped pose_stamped_msg;
pose_stamped_msg.pose.position.x = current_x_loc;
straight_line_path.poses.push_back(pose_stamped_msg);
current_x_loc += distance_between_poses;
}
ASSERT_NEAR(
calculate_path_length(straight_line_path),
(nb_path_points - 1) * distance_between_poses, 1e-5);
ASSERT_NEAR(
calculate_path_length(straight_line_path, straight_line_path.poses.size()),
0.0, 1e-5);
nav_msgs::msg::Path circle_path;
float polar_distance = 2.0;
uint32_t current_polar_angle_deg = 0;
constexpr float pi = 3.14159265358979;
while (current_polar_angle_deg != 360) {
float x_loc = polar_distance * std::cos(current_polar_angle_deg * (pi / 180.0));
float y_loc = polar_distance * std::sin(current_polar_angle_deg * (pi / 180.0));
geometry_msgs::msg::PoseStamped pose_stamped_msg;
pose_stamped_msg.pose.position.x = x_loc;
pose_stamped_msg.pose.position.y = y_loc;
circle_path.poses.push_back(pose_stamped_msg);
current_polar_angle_deg += 1;
}
ASSERT_NEAR(
calculate_path_length(circle_path),
2 * pi * polar_distance, 1e-1);
}
@@ -0,0 +1,112 @@
// Copyright (c) 2020 Samsung Research
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef NAV2_UTIL__TEST__TEST_LIFECYCLE_CLI_NODE_HPP_
#define NAV2_UTIL__TEST__TEST_LIFECYCLE_CLI_NODE_HPP_
#include <cstdlib>
#include <memory>
#include "gtest/gtest.h"
#include "nav2_util/lifecycle_node.hpp"
#include "nav2_util/lifecycle_utils.hpp"
#include "nav2_util/node_thread.hpp"
#include "rclcpp/rclcpp.hpp"
#ifdef _WIN32
#include <windows.h>
#endif
class DummyNode : public nav2_util::LifecycleNode
{
public:
DummyNode()
: nav2_util::LifecycleNode("nav2_test_cli", "")
{
activated = false;
}
nav2_util::CallbackReturn on_activate(const rclcpp_lifecycle::State & /*state*/)
{
activated = true;
return nav2_util::CallbackReturn::SUCCESS;
}
bool activated;
};
class Handle
{
public:
Handle()
{
node = std::make_shared<DummyNode>();
thread = std::make_shared<nav2_util::NodeThread>(node->get_node_base_interface());
}
~Handle()
{
thread.reset();
node.reset();
}
std::shared_ptr<nav2_util::NodeThread> thread;
std::shared_ptr<DummyNode> node;
};
class RclCppFixture
{
public:
RclCppFixture()
{
rclcpp::init(0, nullptr);
}
~RclCppFixture()
{
rclcpp::shutdown();
}
};
RclCppFixture g_rclcppfixture;
TEST(LifeycleCLI, fails_no_node_name)
{
Handle handle;
auto rc = system("ros2 run nav2_util lifecycle_bringup");
(void)rc;
#ifdef _WIN32
Sleep(1000);
#else
sleep(1);
#endif
// check node didn't mode
EXPECT_EQ(handle.node->activated, false);
SUCCEED();
}
TEST(LifeycleCLI, succeeds_node_name)
{
Handle handle;
auto rc = system("ros2 run nav2_util lifecycle_bringup nav2_test_cli");
#ifdef _WIN32
Sleep(3000);
#else
sleep(3);
#endif
// check node moved
(void)rc;
EXPECT_EQ(handle.node->activated, true);
SUCCEED();
}
#endif // NAV2_UTIL__TEST__TEST_LIFECYCLE_CLI_NODE_HPP_
@@ -0,0 +1,85 @@
// Copyright (c) 2019 Intel Corporation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <memory>
#include "gtest/gtest.h"
#include "nav2_util/lifecycle_node.hpp"
#include "rclcpp/rclcpp.hpp"
class RclCppFixture
{
public:
RclCppFixture() {rclcpp::init(0, nullptr);}
~RclCppFixture() {rclcpp::shutdown();}
};
RclCppFixture g_rclcppfixture;
// For the following two tests, if the LifecycleNode doesn't shut down properly,
// the overall test will hang since the rclcpp thread will still be running,
// preventing the executable from exiting (the test will hang)
TEST(LifecycleNode, RclcppNodeExitsCleanly)
{
// Make sure the node exits cleanly when using an rclcpp_node and associated thread
auto node1 = std::make_shared<nav2_util::LifecycleNode>("test_node", "");
std::this_thread::sleep_for(std::chrono::seconds(1));
SUCCEED();
}
TEST(LifecycleNode, MultipleRclcppNodesExitCleanly)
{
// Try a couple nodes w/ rclcpp_node and threads
auto node1 = std::make_shared<nav2_util::LifecycleNode>("test_node1", "");
auto node2 = std::make_shared<nav2_util::LifecycleNode>("test_node2", "");
std::this_thread::sleep_for(std::chrono::seconds(1));
SUCCEED();
}
TEST(LifecycleNode, OnPreshutdownCbFires)
{
// Ensure the on_rcl_preshutdown_cb fires
class MyNodeType : public nav2_util::LifecycleNode
{
public:
MyNodeType(
const std::string & node_name)
: nav2_util::LifecycleNode(node_name) {}
bool fired = false;
protected:
void on_rcl_preshutdown() override
{
fired = true;
nav2_util::LifecycleNode::on_rcl_preshutdown();
}
};
auto node = std::make_shared<MyNodeType>("test_node");
ASSERT_EQ(node->fired, false);
rclcpp::shutdown();
ASSERT_EQ(node->fired, true);
// Fire dtor to ensure nothing insane happens, e.g. exceptions.
node.reset();
SUCCEED();
}
@@ -0,0 +1,60 @@
// Copyright (c) 2019 Intel Corporation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <thread>
#include <vector>
#include "gtest/gtest.h"
#include "nav2_util/lifecycle_utils.hpp"
#include "rclcpp_lifecycle/lifecycle_node.hpp"
#include "rclcpp/rclcpp.hpp"
using nav2_util::startup_lifecycle_nodes;
using nav2_util::reset_lifecycle_nodes;
class RclCppFixture
{
public:
RclCppFixture() {rclcpp::init(0, nullptr);}
~RclCppFixture() {rclcpp::shutdown();}
};
RclCppFixture g_rclcppfixture;
void SpinNodesUntilDone(
std::vector<rclcpp_lifecycle::LifecycleNode::SharedPtr> nodes,
std::atomic<bool> * test_done)
{
rclcpp::executors::SingleThreadedExecutor exec;
for (const auto & node : nodes) {
exec.add_node(node->get_node_base_interface());
}
while (rclcpp::ok() && !(*test_done)) {
exec.spin_some();
}
}
TEST(Lifecycle, interface)
{
std::vector<rclcpp_lifecycle::LifecycleNode::SharedPtr> nodes;
nodes.push_back(rclcpp_lifecycle::LifecycleNode::make_shared("foo"));
nodes.push_back(rclcpp_lifecycle::LifecycleNode::make_shared("bar"));
std::atomic<bool> done(false);
std::thread node_thread(SpinNodesUntilDone, nodes, &done);
startup_lifecycle_nodes("/foo:/bar");
reset_lifecycle_nodes("/foo:/bar");
done = true;
node_thread.join();
SUCCEED();
}
@@ -0,0 +1,128 @@
// Copyright (c) 2019 Intel Corporation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <memory>
#include <string>
#include "nav2_util/node_utils.hpp"
#include "gtest/gtest.h"
#include "rclcpp/rclcpp.hpp"
using nav2_util::sanitize_node_name;
using nav2_util::generate_internal_node_name;
using nav2_util::generate_internal_node;
using nav2_util::add_namespaces;
using nav2_util::time_to_string;
using nav2_util::declare_parameter_if_not_declared;
using nav2_util::get_plugin_type_param;
class RclCppFixture
{
public:
RclCppFixture() {rclcpp::init(0, nullptr);}
~RclCppFixture() {rclcpp::shutdown();}
};
RclCppFixture g_rclcppfixture;
TEST(SanitizeNodeName, SanitizeNodeName)
{
ASSERT_EQ(sanitize_node_name("bar"), "bar");
ASSERT_EQ(sanitize_node_name("/foo/bar"), "_foo_bar");
}
TEST(TimeToString, IsLengthCorrect)
{
ASSERT_EQ(time_to_string(0).length(), 0u);
ASSERT_EQ(time_to_string(1).length(), 1u);
ASSERT_EQ(time_to_string(10).length(), 10u);
ASSERT_EQ(time_to_string(20)[0], '0');
}
TEST(TimeToString, TimeToStringDifferent)
{
auto time1 = time_to_string(8);
auto time2 = time_to_string(8);
ASSERT_NE(time1, time2);
}
TEST(GenerateInternalNodeName, GenerateNodeName)
{
auto defaultName = generate_internal_node_name();
ASSERT_EQ(defaultName[0], '_');
ASSERT_EQ(defaultName.length(), 9u);
}
TEST(AddNamespaces, AddNamespaceSlash)
{
ASSERT_EQ(add_namespaces("hi", "bye"), "hi/bye");
ASSERT_EQ(add_namespaces("hi/", "bye"), "/hi/bye");
}
TEST(DeclareParameterIfNotDeclared, DeclareParameterIfNotDeclared)
{
auto node = std::make_shared<rclcpp::Node>("test_node");
std::string param;
// test declared parameter
node->declare_parameter("foobar", "foo");
declare_parameter_if_not_declared(node, "foobar", rclcpp::ParameterValue{"bar"});
node->get_parameter("foobar", param);
ASSERT_EQ(param, "foo");
// test undeclared parameter
declare_parameter_if_not_declared(node, "waldo", rclcpp::ParameterValue{"fred"});
node->get_parameter("waldo", param);
ASSERT_EQ(param, "fred");
}
TEST(GetPluginTypeParam, GetPluginTypeParam)
{
::testing::FLAGS_gtest_death_test_style = "threadsafe";
auto node = std::make_shared<rclcpp::Node>("test_node");
node->declare_parameter("Foo.plugin", "bar");
ASSERT_EQ(get_plugin_type_param(node, "Foo"), "bar");
ASSERT_EXIT(get_plugin_type_param(node, "Waldo"), ::testing::ExitedWithCode(255), ".*");
}
TEST(TestParamCopying, TestParamCopying)
{
auto node1 = std::make_shared<rclcpp::Node>("test_node1");
auto node2 = std::make_shared<rclcpp::Node>("test_node2");
// Tests for (1) multiple types, (2) recursion, (3) overriding values
node1->declare_parameter("Foo1", rclcpp::ParameterValue(std::string(("bar1"))));
node1->declare_parameter("Foo2", rclcpp::ParameterValue(0.123));
node1->declare_parameter("Foo", rclcpp::ParameterValue(std::string(("bar"))));
node1->declare_parameter("Foo.bar", rclcpp::ParameterValue(std::string(("steve"))));
node2->declare_parameter("Foo", rclcpp::ParameterValue(std::string(("barz2"))));
// Show Node2 is empty of Node1's parameters, but contains its own
EXPECT_FALSE(node2->has_parameter("Foo1"));
EXPECT_FALSE(node2->has_parameter("Foo2"));
EXPECT_FALSE(node2->has_parameter("Foo.bar"));
EXPECT_TRUE(node2->has_parameter("Foo"));
EXPECT_EQ(node2->get_parameter("Foo").as_string(), std::string("barz2"));
nav2_util::copy_all_parameters(node1, node2);
// Test new parameters exist, of expected value, and original param is not overridden
EXPECT_TRUE(node2->has_parameter("Foo1"));
EXPECT_EQ(node2->get_parameter("Foo1").as_string(), std::string("bar1"));
EXPECT_TRUE(node2->has_parameter("Foo2"));
EXPECT_EQ(node2->get_parameter("Foo2").as_double(), 0.123);
EXPECT_TRUE(node2->has_parameter("Foo.bar"));
EXPECT_EQ(node2->get_parameter("Foo.bar").as_string(), std::string("steve"));
EXPECT_TRUE(node2->has_parameter("Foo"));
EXPECT_EQ(node2->get_parameter("Foo").as_string(), std::string("barz2"));
}
@@ -0,0 +1,115 @@
// Copyright (c) 2020 Sarthak Mittal
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <memory>
#include <chrono>
#include "rclcpp/rclcpp.hpp"
#include "nav2_util/odometry_utils.hpp"
#include "nav_msgs/msg/odometry.hpp"
#include "geometry_msgs/msg/twist.hpp"
#include "gtest/gtest.h"
using namespace std::chrono; // NOLINT
using namespace std::chrono_literals; // NOLINT
class RclCppFixture
{
public:
RclCppFixture() {rclcpp::init(0, nullptr);}
~RclCppFixture() {rclcpp::shutdown();}
};
RclCppFixture g_rclcppfixture;
TEST(OdometryUtils, test_smoothed_velocity)
{
auto node = std::make_shared<rclcpp::Node>("test_node");
auto odom_pub = node->create_publisher<nav_msgs::msg::Odometry>("odom", 1);
nav2_util::OdomSmoother odom_smoother(node, 0.3, "odom");
nav_msgs::msg::Odometry odom_msg;
geometry_msgs::msg::Twist twist_msg;
auto time = node->now();
odom_msg.header.stamp = time;
odom_msg.twist.twist.linear.x = 1.0;
odom_msg.twist.twist.linear.y = 1.0;
odom_msg.twist.twist.angular.z = 1.0;
odom_pub->publish(odom_msg);
rclcpp::spin_some(node);
twist_msg = odom_smoother.getTwist();
EXPECT_EQ(twist_msg.linear.x, 1.0);
EXPECT_EQ(twist_msg.linear.y, 1.0);
EXPECT_EQ(twist_msg.angular.z, 1.0);
odom_msg.header.stamp = time + rclcpp::Duration::from_seconds(0.1);
odom_msg.twist.twist.linear.x = 2.0;
odom_msg.twist.twist.linear.y = 2.0;
odom_msg.twist.twist.angular.z = 2.0;
odom_pub->publish(odom_msg);
std::this_thread::sleep_for(100ms);
rclcpp::spin_some(node);
twist_msg = odom_smoother.getTwist();
EXPECT_EQ(twist_msg.linear.x, 1.5);
EXPECT_EQ(twist_msg.linear.y, 1.5);
EXPECT_EQ(twist_msg.angular.z, 1.5);
odom_msg.header.stamp = time + rclcpp::Duration::from_seconds(0.2);
odom_msg.twist.twist.linear.x = 3.0;
odom_msg.twist.twist.linear.y = 3.0;
odom_msg.twist.twist.angular.z = 3.0;
odom_pub->publish(odom_msg);
std::this_thread::sleep_for(100ms);
rclcpp::spin_some(node);
twist_msg = odom_smoother.getTwist();
EXPECT_EQ(twist_msg.linear.x, 2.0);
EXPECT_EQ(twist_msg.linear.y, 2.0);
EXPECT_EQ(twist_msg.angular.z, 2.0);
odom_msg.header.stamp = time + rclcpp::Duration::from_seconds(0.45);
odom_msg.twist.twist.linear.x = 4.0;
odom_msg.twist.twist.linear.y = 4.0;
odom_msg.twist.twist.angular.z = 4.0;
odom_pub->publish(odom_msg);
std::this_thread::sleep_for(100ms);
rclcpp::spin_some(node);
twist_msg = odom_smoother.getTwist();
EXPECT_EQ(twist_msg.linear.x, 3.5);
EXPECT_EQ(twist_msg.linear.y, 3.5);
EXPECT_EQ(twist_msg.angular.z, 3.5);
odom_msg.header.stamp = time + rclcpp::Duration::from_seconds(1.0);
odom_msg.twist.twist.linear.x = 5.0;
odom_msg.twist.twist.linear.y = 5.0;
odom_msg.twist.twist.angular.z = 5.0;
odom_pub->publish(odom_msg);
std::this_thread::sleep_for(100ms);
rclcpp::spin_some(node);
twist_msg = odom_smoother.getTwist();
EXPECT_EQ(twist_msg.linear.x, 5.0);
EXPECT_EQ(twist_msg.linear.y, 5.0);
EXPECT_EQ(twist_msg.angular.z, 5.0);
}
@@ -0,0 +1,60 @@
// Copyright (c) 2020 Samsung Research
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <memory>
#include <cmath>
#include "rclcpp/rclcpp.hpp"
#include "nav2_util/robot_utils.hpp"
#include "tf2_ros/transform_listener.h"
#include "tf2_ros/transform_broadcaster.h"
#include "geometry_msgs/msg/pose_stamped.hpp"
#include "gtest/gtest.h"
#include "nav2_util/node_thread.hpp"
#include "tf2_ros/create_timer_ros.h"
TEST(RobotUtils, LookupExceptionError)
{
rclcpp::init(0, nullptr);
auto node = std::make_shared<rclcpp::Node>("name", rclcpp::NodeOptions());
geometry_msgs::msg::PoseStamped global_pose;
tf2_ros::Buffer tf(node->get_clock());
ASSERT_FALSE(nav2_util::getCurrentPose(global_pose, tf, "map", "base_link", 0.1));
global_pose.header.frame_id = "base_link";
ASSERT_FALSE(nav2_util::transformPoseInTargetFrame(global_pose, global_pose, tf, "map", 0.1));
}
TEST(RobotUtils, validateTwist)
{
geometry_msgs::msg::Twist msg;
EXPECT_TRUE(nav2_util::validateTwist(msg));
msg.linear.x = NAN;
EXPECT_FALSE(nav2_util::validateTwist(msg));
msg.linear.x = 1;
msg.linear.y = NAN;
EXPECT_FALSE(nav2_util::validateTwist(msg));
msg.linear.y = 1;
msg.linear.z = NAN;
EXPECT_FALSE(nav2_util::validateTwist(msg));
msg.linear.z = 1;
msg.angular.x = NAN;
EXPECT_FALSE(nav2_util::validateTwist(msg));
msg.angular.x = 1;
msg.angular.y = NAN;
EXPECT_FALSE(nav2_util::validateTwist(msg));
msg.angular.y = 1;
msg.angular.z = NAN;
EXPECT_FALSE(nav2_util::validateTwist(msg));
}
@@ -0,0 +1,99 @@
// Copyright (c) 2019 Intel Corporation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <memory>
#include <string>
#include "nav2_util/service_client.hpp"
#include "rclcpp/rclcpp.hpp"
#include "std_srvs/srv/empty.hpp"
#include "std_msgs/msg/empty.hpp"
#include "gtest/gtest.h"
using nav2_util::ServiceClient;
using std::string;
class RclCppFixture
{
public:
RclCppFixture() {rclcpp::init(0, nullptr);}
~RclCppFixture() {rclcpp::shutdown();}
};
RclCppFixture g_rclcppfixture;
class TestServiceClient : public ServiceClient<std_srvs::srv::Empty>
{
public:
TestServiceClient(
const std::string & name,
const rclcpp::Node::SharedPtr & provided_node = rclcpp::Node::SharedPtr())
: ServiceClient(name, provided_node) {}
string name() {return node_->get_name();}
const rclcpp::Node::SharedPtr & getNode() {return node_;}
};
TEST(ServiceClient, can_ServiceClient_use_passed_in_node)
{
auto node = rclcpp::Node::make_shared("test_node");
TestServiceClient t("bar", node);
ASSERT_EQ(t.getNode(), node);
ASSERT_EQ(t.name(), "test_node");
}
TEST(ServiceClient, can_ServiceClient_invoke_in_callback)
{
int a = 0;
auto service_node = rclcpp::Node::make_shared("service_node");
auto service = service_node->create_service<std_srvs::srv::Empty>(
"empty_srv",
[&a](std_srvs::srv::Empty::Request::SharedPtr, std_srvs::srv::Empty::Response::SharedPtr) {
a = 1;
});
auto srv_thread = std::thread([&]() {rclcpp::spin(service_node);});
auto pub_node = rclcpp::Node::make_shared("pub_node");
auto pub = pub_node->create_publisher<std_msgs::msg::Empty>(
"empty_topic",
rclcpp::QoS(1).transient_local());
auto pub_thread = std::thread([&]() {rclcpp::spin(pub_node);});
auto sub_node = rclcpp::Node::make_shared("sub_node");
ServiceClient<std_srvs::srv::Empty> client("empty_srv", sub_node);
auto sub = sub_node->create_subscription<std_msgs::msg::Empty>(
"empty_topic",
rclcpp::QoS(1),
[&client](std_msgs::msg::Empty::SharedPtr) {
auto req = std::make_shared<std_srvs::srv::Empty::Request>();
auto res = client.invoke(req);
});
pub->publish(std_msgs::msg::Empty());
rclcpp::spin_some(sub_node);
rclcpp::shutdown();
srv_thread.join();
pub_thread.join();
ASSERT_EQ(a, 1);
}
TEST(ServiceClient, can_ServiceClient_timeout)
{
rclcpp::init(0, nullptr);
auto node = rclcpp::Node::make_shared("test_node");
TestServiceClient t("bar", node);
rclcpp::spin_some(node);
bool ready = t.wait_for_service(std::chrono::milliseconds(10));
rclcpp::shutdown();
ASSERT_EQ(ready, false);
}
@@ -0,0 +1,32 @@
// Copyright (c) 2019 Intel Corporation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <string>
#include "nav2_util/string_utils.hpp"
#include "gtest/gtest.h"
using nav2_util::split;
using nav2_util::Tokens;
TEST(Split, SplitFunction)
{
ASSERT_EQ(split("", ':'), Tokens({""}));
ASSERT_EQ(split("foo", ':'), Tokens{"foo"});
ASSERT_EQ(split("foo:bar", ':'), Tokens({"foo", "bar"}));
ASSERT_EQ(split("foo:bar:", ':'), Tokens({"foo", "bar", ""}));
ASSERT_EQ(split(":", ':'), Tokens({"", ""}));
ASSERT_EQ(split("foo::bar", ':'), Tokens({"foo", "", "bar"}));
ASSERT_TRUE(nav2_util::strip_leading_slash(std::string("/hi")) == std::string("hi"));
}
@@ -0,0 +1,368 @@
// Copyright (c) 2024 GoesM
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <gtest/gtest.h>
#include "nav2_util/validate_messages.hpp"
TEST(ValidateMessagesTest, DoubleValueCheck) {
// Test valid double value
EXPECT_TRUE(nav2_util::validateMsg(3.14));
// Test invalid double value (infinity)
EXPECT_FALSE(nav2_util::validateMsg(std::numeric_limits<double>::infinity()));
// Test invalid double value (NaN)
EXPECT_FALSE(nav2_util::validateMsg(std::numeric_limits<double>::quiet_NaN()));
}
TEST(ValidateMessagesTest, TimeStampCheck)
{
// Test valid time stamp
builtin_interfaces::msg::Time valid_time_stamp;
valid_time_stamp.sec = 123;
valid_time_stamp.nanosec = 456789;
EXPECT_TRUE(nav2_util::validateMsg(valid_time_stamp));
// Test invalid time stamp (nanosec out of range)
builtin_interfaces::msg::Time invalid_time_stamp;
invalid_time_stamp.sec = 123;
invalid_time_stamp.nanosec = 1e9; // 1 second = 1e9 nanoseconds
EXPECT_FALSE(nav2_util::validateMsg(invalid_time_stamp));
}
TEST(ValidateMessagesTest, HeaderCheck)
{
// Test valid header with non-empty frame_id
std_msgs::msg::Header valid_header;
valid_header.stamp.sec = 123;
valid_header.stamp.nanosec = 456789;
valid_header.frame_id = "map";
EXPECT_TRUE(nav2_util::validateMsg(valid_header));
// Test invalid header with empty frame_id
std_msgs::msg::Header invalid_header;
invalid_header.stamp.sec = 123;
invalid_header.stamp.nanosec = 456789;
invalid_header.frame_id = "";
EXPECT_FALSE(nav2_util::validateMsg(invalid_header));
invalid_header.stamp.sec = 123;
invalid_header.stamp.nanosec = 1e9;
invalid_header.frame_id = "map";
EXPECT_FALSE(nav2_util::validateMsg(invalid_header));
}
TEST(ValidateMessagesTest, PointCheck)
{
// Test valid Point message
geometry_msgs::msg::Point valid_point;
valid_point.x = 1.0;
valid_point.y = 2.0;
valid_point.z = 3.0;
EXPECT_TRUE(nav2_util::validateMsg(valid_point));
// Test invalid Point message with NaN value
geometry_msgs::msg::Point invalid_point;
invalid_point.x = 1.0;
invalid_point.y = std::numeric_limits<double>::quiet_NaN();
invalid_point.z = 3.0;
EXPECT_FALSE(nav2_util::validateMsg(invalid_point));
// Test invalid Point message with NaN value
invalid_point.x = std::numeric_limits<double>::quiet_NaN();
invalid_point.y = 2.0;
invalid_point.z = 3.0;
EXPECT_FALSE(nav2_util::validateMsg(invalid_point));
// Test invalid Point message with NaN value
invalid_point.x = 1.0;
invalid_point.y = 2.0;
invalid_point.z = std::numeric_limits<double>::quiet_NaN();
EXPECT_FALSE(nav2_util::validateMsg(invalid_point));
}
TEST(ValidateMessagesTest, QuaternionCheck)
{
// Test valid Quaternion message
geometry_msgs::msg::Quaternion valid_quaternion;
valid_quaternion.x = 0.0;
valid_quaternion.y = 0.0;
valid_quaternion.z = 0.0;
valid_quaternion.w = 1.0;
EXPECT_TRUE(nav2_util::validateMsg(valid_quaternion));
// Test invalid Quaternion message with invalid magnitude
geometry_msgs::msg::Quaternion invalid_quaternion;
invalid_quaternion.x = 0.1;
invalid_quaternion.y = 0.2;
invalid_quaternion.z = 0.3;
invalid_quaternion.w = 0.5; // Invalid magnitude (should be 1.0)
EXPECT_FALSE(nav2_util::validateMsg(invalid_quaternion));
// One NaN value
invalid_quaternion.x = 0.0;
invalid_quaternion.y = std::numeric_limits<double>::quiet_NaN();
invalid_quaternion.z = 0.0;
invalid_quaternion.w = 1.0;
EXPECT_FALSE(nav2_util::validateMsg(invalid_quaternion));
invalid_quaternion.x = std::numeric_limits<double>::quiet_NaN();
invalid_quaternion.y = 0.0;
invalid_quaternion.z = 0.0;
invalid_quaternion.w = 1.0;
EXPECT_FALSE(nav2_util::validateMsg(invalid_quaternion));
invalid_quaternion.x = 0.0;
invalid_quaternion.y = 0.0;
invalid_quaternion.z = std::numeric_limits<double>::quiet_NaN();
invalid_quaternion.w = 1.0;
EXPECT_FALSE(nav2_util::validateMsg(invalid_quaternion));
invalid_quaternion.x = 0.0;
invalid_quaternion.y = 0.0;
invalid_quaternion.z = 1.0;
invalid_quaternion.w = std::numeric_limits<double>::quiet_NaN();
EXPECT_FALSE(nav2_util::validateMsg(invalid_quaternion));
}
TEST(ValidateMessagesTest, PoseCheck)
{
// Test valid Pose message
geometry_msgs::msg::Pose valid_pose;
valid_pose.position.x = 1.0;
valid_pose.position.y = 2.0;
valid_pose.position.z = 3.0;
valid_pose.orientation.x = 1.0;
valid_pose.orientation.y = 0.0;
valid_pose.orientation.z = 0.0;
valid_pose.orientation.w = 0.0;
EXPECT_TRUE(nav2_util::validateMsg(valid_pose));
// Test invalid Pose message with invalid position
geometry_msgs::msg::Pose invalid_pose;
invalid_pose.position.x = 1.0;
invalid_pose.position.y = std::numeric_limits<double>::quiet_NaN();
invalid_pose.position.z = 3.0;
invalid_pose.orientation.x = 1.0;
invalid_pose.orientation.y = 0.0;
invalid_pose.orientation.z = 0.0;
invalid_pose.orientation.w = 0.0;
EXPECT_FALSE(nav2_util::validateMsg(invalid_pose));
// Test invalid Pose message with invalid orientation
invalid_pose.position.x = 1.0;
invalid_pose.position.y = 2.0;
invalid_pose.position.z = 3.0;
invalid_pose.orientation.x = 0.1;
invalid_pose.orientation.y = 0.2;
invalid_pose.orientation.z = 0.3;
invalid_pose.orientation.w = 0.4;
EXPECT_FALSE(nav2_util::validateMsg(invalid_pose));
}
TEST(ValidateMessagesTest, MapMetaDataCheck) {
// Test valid MapMetaData message
nav_msgs::msg::MapMetaData valid_map_meta_data;
valid_map_meta_data.resolution = 0.05;
valid_map_meta_data.width = 100;
valid_map_meta_data.height = 100;
geometry_msgs::msg::Pose valid_origin;
valid_origin.position.x = 0.0;
valid_origin.position.y = 0.0;
valid_origin.position.z = 0.0;
valid_origin.orientation.x = 0.0;
valid_origin.orientation.y = 0.0;
valid_origin.orientation.z = 0.0;
valid_origin.orientation.w = 1.0;
valid_map_meta_data.origin = valid_origin;
EXPECT_TRUE(nav2_util::validateMsg(valid_map_meta_data));
// Test invalid origin message
nav_msgs::msg::MapMetaData invalid_map_meta_data;
invalid_map_meta_data.resolution = 100.0;
invalid_map_meta_data.width = 100;
invalid_map_meta_data.height = 100;
geometry_msgs::msg::Pose invalid_origin;
invalid_origin.position.x = 0.0;
invalid_origin.position.y = 0.0;
invalid_origin.position.z = 0.0;
invalid_origin.orientation.x = 0.0;
invalid_origin.orientation.y = 0.0;
invalid_origin.orientation.z = 1.0;
invalid_origin.orientation.w = 1.0;
invalid_map_meta_data.origin = invalid_origin;
EXPECT_FALSE(nav2_util::validateMsg(invalid_map_meta_data));
// Test invalid resolution message
invalid_map_meta_data.resolution = std::numeric_limits<double>::quiet_NaN();
invalid_map_meta_data.width = 100;
invalid_map_meta_data.height = 100;
invalid_map_meta_data.origin = valid_origin;
EXPECT_FALSE(nav2_util::validateMsg(invalid_map_meta_data));
// Test invalid MapMetaData message with zero width
invalid_map_meta_data.resolution = 0.05;
invalid_map_meta_data.width = 0;
invalid_map_meta_data.height = 100;
invalid_map_meta_data.origin = valid_origin;
EXPECT_FALSE(nav2_util::validateMsg(invalid_map_meta_data));
}
TEST(ValidateMessagesTest, OccupancyGridCheck) {
// Test valid OccupancyGrid message
nav_msgs::msg::OccupancyGrid valid_occupancy_grid;
valid_occupancy_grid.header.frame_id = "map";
valid_occupancy_grid.info.resolution = 0.05;
valid_occupancy_grid.info.width = 100;
valid_occupancy_grid.info.height = 100;
std::vector<int8_t> data(100 * 100, 0); // Initialize with zeros
valid_occupancy_grid.data = data;
EXPECT_TRUE(nav2_util::validateMsg(valid_occupancy_grid));
// Test invalid header message with wrong data size
nav_msgs::msg::OccupancyGrid invalid_occupancy_grid;
invalid_occupancy_grid.header.frame_id = ""; // Incorrect id
invalid_occupancy_grid.info.resolution = 0.05;
invalid_occupancy_grid.info.width = 100;
invalid_occupancy_grid.info.height = 100;
invalid_occupancy_grid.data = data;
EXPECT_FALSE(nav2_util::validateMsg(invalid_occupancy_grid));
// Test invalid info message with wrong data size
invalid_occupancy_grid.header.frame_id = "map";
invalid_occupancy_grid.info.resolution = 0.05;
invalid_occupancy_grid.info.width = 0; // Incorrect width
invalid_occupancy_grid.info.height = 100;
invalid_occupancy_grid.data = data;
EXPECT_FALSE(nav2_util::validateMsg(invalid_occupancy_grid));
// Test invalid OccupancyGrid message with wrong data size
invalid_occupancy_grid.header.frame_id = "map";
invalid_occupancy_grid.info.resolution = 0.05;
invalid_occupancy_grid.info.width = 100;
invalid_occupancy_grid.info.height = 100;
std::vector<int8_t> invalid_data(100 * 99, 0); // Incorrect data size
invalid_occupancy_grid.data = invalid_data;
EXPECT_FALSE(nav2_util::validateMsg(invalid_occupancy_grid));
}
TEST(ValidateMessagesTest, PoseWithCovarianceCheck) {
// Valid message
geometry_msgs::msg::PoseWithCovariance validate_msg;
validate_msg.covariance[0] = 0.25;
// assign other covariance values...
validate_msg.covariance[35] = 0.06853891909122467;
validate_msg.pose.position.x = 0.50010401010515571;
validate_msg.pose.position.y = 1.7468730211257935;
validate_msg.pose.position.z = 0.0;
validate_msg.pose.orientation.x = 0.9440542194053062;
validate_msg.pose.orientation.y = 0.0;
validate_msg.pose.orientation.z = 0.0;
validate_msg.pose.orientation.w = -0.32979028309372299;
EXPECT_TRUE(nav2_util::validateMsg(validate_msg));
// Invalid messages
geometry_msgs::msg::PoseWithCovariance invalidate_msg1;
invalidate_msg1.covariance[0] = 0.25;
// assign other covariance values...
invalidate_msg1.covariance[7] = NAN;
invalidate_msg1.covariance[9] = NAN;
invalidate_msg1.covariance[35] = 0.06853891909122467;
invalidate_msg1.pose.position.x = 0.50010401010515571;
invalidate_msg1.pose.position.y = 1.7468730211257935;
invalidate_msg1.pose.position.z = 0.0;
invalidate_msg1.pose.orientation.x = 0.9440542194053062;
invalidate_msg1.pose.orientation.y = 0.0;
invalidate_msg1.pose.orientation.z = 0.0;
invalidate_msg1.pose.orientation.w = -0.32979028309372299;
EXPECT_FALSE(nav2_util::validateMsg(invalidate_msg1));
geometry_msgs::msg::PoseWithCovariance invalidate_msg2;
invalidate_msg2.covariance[0] = 0.25;
// assign other covariance values...
invalidate_msg2.covariance[35] = 0.06853891909122467;
invalidate_msg2.pose.position.x = NAN;
invalidate_msg2.pose.position.y = 1.7468730211257935;
invalidate_msg2.pose.position.z = 0.0;
invalidate_msg2.pose.orientation.x = 0.9440542194053062;
invalidate_msg2.pose.orientation.y = 0.0;
invalidate_msg2.pose.orientation.z = 0.0;
invalidate_msg2.pose.orientation.w = -0.32979028309372299;
EXPECT_FALSE(nav2_util::validateMsg(invalidate_msg2));
}
TEST(ValidateMessagesTest, PoseWithCovarianceStampedCheck) {
// Valid message
geometry_msgs::msg::PoseWithCovarianceStamped validate_msg;
validate_msg.header.frame_id = "map";
validate_msg.header.stamp.sec = 1711029956;
validate_msg.header.stamp.nanosec = 146734875;
validate_msg.pose.covariance[0] = 0.25;
// assign other covariance values...
validate_msg.pose.covariance[35] = 0.06853891909122467;
validate_msg.pose.pose.position.x = 0.50010401010515571;
validate_msg.pose.pose.position.y = 1.7468730211257935;
validate_msg.pose.pose.position.z = 0.0;
validate_msg.pose.pose.orientation.x = 0.9440542194053062;
validate_msg.pose.pose.orientation.y = 0.0;
validate_msg.pose.pose.orientation.z = 0.0;
validate_msg.pose.pose.orientation.w = -0.32979028309372299;
EXPECT_TRUE(nav2_util::validateMsg(validate_msg));
// Invalid messages
geometry_msgs::msg::PoseWithCovarianceStamped invalidate_msg1;
invalidate_msg1.header.frame_id = "map";
invalidate_msg1.header.stamp.sec = 1711029956;
invalidate_msg1.header.stamp.nanosec = 146734875;
invalidate_msg1.pose.covariance[0] = 0.25;
// assign other covariance values...
invalidate_msg1.pose.covariance[7] = NAN;
invalidate_msg1.pose.covariance[9] = NAN;
invalidate_msg1.pose.covariance[35] = 0.06853891909122467;
invalidate_msg1.pose.pose.position.x = 0.50010401010515571;
invalidate_msg1.pose.pose.position.y = 1.7468730211257935;
invalidate_msg1.pose.pose.position.z = 0.0;
invalidate_msg1.pose.pose.orientation.x = 0.9440542194053062;
invalidate_msg1.pose.pose.orientation.y = 0.0;
invalidate_msg1.pose.pose.orientation.z = 0.0;
invalidate_msg1.pose.pose.orientation.w = -0.32979028309372299;
EXPECT_FALSE(nav2_util::validateMsg(invalidate_msg1));
geometry_msgs::msg::PoseWithCovarianceStamped invalidate_msg2;
invalidate_msg2.header.frame_id = "";
invalidate_msg2.header.stamp.sec = 1711029956;
invalidate_msg2.header.stamp.nanosec = 146734875;
invalidate_msg2.pose.covariance[0] = 0.25;
// assign other covariance values...
invalidate_msg2.pose.covariance[35] = 0.06853891909122467;
invalidate_msg2.pose.pose.position.x = 0.50010401010515571;
invalidate_msg2.pose.pose.position.y = 1.7468730211257935;
invalidate_msg2.pose.pose.position.z = 0.0;
invalidate_msg2.pose.pose.orientation.x = 0.9440542194053062;
invalidate_msg2.pose.pose.orientation.y = 0.0;
invalidate_msg2.pose.pose.orientation.z = 0.0;
invalidate_msg2.pose.pose.orientation.w = -0.32979028309372299;
EXPECT_FALSE(nav2_util::validateMsg(invalidate_msg2));
}
// Add more test cases for other validateMsg functions if needed