Tutorial 7: Parameters Deep Dive (C++)

Parameters let you change robot behavior at runtime without recompiling. Tune PID gains, adjust speed limits, enable/disable features — all while the robot is running.

What You'll Learn

  • horus::Params for typed key-value storage
  • get<T>() with defaults for safe access
  • Live tuning from horus param CLI
  • Organizing parameters by subsystem

Basic Usage

#include <horus/horus.hpp>
using namespace horus::literals;

class TunableController : public horus::Node {
public:
    TunableController(horus::Params& params)
        : Node("tunable_ctrl"), params_(params)
    {
        cmd_sub_ = subscribe<horus::msg::CmdVel>("cmd_vel");
        out_pub_ = advertise<horus::msg::CmdVel>("motor.cmd");
    }

    void tick() override {
        // Read parameters every tick — picks up live changes
        double max_speed = params_.get<double>("max_speed", 0.5);
        double gain      = params_.get<double>("controller_gain", 0.8);
        bool   enabled   = params_.get<bool>("motor_enabled", true);

        if (!enabled) {
            horus::msg::CmdVel stop{};
            out_pub_->send(stop);
            return;
        }

        auto cmd = cmd_sub_->recv();
        if (!cmd) return;

        double scaled = cmd->get()->linear * gain;
        scaled = std::min(scaled, max_speed);

        horus::msg::CmdVel out{};
        out.linear = static_cast<float>(scaled);
        out_pub_->send(out);
    }

private:
    horus::Params& params_;
    horus::Subscriber<horus::msg::CmdVel>* cmd_sub_;
    horus::Publisher<horus::msg::CmdVel>*  out_pub_;
};

int main() {
    horus::Params params;

    // Set defaults
    params.set("max_speed", 0.5);
    params.set("controller_gain", 0.8);
    params.set("motor_enabled", true);
    params.set("robot_name", "atlas");

    horus::Scheduler sched;
    sched.tick_rate(100_hz);

    TunableController ctrl(params);
    sched.add(ctrl).order(10).build();

    sched.spin();
}

Supported Types

Typeset()get<T>()get_*()
doubleset("key", 1.5)get<double>("key", 0.0)get_f64("key")optional<double>
int64_tset("key", int64_t(42))get<int64_t>("key", 0)get_i64("key")optional<int64_t>
boolset("key", true)get<bool>("key", false)get_bool("key")optional<bool>
stringset("key", "value")get<std::string>("key", "")get_string("key")optional<string>

Organizing Parameters

Group by subsystem:

// Locomotion
params.set("loco.max_speed", 0.5);
params.set("loco.max_angular", 1.0);
params.set("loco.wheel_base", 0.3);

// PID
params.set("pid.kp", 2.0);
params.set("pid.ki", 0.1);
params.set("pid.kd", 0.05);

// Safety
params.set("safety.min_distance", 0.3);
params.set("safety.estop_enabled", true);

Pattern: Parameter Validation

void init() override {
    double kp = params_.get<double>("pid.kp", -1.0);
    if (kp < 0) {
        horus::log::error("pid", "pid.kp must be >= 0");
        return;
    }
    double max_speed = params_.get<double>("loco.max_speed", 0.5);
    if (max_speed > 2.0) {
        horus::log::warn("pid", "max_speed > 2.0 m/s — are you sure?");
    }
}

Key Takeaways

  • Read params every tick — captures live changes
  • Always provide defaults: get<double>("key", 0.0) never fails
  • Use dotted naming (pid.kp, loco.max_speed) to organize
  • Validate in init() — log warnings for dangerous values
  • horus::Params is thread-safe for concurrent read/write