8#include <gtest/gtest.h>
14#include <kdl/chaindynparam.hpp>
21namespace fs = std::filesystem;
24static constexpr int kPosGroup = 1;
25static constexpr int kTrqGroup = 2;
27static bool group_enabled(
const mjModel *m,
int group)
29 return !(m->opt.disableactuator & (1 << group));
33static int actuator_of(
const mjModel *m,
const std::string &joint,
int group)
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)
57 "kinova_gen3/gen3.xml",
60 { 0.0, 0.2618, 3.1416, -2.2689, 0.0, 0.9599, 1.5708 },
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;
86 spec_.robots.push_back(rs);
92 ASSERT_EQ(
n_,
static_cast<int>(c.
home.size()));
94 for (
int i = 0; i <
n_; ++i) q(i) = c.
home[i];
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); }
102 return model_->jnt_dofadr[mj_name2id(
model_, mjOBJ_JOINT,
arm_.joint_names[i].c_str())];
105 double max_error(
const std::vector<double> &q_ref)
const
108 for (
int i = 0; i <
n_; ++i) err = std::max(err, std::abs(
arm_.jnt_pos_msr[i] - q_ref[i]));
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";
120 std::string(mj_id2name(model_, mjOBJ_ACTUATOR, motor(i))), arm_.joint_names[i] +
"_torque"
123 EXPECT_TRUE(group_enabled(model_, kPosGroup));
124 EXPECT_FALSE(group_enabled(model_, kTrqGroup));
130 const std::vector<double> q0 = arm_.jnt_pos_msr;
131 std::vector<double> q_ref = q0;
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;
139 if (k >= 500) worst = std::max(worst, max_error(q_ref));
141 EXPECT_LT(worst, 0.05);
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) {
158 EXPECT_FALSE(group_enabled(model_, kPosGroup));
159 EXPECT_TRUE(group_enabled(model_, kTrqGroup));
161 for (
int k = 0; k < 300; ++k) {
163 for (
int i = 0; i < n_; ++i) q(i) = arm_.jnt_pos_msr[i];
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];
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);
174 EXPECT_LT(worst, 0.01) <<
"torque hold after the switch";
178 for (
int k = 0; k < 200; ++k) {
181 worst = std::max(worst, max_error(q_ref));
183 EXPECT_LT(worst, 0.01) <<
"position hold after switching back";
190 for (
int i = 0; i < n_; ++i) q(i) = GetParam().home[i];
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;
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;
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;
213 const int j = GetParam().limited_joint;
214 const double limit = GetParam().limit;
216 arm_.jnt_trq_cmd[j] = 1000.0;
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);
222 for (
int i = 0; i < n_; ++i) EXPECT_EQ(arm_.jnt_saturated[i], i == j) <<
"joint " << i;
224 arm_.jnt_trq_cmd[j] = 10.0;
226 EXPECT_EQ(arm_.jnt_saturated[j], 0);
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;
235 for (
int i = 0; i < n_; ++i) data_->qfrc_applied[dof(i)] = 5.0;
237 arm_.jnt_pos_cmd[1] += 0.1;
239 expect_untouched(
"POSITION");
242 expect_untouched(
"switch to TORQUE");
243 for (
int i = 0; i < n_; ++i) arm_.jnt_trq_cmd[i] = 3.0;
245 expect_untouched(
"TORQUE");
249 expect_untouched(
"switch back through ctrl_mode");
257 spec.
robots[0].modes = { {},
264 for (
int i = 0; i < n_; ++i) q(i) = GetParam().home[i];
267 EXPECT_FALSE(group_enabled(env.
model, kPosGroup));
268 EXPECT_TRUE(group_enabled(env.
model, 3)) <<
"robot 0's VELOCITY group";
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) {
276 for (
int i = 0; i < n_; ++i) {
278 ASSERT_GE(a, 0) << arm.
joint_names[i] <<
"_velocity";
290 spec_.robots.push_back(second);
294 const std::string base = std::string(
"r2_") + c.
base;
295 const std::string tip = std::string(
"r2_") + c.
tip;
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";
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));
310 return std::string(info.param.name);
313TEST(GripperModesTest, GripperStaysInPositionWhileTheArmSwitches)
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";
327 spec.
robots.push_back(rs);
331 mjModel *model = env.
model;
332 mjData *data = env.
data;
336 for (
int i = 0; i < 7; ++i) q(i) = kArms[0].home[i];
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";
348 EXPECT_TRUE(group_enabled(model, 0));
352 auto run = [&](
double finger_cmd) {
353 data->ctrl[fingers] = finger_cmd;
354 for (
int k = 0; k < 500; ++k) {
356 for (
int i = 0; i < 7; ++i) q(i) = arm.
jnt_pos_msr[i];
358 for (
int i = 0; i < 7; ++i)
364 return data->qpos[model->jnt_qposadr[driver]];
366 EXPECT_GT(run(0.82), 0.7) <<
"closes";
367 EXPECT_LT(run(0.0), 0.05) <<
"opens";
373 const std::string
fixture_ = std::string(MJ_KDL_TEST_FIXTURES) +
"/motor_wheel.xml";
381 spec_.timestep = 0.002;
382 spec_.add_floor =
false;
383 spec_.add_skybox =
false;
387 spec_.robots.push_back(rs);
393 int actuator(
const char *name)
const {
return mj_name2id(
model_, mjOBJ_ACTUATOR, name); }
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));
411 ASSERT_NE(vel,
nullptr);
412 ASSERT_NE(wheel,
nullptr);
413 ASSERT_NE(pivot,
nullptr);
417 for (
int k = 0; k < 1000; ++k) {
422 EXPECT_NEAR(wheel->
velocity, 5.0, 0.05);
423 EXPECT_NEAR(pivot->
position, 0.0, 1e-6);
426 const double before = wheel->
velocity;
429 EXPECT_EQ(data_->actuator_force[actuator(
"wheel_velocity")], 0.0);
431 EXPECT_NEAR(wheel->
velocity, before, 0.1) <<
"no jump at the switch";
437 ASSERT_NE(motor,
nullptr);
441 EXPECT_EQ(data_->ctrl[motor->
ctrl_id], 12.0);
460 model_->actuator_forcerange[2 * actuator(
"wheel_velocity") + 1] = 20.0;
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";
495 spec_.robots.push_back(rs);
502 for (
int i = 0; i < 7; ++i) q(i) = kArms[0].home[i];
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;
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));
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));
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) {
539 for (
int i = 0; i < 7; ++i) q(i) = left_.jnt_pos_msr[i];
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];
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]));
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";
559 spec.
robots[0].attachments[1].modes = {};
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");
565 EXPECT_EQ(env.
model->actuator_group[servo], 0) <<
"its own actuators stay ungrouped";
568int main(
int argc,
char *argv[])
570 testing::InitGoogleTest(&argc, argv);
571 return RUN_ALL_TESTS();
double max_error(const std::vector< double > &q_ref) const
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)
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")
std::vector< double > home
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)