Skip to content

File parameter_client.hpp

File List > agnocast_wrapper > parameter_client.hpp

Go to the documentation of this file

// Copyright 2026 TIER IV, Inc.
//
// 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.

#pragma once

#include "nebula_core_ros/agnocast_wrapper/node.hpp"
#include "nebula_core_ros/agnocast_wrapper/runtime.hpp"

#include <rclcpp/callback_group.hpp>
#include <rclcpp/parameter.hpp>
#include <rclcpp/parameter_client.hpp>
#include <rclcpp/qos.hpp>

#include <rclcpp/version.h>

#include <chrono>
#include <cstdint>
#include <functional>
#include <future>
#include <memory>
#include <ratio>
#include <stdexcept>
#include <string>
#include <utility>
#include <variant>
#include <vector>

namespace nebula::agnocast_wrapper
{
namespace detail
{

inline const rclcpp::QoS & checked_parameters_qos(const rclcpp::QoS & qos)
{
  if (qos.durability() == rclcpp::DurabilityPolicy::TransientLocal) {
    throw std::invalid_argument(
      "AsyncParametersClient: transient-local durability is not supported, use volatile instead");
  }
  if (qos.reliability() == rclcpp::ReliabilityPolicy::BestEffort) {
    throw std::invalid_argument(
      "AsyncParametersClient: best-effort reliability is not supported, use reliable instead");
  }
  return qos;
}

inline auto to_rclcpp_parameters_qos(const rclcpp::QoS & qos)
{
#if RCLCPP_VERSION_MAJOR >= 28
  return qos;
#else
  return qos.get_rmw_qos_profile();
#endif
}

}  // namespace detail
}  // namespace nebula::agnocast_wrapper

#ifdef USE_AGNOCAST_ENABLED

#include <agnocast/node/agnocast_parameter_client.hpp>

namespace nebula::agnocast_wrapper
{

class AsyncParametersClient
{
public:
  explicit AsyncParametersClient(
    nebula::agnocast_wrapper::Node * node, const std::string & remote_node_name = "",
    const rclcpp::QoS & qos = rclcpp::ParametersQoS(),
    rclcpp::CallbackGroup::SharedPtr group = nullptr)
  : impl_(
      // A conditional, not an if/else: only the selected branch is evaluated, and the other
      // one's accessor would throw.
      use_agnocast()
        ? decltype(impl_)(
            std::in_place_type<AgnocastImpl>, node->get_agnocast_node().get(), remote_node_name,
            detail::checked_parameters_qos(qos), std::move(group))
        : decltype(impl_)(
            std::in_place_type<RclcppImpl>, node->get_rclcpp_node().get(), remote_node_name,
            detail::to_rclcpp_parameters_qos(detail::checked_parameters_qos(qos)),
            std::move(group)))
  {
  }

  std::shared_future<std::vector<rclcpp::Parameter>> get_parameters(
    const std::vector<std::string> & names,
    std::function<void(std::shared_future<std::vector<rclcpp::Parameter>>)> callback = nullptr)
  {
    return std::visit(
      [&](auto & impl) { return impl.get_parameters(names, std::move(callback)); }, impl_);
  }

  template <typename RepT = int64_t, typename RatioT = std::milli>
  bool wait_for_service(
    std::chrono::duration<RepT, RatioT> timeout = std::chrono::duration<RepT, RatioT>(-1))
  {
    return std::visit([&](auto & impl) { return impl.wait_for_service(timeout); }, impl_);
  }

  bool service_is_ready() const
  {
    return std::visit([](const auto & impl) { return impl.service_is_ready(); }, impl_);
  }

  AsyncParametersClient(const AsyncParametersClient &) = delete;
  AsyncParametersClient & operator=(const AsyncParametersClient &) = delete;
  AsyncParametersClient(AsyncParametersClient &&) = delete;
  AsyncParametersClient & operator=(AsyncParametersClient &&) = delete;

private:
  using RclcppImpl = ::rclcpp::AsyncParametersClient;
  using AgnocastImpl = ::agnocast::AsyncParametersClient;

  std::variant<RclcppImpl, AgnocastImpl> impl_;
};

}  // namespace nebula::agnocast_wrapper

#else  // !USE_AGNOCAST_ENABLED

namespace nebula::agnocast_wrapper
{

// Agnocast-disabled build: a thin composition wrapper over ::rclcpp::AsyncParametersClient rather
// than a derived class, so the full upstream API cannot leak into the =0 build and let =0-only code
// compile that breaks under =1. Signatures and semantics match the agnocast-enabled build above,
// which carries the documentation.
class AsyncParametersClient
{
public:
  explicit AsyncParametersClient(
    nebula::agnocast_wrapper::Node * node, const std::string & remote_node_name = "",
    const rclcpp::QoS & qos = rclcpp::ParametersQoS(),
    rclcpp::CallbackGroup::SharedPtr group = nullptr)
  : impl_(
      node->get_rclcpp_node().get(), remote_node_name,
      detail::to_rclcpp_parameters_qos(detail::checked_parameters_qos(qos)), std::move(group))
  {
  }

  std::shared_future<std::vector<rclcpp::Parameter>> get_parameters(
    const std::vector<std::string> & names,
    std::function<void(std::shared_future<std::vector<rclcpp::Parameter>>)> callback = nullptr)
  {
    return impl_.get_parameters(names, std::move(callback));
  }

  template <typename RepT = int64_t, typename RatioT = std::milli>
  bool wait_for_service(
    std::chrono::duration<RepT, RatioT> timeout = std::chrono::duration<RepT, RatioT>(-1))
  {
    return impl_.wait_for_service(timeout);
  }

  bool service_is_ready() const { return impl_.service_is_ready(); }

  AsyncParametersClient(const AsyncParametersClient &) = delete;
  AsyncParametersClient & operator=(const AsyncParametersClient &) = delete;
  AsyncParametersClient(AsyncParametersClient &&) = delete;
  AsyncParametersClient & operator=(AsyncParametersClient &&) = delete;

private:
  ::rclcpp::AsyncParametersClient impl_;
};

}  // namespace nebula::agnocast_wrapper

#endif