Mujoco KDL Wrapper  0.5.1
MuJoCo + KDL bridge for robot kinematics and dynamics
Loading...
Searching...
No Matches
test_control_modes.cpp
Go to the documentation of this file.
1/* test_control_modes.cpp
2 * Control modes through actuator groups: the actuators build_scene adds, switching without a
3 * jump, torque limits, an arm tracking jnt_vel_cmd in VELOCITY, two robots in different modes, a
4 * gripper that stays in POSITION while its arm switches, and a motor-driven wheel in VELOCITY and
5 * TORQUE (the Eddie case) driven through the Env's scene slots. Arm tests self-skip without the
6 * bundled Gen3. */
7
8#include <gtest/gtest.h>
9
11#include "common.hpp"
12#include "example_paths.hpp"
13
14#include <kdl/chaindynparam.hpp>
15
16#include <cmath>
17#include <filesystem>
18#include <string>
19#include <vector>
20
21namespace fs = std::filesystem;
22
23// Robot r's actuator group for mode m is 1 + 3 * r + m (POSITION 0, TORQUE 1, VELOCITY 2).
24static constexpr int kPosGroup = 1;
25static constexpr int kTrqGroup = 2;
26
27static bool group_enabled(const mjModel *m, int group)
28{
29 return !(m->opt.disableactuator & (1 << group));
30}
31
32// The actuator in group driving the named joint, or -1.
33static int actuator_of(const mjModel *m, const std::string &joint, int group)
34{
35 const int jid = mj_name2id(m, mjOBJ_JOINT, joint.c_str());
36 for (int a = 0; a < m->nu; ++a) {
37 if (m->actuator_trntype[a] == mjTRN_JOINT && m->actuator_trnid[2 * a] == jid
38 && m->actuator_group[a] == group)
39 return a;
40 }
41 return -1;
42}
43
44struct ArmCase
45{
46 const char *name;
47 const char *mjcf;
48 const char *base;
49 const char *tip;
50 std::vector<double> home;
51 int limited_joint; // a joint whose forcerange is `limit`
52 double limit;
53};
54
55static const ArmCase kArms[] = {
56 { "Gen3",
57 "kinova_gen3/gen3.xml",
58 "base_link",
59 "bracelet_link",
60 { 0.0, 0.2618, 3.1416, -2.2689, 0.0, 0.9599, 1.5708 },
61 1,
62 105.0 },
63};
64
65class ArmModesTest : public testing::TestWithParam<ArmCase>
66{
67 protected:
68 std::string mjcf_;
71 mjModel *model_ = nullptr;
72 mjData *data_ = nullptr;
74 int n_ = 0;
75
76 void SetUp() override
77 {
78 const ArmCase &c = GetParam();
80 if (!fs::exists(mjcf_)) GTEST_SKIP() << c.mjcf << " not found";
81 spec_.timestep = 0.002;
82 spec_.add_floor = true;
83 spec_.add_skybox = false;
85 rs.path = mjcf_;
86 spec_.robots.push_back(rs);
87 ASSERT_TRUE(mj_kdl::init_env(&env_, &spec_));
88 model_ = env_.model;
89 data_ = env_.data;
90 ASSERT_TRUE(mj_kdl::init_robot_from_mjcf(&arm_, &env_, c.base, c.tip));
91 n_ = arm_.n_joints;
92 ASSERT_EQ(n_, static_cast<int>(c.home.size()));
94 for (int i = 0; i < n_; ++i) q(i) = c.home[i];
96 }
97
98 int servo(int i) const { return actuator_of(model_, arm_.joint_names[i], kPosGroup); }
99 int motor(int i) const { return actuator_of(model_, arm_.joint_names[i], kTrqGroup); }
100 int dof(int i) const
101 {
102 return model_->jnt_dofadr[mj_name2id(model_, mjOBJ_JOINT, arm_.joint_names[i].c_str())];
103 }
104
105 double max_error(const std::vector<double> &q_ref) const
106 {
107 double err = 0.0;
108 for (int i = 0; i < n_; ++i) err = std::max(err, std::abs(arm_.jnt_pos_msr[i] - q_ref[i]));
109 return err;
110 }
111};
112
113TEST_P(ArmModesTest, TorqueGroupIsAddedAndStartsDisabled)
114{
115 EXPECT_EQ(arm_.ctrl_mode, mj_kdl::CtrlMode::POSITION);
116 for (int i = 0; i < n_; ++i) {
117 ASSERT_GE(servo(i), 0) << "robot 0's POSITION group";
118 ASSERT_GE(motor(i), 0) << "robot 0's TORQUE group";
119 EXPECT_EQ(
120 std::string(mj_id2name(model_, mjOBJ_ACTUATOR, motor(i))), arm_.joint_names[i] + "_torque"
121 );
122 }
123 EXPECT_TRUE(group_enabled(model_, kPosGroup));
124 EXPECT_FALSE(group_enabled(model_, kTrqGroup));
125}
126
127TEST_P(ArmModesTest, PositionTracksAJointTrajectory)
128{
129 mj_kdl::update(&env_);
130 const std::vector<double> q0 = arm_.jnt_pos_msr;
131 std::vector<double> q_ref = q0;
132 double worst = 0.0;
133 for (int k = 0; k < 750; ++k) {
134 const double s = std::min(1.0, k / 500.0);
135 for (int i = 0; i < n_; ++i) q_ref[i] = q0[i] - 0.2 * s;
136 arm_.jnt_pos_cmd = q_ref;
137 mj_kdl::update(&env_);
138 mj_kdl::step(&env_);
139 if (k >= 500) worst = std::max(worst, max_error(q_ref));
140 }
141 EXPECT_LT(worst, 0.05);
142}
143
144TEST_P(ArmModesTest, SwitchesBetweenPositionAndTorqueWithoutAJump)
145{
146 KDL::ChainDynParam dyn(arm_.chain, KDL::Vector(0, 0, spec_.gravity_z));
147 KDL::JntArray q(n_), g(n_);
148
149 mj_kdl::update(&env_);
150 const std::vector<double> q_ref = arm_.jnt_pos_msr;
151 arm_.jnt_pos_cmd = q_ref;
152 for (int k = 0; k < 200; ++k) {
153 mj_kdl::update(&env_);
154 mj_kdl::step(&env_);
155 }
156
158 EXPECT_FALSE(group_enabled(model_, kPosGroup));
159 EXPECT_TRUE(group_enabled(model_, kTrqGroup));
160 double worst = 0.0;
161 for (int k = 0; k < 300; ++k) {
162 mj_kdl::update(&env_);
163 for (int i = 0; i < n_; ++i) q(i) = arm_.jnt_pos_msr[i];
164 dyn.JntToGravity(q, g);
165 for (int i = 0; i < n_; ++i) {
166 arm_.jnt_trq_cmd[i] =
167 g(i) + 50.0 * (q_ref[i] - arm_.jnt_pos_msr[i]) - 5.0 * arm_.jnt_vel_msr[i];
168 }
169 mj_kdl::update(&env_);
170 mj_kdl::step(&env_);
171 worst = std::max(worst, max_error(q_ref));
172 for (int i = 0; i < n_; ++i) EXPECT_EQ(data_->actuator_force[servo(i)], 0.0);
173 }
174 EXPECT_LT(worst, 0.01) << "torque hold after the switch";
175
177 worst = 0.0;
178 for (int k = 0; k < 200; ++k) {
179 mj_kdl::update(&env_);
180 mj_kdl::step(&env_);
181 worst = std::max(worst, max_error(q_ref));
182 }
183 EXPECT_LT(worst, 0.01) << "position hold after switching back";
184}
185
186TEST_P(ArmModesTest, GravityIsCompensatedOnlyOutsideTorque)
187{
188 KDL::ChainDynParam dyn(arm_.chain, KDL::Vector(0, 0, spec_.gravity_z));
189 KDL::JntArray q(n_), g(n_);
190 for (int i = 0; i < n_; ++i) q(i) = GetParam().home[i];
191 dyn.JntToGravity(q, g);
192
193 mj_kdl::update(&env_);
194 mj_forward(model_, data_);
195 for (int i = 0; i < n_; ++i)
196 EXPECT_NEAR(data_->qfrc_gravcomp[dof(i)], g(i), 1e-6) << "POSITION, joint " << i;
197
199 mj_kdl::update(&env_);
200 mj_forward(model_, data_);
201 for (int i = 0; i < n_; ++i)
202 EXPECT_EQ(data_->qfrc_gravcomp[dof(i)], 0.0) << "TORQUE, joint " << i;
203
204 arm_.ctrl_mode = mj_kdl::CtrlMode::POSITION;
205 mj_kdl::update(&env_);
206 mj_forward(model_, data_);
207 for (int i = 0; i < n_; ++i)
208 EXPECT_NEAR(data_->qfrc_gravcomp[dof(i)], g(i), 1e-6) << "back in POSITION, joint " << i;
209}
210
211TEST_P(ArmModesTest, TorqueIsLimitedByTheModelsForcerange)
212{
213 const int j = GetParam().limited_joint;
214 const double limit = GetParam().limit;
216 arm_.jnt_trq_cmd[j] = 1000.0;
217 mj_kdl::update(&env_);
218 mj_forward(model_, data_);
219 EXPECT_DOUBLE_EQ(data_->actuator_force[motor(j)], limit);
220 EXPECT_DOUBLE_EQ(data_->qfrc_actuator[dof(j)], limit);
221 EXPECT_DOUBLE_EQ(mj_kdl::joint_force_limits(&arm_)[j], limit);
222 for (int i = 0; i < n_; ++i) EXPECT_EQ(arm_.jnt_saturated[i], i == j) << "joint " << i;
223
224 arm_.jnt_trq_cmd[j] = 10.0;
225 mj_kdl::update(&env_);
226 EXPECT_EQ(arm_.jnt_saturated[j], 0);
227}
228
229TEST_P(ArmModesTest, QfrcAppliedIsLeftToTheUser)
230{
231 auto expect_untouched = [&](const char *when) {
232 for (int i = 0; i < n_; ++i)
233 EXPECT_EQ(data_->qfrc_applied[dof(i)], 5.0) << when << ", joint " << i;
234 };
235 for (int i = 0; i < n_; ++i) data_->qfrc_applied[dof(i)] = 5.0;
236
237 arm_.jnt_pos_cmd[1] += 0.1;
238 mj_kdl::update(&env_);
239 expect_untouched("POSITION");
240
242 expect_untouched("switch to TORQUE");
243 for (int i = 0; i < n_; ++i) arm_.jnt_trq_cmd[i] = 3.0;
244 mj_kdl::update(&env_);
245 expect_untouched("TORQUE");
246
247 arm_.ctrl_mode = mj_kdl::CtrlMode::POSITION;
248 mj_kdl::update(&env_);
249 expect_untouched("switch back through ctrl_mode");
250}
251
252TEST_P(ArmModesTest, VelocityModeTracksTheJointVelocityCommand)
253{
254 // No gravity, so the velocity actuators track with no steady-state error.
255 mj_kdl::SceneSpec spec = spec_;
256 spec.gravity_z = 0.0;
257 spec.robots[0].modes = { {},
258 { .mode = mj_kdl::CtrlMode::VELOCITY, .joints = {}, .kv = 100.0 } };
259 mj_kdl::Env env;
260 mj_kdl::Robot arm;
261 ASSERT_TRUE(mj_kdl::init_env(&env, &spec));
262 ASSERT_TRUE(mj_kdl::init_robot_from_mjcf(&arm, &env, GetParam().base, GetParam().tip));
263 KDL::JntArray q(n_);
264 for (int i = 0; i < n_; ++i) q(i) = GetParam().home[i];
265 mj_kdl::set_joint_pos(&arm, q);
267 EXPECT_FALSE(group_enabled(env.model, kPosGroup));
268 EXPECT_TRUE(group_enabled(env.model, 3)) << "robot 0's VELOCITY group";
269
270 for (int i = 0; i < n_; ++i) arm.jnt_vel_cmd[i] = i % 2 ? 0.2 : -0.2;
271 for (int k = 0; k < 250; ++k) {
272 mj_kdl::update(&env);
273 mj_kdl::step(&env);
274 }
275 mj_kdl::update(&env);
276 for (int i = 0; i < n_; ++i) {
277 const int a = actuator_of(env.model, arm.joint_names[i], 3);
278 ASSERT_GE(a, 0) << arm.joint_names[i] << "_velocity";
279 EXPECT_DOUBLE_EQ(env.data->ctrl[a], arm.jnt_vel_cmd[i]);
280 EXPECT_NEAR(arm.jnt_vel_msr[i], arm.jnt_vel_cmd[i], 2e-3) << "joint " << i; // max 6e-4
281 }
282}
283
284TEST_P(ArmModesTest, TwoArmsRunDifferentModes)
285{
286 const ArmCase &c = GetParam();
287 mj_kdl::RobotSpec second = spec_.robots.front();
288 second.prefix = "r2_";
289 second.pos[0] = 1.0;
290 spec_.robots.push_back(second);
291 mj_kdl::Env two;
292 ASSERT_TRUE(mj_kdl::init_env(&two, &spec_));
293
294 const std::string base = std::string("r2_") + c.base;
295 const std::string tip = std::string("r2_") + c.tip;
296 mj_kdl::Robot a, b;
297 ASSERT_TRUE(mj_kdl::init_robot_from_mjcf(&a, &two, c.base, c.tip));
298 ASSERT_TRUE(mj_kdl::init_robot_from_mjcf(&b, &two, base.c_str(), tip.c_str()));
299 EXPECT_GE(actuator_of(two.model, a.joint_names[0], kPosGroup), 0) << "a is robot 0";
300 EXPECT_GE(actuator_of(two.model, b.joint_names[0], 4), 0) << "b is robot 1";
301
303 EXPECT_FALSE(group_enabled(two.model, 1));
304 EXPECT_TRUE(group_enabled(two.model, 2));
305 EXPECT_TRUE(group_enabled(two.model, 4)) << "the second arm stays in POSITION";
306 EXPECT_FALSE(group_enabled(two.model, 5));
307}
308
309INSTANTIATE_TEST_SUITE_P(Arms, ArmModesTest, testing::ValuesIn(kArms), [](const auto &info) {
310 return std::string(info.param.name);
311});
312
313TEST(GripperModesTest, GripperStaysInPositionWhileTheArmSwitches)
314{
315 const std::string arm_mjcf = mj_kdl_examples::find_asset("kinova_gen3/gen3.xml");
316 const std::string grp_mjcf = mj_kdl_examples::find_asset("robotiq_2f85/2f85.xml");
317 if (!fs::exists(arm_mjcf)) GTEST_SKIP() << "kinova_gen3/gen3.xml not found";
318 if (!fs::exists(grp_mjcf)) GTEST_SKIP() << "robotiq_2f85/2f85.xml not found";
319
321 rs.path = arm_mjcf;
322 rs.attachments.push_back(mj_kdl_examples::gripper_attachment(grp_mjcf));
324 spec.timestep = 0.002;
325 spec.add_floor = true;
326 spec.add_skybox = false;
327 spec.robots.push_back(rs);
328
329 mj_kdl::Env env;
330 ASSERT_TRUE(mj_kdl::init_env(&env, &spec));
331 mjModel *model = env.model;
332 mjData *data = env.data;
333 mj_kdl::Robot arm;
334 ASSERT_TRUE(mj_kdl::init_robot_from_mjcf(&arm, &env, "base_link", "bracelet_link"));
335 KDL::JntArray q(7), g(7);
336 for (int i = 0; i < 7; ++i) q(i) = kArms[0].home[i];
337 mj_kdl::set_joint_pos(&arm, q);
338
339 const int fingers = mj_name2id(model, mjOBJ_ACTUATOR, "g_fingers_actuator");
340 const int driver = mj_name2id(model, mjOBJ_JOINT, "g_left_driver_joint");
341 ASSERT_GE(fingers, 0);
342 ASSERT_GE(driver, 0);
343 EXPECT_EQ(model->actuator_group[fingers], 0);
344 EXPECT_LT(mj_name2id(model, mjOBJ_ACTUATOR, "g_left_driver_joint_torque"), 0)
345 << "the gripper gets no torque actuator";
346
348 EXPECT_TRUE(group_enabled(model, 0));
349
350 KDL::ChainDynParam dyn(arm.chain, KDL::Vector(0, 0, spec.gravity_z));
351 const std::vector<double> q_ref = arm.jnt_pos_msr;
352 auto run = [&](double finger_cmd) {
353 data->ctrl[fingers] = finger_cmd;
354 for (int k = 0; k < 500; ++k) {
355 mj_kdl::update(&env);
356 for (int i = 0; i < 7; ++i) q(i) = arm.jnt_pos_msr[i];
357 dyn.JntToGravity(q, g);
358 for (int i = 0; i < 7; ++i)
359 arm.jnt_trq_cmd[i] =
360 g(i) + 50.0 * (q_ref[i] - arm.jnt_pos_msr[i]) - 5.0 * arm.jnt_vel_msr[i];
361 mj_kdl::update(&env);
362 mj_kdl::step(&env);
363 }
364 return data->qpos[model->jnt_qposadr[driver]];
365 };
366 EXPECT_GT(run(0.82), 0.7) << "closes";
367 EXPECT_LT(run(0.0), 0.05) << "opens";
368}
369
370class MotorWheelModesTest : public testing::Test
371{
372 protected:
373 const std::string fixture_ = std::string(MJ_KDL_TEST_FIXTURES) + "/motor_wheel.xml";
376 mjModel *model_ = nullptr;
377 mjData *data_ = nullptr;
378
379 void SetUp() override
380 {
381 spec_.timestep = 0.002;
382 spec_.add_floor = false;
383 spec_.add_skybox = false;
385 rs.path = fixture_;
386 rs.modes = { { .mode = mj_kdl::CtrlMode::VELOCITY, .joints = { "wheel" }, .kv = 2.0 } };
387 spec_.robots.push_back(rs);
388 ASSERT_TRUE(mj_kdl::init_env(&env_, &spec_));
389 model_ = env_.model;
390 data_ = env_.data;
391 }
392
393 int actuator(const char *name) const { return mj_name2id(model_, mjOBJ_ACTUATOR, name); }
394};
395
396TEST_F(MotorWheelModesTest, OnlyTheListedJointTakesModes)
397{
398 ASSERT_GE(actuator("wheel_velocity"), 0);
399 EXPECT_EQ(model_->actuator_group[actuator("wheel")], 2) << "a motor is natively TORQUE";
400 EXPECT_EQ(model_->actuator_group[actuator("wheel_velocity")], 3);
401 EXPECT_EQ(model_->actuator_group[actuator("pivot")], 0) << "the pivot is left alone";
402 EXPECT_FALSE(group_enabled(model_, 3));
404}
405
406TEST_F(MotorWheelModesTest, VelocityTracksThenTorqueTakesOver)
407{
408 mj_kdl::SceneActuatorSlot *vel = mj_kdl::bind_scene_actuator(&env_.scene, "wheel_velocity");
409 mj_kdl::SceneJointSlot *wheel = mj_kdl::bind_scene_joint(&env_.scene, "wheel");
410 mj_kdl::SceneJointSlot *pivot = mj_kdl::bind_scene_joint(&env_.scene, "pivot");
411 ASSERT_NE(vel, nullptr);
412 ASSERT_NE(wheel, nullptr);
413 ASSERT_NE(pivot, nullptr);
414
416 vel->command = 5.0;
417 for (int k = 0; k < 1000; ++k) {
418 mj_kdl::update(&env_);
419 mj_kdl::step(&env_);
420 }
421 mj_kdl::update(&env_);
422 EXPECT_NEAR(wheel->velocity, 5.0, 0.05);
423 EXPECT_NEAR(pivot->position, 0.0, 1e-6);
424
426 const double before = wheel->velocity;
427 mj_kdl::step(&env_);
428 mj_kdl::update(&env_);
429 EXPECT_EQ(data_->actuator_force[actuator("wheel_velocity")], 0.0);
430 // One step of coasting on joint damping alone (~21 rad/s^2 here), not a jump.
431 EXPECT_NEAR(wheel->velocity, before, 0.1) << "no jump at the switch";
432}
433
434TEST_F(MotorWheelModesTest, SceneActuatorFlagsAClampedCommand)
435{
436 mj_kdl::SceneActuatorSlot *motor = mj_kdl::bind_scene_actuator(&env_.scene, "wheel");
437 ASSERT_NE(motor, nullptr);
438
439 motor->command = 20.0;
440 mj_kdl::update(&env_);
441 EXPECT_EQ(data_->ctrl[motor->ctrl_id], 12.0);
442 EXPECT_TRUE(motor->saturated);
443
444 motor->command = 5.0;
445 mj_kdl::update(&env_);
446 EXPECT_FALSE(motor->saturated);
447}
448
449TEST_F(MotorWheelModesTest, ForceLimitsFollowTheActiveMode)
450{
451 mj_kdl::Robot wheel;
452 ASSERT_TRUE(mj_kdl::init_robot_from_mjcf(&wheel, &env_, "drive", "wheel"));
453 ASSERT_EQ(wheel.n_joints, 1);
454 ASSERT_EQ(wheel.ctrl_mode, mj_kdl::CtrlMode::TORQUE);
455 EXPECT_DOUBLE_EQ(mj_kdl::joint_force_limits(&wheel)[0], 12.0) << "the motor's ctrlrange";
456
458 EXPECT_DOUBLE_EQ(mj_kdl::joint_force_limits(&wheel)[0], 12.0) << "the velocity forcerange";
459
460 model_->actuator_forcerange[2 * actuator("wheel_velocity") + 1] = 20.0;
461 EXPECT_DOUBLE_EQ(mj_kdl::joint_force_limits(&wheel)[0], 20.0);
462}
463
464// Two Gen3 arms attached to one root that actuates nothing: owners 1 and 2, after robot 0.
465class AttachedArmModesTest : public testing::Test
466{
467 protected:
468 static constexpr int kLeftPos = 4, kLeftTrq = 5, kRightPos = 7, kRightTrq = 8;
469
473
474 static mj_kdl::AttachmentSpec arm(const std::string &mjcf, const char *site, const char *pfx)
475 {
477 a.mjcf_path = mjcf;
479 a.prefix = pfx;
480 a.modes = { mj_kdl::CtrlModeSpec{} };
481 return a;
482 }
483
484 void SetUp() override
485 {
486 const std::string mjcf = mj_kdl_examples::find_asset("kinova_gen3/gen3.xml");
487 if (!fs::exists(mjcf)) GTEST_SKIP() << "kinova_gen3/gen3.xml not found";
488 spec_.timestep = 0.002;
489 spec_.add_floor = false;
490 spec_.add_skybox = false;
492 rs.path = std::string(MJ_KDL_TEST_FIXTURES) + "/arm_mount.xml";
493 rs.attachments.push_back(arm(mjcf, "left_mount", "l_"));
494 rs.attachments.push_back(arm(mjcf, "right_mount", "r_"));
495 spec_.robots.push_back(rs);
496 ASSERT_TRUE(mj_kdl::init_env(&env_, &spec_));
497 ASSERT_TRUE(mj_kdl::init_robot_from_mjcf(&left_, &env_, "base_link", "bracelet_link", "l_")
498 );
499 ASSERT_TRUE(mj_kdl::init_robot_from_mjcf(&right_, &env_, "base_link", "bracelet_link", "r_")
500 );
501 KDL::JntArray q(7);
502 for (int i = 0; i < 7; ++i) q(i) = kArms[0].home[i];
505 }
506};
507
508TEST_F(AttachedArmModesTest, EachArmGetsGroupsOfItsOwn)
509{
510 for (int i = 0; i < 7; ++i) {
511 EXPECT_GE(actuator_of(env_.model, left_.joint_names[i], kLeftPos), 0) << "left " << i;
512 EXPECT_GE(actuator_of(env_.model, left_.joint_names[i], kLeftTrq), 0) << "left " << i;
513 EXPECT_GE(actuator_of(env_.model, right_.joint_names[i], kRightPos), 0) << "right " << i;
514 EXPECT_GE(actuator_of(env_.model, right_.joint_names[i], kRightTrq), 0) << "right " << i;
515 }
516 EXPECT_GE(mj_name2id(env_.model, mjOBJ_ACTUATOR, "l_joint_1_torque"), 0);
517 EXPECT_TRUE(group_enabled(env_.model, kLeftPos));
518 EXPECT_FALSE(group_enabled(env_.model, kLeftTrq));
519 EXPECT_EQ(left_.ctrl_mode, mj_kdl::CtrlMode::POSITION);
520}
521
522TEST_F(AttachedArmModesTest, OneArmSwitchesAndHoldsWhileTheOtherStaysInPosition)
523{
525 EXPECT_FALSE(group_enabled(env_.model, kLeftPos));
526 EXPECT_TRUE(group_enabled(env_.model, kLeftTrq));
527 EXPECT_TRUE(group_enabled(env_.model, kRightPos)) << "the right arm stays in POSITION";
528 EXPECT_FALSE(group_enabled(env_.model, kRightTrq));
529
530 KDL::ChainDynParam dyn(left_.chain, KDL::Vector(0, 0, spec_.gravity_z));
531 KDL::JntArray q(7), g(7);
532 mj_kdl::update(&env_);
533 const std::vector<double> left_ref = left_.jnt_pos_msr;
534 const std::vector<double> right_ref = right_.jnt_pos_msr;
535 right_.jnt_pos_cmd = right_ref;
536 double left_err = 0.0, right_err = 0.0;
537 for (int k = 0; k < 300; ++k) {
538 mj_kdl::update(&env_);
539 for (int i = 0; i < 7; ++i) q(i) = left_.jnt_pos_msr[i];
540 dyn.JntToGravity(q, g);
541 for (int i = 0; i < 7; ++i) {
542 left_.jnt_trq_cmd[i] =
543 g(i) + 50.0 * (left_ref[i] - left_.jnt_pos_msr[i]) - 5.0 * left_.jnt_vel_msr[i];
544 }
545 mj_kdl::update(&env_);
546 mj_kdl::step(&env_);
547 for (int i = 0; i < 7; ++i) {
548 left_err = std::max(left_err, std::abs(left_.jnt_pos_msr[i] - left_ref[i]));
549 right_err = std::max(right_err, std::abs(right_.jnt_pos_msr[i] - right_ref[i]));
550 }
551 }
552 EXPECT_LT(left_err, 0.01) << "torque hold on the attached arm";
553 EXPECT_LT(right_err, 0.01) << "position hold on the other";
554}
555
556TEST_F(AttachedArmModesTest, AnAttachmentWithoutModesGetsNone)
557{
558 mj_kdl::SceneSpec spec = spec_;
559 spec.robots[0].attachments[1].modes = {};
560 mj_kdl::Env env;
561 ASSERT_TRUE(mj_kdl::init_env(&env, &spec));
562 EXPECT_LT(mj_name2id(env.model, mjOBJ_ACTUATOR, "r_joint_1_torque"), 0);
563 const int servo = mj_name2id(env.model, mjOBJ_ACTUATOR, "r_joint_1");
564 ASSERT_GE(servo, 0);
565 EXPECT_EQ(env.model->actuator_group[servo], 0) << "its own actuators stay ungrouped";
566}
567
568int main(int argc, char *argv[])
569{
570 testing::InitGoogleTest(&argc, argv);
571 return RUN_ALL_TESTS();
572}
mj_kdl::SceneSpec spec_
int motor(int i) const
void SetUp() override
int servo(int i) const
double max_error(const std::vector< double > &q_ref) const
int dof(int i) const
mj_kdl::Robot arm_
static constexpr int kRightTrq
static constexpr int kLeftTrq
static mj_kdl::AttachmentSpec arm(const std::string &mjcf, const char *site, const char *pfx)
static constexpr int kLeftPos
static constexpr int kRightPos
virtual int JntToGravity(const JntArray &q, JntArray &gravity)
int actuator(const char *name) const
const std::string fixture_
Status init_env(Env *env, const SceneSpec *spec)
void update(Env *env)
bool step(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)
std::vector< double > joint_force_limits(const Robot *r, double fallback=1e6)
void set_joint_pos(Robot *r, const KDL::JntArray &q)
SceneActuatorSlot * bind_scene_actuator(SceneState *s, const char *name)
SceneJointSlot * bind_scene_joint(SceneState *s, const char *joint_name)
std::string find_asset(const fs::path &relative)
mj_kdl::AttachmentSpec gripper_attachment(const std::string &path, const std::string &site="pinch_site")
Definition common.hpp:149
const char * mjcf
const char * tip
std::vector< double > home
const char * base
const char * name
std::vector< CtrlModeSpec > modes
std::vector< double > jnt_vel_cmd
std::vector< double > jnt_vel_msr
std::vector< double > jnt_pos_msr
std::vector< double > jnt_trq_cmd
std::vector< AttachmentSpec > attachments
std::vector< CtrlModeSpec > modes
std::vector< std::string > joint_names
std::vector< RobotSpec > robots
int main(int argc, char *argv[])
INSTANTIATE_TEST_SUITE_P(Arms, ArmModesTest, testing::ValuesIn(kArms), [](const auto &info) { return std::string(info.param.name);})
TEST_P(ArmModesTest, TorqueGroupIsAddedAndStartsDisabled)
TEST_F(MotorWheelModesTest, OnlyTheListedJointTakesModes)
TEST(GripperModesTest, GripperStaysInPositionWhileTheArmSwitches)