Program Listing for File parameter.h

Return to documentation for file (/tmp/ws/src/hatchbed_common/include/hatchbed_common/parameter.h)

#pragma once

#include <functional>
#include <memory>
#include <mutex>
#include <sstream>
#include <string>
#include <utility>
#include <vector>

#include <rclcpp/rclcpp.hpp>
#include <rcutils/logging.h>

namespace hatchbed_common {

template <class T>
struct OwnedStore {
    using Ptr = std::shared_ptr<OwnedStore>;
    OwnedStore(const T& value) : value(value) {}

    T value;
    std::mutex mutex;
};

template <class T>
struct BorrowedStore {
    using Ptr = std::shared_ptr<BorrowedStore>;
    BorrowedStore(T* value) : value(value) {}

    T* value;
    std::mutex mutex;
};

struct EnumOption {
    int value;
    std::string name;
    std::string description;
};

class ParamHandler;

template <class T>
class Parameter {
    public:
    class Declared {
        public:
        Declared(const Parameter& param) : param_(param) {}

        T value() { return param_.value(); }
        bool update(const T& value) { return param_.update(value, false); }

        private:
        Parameter param_;
    };

    Parameter(T* store, const std::string& ns, const std::string& name, T default_val, const std::string& description,
              std::shared_ptr<rclcpp::Node> node)
      : namespace_(ns),
        name_(name),
        default_val_(default_val),
        description_(description),
        node_(node)
    {
        if (!store) {
            owned_store_ = std::make_shared<OwnedStore<T>>(default_val_);
        }
        else {
            borrowed_store_ = std::make_shared<BorrowedStore<T>>(store);
            *(borrowed_store_->value) = default_val_;
        }
    }

    Parameter() = default;
    Parameter(const Parameter& parameter) = default;
    virtual ~Parameter() = default;

    virtual Declared declare() {
        registerParam();
        return Declared(*this);
    }

    virtual Parameter<T>& callback(std::function<void(T)> callback) {
        is_dynamic_ = true;
        user_callback_ = callback;
        return *this;
    }

    virtual Parameter<T>& dynamic() {
        is_dynamic_ = true;
        return *this;
    }

    T defaultValue() const {
        return default_val_;
    }

    std::string qualifiedName() const {
        if (!is_dynamic_) {
            return name_ + " (readonly)";
        }
        return name_;
    }

    std::string description() const {
        return description_;
    }

    bool isDynamic() const {
        return is_dynamic_;
    }

    protected:
    T value() const {
        if (owned_store_) {
            std::scoped_lock lock(owned_store_->mutex);
            return owned_store_->value;
        }

        std::scoped_lock lock(borrowed_store_->mutex);
        return *borrowed_store_->value;
    }

    virtual bool update(const T& value, bool from_callback = false) {
        if (initialized_ && !is_dynamic_) {
            RCLCPP_WARN_STREAM(node_->get_logger(), namespace_ << "/" << name_ << " is static and can't be updated.");
            return false;
        }

        bool did_change = false;
        if (owned_store_) {
            std::scoped_lock lock(owned_store_->mutex);
            did_change = owned_store_->value != value;
            if (did_change && from_callback) {
                RCLCPP_INFO_STREAM(node_->get_logger(), "updated <" << namespace_ << "/" << name_ << ">: "
                    << toString(owned_store_->value) << " to " << toString(value));
            }
            owned_store_->value = value;
        }
        else {
            std::scoped_lock lock(borrowed_store_->mutex);
            did_change = *borrowed_store_->value != value;
            if (did_change && from_callback) {
                RCLCPP_INFO_STREAM(node_->get_logger(), "updated <" << namespace_ << "/" << name_ << ">: "
                    << toString(*borrowed_store_->value) <<" to " << toString(value));
            }
            *borrowed_store_->value = value;
        }

        if (!from_callback && did_change) {
            node_->set_parameter({ name_, rclcpp::ParameterValue(this->value()) });
        }

        if (user_callback_ && did_change) {
            user_callback_(value);
        }

        return true;
    }

    virtual std::string toString(const T& value) const {
        std::stringstream ss;
        ss << value;
        return ss.str();
    }

