Mujoco KDL Wrapper  0.2.2
MuJoCo + KDL bridge for robot kinematics and dynamics
Loading...
Searching...
No Matches
admittance_ft_common.hpp
Go to the documentation of this file.
1#pragma once
2
4#include "example_paths.hpp"
5
6#include <kdl/chainfksolverpos_recursive.hpp>
7#include <kdl/chainhdsolver_vereshchagin_fixed_joint.hpp>
8#include <kdl/chainidsolver_recursive_newton_euler.hpp>
9#include <kdl/chainiksolvervel_wdls.hpp>
10#include <kdl/chainjnttojacsolver.hpp>
11
12#include <algorithm>
13#include <array>
14#include <cmath>
15#include <iomanip>
16#include <iostream>
17#include <memory>
18#include <string>
19#include <vector>
20
22{
23
24static constexpr double kHomePose[7] = { 0.0, 0.2618, 3.1416, -2.2689, 0.0, 0.9599, 1.5708 };
25static constexpr double kTableZ = 0.70;
26static constexpr double kMAdm = 8.0;
27static constexpr double kDAdm = 80.0;
28static constexpr double kKAdm = 0.0;
29static constexpr double kForceDeadband = 2.5;
30static constexpr double kMaxOffset = 0.20;
31static constexpr double kMaxVel = 0.25;
32static constexpr double kTeachTime = 16.0;
33static constexpr double kTeachRadius = 0.04;
34static constexpr double kTeachRise = 0.10;
35static constexpr double kTeachTurns = 5.0;
36static constexpr int kSettleSteps = 300;
37static constexpr double kHandoffTareTime = 1.0;
38static constexpr const char *kToolBody = "g_base";
39static constexpr const char *kGripperActuator = "g_fingers_actuator";
40static constexpr double kSelfcheckPush[3] = { 8.0, 12.0, 6.0 };
41
42inline double clamp(double v, double lo, double hi) { return std::max(lo, std::min(hi, v)); }
43
44inline KDL::JntArray home_q(unsigned n)
45{
46 KDL::JntArray q(n);
47 for (unsigned i = 0; i < n; ++i) q(i) = kHomePose[i];
48 return q;
49}
50
51inline KDL::Vector vclamp(const KDL::Vector &v, double limit)
52{
53 return KDL::Vector(
54 clamp(v.x(), -limit, limit),
55 clamp(v.y(), -limit, limit),
56 clamp(v.z(), -limit, limit)
57 );
58}
59
60inline double norm3(const KDL::Vector &v) { return std::sqrt(v.x() * v.x() + v.y() * v.y() + v.z() * v.z()); }
61
62inline KDL::Rotation mj_xmat_to_kdl_rot(const double *m)
63{
64 return KDL::Rotation(m[0], m[1], m[2], m[3], m[4], m[5], m[6], m[7], m[8]);
65}
66
67inline KDL::Frame site_frame_by_id(const mjModel *model, const mjData *data, int id)
68{
69 (void)model;
70 const double *p = data->site_xpos + 3 * id;
71 const double *R = data->site_xmat + 9 * id;
72 return KDL::Frame(mj_xmat_to_kdl_rot(R), KDL::Vector(p[0], p[1], p[2]));
73}
74
75inline KDL::Frame current_tcp(KDL::ChainFkSolverPos_recursive &fk, const mj_kdl::Robot &robot)
76{
77 KDL::JntArray q(robot.n_joints);
78 for (int i = 0; i < robot.n_joints; ++i) q(i) = robot.jnt_pos_msr[i];
79 KDL::Frame out;
80 fk.JntToCart(q, out);
81 return out;
82}
83
85{
86 KDL::Vector bias = KDL::Vector::Zero();
87 KDL::Vector offset = KDL::Vector::Zero();
88 KDL::Vector vel = KDL::Vector::Zero();
89};
90
91inline KDL::Vector spiral_force(double t)
92{
93 if (t < 0.0 || t > kTeachTime) return KDL::Vector::Zero();
94 const double theta = 2.0 * M_PI * kTeachTurns * t / kTeachTime;
95 const double theta_dot = 2.0 * M_PI * kTeachTurns / kTeachTime;
96 return KDL::Vector(
97 kDAdm * (-kTeachRadius * theta_dot * std::sin(theta)),
98 kDAdm * ( kTeachRadius * theta_dot * std::cos(theta)),
99 kDAdm * ( kTeachRise / kTeachTime)
100 );
101}
102
103inline void admittance_update(AdmState &s, const KDL::Vector &force, double dt)
104{
105 if (norm3(force) == 0.0) {
106 s.vel = KDL::Vector::Zero();
107 return;
108 }
109 KDL::Vector acc(
110 (force.x() - kDAdm * s.vel.x() - kKAdm * s.offset.x()) / kMAdm,
111 (force.y() - kDAdm * s.vel.y() - kKAdm * s.offset.y()) / kMAdm,
112 (force.z() - kDAdm * s.vel.z() - kKAdm * s.offset.z()) / kMAdm
113 );
114 s.vel = vclamp(s.vel + acc * dt, kMaxVel);
115 s.offset = vclamp(s.offset + s.vel * dt, kMaxOffset);
116}
117
118inline void set_body_wrench(mjModel *model, mjData *data, const char *body, const KDL::Vector &force)
119{
120 const int id = mj_name2id(model, mjOBJ_BODY, body);
121 if (id < 0) return;
122 data->xfrc_applied[6 * id + 0] = force.x();
123 data->xfrc_applied[6 * id + 1] = force.y();
124 data->xfrc_applied[6 * id + 2] = force.z();
125 data->xfrc_applied[6 * id + 3] = 0.0;
126 data->xfrc_applied[6 * id + 4] = 0.0;
127 data->xfrc_applied[6 * id + 5] = 0.0;
128}
129
130inline void close_gripper(mjModel *model, mjData *data)
131{
132 const int id = mj_name2id(model, mjOBJ_ACTUATOR, kGripperActuator);
133 if (id >= 0) data->ctrl[id] = 255.0;
134}
135
136inline KDL::Vector tare_force(const mj_kdl::Robot &robot)
137{
138 const mj_kdl::ForceTorqueSensor *ft = mj_kdl::find_ft_sensor(&robot, "wrist_ft");
139 if (!ft || ft->frame_site_id < 0) return KDL::Vector::Zero();
140 return site_frame_by_id(robot.model, robot.data, ft->frame_site_id).M * ft->wrench.force;
141}
142
143inline KDL::Vector measured_force(const mj_kdl::Robot &robot, const AdmState &s)
144{
145 const mj_kdl::ForceTorqueSensor *ft = mj_kdl::find_ft_sensor(&robot, "wrist_ft");
146 if (!ft || ft->frame_site_id < 0) return KDL::Vector::Zero();
147 const KDL::Vector f_world = site_frame_by_id(robot.model, robot.data, ft->frame_site_id).M * ft->wrench.force;
148 const KDL::Vector f_ext = s.bias - f_world;
149 return norm3(f_ext) < kForceDeadband ? KDL::Vector::Zero() : f_ext;
150}
151
153{
154 mjModel *model = nullptr;
155 mjData *data = nullptr;
159 int tool_body_id = -1;
160
161 void cleanup()
162 {
165 model = nullptr;
166 data = nullptr;
167 }
168};
169
171{
172 const std::string arm = mj_kdl_examples::menagerie_model("kinova_gen3/gen3.xml");
173 const std::string ft = mj_kdl_examples::asset("ft_sensor.xml");
174 const std::string gripper = mj_kdl_examples::asset("robotiq_2f85/2f85.xml");
175 const std::string table_path = mj_kdl_examples::asset("table.xml");
176
178 table.name = "table";
179 table.mjcf_path = table_path;
180 table.pos[0] = 0.0;
181 table.pos[1] = 0.0;
182 table.pos[2] = kTableZ;
183 table.fixed = true;
184
186 ft_spec.mjcf_path = ft.c_str();
187 ft_spec.attach_to = { mj_kdl::AttachKind::Site, "pinch_site" };
188
189 mj_kdl::AttachmentSpec gripper_spec;
190 gripper_spec.mjcf_path = gripper.c_str();
191 gripper_spec.attach_to = { mj_kdl::AttachKind::Site, "wrist_ft_site" };
192 gripper_spec.prefix = "g_";
193
194 mj_kdl::RobotSpec robot_spec;
195 robot_spec.path = arm.c_str();
196 const std::string table_top = mj_kdl::scene_object_site_name(table, "table_top");
197 robot_spec.attach_to = { mj_kdl::AttachKind::Site, table_top.c_str() };
198 robot_spec.attachments.push_back(ft_spec);
199 robot_spec.attachments.push_back(gripper_spec);
200
201 h.scene.timestep = 0.002;
202 h.scene.add_floor = true;
203 h.scene.add_skybox = true;
204 h.scene.objects.push_back(table);
205 h.scene.robots.push_back(robot_spec);
206
207 if (!mj_kdl::build_scene(&h.model, &h.data, &h.scene)) return false;
208
210 ft_sensor.name = "wrist_ft";
211 ft_sensor.frame_site = "wrist_ft_site";
212
214 tool.tool_body = "g_base";
215 tool.tcp_site = "g_pinch";
216 tool.ft_sensors.push_back(ft_sensor);
217
218 if (!mj_kdl::init_robot_from_mjcf(&h.robot, h.model, h.data, "base_link", "bracelet_link", "", &tool)) return false;
219 h.env.spec = h.scene;
220 h.env.model = h.model;
221 h.env.data = h.data;
223 h.tool_body_id = mj_name2id(h.model, mjOBJ_BODY, kToolBody);
224 return h.tool_body_id >= 0;
225}
226
228{
229public:
230 virtual ~Controller() = default;
231 virtual const char *name() const = 0;
232 virtual mj_kdl::CtrlMode mode() const = 0;
233 virtual void reset() = 0;
234 virtual void track(const KDL::Frame &target) = 0;
235};
236
237inline KDL::Frame admittance_step(
238 mj_kdl::Robot &robot,
239 Controller &ctrl,
240 AdmState &state,
241 const KDL::Frame &nominal,
242 const KDL::Vector &force,
243 double dt
244)
245{
246 (void)robot;
247 admittance_update(state, force, dt);
248 KDL::Frame target(nominal.M, nominal.p + state.offset);
249 ctrl.track(target);
250 return target;
251}
252
253inline void settle_and_tare(SceneHandles &h, Controller &ctrl, AdmState &state)
254{
256 KDL::ChainFkSolverPos_recursive fk(h.robot.chain);
257 const KDL::Frame home = current_tcp(fk, h.robot);
258 for (int i = 0; i < kSettleSteps; ++i) {
261 ctrl.track(home);
262 if (!mj_kdl::step(&h.robot)) break;
263 }
265 state.bias = tare_force(h.robot);
266}
267
269{
270 double helix_react = 0.0;
271 double helix_track_err = 0.0;
272 double helix_settle_err = 0.0;
273 double handoff_force = 0.0;
274 double push_response = 0.0;
275 double push_dy = 0.0;
276 double push_recovery_err = 0.0;
277 double hold_drift = 0.0;
278};
279
280inline Metrics run_selfcheck(SceneHandles &h, Controller &ctrl, AdmState &state, const KDL::Frame &nominal)
281{
282 KDL::ChainFkSolverPos_recursive fk(h.robot.chain);
283 Metrics m;
284
285 const double t0 = h.data->time;
286 while (h.data->time - t0 < kTeachTime) {
287 const double t = h.data->time - t0;
290 KDL::Frame target = admittance_step(h.robot, ctrl, state, nominal, spiral_force(t), h.scene.timestep);
291 KDL::Frame tcp = current_tcp(fk, h.robot);
292 m.helix_react = std::max(m.helix_react, norm3(state.offset));
293 m.helix_track_err = std::max(m.helix_track_err, norm3(tcp.p - target.p));
294 if (!mj_kdl::step(&h.robot)) break;
295 }
296
297 const double th = h.data->time;
298 while (h.data->time - th < kHandoffTareTime) {
301 KDL::Frame target = admittance_step(h.robot, ctrl, state, nominal, KDL::Vector::Zero(), h.scene.timestep);
302 KDL::Frame tcp = current_tcp(fk, h.robot);
303 m.helix_track_err = std::max(m.helix_track_err, norm3(tcp.p - target.p));
304 if (!mj_kdl::step(&h.robot)) break;
305 }
306
308 state.bias = tare_force(h.robot);
309 for (int i = 0; i < 100; ++i) {
312 KDL::Vector f = measured_force(h.robot, state);
313 m.handoff_force = std::max(m.handoff_force, norm3(f));
314 admittance_step(h.robot, ctrl, state, nominal, f, h.scene.timestep);
315 if (!mj_kdl::step(&h.robot)) break;
316 }
317
318 const double ts = h.data->time;
319 while (h.data->time - ts < 0.5) {
322 KDL::Frame target = admittance_step(h.robot, ctrl, state, nominal, KDL::Vector::Zero(), h.scene.timestep);
323 KDL::Frame tcp = current_tcp(fk, h.robot);
324 m.helix_settle_err = std::max(m.helix_settle_err, norm3(tcp.p - target.p));
325 if (!mj_kdl::step(&h.robot)) break;
326 }
327
328 const KDL::Vector pre_push = state.offset;
329 const double tp = h.data->time;
330 KDL::Vector settled = pre_push;
331 bool have_recovery = false;
332 while (h.data->time - tp < 4.0) {
333 const double t = h.data->time - tp;
335 h.model, h.data, kToolBody,
336 t < 1.0 ? KDL::Vector(kSelfcheckPush[0], kSelfcheckPush[1], kSelfcheckPush[2]) : KDL::Vector::Zero()
337 );
340 KDL::Frame target = admittance_step(h.robot, ctrl, state, nominal, measured_force(h.robot, state), h.scene.timestep);
341 KDL::Frame tcp = current_tcp(fk, h.robot);
342 if (!have_recovery && t >= 2.0) {
343 m.push_recovery_err = norm3(tcp.p - target.p);
344 have_recovery = true;
345 }
346 if (t >= 2.5) settled = state.offset;
347 if (!mj_kdl::step(&h.robot)) break;
348 }
349 set_body_wrench(h.model, h.data, kToolBody, KDL::Vector::Zero());
350
351 const KDL::Vector response = settled - pre_push;
352 m.push_response = norm3(response);
353 m.push_dy = response.y();
354 m.hold_drift = norm3(state.offset - settled);
355 return m;
356}
357
358inline int finish_headless(const Metrics &m)
359{
360 std::cout << std::fixed << std::setprecision(4)
361 << "helix force response (max offset): " << m.helix_react << " m\n"
362 << "helix TCP tracking error: " << m.helix_track_err << " m\n"
363 << "helix settle error: " << m.helix_settle_err << " m\n"
364 << "FT handoff residual force: " << m.handoff_force << " N\n"
365 << "FT push response (offset norm): " << m.push_response << " m\n"
366 << "FT push response (offset dY): " << m.push_dy << " m\n"
367 << "push release recovery error: " << m.push_recovery_err << " m\n"
368 << "hold drift after push released: " << m.hold_drift << " m\n";
369 if (m.helix_react <= 0.05 || m.helix_track_err >= 0.006 || m.helix_settle_err >= 0.004
370 || m.handoff_force != 0.0 || m.push_response <= 0.05 || m.push_recovery_err >= 0.006
371 || m.hold_drift >= 0.01) {
372 return 1;
373 }
374 std::cout << "OK: admittance responded to helix + FT push and held on release\n";
375 return 0;
376}
377
378inline void run_gui(SceneHandles &h, Controller &ctrl, AdmState &state, const KDL::Frame &nominal)
379{
380 mj_kdl::Viewer viewer{};
381 mj_kdl::set_free_camera(&viewer, 1.55, 145.0, -24.0, { 0.05, 0.0, kTableZ + 0.35 });
382 if (!mj_kdl::init_window_sim(&viewer, &h.robot, ctrl.name())) return;
383
384 KDL::ChainFkSolverPos_recursive fk(h.robot.chain);
385 double start = h.data->time;
386 double prev = h.data->time;
387 bool handoff_tared = false;
388 bool have_prev = false;
389 KDL::Vector target_prev, tcp_prev;
390 int trace_step = 0;
391
392 while (mj_kdl::is_running(&viewer)) {
393 if (h.data->time < prev - 1e-6) {
394 mj_kdl::reset(&h.env);
395 ctrl.reset();
396 state = AdmState{};
397 start = h.data->time;
398 handoff_tared = false;
399 have_prev = false;
400 }
401 prev = h.data->time;
402 const double t = h.data->time - start;
405
406 KDL::Vector force = KDL::Vector::Zero();
407 if (t < kTeachTime) {
408 force = spiral_force(t);
409 } else if (t >= kTeachTime + kHandoffTareTime) {
410 if (!handoff_tared) {
411 state.bias = tare_force(h.robot);
412 handoff_tared = true;
413 }
414 force = measured_force(h.robot, state);
415 }
416 KDL::Frame target = admittance_step(h.robot, ctrl, state, nominal, force, h.scene.timestep);
417 KDL::Frame tcp = current_tcp(fk, h.robot);
418 KDL::Frame world_base;
419 mj_kdl::get_body_frame(h.model, h.data, "base_link", &world_base);
420 KDL::Vector target_xyz = world_base * target.p;
421 KDL::Vector tcp_xyz = world_base * tcp.p;
422 ++trace_step;
423 if (have_prev && trace_step % 5 == 0) {
424 const float yellow[4] = { 1.0f, 0.95f, 0.0f, 1.0f };
425 const float green[4] = { 0.0f, 1.0f, 0.2f, 1.0f };
426 mj_kdl::add_trace_segment(&viewer, target_prev, target_xyz, yellow);
427 mj_kdl::add_trace_segment(&viewer, tcp_prev, tcp_xyz, green);
428 }
429 target_prev = target_xyz;
430 tcp_prev = tcp_xyz;
431 have_prev = true;
432
433 if (!mj_kdl::step(&viewer, h.model, h.data)) break;
434 }
435 set_body_wrench(h.model, h.data, kToolBody, KDL::Vector::Zero());
436 mj_kdl::cleanup(&viewer);
437}
438
439inline int run(int argc, char **argv, std::unique_ptr<Controller> (*make_controller)(SceneHandles &))
440{
441 bool headless = false;
442 for (int i = 1; i < argc; ++i)
443 if (std::string(argv[i]) == "--headless") headless = true;
444
445 SceneHandles h;
446 if (!build_scene(h)) {
447 std::cerr << "failed to build admittance FT scene\n";
448 h.cleanup();
449 return 1;
450 }
451 std::unique_ptr<Controller> ctrl = make_controller(h);
452 h.robot.ctrl_mode = ctrl->mode();
453
454 const KDL::JntArray q_home = home_q(h.robot.n_joints);
456 mj_kdl::set_joint_pos(&h.robot, q_home, false);
457 ctrl->reset();
458 set_body_wrench(h.model, h.data, kToolBody, KDL::Vector::Zero());
459 };
460 mj_kdl::reset(&h.env);
461
462 AdmState state;
463 settle_and_tare(h, *ctrl, state);
464 KDL::ChainFkSolverPos_recursive fk(h.robot.chain);
465 KDL::Frame nominal = current_tcp(fk, h.robot);
466
467 std::cout << std::fixed << std::setprecision(3)
468 << "FT bias: [" << state.bias.x() << ", " << state.bias.y() << ", " << state.bias.z() << "] N\n";
469 int rc = 0;
470 if (headless) {
471 rc = finish_headless(run_selfcheck(h, *ctrl, state, nominal));
472 } else {
473 run_gui(h, *ctrl, state, nominal);
474 std::cout << std::fixed << std::setprecision(4)
475 << "final offset: [" << state.offset.x() << ", " << state.offset.y() << ", "
476 << state.offset.z() << "] m\n";
477 }
478 h.cleanup();
479 return rc;
480}
481
482} // namespace admittance_ft
virtual const char * name() const =0
virtual void track(const KDL::Frame &target)=0
virtual mj_kdl::CtrlMode mode() const =0
virtual void reset()=0
virtual ~Controller()=default
void env_add_robot(Env *env, Robot *robot)
ResetInfo reset(Env *env, const ResetOptions *options=nullptr)
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)
const ForceTorqueSensor * find_ft_sensor(const Robot *r, const char *name)
bool get_body_frame(const mjModel *model, mjData *data, const char *body_name, KDL::Frame *out)
bool build_scene(mjModel **out_model, mjData **out_data, const SceneSpec *spec)
void destroy_scene(mjModel *model, mjData *data)
std::string scene_object_site_name(const SceneObject &obj, const char *site_name)
bool init_window_sim(Viewer *v, Robot *r, const char *title="MuJoCo")
void add_trace_segment(Viewer *v, const KDL::Vector &a, const KDL::Vector &b, const float rgba[4]=nullptr)
bool is_running(const Viewer *v)
Metrics run_selfcheck(SceneHandles &h, Controller &ctrl, AdmState &state, const KDL::Frame &nominal)
KDL::JntArray home_q(unsigned n)
KDL::Rotation mj_xmat_to_kdl_rot(const double *m)
KDL::Vector tare_force(const mj_kdl::Robot &robot)
KDL::Vector vclamp(const KDL::Vector &v, double limit)
KDL::Vector measured_force(const mj_kdl::Robot &robot, const AdmState &s)
int run(int argc, char **argv, std::unique_ptr< Controller >(*make_controller)(SceneHandles &))
void run_gui(SceneHandles &h, Controller &ctrl, AdmState &state, const KDL::Frame &nominal)
KDL::Frame admittance_step(mj_kdl::Robot &robot, Controller &ctrl, AdmState &state, const KDL::Frame &nominal, const KDL::Vector &force, double dt)
int finish_headless(const Metrics &m)
void close_gripper(mjModel *model, mjData *data)
KDL::Frame current_tcp(KDL::ChainFkSolverPos_recursive &fk, const mj_kdl::Robot &robot)
void admittance_update(AdmState &s, const KDL::Vector &force, double dt)
void settle_and_tare(SceneHandles &h, Controller &ctrl, AdmState &state)
double norm3(const KDL::Vector &v)
double clamp(double v, double lo, double hi)
KDL::Vector spiral_force(double t)
KDL::Frame site_frame_by_id(const mjModel *model, const mjData *data, int id)
bool build_scene(SceneHandles &h)
void set_body_wrench(mjModel *model, mjData *data, const char *body, const KDL::Vector &force)
std::string asset(const fs::path &relative)
std::string menagerie_model(const fs::path &relative)
void set_free_camera(Viewer *v, double distance, double azimuth, double elevation, const std::array< double, 3 > &lookat)
ResetHook on_reset
std::vector< AttachmentSpec > attachments
std::vector< double > jnt_pos_msr
std::vector< RobotSpec > robots
std::vector< SceneObject > objects
std::vector< ForceTorqueSensorSpec > ft_sensors