mj-kdl-wrapper  0.1.0
MuJoCo + KDL bridge for robot kinematics and dynamics
Loading...
Searching...
No Matches
test_mjcf_vel_ctrl.cpp
Go to the documentation of this file.
1/* test_mjcf_vel_ctrl.cpp
2 * Joint velocity control on the Kinova GEN3 (MJCF).
3 *
4 * gen3.xml has high-gain position actuators (kp=2000, kv=100).
5 * Velocity control is implemented in POSITION mode by integrating the
6 * desired velocity into a position setpoint each step:
7 *
8 * vel_des[i] = clamp(Kv * (target[i] - q[i]), -maxVel, maxVel)
9 * pos_cmd[i] += vel_des[i] * dt
10 *
11 * The position servo tracks the advancing setpoint, making the joint
12 * follow the commanded velocity profile. */
13
15#include "test_utils.hpp"
16
17#include <gtest/gtest.h>
18
19#include <algorithm>
20#include <cmath>
21#include <filesystem>
22#include <iomanip>
23#include <iostream>
24#include <string>
25
26static constexpr double kHomePose[7] = { 0.0, 0.2618, 3.1416, -2.2689, 0.0, 0.9599, 1.5708 };
27static constexpr double kTargetPose[7] = { 0.3, 0.5, 2.9, -2.0, 0.3, 1.2, 1.3 };
28static constexpr double kKv = 2.0; // P gain [rad/s per rad error]
29static constexpr double kMaxVel = 0.6; // velocity clamp [rad/s]
30static constexpr double kTol = 0.02; // convergence tolerance [rad]
31static constexpr double kTimeout = 5.0; // max simulation time [s]
32
33namespace fs = std::filesystem;
34static fs::path repo_root() { return fs::path(__FILE__).parent_path().parent_path(); }
35
36class MjcfVelCtrlTest : public testing::Test
37{
38 protected:
39 fs::path root_;
40 mjModel *model_ = nullptr;
41 mjData *data_ = nullptr;
43 unsigned n_ = 0;
44
45 void SetUp() override
46 {
47 root_ = repo_root();
48 if (!fs::exists(root_ / "third_party/menagerie")) {
49 GTEST_SKIP() << "third_party/menagerie/ not found";
50 return;
51 }
52
53 std::string arm_mjcf = (root_ / "third_party/menagerie/kinova_gen3/gen3.xml").string();
54
57 r.path = arm_mjcf.c_str();
58 sc.robots.push_back(r);
59
60 ASSERT_TRUE(mj_kdl::build_scene(&model_, &data_, &sc));
61 ASSERT_TRUE(mj_kdl::init_robot_from_mjcf(&s_, model_, data_, "base_link", "bracelet_link"));
62
63 n_ = static_cast<unsigned>(s_.n_joints);
64
65 KDL::JntArray q_home(n_);
66 for (unsigned i = 0; i < n_; ++i) q_home(i) = kHomePose[i];
67 mj_kdl::set_joint_pos(&s_, q_home);
68
69 // POSITION mode: initialise pos_cmd to home so the servo starts settled.
71 for (unsigned i = 0; i < n_; ++i) { s_.jnt_pos_cmd[i] = kHomePose[i]; }
73 }
74
75 void TearDown() override
76 {
77 if (model_) {
80 }
81 }
82};
83
85{
86 const double dt = model_->opt.timestep;
87 bool arrived = false;
88
89 while (data_->time < kTimeout && !arrived) {
90 mj_kdl::update(&s_); // reads sensors, applies prev pos_cmd to servo
91
92 double max_err = 0.0;
93 for (unsigned i = 0; i < n_; ++i) {
94 double err = kTargetPose[i] - s_.jnt_pos_msr[i];
95 max_err = std::max(max_err, std::abs(err));
96 double vel = std::clamp(kKv * err, -kMaxVel, kMaxVel);
97 s_.jnt_pos_cmd[i] += vel * dt; // integrate velocity into position setpoint
98 }
99
100 if (max_err < kTol) {
101 arrived = true;
102 // Freeze setpoint at current position to avoid overshoot.
103 for (unsigned i = 0; i < n_; ++i) s_.jnt_pos_cmd[i] = s_.jnt_pos_msr[i];
104 }
105
106 mj_kdl::step(&s_);
107 }
108
109 double max_err = 0.0;
110 for (unsigned i = 0; i < n_; ++i)
111 max_err = std::max(max_err, std::abs(kTargetPose[i] - s_.jnt_pos_msr[i]));
112
113 TEST_INFO(
114 "MJCF velocity ctrl: " << (arrived ? "converged" : "TIMEOUT") << " max_err=" << std::fixed
115 << std::setprecision(4) << max_err << " rad t=" << data_->time << " s"
116 );
117
118 EXPECT_TRUE(arrived) << "velocity controller did not converge within " << kTimeout << " s";
119 EXPECT_LE(max_err, kTol * 2);
120}
121
122int main(int argc, char *argv[])
123{
124 testing::InitGoogleTest(&argc, argv);
125 return RUN_ALL_TESTS();
126}
void TearDown() override
void SetUp() override
void step(Robot *s)
bool init_robot_from_mjcf(Robot *r, mjModel *model, mjData *data, const char *base_body, const char *tip_body, const char *prefix="", const char *tool_body=nullptr)
void cleanup(Robot *r)
void set_joint_pos(Robot *r, const KDL::JntArray &q, bool call_forward=true)
void update(Robot *r)
bool build_scene(mjModel **out_model, mjData **out_data, const SceneSpec *spec)
void destroy_scene(mjModel *model, mjData *data)
std::vector< double > jnt_pos_cmd
std::vector< RobotSpec > robots
int main(int argc, char *argv[])
TEST_F(MjcfVelCtrlTest, Convergence)
#define TEST_INFO(msg)