Mujoco KDL Wrapper  0.1.0
MuJoCo + KDL bridge for robot kinematics and dynamics
Loading...
Searching...
No Matches
test_mjcf_trq_ctrl.cpp
Go to the documentation of this file.
1/* test_mjcf_trq_ctrl.cpp
2 * Torque-mode control on the Kinova GEN3 + Robotiq 2F-85 (MJCF). */
3
5
6#include <gtest/gtest.h>
7
8#include <kdl/chaindynparam.hpp>
9#include <kdl/chainfksolverpos_recursive.hpp>
10
11#include <algorithm>
12#include <cmath>
13#include <filesystem>
14#include <memory>
15#include <string>
16
17namespace fs = std::filesystem;
18
19static constexpr double kHomePose[7] = { 0.0, 0.2618, 3.1416, -2.2689, 0.0, 0.9599, 1.5708 };
20static constexpr double kKp[7] = { 100, 200, 100, 200, 100, 200, 100 };
21static constexpr double kKd[7] = { 10, 20, 10, 20, 10, 20, 10 };
22
23static fs::path repo_root() { return fs::path(__FILE__).parent_path().parent_path(); }
24
25class MjcfTrqCtrlTest : public testing::Test
26{
27 protected:
28 fs::path root_;
29 mjModel *model_ = nullptr;
30 mjData *data_ = nullptr;
31
33 unsigned n_ = 0;
34 std::unique_ptr<KDL::ChainFkSolverPos_recursive> fk_;
35 std::unique_ptr<KDL::ChainDynParam> dyn_;
36 KDL::JntArray q_home_;
37
38 void SetUp() override
39 {
40 root_ = repo_root();
41 const std::string arm_mjcf =
42 (root_ / "third_party/menagerie/kinova_gen3/gen3.xml").string();
43 const std::string grp_mjcf =
44 (root_ / "third_party/menagerie/robotiq_2f85/2f85.xml").string();
45 if (!fs::exists(arm_mjcf)) {
46 GTEST_SKIP() << arm_mjcf << " not found";
47 return;
48 }
49 if (!fs::exists(grp_mjcf)) {
50 GTEST_SKIP() << grp_mjcf << " not found";
51 return;
52 }
53
55 .mjcf_path = grp_mjcf.c_str(),
56 .attach_to = "bracelet_link",
57 .prefix = "g_",
58 .pos = { 0.0, 0.0, -0.061525 },
59 .euler = { 180.0, 0.0, 0.0 },
60 .contact_exclusions = {},
61 };
63 rs.path = arm_mjcf.c_str();
64 rs.attachments.push_back(gs);
65
67 sc.robots.push_back(rs);
68
69 ASSERT_TRUE(mj_kdl::build_scene(&model_, &data_, &sc));
70 const mj_kdl::ToolFrameSpec tool{ .tool_body = "g_base", .tcp_site = "g_pinch" };
71 ASSERT_TRUE(
73 &s_, model_, data_, "base_link", "bracelet_link", "", &tool
74 )
75 );
76
77 n_ = s_.chain.getNrOfJoints();
78 fk_ = std::make_unique<KDL::ChainFkSolverPos_recursive>(s_.chain);
79 dyn_ = std::make_unique<KDL::ChainDynParam>(s_.chain, KDL::Vector(0, 0, -9.81));
80
81 q_home_.resize(n_);
82 for (unsigned i = 0; i < n_; ++i) q_home_(i) = kHomePose[i];
84 mj_forward(model_, data_);
85 }
86
87 void TearDown() override
88 {
89 if (model_) {
92 }
93 }
94};
95
96TEST_F(MjcfTrqCtrlTest, GravityAccuracy)
97{
98 // At q=0 the arm is upright. KDL now includes gripper inertia via tool_body.
99 KDL::JntArray q_zero(n_);
100 mj_kdl::set_joint_pos(&s_, q_zero);
101 mj_forward(model_, data_);
102
103 KDL::JntArray g(n_);
104 ASSERT_GE(dyn_->JntToGravity(q_zero, g), 0);
105
106 double max_err = 0.0;
107 for (unsigned i = 0; i < n_; ++i)
108 max_err = std::max(max_err, std::abs(g(i) - data_->qfrc_bias[s_.kdl_to_mj_dof[i]]));
109 EXPECT_LE(max_err, 5e-2);
110}
111
112TEST_F(MjcfTrqCtrlTest, ImpedanceDrift)
113{
114 KDL::Frame ee_init;
115 fk_->JntToCart(q_home_, ee_init);
116
117 s_.ctrl_mode = mj_kdl::CtrlMode::TORQUE;
118
119 KDL::JntArray q(n_), g(n_);
120 for (int i = 0; i < 500; ++i) {
121 mj_kdl::update(&s_);
122 for (unsigned j = 0; j < n_; ++j) q(j) = s_.jnt_pos_msr[j];
123 dyn_->JntToGravity(q, g);
124 for (unsigned j = 0; j < n_; ++j) {
125 s_.jnt_trq_cmd[j] =
126 kKp[j] * (kHomePose[j] - s_.jnt_pos_msr[j]) - kKd[j] * s_.jnt_vel_msr[j] + g(j);
127 }
128 mj_kdl::step(&s_);
129 }
130
131 KDL::JntArray q_end(n_);
132 for (unsigned j = 0; j < n_; ++j) q_end(j) = s_.jnt_pos_msr[j];
133 KDL::Frame ee_end;
134 fk_->JntToCart(q_end, ee_end);
135 double drift = (ee_init.p - ee_end.p).Norm();
136 EXPECT_LE(drift, 0.005);
137}
138
139TEST_F(MjcfTrqCtrlTest, TrqMsrReadsQfrcActuator)
140{
141 // jnt_trq_msr must reflect qfrc_actuator (the net actuator output torque),
142 // NOT qfrc_bias (gravitational/Coriolis torques). After update() with zero
143 // commands the robot is not yet running, so qfrc_actuator may be small but
144 // the values must match element-wise.
145 s_.ctrl_mode = mj_kdl::CtrlMode::TORQUE;
146 for (unsigned i = 0; i < n_; ++i) s_.jnt_trq_cmd[i] = 0.0;
147 mj_kdl::update(&s_);
148 mj_kdl::step(&s_);
149 mj_kdl::update(&s_);
150
151 for (unsigned i = 0; i < static_cast<unsigned>(s_.n_joints); ++i) {
152 double expected = s_.data->qfrc_actuator[s_.kdl_to_mj_dof[i]];
153 EXPECT_DOUBLE_EQ(s_.jnt_trq_msr[i], expected)
154 << "jnt_trq_msr[" << i << "] does not match qfrc_actuator";
155 }
156}
157
158int main(int argc, char *argv[])
159{
160 testing::InitGoogleTest(&argc, argv);
161 return RUN_ALL_TESTS();
162}
void SetUp() override
std::unique_ptr< KDL::ChainDynParam > dyn_
std::unique_ptr< KDL::ChainFkSolverPos_recursive > fk_
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::vector< AttachmentSpec > attachments
std::vector< RobotSpec > robots
int main(int argc, char *argv[])
TEST_F(MjcfTrqCtrlTest, GravityAccuracy)