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
@@ -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_