mj-kdl-wrapper  0.1.0
MuJoCo + KDL bridge for robot kinematics and dynamics
Loading...
Searching...
No Matches
ex_impedance.cpp
Go to the documentation of this file.
1/* ex_impedance.cpp
2 * Cartesian-impedance-like joint-space controller on Kinova GEN3 + Robotiq 2F-85.
3 *
4 * Control law (per joint):
5 * tau[i] = Kp[i]*(q_home[i] - q[i]) - Kd[i]*qdot[i] + g_kdl[i]
6 * where g_kdl is computed via KDL::ChainDynParam::JntToGravity (includes gripper inertia).
7 * Applied via TORQUE mode (qfrc_applied). The gripper cycles open and closed every 3 s.
8 *
9 * Requires third_party/menagerie (MuJoCo Menagerie submodule).
10 *
11 * Usage:
12 * ex_impedance [--headless]
13 *
14 * With --headless runs 200 steps and prints EE drift. */
15
17
18#include <kdl/chaindynparam.hpp>
19#include <kdl/chainfksolverpos_recursive.hpp>
20
21#include <cmath>
22#include <filesystem>
23#include <iomanip>
24#include <iostream>
25#include <string>
26
27static constexpr double kHomePose[7] = { 0.0, 0.2618, 3.1416, -2.2689, 0.0, 0.9599, 1.5708 };
28
29// Impedance gains - tuned for Gen3 joint sizes.
30static constexpr double kKp[7] = { 100, 200, 100, 200, 100, 200, 100 };
31static constexpr double kKd[7] = { 10, 20, 10, 20, 10, 20, 10 };
32
33
34namespace fs = std::filesystem;
35static fs::path repo_root() { return fs::path(__FILE__).parent_path().parent_path().parent_path(); }
36
37int main(int argc, char *argv[])
38{
39 bool headless = false;
40 for (int i = 1; i < argc; ++i)
41 if (std::string(argv[i]) == "--headless") headless = true;
42
43 const fs::path root = repo_root();
44 if (!fs::exists(root / "third_party/menagerie")) {
45 std::cerr << "third_party/menagerie/ not found - run: "
46 "git submodule update --init third_party/menagerie\n";
47 return 1;
48 }
49
50 const std::string arm_mjcf = (root / "third_party/menagerie/kinova_gen3/gen3.xml").string();
51 const std::string grp_mjcf = (root / "third_party/menagerie/robotiq_2f85/2f85.xml").string();
52
54 gs.mjcf_path = grp_mjcf.c_str();
55 gs.attach_to = "bracelet_link";
56 gs.prefix = "g_";
57 gs.pos[2] = -0.061525;
58 gs.euler[0] = 180.0; // 180 deg around X to flip gripper
59
61 rs.path = arm_mjcf.c_str();
62 rs.attachments.push_back(gs);
63
65 sc.robots.push_back(rs);
66
67 mjModel *model = nullptr;
68 mjData *data = nullptr;
69 if (!mj_kdl::build_scene(&model, &data, &sc)) {
70 std::cerr << "build_scene() failed\n";
71 return 1;
72 }
73
74 mj_kdl::Robot robot;
76 &robot, model, data, "base_link", "bracelet_link", "", "g_base"
77 )) {
78 std::cerr << "init_robot_from_mjcf() failed\n";
79 mj_kdl::destroy_scene(model, data);
80 return 1;
81 }
82
83 unsigned n = robot.chain.getNrOfJoints();
84 int fingers_act = mj_name2id(model, mjOBJ_ACTUATOR, "g_fingers_actuator");
85 int key_id = mj_name2id(model, mjOBJ_KEY, "home");
86
87 KDL::ChainDynParam dyn(robot.chain, KDL::Vector(0.0, 0.0, -9.81));
88 KDL::JntArray q_home(n), q(n), g(n);
89 for (unsigned i = 0; i < n; ++i) q_home(i) = kHomePose[i];
90
92
93 auto seed_reset_state = [&](mjData *d) {
94 for (unsigned i = 0; i < n; ++i) {
95 robot.jnt_pos_cmd[i] = d->qpos[robot.kdl_to_mj_qpos[i]];
96 robot.jnt_trq_cmd[i] = 0.0;
97 d->qfrc_applied[robot.kdl_to_mj_dof[i]] = 0.0;
98 }
99 mj_kdl::update(&robot); // seed *_msr and neutralize arm position actuators
100 if (fingers_act >= 0) d->ctrl[fingers_act] = 255.0;
101 };
102
103 auto reset_to_home = [&](mjData *d) {
104 if (key_id >= 0) {
105 mj_resetDataKeyframe(model, d, key_id);
106 } else {
107 mj_resetData(model, d);
108 mj_kdl::set_joint_pos(&robot, q_home, false);
109 }
110 mj_forward(model, d);
111 seed_reset_state(d);
112 };
113
114 reset_to_home(data);
115
116 double prev_sim_time = data->time;
117
118 /*
119 * Per-step sequence:
120 * 1. update() -- refresh *_msr from current state, write old jnt_trq_cmd to qfrc_applied
121 * 2. Reset check -- if sim time went backward, zero torques and return
122 * 3. Compute jnt_trq_cmd from fresh *_msr
123 * 4. Write jnt_trq_cmd directly to qfrc_applied (overrides stale values from step 1)
124 * 5. Set gripper ctrl
125 */
126 auto step_impedance = [&]() {
128 &robot
129 ); // refresh *_msr; old jnt_trq_cmd -> qfrc_applied (overwritten below)
130
131 if (data->time < prev_sim_time - 1e-6) {
132 // Simulation was reset: restore the home state and re-seed commands.
133 reset_to_home(data);
134 prev_sim_time = data->time;
135 return;
136 }
137 prev_sim_time = data->time;
138
139 for (unsigned i = 0; i < n; ++i) q(i) = robot.jnt_pos_msr[i];
140 dyn.JntToGravity(q, g);
141 for (unsigned i = 0; i < n; ++i) {
142 robot.jnt_trq_cmd[i] =
143 kKp[i] * (kHomePose[i] - robot.jnt_pos_msr[i]) - kKd[i] * robot.jnt_vel_msr[i] + g(i);
144 }
145 data->ctrl[fingers_act] = (std::fmod(data->time, 6.0) < 3.0) ? 255.0 : 0.0;
146 };
147
148 if (headless) {
149 KDL::ChainFkSolverPos_recursive fk(robot.chain);
150 KDL::JntArray q0(n);
151 for (unsigned i = 0; i < n; ++i) q0(i) = robot.jnt_pos_cmd[i];
152 KDL::Frame ee_start;
153 fk.JntToCart(q0, ee_start);
154
155 for (int step = 0; step < 200; ++step) {
156 step_impedance();
157 mj_kdl::step(&robot);
158 }
159
160 KDL::JntArray q_end(n);
161 for (unsigned i = 0; i < n; ++i) q_end(i) = robot.jnt_pos_msr[i];
162 KDL::Frame ee_end;
163 fk.JntToCart(q_end, ee_end);
164 double drift = (ee_start.p - ee_end.p).Norm();
165 std::cout << "EE drift after 200 steps: " << std::fixed << std::setprecision(3)
166 << drift * 1000.0 << " mm\n";
167 } else {
168 mj_kdl::Viewer viewer;
169 if (!mj_kdl::init_window_sim(&viewer, &robot)) {
170 std::cerr << "init_window_sim() failed\n";
171 mj_kdl::cleanup(&robot);
172 mj_kdl::destroy_scene(model, data);
173 return 1;
174 }
175
176 while (true) {
177 step_impedance();
178 if (!mj_kdl::tick(&viewer, model, data)) break;
179 }
180
181 mj_kdl::cleanup(&viewer);
182 }
183
184 mj_kdl::cleanup(&robot);
185 mj_kdl::destroy_scene(model, data);
186 return 0;
187}
int main(int argc, char *argv[])
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)
bool init_window_sim(Viewer *v, Robot *r, const char *title="MuJoCo")
bool tick(Viewer *v, Robot *r)
std::vector< AttachmentSpec > attachments
std::vector< int > kdl_to_mj_qpos
std::vector< int > kdl_to_mj_dof
std::vector< double > jnt_pos_msr
std::vector< double > jnt_pos_cmd
std::vector< double > jnt_vel_msr
std::vector< double > jnt_trq_cmd
std::vector< RobotSpec > robots