Mujoco KDL Wrapper  0.5.1
MuJoCo + KDL bridge for robot kinematics and dynamics
Loading...
Searching...
No Matches
ex_achd_pick_place.cpp
Go to the documentation of this file.
1/* ex_achd_pick_place.cpp
2 * The table pick-place with Cartesian acceleration-constrained hybrid dynamics:
3 * KDL ChainHdSolver_Vereshchagin + RNEA in TORQUE mode:
4 *
5 * Xddot_des = Kp * diff(T_tcp, T_target) + Ki * integral + Kd * d/dt(diff)
6 * beta = alpha^T * Xddot_des, with alpha = I_6
7 * qddot = ACHD(q, qdot, alpha, beta, f_ext)
8 * tau = RNEA(q, qdot, qddot)
9 *
10 * In every phase an ACHD-only upward wrench on half_arm_2_link keeps the elbow from dropping
11 * while the TCP task holds.
12 *
13 * Usage:
14 * ex_achd_pick_place [--headless] [--record output.mp4]
15 *
16 * Runs the sequence once and prints the cube's final position; --headless skips the viewer and
17 * exits 1 unless the cube rests on the table within kMaxPlaceErr of the place spot and the
18 * elbow stayed within kMaxElbowDrop of its reference; --record
19 * writes an MP4 offscreen (EGL + ffmpeg) and implies --headless. */
20
21#include "common.hpp"
22#include "example_paths.hpp"
23
24#include <kdl/chainfksolvervel_recursive.hpp>
25#include <kdl/chainhdsolver_vereshchagin.hpp>
26#include <kdl/chainidsolver_recursive_newton_euler.hpp>
27
28#include <algorithm>
29#include <array>
30#include <cmath>
31#include <iomanip>
32#include <iostream>
33#include <string>
34#include <vector>
35
36namespace ex = mj_kdl_examples;
37
38static constexpr double kMaxPlaceErr = 0.005; // [m] in the table plane
39static constexpr double kMaxElbowDrop = 0.10; // [m] below the support reference
40
41// With alpha = I_6 these are the desired TCP linear/angular accelerations.
42static constexpr double kKpLin = 200.0;
43static constexpr double kKiLin = 100.0;
44static constexpr double kKdLin = 40.0;
45static constexpr double kKpRot = 120.0;
46static constexpr double kKiRot = 50.0;
47static constexpr double kKdRot = 80.0;
48static constexpr double kBetaMaxLin = 120.0;
49static constexpr double kBetaMaxRot = 80.0;
50static constexpr double kIntegralMax = 0.5;
51static constexpr int kRecordFps = 30;
52
53static constexpr const char *kSupportLink = "half_arm_2_link";
54static constexpr double kSupportKp = 800.0;
55static constexpr double kSupportKd = 80.0;
56static constexpr double kSupportFMax = 45.0;
57static constexpr double kSupportLift = 0.06;
58
59static double smoothstep(double t)
60{
61 t = ex::clamp01(t);
62 return t * t * (3.0 - 2.0 * t);
63}
64
65struct Phase
66{
67 const char *name;
69 double duration;
70 double timeout;
74};
75
77{
78 explicit AchdController(const mj_kdl::Robot &r, double gravity_z)
79 : robot(r), n(r.chain.getNrOfJoints()), fk_pos(r.chain), fk_vel(r.chain),
80 achd(r.chain, KDL::Twist(KDL::Vector(0.0, 0.0, -gravity_z), KDL::Vector::Zero()), 6),
81 rnea(r.chain, KDL::Vector(0.0, 0.0, gravity_z)), q(n), qdot(n), qddot(n), ff_tau(n),
82 constraint_tau(n), tau(n), beta(6), alpha(6),
83 f_ext(r.chain.getNrOfSegments(), KDL::Wrench::Zero()),
84 f_zero(r.chain.getNrOfSegments(), KDL::Wrench::Zero())
85 {
86 for (unsigned i = 0; i < 6; ++i) alpha(i, i) = 1.0;
87 }
88
89 void new_phase()
90 {
91 err_i.fill(0.0);
92 first = true;
93 }
94
95 // The TCP frame of the measured state; call once per cycle before control().
97 {
99 fk_pos.JntToCart(q, tcp);
100 return tcp;
101 }
102
103 // ACHD qddot for the TCP target, priced by RNEA into jnt_trq_cmd; false if a solver failed.
104 bool control(mj_kdl::Robot &out, const KDL::Frame &target, const KDL::Twist &target_twist)
105 {
106 KDL::FrameVel tcp_vel;
107 fk_vel.JntToCart(KDL::JntArrayVel(q, qdot), tcp_vel);
108 const KDL::Twist w = tcp_vel.deriv();
109 const KDL::Twist err = KDL::diff(tcp, target);
110 const double dt = robot.model->opt.timestep;
111 const double e[6] = { err.vel.x(), err.vel.y(), err.vel.z(),
112 err.rot.x(), err.rot.y(), err.rot.z() };
113 if (first) std::copy(e, e + 6, err_prev.begin());
114 first = false;
115 // The rotation is damped against the measured rate: the target's own turn is no error.
116 const double d_rot[3] = { target_twist.rot.x() - w.rot.x(),
117 target_twist.rot.y() - w.rot.y(),
118 target_twist.rot.z() - w.rot.z() };
119 for (unsigned i = 0; i < 6; ++i) {
120 err_i[i] = ex::clamp_abs(err_i[i] + e[i] * dt, kIntegralMax);
121 const bool lin = i < 3;
122 const double d = lin ? (e[i] - err_prev[i]) / dt : d_rot[i - 3];
123 const double accel = lin ? kKpLin * e[i] + kKiLin * err_i[i] + kKdLin * d
124 : kKpRot * e[i] + kKiRot * err_i[i] + kKdRot * d;
125 beta(i) = ex::clamp_abs(accel, lin ? kBetaMaxLin : kBetaMaxRot);
126 err_prev[i] = e[i];
127 }
129 if (achd.CartToJnt(q, qdot, qddot, alpha, beta, f_ext, ff_tau, constraint_tau) < 0)
130 return false;
131 if (rnea.CartToJnt(q, qdot, qddot, f_zero, tau) < 0) return false;
132 // update() clamps each torque to its joint's limit and reports it in jnt_saturated.
133 for (unsigned i = 0; i < n; ++i) out.jnt_trq_cmd[i] = tau(i);
134 return true;
135 }
136
138 unsigned n;
147 std::array<double, 6> err_i{}, err_prev{};
148 bool first = true;
149};
150
151int main(int argc, char *argv[])
152{
153 const ex::Args args = ex::parse_args(argc, argv, "achd_pick_place.mp4");
154 const bool headless = args.headless;
155
156 mj_kdl::RobotSpec robot_spec;
157 robot_spec.path = ex::asset("kinova_gen3/gen3.xml");
158 robot_spec.pos[2] = ex::kTableZ;
159 robot_spec.attachments.push_back(ex::gripper_attachment(ex::asset("robotiq_2f85/2f85.xml")));
160
162 scene.robots.push_back(robot_spec);
163 scene.objects.push_back(ex::table_object(ex::asset("table.xml"), ex::kTableZ));
165
166 mj_kdl::Env env;
167 mj_kdl::Robot robot;
168 if (!mj_kdl::init_env(&env, &scene)) return 1;
170 if (!mj_kdl::init_robot_from_mjcf(&robot, &env, "base_link", "bracelet_link", "", &tool))
171 return 1;
174 mj_kdl::bind_scene_actuator(&env.scene, "g_fingers_actuator");
176 if (!fingers || !cube) return 1;
177
178 int support_segment = -1;
179 for (unsigned i = 0; i < robot.chain.getNrOfSegments(); ++i)
180 if (robot.chain.getSegment(i).getName() == kSupportLink)
181 support_segment = static_cast<int>(i);
182 if (support_segment < 0) {
183 std::cerr << "support segment not found: " << kSupportLink << "\n";
184 return 1;
185 }
186
187 const unsigned n = robot.chain.getNrOfJoints();
188 const KDL::JntArray q_home = ex::home_q(n);
189 AchdController ctrl(robot, scene.gravity_z);
190 KDL::ChainDynParam dyn(robot.chain, KDL::Vector(0.0, 0.0, scene.gravity_z));
191
192 KDL::Frame home_tcp;
193 ctrl.fk_pos.JntToCart(q_home, home_tcp);
194 const auto at = [&](const double xy[2], double z) {
195 return KDL::Frame(robot.tip_T_tcp.M, KDL::Vector(xy[0], xy[1], z));
196 };
197 const double z_grasp = ex::kCubeHS, z_above = z_grasp + 0.20, z_lift = z_grasp + 0.30;
198 const double close = ex::kGripperClosed;
199 const auto &pick = ex::kPickXY, &place = ex::kPlaceXY;
200 // clang-format off
201 const std::vector<Phase> phases = {
202 { "HOME", home_tcp, 1.0, 2.5, 0.03, 0.05, 0.0 },
203 { "PICK_ABOVE", at(pick, z_above), 8.0, 14.0, 0.04, 0.03, 0.0 },
204 { "PICK", at(pick, z_grasp), 5.0, 12.0, 0.02, 0.03, 0.0 },
205 { "CLOSE", at(pick, z_grasp), 1.5, 2.5, -1.0, -1.0, close },
206 { "LIFT", at(pick, z_lift), 3.0, 8.0, 0.04, 0.03, close },
207 { "PLACE_ABOVE", at(place, z_above), 5.0, 12.0, 0.04, 0.03, close },
208 { "PLACE", at(place, z_grasp), 5.0, 14.0, 0.02, 0.03, close },
209 { "OPEN", at(place, z_grasp), 1.0, 2.0, -1.0, -1.0, 0.0 },
210 { "RETREAT", at(place, z_above), 3.0, 6.0, 0.04, 0.08, 0.0 },
211 { "HOLD", at(place, z_above), 4.0, 4.0, -1.0, -1.0, 0.0 },
212 };
213 // clang-format on
214
215 bool restart = false;
216 env.on_reset = [&](mj_kdl::ResetContext *ctx) {
217 mj_kdl::set_joint_pos(&robot, q_home);
218 ex::place_cube(env);
219 ctx->data->ctrl[fingers->ctrl_id] = 0.0;
220 ex::prime_gravity(robot, dyn, q_home);
221 restart = true;
222 };
223 mj_kdl::reset(&env);
224 if (!headless && !mj_kdl::open_viewer(&env)) return 1;
225
226 mj_kdl::VideoRecorder recorder;
227 bool recording = false;
228 if (args.record) {
230 &recorder, env.model, args.record_path.c_str(), mj_kdl::VideoResolution::R720p, kRecordFps
231 );
232 if (!s) {
233 std::cerr << "init_video_recorder() failed: " << s.error << "\n";
234 return 1;
235 }
236 recorder.cam.azimuth = 145.0;
237 recorder.cam.elevation = -22.0;
238 recorder.cam.distance = 1.35;
239 recorder.cam.lookat[0] = 0.12;
240 recorder.cam.lookat[1] = 0.12;
241 recorder.cam.lookat[2] = 0.90;
242 recording = true;
243 }
244 const int steps_per_frame =
245 std::max(1, static_cast<int>(1.0 / (kRecordFps * env.model->opt.timestep)));
246
247 bool aborted = false, solver_failed = false, record_failed = false;
248 int sim_step = 0;
249 double elbow_drop = 0.0; // [m] below the support reference, the worst over the run
250 do {
251 restart = false;
252 elbow_drop = 0.0;
253 bool support_valid = false;
254 double support_z_ref = 0.0;
255 double support_prev_z = 0.0;
256 for (const Phase &phase : phases) {
257 if (aborted || restart) break;
258 std::cout << "State: " << phase.name << "\n";
259 const double t_enter = env.data->time;
260 const KDL::Frame phase_start = ctrl.measure();
261 KDL::Frame link;
262 ctrl.fk_pos.JntToCart(ctrl.q, link, support_segment + 1);
263 if (!support_valid || std::string(phase.name) == "PLACE_ABOVE") {
264 support_z_ref = ex::kTableZ + link.p.z() + kSupportLift;
265 support_prev_z = support_z_ref;
266 support_valid = true;
267 }
268 ctrl.new_phase();
269 KDL::Frame prev_target = phase_start;
270 while (true) {
271 mj_kdl::update(&env);
272 const KDL::Frame &tcp = ctrl.measure();
273 const double t_rel = env.data->time - t_enter;
274 const double dt = env.model->opt.timestep;
275 const double s = phase.duration > 0.0 ? smoothstep(t_rel / phase.duration) : 1.0;
276 const KDL::Frame target =
277 KDL::addDelta(phase_start, KDL::diff(phase_start, phase.target), s);
278 const KDL::Twist target_twist = KDL::diff(prev_target, target, dt);
279 prev_target = target;
280
281 std::fill(ctrl.f_ext.begin(), ctrl.f_ext.end(), KDL::Wrench::Zero());
282 ctrl.fk_pos.JntToCart(ctrl.q, link, support_segment + 1);
283 const double z = ex::kTableZ + link.p.z();
284 const double vz = (z - support_prev_z) / dt;
285 support_prev_z = z;
286 elbow_drop = std::max(elbow_drop, support_z_ref - kSupportLift - z);
287 const double fz =
288 std::clamp(kSupportKp * (support_z_ref - z) - kSupportKd * vz, 0.0, kSupportFMax);
289 ctrl.f_ext[support_segment] =
290 KDL::Wrench(KDL::Vector(0.0, 0.0, fz), KDL::Vector::Zero());
291 if (!ctrl.control(robot, target, target_twist)) {
292 std::cerr << "ACHD/RNEA failed in " << phase.name << "\n";
293 solver_failed = aborted = true;
294 break;
295 }
296 fingers->command = phase.gripper_cmd;
297
298 const KDL::Twist err = KDL::diff(tcp, phase.target);
299 const bool settled =
300 err.vel.Norm() <= phase.settle_pos_tol && err.rot.Norm() <= phase.settle_rot_tol;
301 const bool done_pose = phase.settle_pos_tol < 0.0 || settled;
302 const bool done_timeout = phase.timeout > 0.0 && t_rel >= phase.timeout;
303 if ((t_rel >= phase.duration && done_pose) || done_timeout) {
304 std::cout << " pos_err=" << std::fixed << std::setprecision(3)
305 << err.vel.Norm() << " rot_err=" << err.rot.Norm() << " t=" << t_rel
306 << "\n";
307 break;
308 }
309
310 if (!mj_kdl::step(&env)) {
311 aborted = true;
312 break;
313 }
314 if (restart) break;
316 if (recording && ++sim_step % steps_per_frame == 0
317 && !mj_kdl::record_frame(&recorder, &env)) {
318 std::cerr << "record_frame() failed at step " << sim_step << "\n";
319 mj_kdl::cleanup(&recorder);
320 recording = false;
321 record_failed = true;
322 }
323 }
324 }
325 } while (restart);
326 mj_kdl::update(&env);
327 if (recording) {
328 mj_kdl::cleanup(&recorder);
329 std::cout << "Saved recording: " << args.record_path << "\n";
330 }
331
332 const KDL::Vector c = cube->pose.p;
333 const double place_xy = std::hypot(c.x() - ex::kPlaceXY[0], c.y() - ex::kPlaceXY[1]);
334 const bool on_table = std::abs(c.z() - (ex::kTableZ + ex::kCubeHS)) < 0.002;
335 std::cout << std::fixed << std::setprecision(4) << "cube final position: [" << c.x() << ", "
336 << c.y() << ", " << c.z() << "] place error " << place_xy * 1000.0 << " mm (limit "
337 << kMaxPlaceErr * 1000.0 << " mm)" << (on_table ? "" : ", not on the table") << "\n";
338 std::cout << "elbow drop: " << elbow_drop * 1000.0 << " mm (limit " << kMaxElbowDrop * 1000.0
339 << " mm)\n";
340 mj_kdl::cleanup(&env);
341 const bool ok = !aborted && !solver_failed && !record_failed && on_table
342 && place_xy <= kMaxPlaceErr && elbow_drop <= kMaxElbowDrop;
343 return headless ? ex::verdict(ok, "the cube was placed with the elbow held up") : 0;
344}
virtual int JntToCart(const JntArray &q_in, Frame &p_out, int segmentNr=-1)
const Segment & getSegment(unsigned int nr) const
unsigned int getNrOfJoints() const
unsigned int getNrOfSegments() const
Twist deriv() const
Rotation M
const std::string & getName() const
Vector rot
Vector vel
double z() const
double x() const
static Vector Zero()
double Norm(double eps=epsilon) const
double y() const
static Wrench Zero()
int main(int argc, char *argv[])
ResetInfo reset(Env *env, const ResetOptions *options=nullptr)
Status init_env(Env *env, const SceneSpec *spec)
void update(Env *env)
bool step(Env *env)
Status init_video_recorder(VideoRecorder *vr, mjModel *model, const char *out_path, int width=1280, int height=720, int fps=60)
bool record_frame(VideoRecorder *vr, Env *env)
Status set_control_mode(Robot *r, CtrlMode mode)
Status init_robot_from_mjcf(Robot *r, Env *env, 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)
void cleanup(Robot *r)
SceneActuatorSlot * bind_scene_actuator(SceneState *s, const char *name)
SceneFreeBodySlot * bind_scene_free_body(SceneState *s, const char *body_name)
Status open_viewer(Env *env, const char *title="MuJoCo")
void pace_realtime(Env *env)
IMETHOD Vector diff(const Vector &p_w_a, const Vector &p_w_b, double dt=1)
IMETHOD void SetToZero(Vector &v)
IMETHOD Vector addDelta(const Vector &p_w_a, const Vector &p_w_da, double dt=1)
std::vector< Wrench > Wrenches
std::string asset(const fs::path &relative)
double clamp_abs(double v, double limit)
Definition common.hpp:66
constexpr double kGripperClosed
Definition common.hpp:19
double clamp01(double v)
Definition common.hpp:64
int verdict(bool ok, const std::string &goal)
Definition common.hpp:58
mj_kdl::SceneSpec scene_spec()
Definition common.hpp:138
mj_kdl::ToolFrameSpec gripper_tool(const std::string &prefix="")
Definition common.hpp:159
constexpr double kCubeHS
Definition common.hpp:29
constexpr double kTableZ
Definition common.hpp:28
constexpr double kPickXY[2]
Definition common.hpp:30
mj_kdl::AttachmentSpec gripper_attachment(const std::string &path, const std::string &site="pinch_site")
Definition common.hpp:149
constexpr double kPlaceXY[2]
Definition common.hpp:31
KDL::JntArray home_q(unsigned n)
Definition common.hpp:68
void read_q(const mj_kdl::Robot &robot, KDL::JntArray &q)
Definition common.hpp:80
void place_cube(mj_kdl::Env &env)
Definition common.hpp:197
mj_kdl::SceneObject table_object(const std::string &path, double surface_z)
Definition common.hpp:168
void prime_gravity(mj_kdl::Robot &robot, KDL::ChainDynParam &dyn, const KDL::JntArray &q)
Definition common.hpp:131
Args parse_args(int argc, char **argv, const std::string &default_record_path="")
Definition common.hpp:41
mj_kdl::SceneObject cube_object(double x, double y, double surface_z)
Definition common.hpp:178
bool control(mj_kdl::Robot &out, const KDL::Frame &target, const KDL::Twist &target_twist)
KDL::JntArray constraint_tau
const mj_kdl::Robot & robot
AchdController(const mj_kdl::Robot &r, double gravity_z)
std::array< double, 6 > err_prev
KDL::ChainHdSolver_Vereshchagin achd
KDL::ChainIdSolver_RNE rnea
std::array< double, 6 > err_i
KDL::ChainFkSolverVel_recursive fk_vel
KDL::ChainFkSolverPos_recursive fk_pos
const KDL::Frame & measure()
double settle_rot_tol
double gripper_cmd
KDL::Frame target
double settle_pos_tol
const char * name
ResetHook on_reset
SceneState scene
std::vector< double > jnt_trq_cmd
std::vector< AttachmentSpec > attachments
KDL::Frame tip_T_tcp
std::vector< RobotSpec > robots
std::vector< SceneObject > objects
std::string error
std::string record_path
Definition common.hpp:37