15 #include "nav2_costmap_2d/costmap_filters/zone_parameter_filter.hpp"
26 #include "nav2_costmap_2d/costmap_filters/filter_values.hpp"
27 #include "nav2_util/occ_grid_utils.hpp"
32 ZoneParameterFilter::ZoneParameterFilter()
33 : filter_info_sub_(nullptr),
35 state_event_pub_(nullptr),
36 filter_mask_(nullptr),
41 void ZoneParameterFilter::initializeFilter(
42 const std::string & filter_info_topic)
44 std::lock_guard<CostmapFilter::mutex_t> guard(*getMutex());
46 auto node = node_.lock();
48 throw std::runtime_error{
"Failed to lock node"};
51 global_frame_ = layered_costmap_->getGlobalFrameID();
53 node->declare_or_get_parameter<std::string>(
54 name_ +
"." +
"state_event_topic", std::string(
"zone_filter_state"));
55 filter_info_topic_ = joinWithParentNamespace(filter_info_topic);
58 "ZoneParameterFilter: Subscribing to \"%s\" topic for filter info...",
59 filter_info_topic_.c_str());
61 filter_info_sub_ = node->create_subscription<nav2_msgs::msg::CostmapFilterInfo>(
63 std::bind(&ZoneParameterFilter::filterInfoCallback,
this, std::placeholders::_1),
67 node->create_publisher<std_msgs::msg::UInt8>(joinWithParentNamespace(state_event_topic_));
68 state_event_pub_->on_activate();
73 void ZoneParameterFilter::filterInfoCallback(
74 const nav2_msgs::msg::CostmapFilterInfo::ConstSharedPtr & msg)
76 std::lock_guard<CostmapFilter::mutex_t> guard(*getMutex());
78 auto node = node_.lock();
80 throw std::runtime_error{
"Failed to lock node"};
86 "ZoneParameterFilter: Received filter info from %s topic.", filter_info_topic_.c_str());
90 "ZoneParameterFilter: New costmap filter info arrived from %s topic. "
91 "Updating old filter info.",
92 filter_info_topic_.c_str());
96 if (msg->type != ZONE_PARAMETER_FILTER) {
99 "ZoneParameterFilter: CostmapFilterInfo type is %i, expected %i (ZONE_PARAMETER_FILTER)",
100 msg->type, ZONE_PARAMETER_FILTER);
104 if (msg->base != BASE_DEFAULT || msg->multiplier != MULTIPLIER_DEFAULT) {
107 "ZoneParameterFilter: base=%f and multiplier=%f are unused by this filter "
108 "(state mapping is config-driven). Expected defaults (%f, %f).",
109 msg->base, msg->multiplier, BASE_DEFAULT, MULTIPLIER_DEFAULT);
112 filter_info_received_ =
true;
113 mask_topic_ = joinWithParentNamespace(msg->filter_mask_topic);
117 "ZoneParameterFilter: Subscribing to \"%s\" topic for filter mask...",
118 mask_topic_.c_str());
119 mask_sub_ = node->create_subscription<nav_msgs::msg::OccupancyGrid>(
121 std::bind(&ZoneParameterFilter::maskCallback,
this, std::placeholders::_1),
125 void ZoneParameterFilter::maskCallback(
126 const nav_msgs::msg::OccupancyGrid::ConstSharedPtr & msg)
128 std::lock_guard<CostmapFilter::mutex_t> guard(*getMutex());
133 "ZoneParameterFilter: Received filter mask from %s topic.", mask_topic_.c_str());
137 "ZoneParameterFilter: New filter mask arrived from %s topic. Updating old filter mask.",
138 mask_topic_.c_str());
139 filter_mask_.reset();
145 void ZoneParameterFilter::loadStateConfig()
147 auto node = node_.lock();
149 throw std::runtime_error{
"Failed to lock node"};
154 [&](
const std::string & prefix) -> std::optional<StateParamEntry> {
155 const std::string target_node =
156 node->declare_or_get_parameter<std::string>(prefix +
".node", std::string(
""));
157 const std::string param_name =
158 node->declare_or_get_parameter<std::string>(prefix +
".parameter", std::string(
""));
159 if (target_node.empty() || param_name.empty()) {
162 "ZoneParameterFilter: '%s' must declare non-empty 'node' and 'parameter'.",
166 const std::string value_key = prefix +
".value";
167 if (!node->has_parameter(value_key)) {
168 rcl_interfaces::msg::ParameterDescriptor descriptor;
169 descriptor.dynamic_typing =
true;
170 node->declare_parameter(value_key, rclcpp::ParameterValue{}, descriptor);
172 const rclcpp::Parameter value_param = node->get_parameter(value_key);
173 if (value_param.get_type() == rclcpp::ParameterType::PARAMETER_NOT_SET) {
174 RCLCPP_ERROR(logger_,
"ZoneParameterFilter: '%s' is not set.", value_key.c_str());
178 target_node, rclcpp::Parameter(param_name, value_param.get_parameter_value())};
181 const std::vector<std::string> state_names =
182 node->declare_or_get_parameter<std::vector<std::string>>(
183 name_ +
".states", std::vector<std::string>{});
185 if (state_names.empty()) {
188 "ZoneParameterFilter: 'states' is empty; this filter will only handle "
189 "state 0 (reset). Configure `states: [name_a, ...]` in YAML.");
192 for (
const auto & state_name : state_names) {
193 const std::string state_prefix = name_ +
"." + state_name;
195 const int64_t id_i64 =
196 node->declare_or_get_parameter<int64_t>(state_prefix +
".id", 0);
197 if (id_i64 <= 0 || id_i64 > 255) {
200 "ZoneParameterFilter: state '%s' has id %ld outside the valid range "
201 "[1, 255] (0 is reserved for reset); skipping.",
202 state_name.c_str(), id_i64);
205 const uint8_t state_id =
static_cast<uint8_t
>(id_i64);
207 const std::vector<std::string> setpoint_names =
208 node->declare_or_get_parameter<std::vector<std::string>>(
209 state_prefix +
".setpoints", std::vector<std::string>{});
211 std::vector<StateParamEntry> params_for_state;
212 for (
const auto & setpoint_name : setpoint_names) {
213 if (
auto entry = read_entry(state_prefix +
"." + setpoint_name)) {
214 params_for_state.push_back(std::move(*entry));
218 if (params_for_state.empty()) {
221 "ZoneParameterFilter: state '%s' (id %u) declares no valid setpoints.",
222 state_name.c_str(), state_id);
225 state_param_map_[state_id] = std::move(params_for_state);
228 "ZoneParameterFilter: state '%s' (id %u) -> %zu setpoint(s).",
229 state_name.c_str(), state_id, state_param_map_[state_id].size());
233 const std::vector<std::string> nominal_names =
234 node->declare_or_get_parameter<std::vector<std::string>>(
235 name_ +
".nominal_defaults", std::vector<std::string>{});
236 for (
const auto & nominal_name : nominal_names) {
237 if (
auto entry = read_entry(name_ +
".nominal_defaults." + nominal_name)) {
238 nominal_defaults_[entry->target_node].push_back(entry->param);
243 "ZoneParameterFilter: %zu nominal default(s) loaded for state-0 reset.",
244 nominal_names.size());
249 const auto node_it = nominal_defaults_.find(e.target_node);
250 if (node_it == nominal_defaults_.end()) {
253 for (
const auto & nominal : node_it->second) {
254 if (nominal.get_name() == e.param.get_name()) {
260 for (
const auto & [state_id, entries] : state_param_map_) {
261 for (
const auto & entry : entries) {
262 if (!has_nominal(entry)) {
265 "ZoneParameterFilter: state id %u sets '%s' on '%s' but no matching "
266 "nominal_defaults entry exists; state-0 reset will NOT restore it.",
267 state_id, entry.param.get_name().c_str(), entry.target_node.c_str());
273 std::set<std::string> all_target_nodes;
274 for (
const auto & [_state_id, entries] : state_param_map_) {
275 for (
const auto & e : entries) {
276 all_target_nodes.insert(e.target_node);
279 for (
const auto & [target_node, _params] : nominal_defaults_) {
280 all_target_nodes.insert(target_node);
282 for (
const auto & target_node : all_target_nodes) {
283 param_clients_.emplace(
285 std::make_shared<rclcpp::AsyncParametersClient>(
286 node->get_node_base_interface(),
287 node->get_node_topics_interface(),
288 node->get_node_graph_interface(),
289 node->get_node_services_interface(),
294 "ZoneParameterFilter: %zu AsyncParametersClient(s) built at init.",
295 param_clients_.size());
298 void ZoneParameterFilter::process(
300 int ,
int ,
int ,
int ,
301 const geometry_msgs::msg::Pose & pose)
303 std::lock_guard<CostmapFilter::mutex_t> guard(*getMutex());
305 checkPendingParameterUpdates();
308 RCLCPP_WARN_THROTTLE(
309 logger_, *(clock_), 2000,
310 "ZoneParameterFilter: Filter mask was not received");
314 geometry_msgs::msg::Pose mask_pose;
315 if (!transformPose(global_frame_, pose, filter_mask_->header.frame_id, mask_pose)) {
319 unsigned int mask_robot_i, mask_robot_j;
320 if (!nav2_util::worldToMap(
321 filter_mask_, mask_pose.position.x, mask_pose.position.y,
322 mask_robot_i, mask_robot_j))
324 if (state_initialized_ && current_state_ != 0) {
327 "ZoneParameterFilter: Robot outside filter mask; resetting to nominal defaults.");
334 const int8_t mask_data = getMaskData(filter_mask_, mask_robot_i, mask_robot_j);
337 RCLCPP_WARN_THROTTLE(
338 logger_, *(clock_), 2000,
339 "ZoneParameterFilter: Filter mask cell [%u, %u] is unknown; not changing state.",
340 mask_robot_i, mask_robot_j);
344 const uint8_t new_state =
static_cast<uint8_t
>(mask_data);
346 if (state_initialized_ && new_state == current_state_) {
350 applyState(new_state);
352 if (state_event_pub_) {
353 auto event_msg = std::make_unique<std_msgs::msg::UInt8>();
354 event_msg->data = new_state;
355 state_event_pub_->publish(std::move(event_msg));
358 current_state_ = new_state;
359 state_initialized_ =
true;
362 void ZoneParameterFilter::applyState(uint8_t new_state)
364 if (new_state == 0) {
366 RCLCPP_INFO(logger_,
"ZoneParameterFilter: Entered state 0 (reset to nominal).");
370 auto it = state_param_map_.find(new_state);
371 if (it == state_param_map_.end()) {
372 throw std::runtime_error(
373 std::string(
"ZoneParameterFilter: unknown state ") +
374 std::to_string(new_state) +
375 " encountered; declare a state with id " +
376 std::to_string(new_state) +
" under the filter's `states` list.");
379 std::set<std::pair<std::string, std::string>> m_keys;
380 for (
const auto & entry : it->second) {
381 m_keys.emplace(entry.target_node, entry.param.get_name());
390 std::map<std::string, std::vector<rclcpp::Parameter>> reset_per_node;
391 if (state_initialized_ && current_state_ != 0) {
392 auto prev_it = state_param_map_.find(current_state_);
393 if (prev_it != state_param_map_.end()) {
394 for (
const auto & entry : prev_it->second) {
395 const auto key = std::make_pair(entry.target_node, entry.param.get_name());
396 if (m_keys.count(key) > 0) {
399 const auto node_it = nominal_defaults_.find(entry.target_node);
400 if (node_it == nominal_defaults_.end()) {
405 for (
const auto & nominal : node_it->second) {
406 if (nominal.get_name() == entry.param.get_name()) {
407 reset_per_node[entry.target_node].push_back(nominal);
415 "ZoneParameterFilter: current state %u is not in state_param_map_ "
416 "(should have been recorded when the state was applied).",
422 std::map<std::string, std::vector<rclcpp::Parameter>> per_node_params;
423 for (
const auto & entry : it->second) {
424 per_node_params[entry.target_node].push_back(entry.param);
428 size_t reset_count = 0;
429 for (
const auto & [target_node, params] : reset_per_node) {
430 issueAsyncSetParameters(target_node, params);
431 reset_count += params.size();
434 for (
const auto & [target_node, params] : per_node_params) {
435 issueAsyncSetParameters(target_node, params);
440 "ZoneParameterFilter: Entered state %u (reset %zu N-only parameter(s); "
441 "applied %zu parameter(s) across %zu node(s)).",
442 new_state, reset_count, it->second.size(), per_node_params.size());
445 void ZoneParameterFilter::resetToNominal()
447 for (
const auto & [target_node, params] : nominal_defaults_) {
448 issueAsyncSetParameters(target_node, params);
452 void ZoneParameterFilter::issueAsyncSetParameters(
453 const std::string & target_node,
454 const std::vector<rclcpp::Parameter> & params)
456 auto client_it = param_clients_.find(target_node);
457 if (client_it == param_clients_.end()) {
460 "ZoneParameterFilter: no client for target_node '%s' "
461 "(should have been built at config-load).",
462 target_node.c_str());
466 pending_futures_.push_back(client_it->second->set_parameters(params));
469 void ZoneParameterFilter::checkPendingParameterUpdates()
475 auto it = pending_futures_.begin();
476 while (it != pending_futures_.end()) {
477 if (it->wait_for(std::chrono::seconds(0)) != std::future_status::ready) {
485 const auto ready_future = *it;
486 it = pending_futures_.erase(it);
489 const auto & results = ready_future.get();
490 for (
const auto & r : results) {
492 throw std::runtime_error(
493 "ZoneParameterFilter: set_parameters failed: " + r.reason);
499 void ZoneParameterFilter::resetFilter()
501 std::lock_guard<CostmapFilter::mutex_t> guard(*getMutex());
503 filter_info_sub_.reset();
505 if (state_event_pub_) {
506 state_event_pub_->on_deactivate();
507 state_event_pub_.reset();
510 filter_mask_.reset();
511 filter_info_received_ =
false;
512 state_initialized_ =
false;
516 bool ZoneParameterFilter::isActive()
518 std::lock_guard<CostmapFilter::mutex_t> guard(*getMutex());
519 return filter_mask_ !=
nullptr;
524 #include "pluginlib/class_list_macros.hpp"
A QoS profile for latched, reliable topics with a history of 10 messages.
A 2D costmap provides a mapping between points in the world and their associated "costs".
Abstract class for layered costmap plugin implementations.
Costmap filter that applies a configured set of ROS parameters based on the mask value at the robot's...