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