    virtual void registerParam() {
        rcl_interfaces::msg::ParameterDescriptor descriptor;
        descriptor.description = description_;
        descriptor.read_only = !is_dynamic_;
        node_->declare_parameter(name_, rclcpp::ParameterValue(default_val_), descriptor);

        update(getParameter());
        RCLCPP_INFO_STREAM(this->node_->get_logger(), this->namespace_ << "/" << this->name_ << ": " << toString(this->value()));
        initialized_ = true;
    }

    T getParameter() {
        auto p = node_->get_parameter(name_);
        return p.get_value<T>();
    }

    std::string namespace_;
    std::string name_;
    T default_val_;
    std::string description_;
    std::shared_ptr<rclcpp::Node> node_;
    typename BorrowedStore<T>::Ptr borrowed_store_;
    typename OwnedStore<T>::Ptr owned_store_;

    bool initialized_ = false;
    bool is_dynamic_ = false;
    std::function<void(T)> user_callback_;

    friend class ParamHandler;
};

class BoolParameter : public Parameter<bool> {
    public:
    BoolParameter(bool* store, const std::string& ns, const std::string& name, bool default_val,
                  const std::string& description, std::shared_ptr<rclcpp::Node> node)
      : Parameter<bool>(store, ns, name, default_val, description, node) {}

    BoolParameter() = default;
    BoolParameter(const BoolParameter& parameter) = default;
    virtual ~BoolParameter() = default;

    protected:
    virtual std::string toString(const bool& value) const override {
        if (value) {
            return "true";
        }
        return "false";
    }
};

template <typename T>
class NumericParameter : public Parameter<T> {
    public:
    NumericParameter(T* store, const std::string& ns, const std::string& name, T default_val,
                     const std::string& description, std::shared_ptr<rclcpp::Node> node)
      : Parameter<T>(store, ns, name, default_val, description, node) {}

    NumericParameter() = default;
    NumericParameter(const NumericParameter& parameter) = default;
    virtual ~NumericParameter() = default;

    virtual NumericParameter<T>& callback(std::function<void(T)> callback) override {
        Parameter<T>::callback(callback);
        return *this;
    }

    virtual NumericParameter<T>& dynamic() override {
        Parameter<T>::dynamic();
        return *this;
    }

    virtual NumericParameter<T>& min(T min) {
        min_ = min;
        has_range_ = true;
        if (max_ < min_) {
            max_ = min_;
        }
        this->default_val_ = clamp(this->default_val_);
        return *this;
    }

    virtual NumericParameter<T>& max(T max) {
        max_ = max;
        has_range_ = true;
        if (min_ > max_) {
            min_ = max_;
        }
        this->default_val_ = clamp(this->default_val_);
        return *this;
    }

    virtual NumericParameter<T>& step(T step) {
        step_ = step;
        return *this;
    }

    T min() const {
        return min_;
    }

    bool hasRange() const {
        return has_range_;
    }


    T max() const {
        return max_;
    }

    T clamp(const T& value) const {
        T clamped = value;

        if (has_range_ && clamped < min_) {
            clamped = min_;
        }
        if (has_range_ && clamped > max_) {
            clamped = max_;
        }

        return clamped;
    }

    protected:
    virtual bool update(const T& value, bool from_callback = false) {
        if (has_range_ && (value < min_ || value > max_)) {
            RCLCPP_WARN_STREAM(this->node_->get_logger(),  this->namespace_ << "/" << this->name_
                               << ": value out of range <" << value << ">");
            return false;
        }

        return Parameter<T>::update(value, from_callback);
    }

    virtual std::string toString(const T& value) const override {
        return std::to_string(value);
    }

    virtual void registerParam() override {
        rcl_interfaces::msg::ParameterDescriptor descriptor;
        descriptor.description = this->description_;
        descriptor.read_only = !this->is_dynamic_;
        if (has_range_) {
            descriptor.floating_point_range.resize(1);
            descriptor.floating_point_range[0].from_value = min_;
            descriptor.floating_point_range[0].to_value = max_;
            descriptor.floating_point_range[0].step = step_;
        }
        this->node_->declare_parameter(this->name_, rclcpp::ParameterValue(this->default_val_), descriptor);
        this->update(this->getParameter());

        RCLCPP_INFO_STREAM(this->node_->get_logger(), this->namespace_ << "/" << this->name_ << ": " << toString(this->value()));
        this->initialized_ = true;
    }

