Mujoco KDL Wrapper  0.2.2
MuJoCo + KDL bridge for robot kinematics and dynamics
Loading...
Searching...
No Matches
test_mjcf_pos_ctrl.cpp
Go to the documentation of this file.
1/* test_mjcf_pos_ctrl.cpp
2 * Joint position control on the Kinova GEN3 (MJCF).
3 *
4 * gen3.xml has high-gain position actuators (kp=2000, kv=100).
5 * CtrlMode::POSITION writes the target joint position directly to ctrl[],
6 * and the built-in servo drives the joint to that position.
7 *
8 * Uses a linearly interpolated trajectory from home to a nearby target pose. */
9
11#include "example_paths.hpp"
12
13#include <gtest/gtest.h>
14
15#include <algorithm>
16#include <cmath>
17#include <filesystem>
18#include <string>
19
20static constexpr double kHomePose[7] = { 0.0, 0.2618, 3.1416, -2.2689, 0.0, 0.9599, 1.5708 };
21static constexpr double kTargetPose[7] = { 0.3, 0.5, 2.9, -2.0, 0.3, 1.2, 1.3 };
22static constexpr double kMotionDuration = 1.5; // s - linear interp from home to target
23static constexpr double kSettleTime = 0.5; // s - extra time to let servo settle
24static constexpr double kErrTol = 0.05; // rad
25
26namespace fs = std::filesystem;
27class MjcfPosCtrlTest : public testing::Test
28{
29 protected:
30 fs::path root_;
31 mjModel *model_ = nullptr;
32 mjData *data_ = nullptr;
34 unsigned n_ = 0;
35
36 void SetUp() override
37 {
38 std::string arm_mjcf = mj_kdl_examples::find_menagerie_model("kinova_gen3/gen3.xml");
39 if (!fs::exists(arm_mjcf)) {
40 GTEST_SKIP() << arm_mjcf << " not found";
41 return;
42 }
43
45 sc.timestep = 0.002;
46 sc.add_floor = true;
47 sc.add_skybox = true;
48 sc.robots.push_back(mj_kdl::RobotSpec{ .path = arm_mjcf.c_str(), .attachments = {} });
49
50 ASSERT_TRUE(mj_kdl::build_scene(&model_, &data_, &sc));
51 ASSERT_TRUE(mj_kdl::init_robot_from_mjcf(&s_, model_, data_, "base_link", "bracelet_link"));
52
53 n_ = static_cast<unsigned>(s_.n_joints);
54
55 KDL::JntArray q_home(n_);
56 for (unsigned i = 0; i < n_; ++i) q_home(i) = kHomePose[i];
57 mj_kdl::set_joint_pos(&s_, q_home, false);
58 mj_forward(model_, data_);
59
61 for (unsigned i = 0; i < n_; ++i) { s_.jnt_pos_cmd[i] = kHomePose[i]; }
63 }
64
65 void TearDown() override
66 {
67 if (model_) {
70 }
71 }
72};
73
74TEST_F(MjcfPosCtrlTest, TrajectoryTracking)
75{
76 const double t_start = data_->time;
77 const double t_end = t_start + kMotionDuration + kSettleTime;
78
79 while (data_->time < t_end) {
80 mj_kdl::update(&s_);
81 double alpha = std::clamp((data_->time - t_start) / kMotionDuration, 0.0, 1.0);
82 for (unsigned i = 0; i < n_; ++i)
83 s_.jnt_pos_cmd[i] = kHomePose[i] + alpha * (kTargetPose[i] - kHomePose[i]);
84 mj_kdl::step(&s_);
85 }
86
87 double max_err = 0.0;
88 for (unsigned i = 0; i < n_; ++i)
89 max_err = std::max(max_err, std::abs(kTargetPose[i] - s_.jnt_pos_msr[i]));
90
91 EXPECT_LE(max_err, kErrTol);
92}
93
94TEST_F(MjcfPosCtrlTest, ClampCtrlrange)
95{
96 // Set a command far outside any physical joint range and call update().
97 // The ctrl[] written to MuJoCo must be clamped to [ctrlrange_lo, ctrlrange_hi].
98 for (unsigned i = 0; i < n_; ++i) s_.jnt_pos_cmd[i] = 1e9;
99 mj_kdl::update(&s_);
100
101 for (unsigned i = 0; i < n_; ++i) {
102 const int ci = s_.kdl_to_mj_ctrl[i];
103 if (ci < 0) continue;
104 if (!model_->actuator_ctrllimited[ci]) continue;
105 double lo = model_->actuator_ctrlrange[2 * ci];
106 double hi = model_->actuator_ctrlrange[2 * ci + 1];
107 EXPECT_LE(data_->ctrl[ci], hi + 1e-12)
108 << "ctrl[" << ci << "] exceeds ctrlrange upper bound";
109 EXPECT_GE(data_->ctrl[ci], lo - 1e-12)
110 << "ctrl[" << ci << "] below ctrlrange lower bound";
111 }
112}
113
114TEST_F(MjcfPosCtrlTest, QfrcAppliedUnchangedInPositionMode)
115{
116 // In POSITION mode, update() must NOT zero qfrc_applied (user-set values
117 // should be preserved so external disturbances can be applied).
118 const int dof0 = s_.kdl_to_mj_dof[0];
119 data_->qfrc_applied[dof0] = 5.0; // sentinel external disturbance
120 mj_kdl::update(&s_);
121 EXPECT_DOUBLE_EQ(data_->qfrc_applied[dof0], 5.0)
122 << "update() clobbered qfrc_applied in POSITION mode";
123}
124
125int main(int argc, char *argv[])
126{
127 testing::InitGoogleTest(&argc, argv);
128 return RUN_ALL_TESTS();
129}
void SetUp() override
void TearDown() override
bool step(Robot *s)
void cleanup(Robot *r)
bool init_robot_from_mjcf(Robot *r, mjModel *model, mjData *data, const char *base_body, const char *tip_body, const char *prefix="", const ToolFrameSpec *tool=nullptr)
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::string find_menagerie_model(const fs::path &relative)
std::vector< RobotSpec > robots
int main(int argc, char *argv[])
TEST_F(MjcfPosCtrlTest, TrajectoryTracking)