445 lines
14 KiB
C++
445 lines
14 KiB
C++
//
|
|
// Created by lgv on 2025/8/4.
|
|
//
|
|
|
|
|
|
// #include "humanoid_robot.h"
|
|
|
|
#include <csignal>
|
|
#include <atomic>
|
|
#include <thread>
|
|
#include <chrono>
|
|
#include <iostream>
|
|
#include <unordered_map>
|
|
#include <array>
|
|
#include <algorithm>
|
|
#include <limits>
|
|
#include <cmath>
|
|
|
|
#include "gtest/gtest.h"
|
|
#include <glog/logging.h>
|
|
#include "../../../../device_manager/include/device_manager.h"
|
|
#include <libgen.h>
|
|
|
|
#include "controller/include/ibvs_controller.h"
|
|
#include "cmvr/msgs/can_card_parameter.grpc.pb.h"
|
|
#include "../include/humanoid_robot.h"
|
|
#include "motor/ti5_motor/canopen/ti5_motor_canopen_protocol.h"
|
|
#include "../../../../utils/base/include/abstract_interpolation.h"
|
|
#include "data_center/include/motors_info.h"
|
|
|
|
using namespace cmvr::device;
|
|
|
|
namespace {
|
|
|
|
constexpr std::array<const char*, 7> kRightArmJointNames = {
|
|
"R_SHOULDER_P",
|
|
"R_SHOULDER_R",
|
|
"R_SHOULDER_Y",
|
|
"R_ELBOW_R",
|
|
"R_WRIST_P",
|
|
"R_WRIST_Y",
|
|
"R_WRIST_R"
|
|
};
|
|
|
|
std::vector<JointPoint> buildRightArmJointCmd(const std::vector<double>& q_cmd,
|
|
double vel) {
|
|
std::vector<JointPoint> cmd;
|
|
cmd.reserve(kRightArmJointNames.size());
|
|
for (size_t i = 0; i < kRightArmJointNames.size(); ++i) {
|
|
cmd.emplace_back(kRightArmJointNames[i], q_cmd[i], vel);
|
|
}
|
|
return cmd;
|
|
}
|
|
|
|
std::vector<JointPoint> buildRightArmJointCmdWithVel(const std::vector<double>& q_cmd,
|
|
const std::vector<double>& qd_abs,
|
|
double vel_fallback = 0.1) {
|
|
std::vector<JointPoint> cmd;
|
|
cmd.reserve(kRightArmJointNames.size());
|
|
for (size_t i = 0; i < kRightArmJointNames.size(); ++i) {
|
|
double vel = vel_fallback;
|
|
if (i < qd_abs.size() && std::isfinite(qd_abs[i]) && qd_abs[i] > 0.0) {
|
|
vel = qd_abs[i];
|
|
}
|
|
cmd.emplace_back(kRightArmJointNames[i], q_cmd[i], vel);
|
|
}
|
|
return cmd;
|
|
}
|
|
|
|
std::unordered_map<std::string, double> makeRightArmQMap() {
|
|
std::unordered_map<std::string, double> q_map;
|
|
q_map.reserve(kRightArmJointNames.size());
|
|
for (const auto* name : kRightArmJointNames) {
|
|
q_map.emplace(name, 0.0);
|
|
}
|
|
return q_map;
|
|
}
|
|
|
|
constexpr std::array<double, 7> kRightArmQMin = {
|
|
-3.14, // R_SHOULDER_P
|
|
-0.78, // R_SHOULDER_R
|
|
-3.14, // R_SHOULDER_Y
|
|
0.00, // R_ELBOW_R
|
|
-3.14, // R_WRIST_P
|
|
-0.78, // R_WRIST_Y
|
|
-0.55 // R_WRIST_R
|
|
};
|
|
|
|
constexpr std::array<double, 7> kRightArmQMax = {
|
|
3.14, // R_SHOULDER_P
|
|
1.57, // R_SHOULDER_R
|
|
3.14, // R_SHOULDER_Y
|
|
2.05, // R_ELBOW_R
|
|
3.14, // R_WRIST_P
|
|
0.78, // R_WRIST_Y
|
|
1.57 // R_WRIST_R
|
|
};
|
|
|
|
double applySoftJointLimitVelocity(double q,
|
|
double v,
|
|
double q_min,
|
|
double q_max,
|
|
double soft_margin,
|
|
double hard_margin) {
|
|
if (q_max <= q_min) return 0.0;
|
|
const double hard = std::max(1e-4, hard_margin);
|
|
const double soft = std::max(hard + 1e-4, soft_margin);
|
|
|
|
if (v < 0.0) {
|
|
const double q_hard = q_min + hard;
|
|
const double q_soft = q_min + soft;
|
|
if (q <= q_hard) return 0.0;
|
|
if (q < q_soft) {
|
|
const double s = std::clamp((q - q_hard) / (q_soft - q_hard), 0.0, 1.0);
|
|
return v * s;
|
|
}
|
|
} else if (v > 0.0) {
|
|
const double q_hard = q_max - hard;
|
|
const double q_soft = q_max - soft;
|
|
if (q >= q_hard) return 0.0;
|
|
if (q > q_soft) {
|
|
const double s = std::clamp((q_hard - q) / (q_hard - q_soft), 0.0, 1.0);
|
|
return v * s;
|
|
}
|
|
}
|
|
return v;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
TEST(HumanoidRobotTest,GetState) {
|
|
std::string config_path = "/home/lgv/cmvr/cmvr-es/config/cabin_robot.xml";
|
|
const XmlNode config(config_path);
|
|
|
|
if (!config.hasChild("DeviceManager")){
|
|
LOG(ERROR) << "Device Manager node not found";
|
|
}
|
|
auto dmgr_cfg = config.getChild("DeviceManager");
|
|
auto &dmgr = DeviceManager::getInstance(dmgr_cfg);
|
|
|
|
auto robot = dmgr.getDevice<AbstractRobot>("hc01");
|
|
robot->torqueOff();
|
|
std::vector<cmvr::device::JointState> joints;
|
|
|
|
while (true) {
|
|
joints.clear();
|
|
robot->getJointsState(joints);
|
|
for (const auto &js : joints) {
|
|
LOG(INFO) << "Joint: " << js.name
|
|
<< ", Position: " << js.position
|
|
<< ", Velocity: " << js.velocity
|
|
<< std::endl;
|
|
}
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
|
|
}
|
|
}
|
|
|
|
|
|
TEST(HumanoidRobotTest,MyRobotTest) {
|
|
|
|
std::string config_path = "/home/lgv/cmvr/cmvr-es/config/cabin_robot.xml";
|
|
const XmlNode config(config_path);
|
|
|
|
if (!config.hasChild("DeviceManager")){
|
|
LOG(ERROR) << "Device Manager node not found";
|
|
}
|
|
auto dmgr_cfg = config.getChild("DeviceManager");
|
|
auto &dmgr = DeviceManager::getInstance(dmgr_cfg);
|
|
|
|
auto robot = dmgr.getDevice<AbstractRobot>("hc01");
|
|
robot->calibrateZeroQ("R_WRIST_R");
|
|
|
|
// std::vector<JointPoint> cmd{};
|
|
|
|
}
|
|
|
|
TEST(HumanoidRobotTest,ServoJAndGetJointQSmokeTest) {
|
|
const XmlNode config("/home/lgv/cmvr/cmvr-es/cmvr-es/common/config/cabin_robot.xml");
|
|
ASSERT_TRUE(config.hasChild("DeviceManager")) << "DeviceManager node not found";
|
|
auto dmgr_cfg = config.getChild("DeviceManager");
|
|
auto& dmgr = DeviceManager::getInstance(dmgr_cfg);
|
|
|
|
auto robot = dmgr.getDevice<AbstractRobot>("hc01");
|
|
|
|
auto q_map_now = makeRightArmQMap();
|
|
std::vector<double> q_now(kRightArmJointNames.size(), 0.0);
|
|
auto refreshRightArmQ = [&]() -> bool {
|
|
robot->getJointQ(q_map_now);
|
|
for (size_t i = 0; i < kRightArmJointNames.size(); ++i) {
|
|
const auto it = q_map_now.find(kRightArmJointNames[i]);
|
|
if (it == q_map_now.end()) return false;
|
|
q_now[i] = it->second;
|
|
}
|
|
return true;
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ASSERT_TRUE(refreshRightArmQ()) << "Failed to read right-arm joint q";
|
|
|
|
|
|
std::vector<JointPoint> cmd{};
|
|
cmd = {
|
|
// {"R_SHOULDER_P", 0.0},
|
|
// {"R_SHOULDER_R", 0.0},
|
|
// {"R_SHOULDER_Y", 0.0},
|
|
// {"R_ELBOW_R", 0.0},
|
|
// {"R_WRIST_P", 0.0},
|
|
// {"R_WRIST_Y", 0.0},
|
|
{"R_WRIST_R", 0.2},
|
|
};
|
|
// auto joint_cmd = buildRightArmJointCmd(q_now, 0.8);
|
|
ASSERT_NO_THROW(robot->servoJ(cmd, 0.8, 0.01));
|
|
while (true) {
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(10000));
|
|
}
|
|
|
|
}
|
|
|
|
|
|
TEST(HumanoidRobotTest,speedJTest) {
|
|
const XmlNode config("/home/lgv/cmvr/cmvr-es/cmvr-es/common/config/cabin_robot.xml");
|
|
ASSERT_TRUE(config.hasChild("DeviceManager")) << "DeviceManager node not found";
|
|
auto dmgr_cfg = config.getChild("DeviceManager");
|
|
auto& dmgr = DeviceManager::getInstance(dmgr_cfg);
|
|
|
|
auto robot = dmgr.getDevice<AbstractRobot>("hc01");
|
|
std::vector<JointVelocityCommand> cmd{};
|
|
cmd = {
|
|
// {"R_SHOULDER_P", 0.0},
|
|
// {"R_SHOULDER_R", 0.0},
|
|
// {"R_SHOULDER_Y", 0.0},
|
|
// {"R_ELBOW_R", 0.0},
|
|
// {"R_WRIST_P", 0.0},
|
|
// {"R_WRIST_Y", 0.0},
|
|
{"R_WRIST_R", 0.2},
|
|
};
|
|
robot->speedJ(cmd);
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
|
|
// robot->speedJ(0);
|
|
//
|
|
std::vector<JointPoint> cmd1{};
|
|
cmd1 = {
|
|
// {"R_SHOULDER_P", 0.0},
|
|
// {"R_SHOULDER_R", 0.0},
|
|
// {"R_SHOULDER_Y", 0.0},
|
|
// {"R_ELBOW_R", 0.0},
|
|
// {"R_WRIST_P", 0.0},
|
|
// {"R_WRIST_Y", 0.0},
|
|
{"R_WRIST_R", 0},
|
|
};
|
|
robot->moveJ(cmd1,2.0,10.0);
|
|
// robot->eStop();
|
|
|
|
//
|
|
}
|
|
TEST(HumanoidRobotTest,IBVSWithRealRobot) {
|
|
const XmlNode config("/home/lgv/cmvr/cmvr-es/cmvr-es/common/config/cabin_robot.xml");
|
|
ASSERT_TRUE(config.hasChild("DeviceManager")) << "DeviceManager node not found";
|
|
auto dmgr_cfg = config.getChild("DeviceManager");
|
|
auto& dmgr = DeviceManager::getInstance(dmgr_cfg);
|
|
|
|
auto robot = dmgr.getDevice<AbstractRobot>("hc01");
|
|
auto camera = dmgr.getDevice<AbstractCamera>("cam2");
|
|
ASSERT_NE(robot, nullptr);
|
|
ASSERT_NE(camera, nullptr);
|
|
|
|
std::vector<JointPoint> init_cmd{};
|
|
init_cmd = {
|
|
{"R_SHOULDER_P", -0.2423},
|
|
{"R_SHOULDER_R", 1.2929},
|
|
{"R_SHOULDER_Y", 1.61},
|
|
{"R_ELBOW_R", 1.58},
|
|
{"R_WRIST_P", -2.8792},
|
|
{"R_WRIST_Y", 0.1150},
|
|
{"R_WRIST_R", -0.08},
|
|
};
|
|
robot->moveJ(init_cmd,1.0,2.0);
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
|
|
|
|
ASSERT_NO_THROW(camera->start());
|
|
struct RuntimeGuard {
|
|
std::shared_ptr<AbstractCamera> camera;
|
|
std::shared_ptr<AbstractRobot> robot;
|
|
~RuntimeGuard() {
|
|
if (camera) {
|
|
try {
|
|
camera->stop();
|
|
} catch (...) {
|
|
}
|
|
}
|
|
if (robot) {
|
|
try {
|
|
robot->eStop();
|
|
} catch (...) {
|
|
}
|
|
}
|
|
}
|
|
} guard{camera, robot};
|
|
|
|
cmvr::IbvsController ibvs_controller;
|
|
ibvs_controller.setMu(0.1);
|
|
ibvs_controller.setLambda(0.6);
|
|
ibvs_controller.setQdotMax(0.6);
|
|
ibvs_controller.setJointLimitAvoidance(true, 0.2, 0.15, 0.25);
|
|
ibvs_controller.setTagSize(0.12);
|
|
ibvs_controller.setTrackedTagId(0);
|
|
ibvs_controller.setTarget(0.0, 0.0, 0.40);
|
|
ibvs_controller.setDepthMode(cmvr::IbvsController::DepthMode::MONOCULAR);
|
|
ibvs_controller.setDepthZGain(1.0);
|
|
Eigen::Matrix3d I = Eigen::Matrix3d::Identity();
|
|
// ibvs_controller.setAlignCameraToVisp(I); // RealSense optical -> ViSP
|
|
// ibvs_controller.setAlignCameraToUrdf(I); // optical -> URDF 的 R_CAM
|
|
Eigen::Matrix3d RxPi;
|
|
RxPi << 1,0,0,
|
|
0,-1,0,
|
|
0,0,-1;
|
|
ibvs_controller.setAlignCameraToUrdf(RxPi);
|
|
ibvs_controller.setAlignCameraToVisp(RxPi);
|
|
|
|
|
|
|
|
// ibvs_controller.setVelocityLimit6({0.15, 0.15, 0.20, 0, 0, 0});
|
|
// ibvs_controller.setVelocityLimit6({0.03,0.03,0.03,0.005,0.005,0.005});
|
|
ibvs_controller.setQdotMax(0.15);
|
|
ibvs_controller.setJointLimitAvoidance(true, 0.2, 0.15, 0.25);
|
|
|
|
|
|
|
|
ASSERT_TRUE(ibvs_controller.init(camera,
|
|
"/home/lgv/cmvr/cmvr-es/model/xiaoyan_description/dual_arm.urdf",
|
|
"PELVIS_S",
|
|
"R_WRIST_R_S",
|
|
"R_CAM"))
|
|
<< "IbvsController init failed";
|
|
|
|
auto q_map_now = makeRightArmQMap();
|
|
robot->getJointQ(q_map_now);
|
|
std::vector<double> q_now(kRightArmJointNames.size(), 0.0);
|
|
auto refreshRightArmQ = [&]() -> bool {
|
|
for (size_t i = 0; i < kRightArmJointNames.size(); ++i) {
|
|
const auto it = q_map_now.find(kRightArmJointNames[i]);
|
|
if (it == q_map_now.end()) {
|
|
return false;
|
|
}
|
|
q_now[i] = it->second;
|
|
// std::cout << kRightArmJointNames[i] << " " << q_now[i] << std::endl;
|
|
}
|
|
return true;
|
|
};
|
|
|
|
ASSERT_TRUE(refreshRightArmQ())
|
|
<< "Failed to extract right-arm 7 joints from robot state";
|
|
ibvs_controller.reset(q_now);
|
|
|
|
|
|
const int max_steps = 30000;
|
|
const int log_every = 1;
|
|
const int cycle_ms = 10;
|
|
const double soft_margin = 0.20; // rad
|
|
const double hard_margin = 0.04; // rad
|
|
const double qdot_deadband = 0.01; // rad/s
|
|
const double qdot_lpf_alpha = 0.75;
|
|
const double qdot_acc_limit = 1.5; // rad/s^2
|
|
const double qdot_max_send = 0.25; // rad/s
|
|
|
|
int ok_steps = 0;
|
|
int fail_steps = 0;
|
|
std::vector<double> qdot_lpf(kRightArmJointNames.size(), 0.0);
|
|
std::vector<double> qdot_cmd_prev(kRightArmJointNames.size(), 0.0);
|
|
auto t_prev = std::chrono::steady_clock::now();
|
|
|
|
for (int step = 0; step < max_steps; ++step) {
|
|
robot->getJointQ(q_map_now);
|
|
if (!refreshRightArmQ()) {
|
|
++fail_steps;
|
|
if ((step % log_every) == 0) {
|
|
std::cout << "[IBVS_REAL] step=" << step << " joint extract failed\n";
|
|
}
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(cycle_ms));
|
|
continue;
|
|
}
|
|
|
|
const auto t_now = std::chrono::steady_clock::now();
|
|
double dt = std::chrono::duration<double>(t_now - t_prev).count();
|
|
t_prev = t_now;
|
|
dt = std::clamp(dt, 0.001, 0.1);
|
|
|
|
std::vector<double> qdot_next;
|
|
const bool ok = ibvs_controller.compute(q_now, qdot_next);
|
|
|
|
|
|
|
|
std::vector<JointVelocityCommand> qd_send;
|
|
for (size_t i = 0; i < kRightArmJointNames.size(); ++i) {
|
|
qdot_lpf[i] = qdot_lpf_alpha * qdot_lpf[i] + (1.0 - qdot_lpf_alpha) * qdot_next[i];
|
|
double v = applySoftJointLimitVelocity(q_now[i], qdot_lpf[i],
|
|
kRightArmQMin[i], kRightArmQMax[i],
|
|
soft_margin, hard_margin);
|
|
if (std::abs(v) < qdot_deadband) {
|
|
v = 0.0;
|
|
}
|
|
const double dv_max = qdot_acc_limit * dt;
|
|
v = std::clamp(v, qdot_cmd_prev[i] - dv_max, qdot_cmd_prev[i] + dv_max);
|
|
v = std::clamp(v, -qdot_max_send, qdot_max_send);
|
|
qdot_cmd_prev[i] = v;
|
|
qd_send.push_back({kRightArmJointNames[i],v});
|
|
}
|
|
|
|
robot->speedJ(qd_send);
|
|
++ok_steps;
|
|
|
|
if ((step % log_every) == 0) {
|
|
const auto& t_co = ibvs_controller.lastTagPositionVisp();
|
|
const auto& v_c = ibvs_controller.lastCameraTwistVisp();
|
|
std::cout << "[IBVS_REAL] step=" << step
|
|
<< " tag=[" << t_co.x() << ", " << t_co.y() << ", " << t_co.z() << "]"
|
|
<< " v_c=[" << v_c[0] << ", " << v_c[1] << ", " << v_c[2]
|
|
<< ", " << v_c[3] << ", " << v_c[4] << ", " << v_c[5] << "]"
|
|
<< " z_source=" << cmvr::IbvsController::depthUsageToString(ibvs_controller.lastDepthUsage())
|
|
<< std::endl;
|
|
std::cout << "qd_cmd: " ;
|
|
for (const auto& it : qd_send) {
|
|
std::cout << it.vel << " ";
|
|
}
|
|
std::cout << std::endl;
|
|
}
|
|
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(cycle_ms));
|
|
}
|
|
|
|
std::cout << "[IBVS_REAL] finished steps=" << max_steps
|
|
<< " ok_steps=" << ok_steps
|
|
<< " fail_steps=" << fail_steps
|
|
<< std::endl;
|
|
|
|
robot->speedJ(0);
|
|
|
|
EXPECT_GT(ok_steps, 0) << "No successful IBVS control steps.";
|
|
}
|
|
|