    T min_ = -10000;
    T max_ = 10000;
    T step_ = 0;
    bool has_range_ = false;

    friend class ParamHandler;
};

class IntParameter : public NumericParameter<int> {
    public:
    IntParameter(int* store, const std::string& ns, const std::string& name, int default_val,
                 const std::string& description, std::shared_ptr<rclcpp::Node> node)
      : NumericParameter<int>(store, ns, name, default_val, description, node) {}

    IntParameter() = default;
    IntParameter(const IntParameter& parameter) = default;
    virtual ~IntParameter() = default;

    virtual IntParameter& callback(std::function<void(int)> callback) override {
        NumericParameter<int>::callback(callback);
        return *this;
    }

    virtual IntParameter& dynamic() override {
        NumericParameter<int>::dynamic();
        return *this;
    }

    virtual IntParameter& min(int min) override {
        if (enums_.empty()) {
            NumericParameter<int>::min(min);
        }
        return *this;
    }

    virtual IntParameter& max(int max) override {
        if (enums_.empty()) {
            NumericParameter<int>::max(max);
        }
        return *this;
    }

    virtual IntParameter& step(int step) {
        if (enums_.empty()) {
            NumericParameter<int>::step(step);
        }
        return *this;
    }

    virtual IntParameter& enumerate(const std::vector<EnumOption>& enums) {
        enums_ = enums;
        if (!enums_.empty()) {
            this->has_range_ = false;
            this->step_ = 0;
            this->min_ = enums_.front().value;
            this->max_ = enums_.front().value;
            for (const auto& option: enums_) {
                this->min_ = std::min(this->min_, option.value);
                this->max_ = std::max(this->max_, option.value);
            }
        }
        return *this;
    }

    const std::vector<EnumOption>& enums() const {
        return enums_;
    }

    protected:
    virtual bool update(const int& value, bool from_callback = false) {
        if (enums_.empty()) {
            return NumericParameter<int>::update(value, from_callback);
        }

        bool valid = false;
        for (const auto& option: enums_) {
            if (option.value == value) {
                valid = true;
                break;
            }
        }

        if (valid) {
            return Parameter<int>::update(value, from_callback);
        }

        return false;
    }

    virtual std::string toString(const int& value) const override {
        std::string str = std::to_string(value);

        for (const auto& option: enums_) {
            if (option.value == value) {
                str += " (" + option.name + ")";
            }
        }
        return str;
    }

    virtual void registerParam() override {
        rcl_interfaces::msg::ParameterDescriptor descriptor;
        descriptor.description = description_;

        // generate enum description
        if (!enums_.empty()) {
            descriptor.description += "\nEnumeration:";
            for (const auto& option: enums_) {
                descriptor.description += "\n  " + option.name + "(" + std::to_string(option.value) + "): " + option.description;
            }
        }

        descriptor.read_only = !is_dynamic_;

        if (has_range_ || !enums_.empty()) {
            descriptor.integer_range.resize(1);
            descriptor.integer_range[0].from_value = min_;
            descriptor.integer_range[0].to_value = max_;
            descriptor.integer_range[0].step = step_;
        }
        node_->declare_parameter(name_, rclcpp::ParameterValue(default_val_), descriptor);

        update(getParameter());

        RCLCPP_INFO_STREAM(node_->get_logger(), namespace_ << "/" << name_ << ": " << toString(value()));
        initialized_ = true;
    }

    std::vector<EnumOption> enums_;

    friend class ParamHandler;
};

template class Parameter<bool>;
template class Parameter<std::string>;
template class NumericParameter<int>;
template class NumericParameter<double>;

typedef Parameter<std::string> StringParameter;
typedef NumericParameter<double> DoubleParameter;

}  // namespace hatchbed_common