Compare commits

...

4 Commits

399 changed files with 19695 additions and 19619 deletions

View File

@ -106,23 +106,29 @@ add_executable(cmvr_es cmvr-es/main.cpp)
target_include_directories(cmvr_es PRIVATE ${GLOG_INCLUDE_DIRS}) target_include_directories(cmvr_es PRIVATE ${GLOG_INCLUDE_DIRS})
target_link_libraries(cmvr_es PRIVATE target_link_libraries(cmvr_es PRIVATE
cmvr_es::proto cmvr_es::proto
cmvr_es::logging
service service
${GLOG_LIBRARIES} ${GLOG_LIBRARIES}
jsoncpp jsoncpp
cmvr_es::utils
cmvr_es::service cmvr_es::service
cmvr_es::monitor_manager
cmvr_es::hardware cmvr_es::hardware
cmvr_es::device::canbus cmvr_es::device::canbus
cmvr_es::device::ti5motor cmvr_es::device::ti5motor
cmvr_es::controller cmvr_es::algorithms::controller
cmvr_es::data_center
cmvr_es::ik_solver cmvr_es::ik_solver
cmvr_es::planner cmvr_es::base_motion
cmvr_es::device::humanoid_robot
cmvr_es::common cmvr_es::common
cmvr_es::applications cmvr_es::task
cmvr_es::task_manager
ccd
fcl
) )
install(TARGETS cmvr_es RUNTIME DESTINATION bin) install(TARGETS cmvr_es RUNTIME DESTINATION bin)
install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/cmvr-es/common/config DESTINATION bin) install(CODE [[
file(REMOVE_RECURSE
"${CMAKE_INSTALL_PREFIX}/bin/config"
"${CMAKE_INSTALL_PREFIX}/bin/model")
]])
install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/cmvr-es/config DESTINATION bin)
install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/model DESTINATION bin)

View File

@ -1,16 +1,12 @@
add_subdirectory(utils) add_subdirectory(common/base/logging)
add_subdirectory(hardware) link_libraries(cmvr_es::logging)
add_subdirectory(devices)
add_subdirectory(device_manager)
add_subdirectory(monitor)
add_subdirectory(monitor_manager)
add_subdirectory(service)
add_subdirectory(perception)
add_subdirectory(controller)
add_subdirectory(planner)
add_subdirectory(ik_solver)
add_subdirectory(data_center)
add_subdirectory(applications)
add_subdirectory(simulate)
add_subdirectory(common) add_subdirectory(common)
add_subdirectory(hardware)
add_subdirectory(algorithms)
add_subdirectory(devices)
add_subdirectory(manager/device_manager)
add_subdirectory(task)
add_subdirectory(manager/task_manager)
add_subdirectory(service)
add_subdirectory(simulate)

View File

@ -0,0 +1,4 @@
add_subdirectory(motion_planner)
add_subdirectory(kinematics/ik_solver)
add_subdirectory(perception)
add_subdirectory(controllers)

View File

@ -1,7 +1,8 @@
add_subdirectory(arm_control)
#find_package(VISP REQUIRED) #find_package(VISP REQUIRED)
# relocation ... can not be used when making a shared object; recompile with -fPIC SRC test test # relocation ... can not be used when making a shared object; recompile with -fPIC SRC test test
# -lgtest -lgtest_main , libcontroller.so gtest # -lgtest -lgtest_main , libcontroller.so gtest
# add_library(controller SHARED # add_library(controller SHARED
@ -10,8 +11,8 @@
# src/controller_test.cpp # src/controller_test.cpp
#) #)
file(GLOB SRC file(GLOB SRC
${CMAKE_CURRENT_SOURCE_DIR}/src/pid_controller.cpp ${CMAKE_CURRENT_SOURCE_DIR}/pid/src/pid_controller.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/ibvs_controller.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ibvs/src/ibvs_controller.cpp
) )
@ -24,7 +25,6 @@ target_link_libraries(controller PUBLIC
protobuf protobuf
cmvr_es::perception cmvr_es::perception
cmvr_es::ik_solver cmvr_es::ik_solver
cmvr_es::device::humanoid_robot
gtest gtest
gtest_main gtest_main
pthread pthread
@ -55,7 +55,7 @@ target_link_libraries(controller PUBLIC
pinocchio_parsers pinocchio_parsers
) )
add_library(cmvr_es::controller ALIAS controller) add_library(cmvr_es::algorithms::controller ALIAS controller)
install(TARGETS controller LIBRARY DESTINATION lib) install(TARGETS controller LIBRARY DESTINATION lib)
@ -65,18 +65,17 @@ install(TARGETS controller LIBRARY DESTINATION lib)
# -------------------------------------------------------- # --------------------------------------------------------
find_package(realsense2 REQUIRED) find_package(realsense2 REQUIRED)
add_executable(controller_test add_executable(controller_test
${CMAKE_CURRENT_SOURCE_DIR}/src/controller_test.cpp ${CMAKE_CURRENT_SOURCE_DIR}/tests/src/controller_test.cpp
) )
target_link_libraries(controller_test target_link_libraries(controller_test
PRIVATE PRIVATE
cmvr_es::utils
cmvr_es::perception cmvr_es::perception
cmvr_es::ik_solver cmvr_es::ik_solver
cmvr_es::planner cmvr_es::base_motion
cmvr_es::proto cmvr_es::proto
cmvr_es::mujoco_viewer cmvr_es::mujoco_viewer
cmvr_es::controller cmvr_es::algorithms::controller
cmvr_es::device::mujoco_camera cmvr_es::device::mujoco_camera
gtest gtest
gtest_main gtest_main

View File

@ -0,0 +1,13 @@
add_library(arm_control SHARED
src/cartesian_velocity_controller.cpp
)
target_include_directories(arm_control PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(arm_control
PUBLIC
cmvr_es::algorithms::arm_motion
)
add_library(cmvr_es::algorithms::arm_control ALIAS arm_control)
install(TARGETS arm_control LIBRARY DESTINATION lib)

View File

@ -0,0 +1,80 @@
#ifndef CMVR_ES_CARTESIAN_VELOCITY_CONTROLLER_H
#define CMVR_ES_CARTESIAN_VELOCITY_CONTROLLER_H
#include <atomic>
#include <condition_variable>
#include <cstddef>
#include <cstdint>
#include <functional>
#include <memory>
#include <mutex>
#include <thread>
#include <vector>
#include "algorithms/motion_planner/arm_motion/cartesian_motion/cartesian_motion_planner.h"
#include "common/types/arm/arm_types.h"
namespace cmvr::device {
class CartesianVelocityController {
public:
struct Config {
double control_period_s{0.001};
double stop_twist_norm{1e-9};
double stop_command_velocity_norm{1e-3};
double stop_measured_velocity_norm{1e-2};
};
using ReadStateCallback = std::function<bool(std::vector<double>& q, std::vector<double>& qd)>;
using SendVelocityCallback = std::function<Result(const JointVelocityCommand& velocity, double acceleration)>;
CartesianVelocityController(Config config,
std::shared_ptr<CartesianMotionPlanner> planner,
std::size_t dof,
ReadStateCallback read_state,
SendVelocityCallback send_velocity);
~CartesianVelocityController();
CartesianVelocityController(const CartesianVelocityController&) = delete;
CartesianVelocityController& operator=(const CartesianVelocityController&) = delete;
Result speedL(const CartesianVelocity& velocity,
double acceleration,
double duration,
FrameType frame);
Result stop(double acceleration);
void shutdown();
bool busy() const { return busy_.load(); }
CartesianVelocity getCommandTwistBase() const;
private:
void ensureWorkerStarted_();
void workerLoop_();
void sendZero_();
static double velocityNorm_(const std::vector<double>& velocity);
static double twistNorm_(const CartesianVelocity& velocity);
private:
Config config_;
std::shared_ptr<CartesianMotionPlanner> planner_;
std::size_t dof_{0};
ReadStateCallback read_state_;
SendVelocityCallback send_velocity_;
std::unique_ptr<std::thread> worker_;
mutable std::mutex mutex_;
std::condition_variable cv_;
std::atomic<bool> stop_requested_{false};
bool command_active_{false};
CartesianVelocity target_twist_{};
FrameType target_frame_{FrameType::Base};
double target_acceleration_{0.25};
std::uint64_t command_version_{0};
std::atomic<bool> busy_{false};
};
} // namespace cmvr::device
#endif // CMVR_ES_CARTESIAN_VELOCITY_CONTROLLER_H

View File

@ -0,0 +1,282 @@
#include "algorithms/controllers/arm_control/include/cartesian_velocity_controller.h"
#include <chrono>
#include <cmath>
#include <thread>
#include <utility>
#include "common/base/logging/logger.h"
namespace cmvr::device {
namespace {
CartesianVelocityController::Config normalizeConfig(CartesianVelocityController::Config config)
{
const CartesianVelocityController::Config defaults;
if (config.control_period_s <= 0.0) {
config.control_period_s = defaults.control_period_s;
}
if (config.stop_twist_norm <= 0.0) {
config.stop_twist_norm = defaults.stop_twist_norm;
}
if (config.stop_command_velocity_norm <= 0.0) {
config.stop_command_velocity_norm = defaults.stop_command_velocity_norm;
}
if (config.stop_measured_velocity_norm <= 0.0) {
config.stop_measured_velocity_norm = defaults.stop_measured_velocity_norm;
}
return config;
}
} // namespace
CartesianVelocityController::CartesianVelocityController(
Config config,
std::shared_ptr<CartesianMotionPlanner> planner,
const std::size_t dof,
ReadStateCallback read_state,
SendVelocityCallback send_velocity)
: config_(normalizeConfig(config)),
planner_(std::move(planner)),
dof_(dof),
read_state_(std::move(read_state)),
send_velocity_(std::move(send_velocity))
{
}
CartesianVelocityController::~CartesianVelocityController()
{
shutdown();
}
Result CartesianVelocityController::speedL(const CartesianVelocity& velocity,
const double acceleration,
const double duration,
const FrameType frame)
{
if (!planner_ || !read_state_ || !send_velocity_ || dof_ == 0 || acceleration <= 0.0) {
return Result::failure(ArmErrorCode::InvalidArgument, "speedL invalid input");
}
if ((!worker_ || !worker_->joinable()) && busy_.exchange(true)) {
return Result::failure(ArmErrorCode::RobotNotReady, "arm is busy");
}
ensureWorkerStarted_();
std::uint64_t command_version = 0;
{
std::lock_guard<std::mutex> lock(mutex_);
target_twist_ = velocity;
target_acceleration_ = acceleration;
target_frame_ = frame;
command_active_ = true;
command_version = ++command_version_;
}
cv_.notify_all();
if (duration > 0.0) {
std::this_thread::sleep_for(std::chrono::duration<double>(duration));
bool should_stop = false;
{
std::lock_guard<std::mutex> lock(mutex_);
if (command_version_ == command_version) {
target_twist_ = {};
target_frame_ = FrameType::Base;
command_active_ = true;
++command_version_;
should_stop = true;
}
}
if (should_stop) {
cv_.notify_all();
}
}
return Result::success();
}
Result CartesianVelocityController::stop(const double acceleration)
{
(void)acceleration;
if (!worker_ || !worker_->joinable()) {
return Result::success();
}
{
std::lock_guard<std::mutex> lock(mutex_);
target_twist_ = {};
target_frame_ = FrameType::Base;
command_active_ = true;
++command_version_;
}
cv_.notify_all();
return Result::success();
}
void CartesianVelocityController::shutdown()
{
if (!worker_ || !worker_->joinable()) {
busy_.store(false);
return;
}
{
std::lock_guard<std::mutex> lock(mutex_);
stop_requested_.store(true);
command_active_ = false;
target_twist_ = {};
target_frame_ = FrameType::Base;
}
cv_.notify_all();
worker_->join();
worker_.reset();
stop_requested_.store(false);
busy_.store(false);
}
CartesianVelocity CartesianVelocityController::getCommandTwistBase() const
{
if (!planner_) {
return {};
}
return planner_->getSpeedLCommandTwistBase();
}
void CartesianVelocityController::ensureWorkerStarted_()
{
if (worker_ && worker_->joinable()) {
return;
}
stop_requested_.store(false);
worker_ = std::make_unique<std::thread>(&CartesianVelocityController::workerLoop_, this);
}
void CartesianVelocityController::workerLoop_()
{
const double dt = config_.control_period_s;
auto next_tick = std::chrono::steady_clock::now();
while (true) {
CartesianVelocity target_twist;
double acceleration = 0.25;
FrameType target_frame = FrameType::Base;
{
std::unique_lock<std::mutex> lock(mutex_);
cv_.wait(lock, [&]() {
return stop_requested_.load() || command_active_;
});
if (stop_requested_.load()) {
break;
}
target_twist = target_twist_;
acceleration = target_acceleration_;
target_frame = target_frame_;
}
next_tick = std::chrono::steady_clock::now();
while (true) {
{
std::lock_guard<std::mutex> lock(mutex_);
if (stop_requested_.load()) {
sendZero_();
busy_.store(false);
return;
}
if (!command_active_) {
break;
}
target_twist = target_twist_;
acceleration = target_acceleration_;
target_frame = target_frame_;
}
if (!planner_->updateSpeedLAcceleration(acceleration)) {
CMVR_LOG(ERROR) << "[CartesianVelocityController][speedL] updateSpeedLAcceleration failed, acceleration="
<< acceleration;
sendZero_();
busy_.store(false);
return;
}
std::vector<double> q_now;
std::vector<double> qd_now;
if (!read_state_(q_now, qd_now)) {
CMVR_LOG(ERROR) << "[CartesianVelocityController][speedL] read_state failed";
sendZero_();
busy_.store(false);
return;
}
std::vector<double> qd_cmd;
if (!planner_->speedLStep(target_twist, dt, q_now, qd_now, qd_cmd, target_frame)) {
CMVR_LOG(ERROR) << "[CartesianVelocityController][speedL] speedLStep failed, target_twist=["
<< target_twist.vx << ", " << target_twist.vy << ", "
<< target_twist.vz << ", " << target_twist.wx << ", "
<< target_twist.wy << ", " << target_twist.wz
<< "], frame=" << (target_frame == FrameType::Tool ? "Tool" : "Base");
sendZero_();
busy_.store(false);
return;
}
JointVelocityCommand velocity_command;
velocity_command.velocity = qd_cmd;
const auto send_result = send_velocity_(velocity_command, acceleration);
if (!send_result.ok()) {
CMVR_LOG(ERROR) << "[CartesianVelocityController][speedL] send_velocity failed: "
<< send_result.message;
sendZero_();
busy_.store(false);
return;
}
if (twistNorm_(target_twist) < config_.stop_twist_norm &&
velocityNorm_(qd_cmd) < config_.stop_command_velocity_norm &&
velocityNorm_(qd_now) < config_.stop_measured_velocity_norm) {
{
std::lock_guard<std::mutex> lock(mutex_);
command_active_ = false;
}
sendZero_();
busy_.store(false);
break;
}
next_tick += std::chrono::duration_cast<std::chrono::steady_clock::duration>(
std::chrono::duration<double>(dt));
std::this_thread::sleep_until(next_tick);
}
}
sendZero_();
busy_.store(false);
}
void CartesianVelocityController::sendZero_()
{
if (!send_velocity_) {
return;
}
JointVelocityCommand zero;
zero.velocity.assign(dof_, 0.0);
(void)send_velocity_(zero, 0.0);
}
double CartesianVelocityController::velocityNorm_(const std::vector<double>& velocity)
{
double value = 0.0;
for (const double item : velocity) {
value += item * item;
}
return std::sqrt(value);
}
double CartesianVelocityController::twistNorm_(const CartesianVelocity& velocity)
{
return std::sqrt(velocity.vx * velocity.vx +
velocity.vy * velocity.vy +
velocity.vz * velocity.vz +
velocity.wx * velocity.wx +
velocity.wy * velocity.wy +
velocity.wz * velocity.wz);
}
} // namespace cmvr::device

View File

@ -14,8 +14,8 @@
#include <visp3/visual_features/vpFeaturePoint.h> #include <visp3/visual_features/vpFeaturePoint.h>
#include <visp3/vs/vpServo.h> #include <visp3/vs/vpServo.h>
#include "ik_solver/include/pinocchio_dls_ik_solver.h" #include "algorithms/kinematics/ik_solver/pinocchio/include/pinocchio_ik_base.h"
#include "perception/include/apriltag_perception.h" #include "algorithms/perception/apriltag/include/apriltag_perception.h"
namespace cmvr { namespace cmvr {
@ -66,16 +66,12 @@ public:
IbvsController(); IbvsController();
/** /**
* @brief DLS IK * @brief Pinocchio kinematics solver
* @param urdf_path URDF * @param solver RobotArm Pinocchio IK solver
* @param base_link IK link
* @param flange_link IK link
* @param camera_link URDF link `u` * @param camera_link URDF link `u`
* @return `true` * @return `true`
*/ */
bool init(const std::string& urdf_path, bool init(std::shared_ptr<cmvr::PinocchioIKBase> solver,
const std::string& base_link,
const std::string& flange_link,
const std::string& camera_link); const std::string& camera_link);
/** /**
@ -290,8 +286,8 @@ private:
private: private:
bool initialized_{false}; bool initialized_{false};
std::string base_frame_name_;
std::string camera_frame_name_; std::string camera_frame_name_;
std::shared_ptr<cmvr::PinocchioIKBase> solver_{nullptr};
std::shared_ptr<cmvr::perception::AprilTagPerception> perception_{nullptr}; std::shared_ptr<cmvr::perception::AprilTagPerception> perception_{nullptr};
double lambda_{0.7}; double lambda_{0.7};
@ -336,7 +332,6 @@ private:
vpFeaturePoint s_star_[4]; vpFeaturePoint s_star_[4];
int tracked_tag_id_{-1}; int tracked_tag_id_{-1};
std::unique_ptr<PinocchioDlsIKSolver> dls_solver_{nullptr};
bool has_joint_position_limits_{false}; bool has_joint_position_limits_{false};
Eigen::VectorXd q_lower_limits_; Eigen::VectorXd q_lower_limits_;

View File

@ -1,7 +1,8 @@
#include "controller/include/ibvs_controller.h" #include "algorithms/controllers/ibvs/include/ibvs_controller.h"
#include "common/utils/image/image_process.h" #include "algorithms/kinematics/ik_solver/pinocchio/include/pinocchio_dls_ik_solver.h"
#include "common/math/include/support_functions.h" #include "common/vision/image_projection.h"
#include "common/math/support_functions.h"
#include <algorithm> #include <algorithm>
#include <cmath> #include <cmath>
@ -107,24 +108,20 @@ IbvsController::IbvsController() {
initTask(); initTask();
} }
bool IbvsController::init(const std::string& urdf_path, bool IbvsController::init(std::shared_ptr<cmvr::PinocchioIKBase> solver,
const std::string& base_link,
const std::string& flange_link,
const std::string& camera_link) { const std::string& camera_link) {
base_frame_name_ = base_link; solver_ = std::move(solver);
camera_frame_name_ = camera_link; camera_frame_name_ = camera_link;
initialized_ = solver_ != nullptr && !camera_frame_name_.empty();
dls_solver_ = std::make_unique<PinocchioDlsIKSolver>(
urdf_path, base_link, flange_link, camera_frame_name_, 100, 1e-6, 1e-6, mu_);
initialized_ = dls_solver_->init();
if (initialized_) { if (initialized_) {
dls_solver_->setJointLimitAvoidance(limit_avoidance_enabled_, if (auto dls_solver = std::dynamic_pointer_cast<PinocchioDlsIKSolver>(solver_)) {
limit_avoidance_gain_, dls_solver->setJointLimitAvoidance(limit_avoidance_enabled_,
limit_avoidance_margin_ratio_, limit_avoidance_gain_,
limit_avoidance_max_push_); limit_avoidance_margin_ratio_,
limit_avoidance_max_push_);
}
has_joint_position_limits_ = has_joint_position_limits_ =
dls_solver_->getJointPositionLimits(q_lower_limits_, q_upper_limits_); solver_->getJointPositionLimits(q_lower_limits_, q_upper_limits_);
} else { } else {
has_joint_position_limits_ = false; has_joint_position_limits_ = false;
q_lower_limits_.resize(0); q_lower_limits_.resize(0);
@ -209,11 +206,11 @@ bool IbvsController::computeQdot(const std::vector<double>& joints_angle,
} }
bool IbvsController::getChainJointNames(std::vector<std::string>& joint_names) const { bool IbvsController::getChainJointNames(std::vector<std::string>& joint_names) const {
if (!dls_solver_) { if (!solver_) {
joint_names.clear(); joint_names.clear();
return false; return false;
} }
return dls_solver_->getChainJointNames(joint_names); return solver_->getChainJointNames(joint_names);
} }
bool IbvsController::computeInternal(const std::vector<double>& joints_angle, bool IbvsController::computeInternal(const std::vector<double>& joints_angle,
@ -225,7 +222,7 @@ bool IbvsController::computeInternal(const std::vector<double>& joints_angle,
last_tag_pos_visp_.setZero(); last_tag_pos_visp_.setZero();
last_v_camera_visp_.setZero(); last_v_camera_visp_.setZero();
if (!initialized_ || !dls_solver_) { if (!initialized_ || !solver_) {
last_compute_status_ = ComputeStatus::NOT_READY; last_compute_status_ = ComputeStatus::NOT_READY;
return false; return false;
} }
@ -396,28 +393,56 @@ bool IbvsController::computeInternal(const std::vector<double>& joints_angle,
twist_urdf.head<3>() = R_camera_urdf_ * twist_cam.head<3>(); twist_urdf.head<3>() = R_camera_urdf_ * twist_cam.head<3>();
twist_urdf.tail<3>() = R_camera_urdf_ * twist_cam.tail<3>(); twist_urdf.tail<3>() = R_camera_urdf_ * twist_cam.tail<3>();
// IK Eigen::MatrixXd jacobian_base;
dls_solver_->update_joints_state(joints_angle); Eigen::Matrix3d base_R_camera = Eigen::Matrix3d::Identity();
if (!solver_->computeJacobianBaseAtQ(joints_angle,
camera_frame_name_,
jacobian_base,
base_R_camera)) {
last_compute_status_ = ComputeStatus::IK_FAILED;
return false;
}
Eigen::Matrix<double, 6, 1> twist_base;
twist_base.head<3>() = base_R_camera * twist_urdf.head<3>();
twist_base.tail<3>() = base_R_camera * twist_urdf.tail<3>();
std::vector<double> qdot; std::vector<double> qdot;
const bool ok = dls_solver_->ik( const bool ok = solver_->solveVelocityBase(jacobian_base,
base_frame_name_, camera_frame_name_, twist_urdf, twist_base,
qdot, mu_, std::numeric_limits<double>::infinity()); joints_angle,
qdot,
std::numeric_limits<double>::infinity());
if (!ok || qdot.size() != joints_angle.size()) { if (!ok || qdot.size() != joints_angle.size()) {
last_compute_status_ = ComputeStatus::IK_FAILED; last_compute_status_ = ComputeStatus::IK_FAILED;
return false; return false;
} }
Eigen::Map<const Eigen::VectorXd> q_chain(joints_angle.data(), static_cast<Eigen::Index>(joints_angle.size()));
Eigen::Map<const Eigen::VectorXd> qdot_vec(qdot.data(), static_cast<Eigen::Index>(qdot.size()));
const Eigen::VectorXd qdot_soft_limited = dls_solver_->applyJointSoftLimitVelocity(q_chain, qdot_vec);
qdot_out.resize(qdot.size()); qdot_out.resize(qdot.size());
for (size_t i = 0; i < qdot.size(); ++i) { for (size_t i = 0; i < qdot.size(); ++i) {
qdot_out[i] = SupportFunctions::clamp(qdot_soft_limited[static_cast<Eigen::Index>(i)], qdot_out[i] = SupportFunctions::clamp(qdot[i], -qdot_max_, qdot_max_);
-qdot_max_,
qdot_max_); if (!has_joint_position_limits_ ||
i >= static_cast<size_t>(q_lower_limits_.size()) ||
i >= static_cast<size_t>(q_upper_limits_.size())) {
continue;
}
const double lower = q_lower_limits_[static_cast<Eigen::Index>(i)];
const double upper = q_upper_limits_[static_cast<Eigen::Index>(i)];
if (!std::isfinite(lower) || !std::isfinite(upper) || upper <= lower) {
continue;
}
const double span = upper - lower;
const double margin = std::max(0.02, 0.08 * span);
const double q = joints_angle[i];
if (qdot_out[i] < 0.0 && q < lower + margin) {
qdot_out[i] *= SupportFunctions::clamp((q - lower) / margin, 0.0, 1.0);
} else if (qdot_out[i] > 0.0 && q > upper - margin) {
qdot_out[i] *= SupportFunctions::clamp((upper - q) / margin, 0.0, 1.0);
}
} }
last_compute_status_ = ComputeStatus::OK; last_compute_status_ = ComputeStatus::OK;
@ -539,11 +564,11 @@ void IbvsController::setJointLimitAvoidance(bool enable,
limit_avoidance_margin_ratio_ = std::clamp(margin_ratio, 1e-3, 0.49); limit_avoidance_margin_ratio_ = std::clamp(margin_ratio, 1e-3, 0.49);
limit_avoidance_max_push_ = max_push; limit_avoidance_max_push_ = max_push;
if (dls_solver_) { if (auto dls_solver = std::dynamic_pointer_cast<PinocchioDlsIKSolver>(solver_)) {
dls_solver_->setJointLimitAvoidance(limit_avoidance_enabled_, dls_solver->setJointLimitAvoidance(limit_avoidance_enabled_,
limit_avoidance_gain_, limit_avoidance_gain_,
limit_avoidance_margin_ratio_, limit_avoidance_margin_ratio_,
limit_avoidance_max_push_); limit_avoidance_max_push_);
} }
} }

View File

@ -2,7 +2,7 @@
// Created by lgv on 11/27/25. // Created by lgv on 11/27/25.
// //
#include "controller/include/pid_controller.h" #include "algorithms/controllers/pid/include/pid_controller.h"
#include <cmath> #include <cmath>
namespace cmvr { namespace cmvr {

View File

@ -16,9 +16,9 @@
#include <vector> #include <vector>
#include "controller/include/ibvs_controller.h" #include "algorithms/controllers/ibvs/include/ibvs_controller.h"
#include "devices/camera/mujoco_camera/include/mujoco_camera.h" #include "devices/camera/mujoco_camera/include/mujoco_camera.h"
#include "ik_solver/include/pinocchio_dls_ik_solver.h" #include "algorithms/kinematics/ik_solver/pinocchio/include/pinocchio_dls_ik_solver.h"
#include "simulate/mujoco/mujoco_viewer/include/mujoco_viewer.h" #include "simulate/mujoco/mujoco_viewer/include/mujoco_viewer.h"
using namespace cmvr; using namespace cmvr;
@ -551,7 +551,23 @@ protected:
ibvs_controller_->setAlignCameraToVisp(R_align); ibvs_controller_->setAlignCameraToVisp(R_align);
ibvs_controller_->setAlignCameraToUrdf(R_align); ibvs_controller_->setAlignCameraToUrdf(R_align);
if (!ibvs_controller_->init(urdf_path_for_check_, "PELVIS_S", "R_WRIST_R_S", camera_frame_name_for_check_)) { config::PinocchioDlsIKConfig dls_cfg;
dls_cfg.set_urdf_path(urdf_path_for_check_);
dls_cfg.set_base_frame_name("PELVIS_S");
dls_cfg.set_flange_frame_name("R_WRIST_R_S");
dls_cfg.set_tcp_frame_name(camera_frame_name_for_check_);
dls_cfg.set_max_iters(100);
dls_cfg.set_pos_eps(1e-6);
dls_cfg.set_rot_eps(1e-6);
dls_cfg.set_damping(mu_);
auto solver = std::make_shared<PinocchioDlsIKSolver>(dls_cfg);
if (!solver->init()) {
std::cout << "[IBVS] PinocchioDlsIKSolver init failed" << std::endl;
ready_ = false;
return;
}
if (!ibvs_controller_->init(solver, camera_frame_name_for_check_)) {
std::cout << "[IBVS] IbvsController init failed" << std::endl; std::cout << "[IBVS] IbvsController init failed" << std::endl;
ready_ = false; ready_ = false;
return; return;

View File

@ -1,15 +1,14 @@
add_library(ik_solver SHARED add_library(ik_solver SHARED
${CMAKE_CURRENT_SOURCE_DIR}/src/ik_solver.cpp ${CMAKE_CURRENT_SOURCE_DIR}/common/src/ik_solver.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/ik_solver_creator.cpp ${CMAKE_CURRENT_SOURCE_DIR}/common/src/urdf_parser.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/urdf_parser.cpp ${CMAKE_CURRENT_SOURCE_DIR}/pinocchio/src/pinocchio_ik_base.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/pinocchio_ik_base.cpp ${CMAKE_CURRENT_SOURCE_DIR}/pinocchio/src/pinocchio_dls_ik_solver.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/pinocchio_dls_ik_solver.cpp ${CMAKE_CURRENT_SOURCE_DIR}/pinocchio/src/pinocchio_qp_ik_solver.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/pinocchio_qp_ik_solver.cpp ${CMAKE_CURRENT_SOURCE_DIR}/srs/src/srs_ik_solver.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/bias_srs_ik_slover.cpp ${CMAKE_CURRENT_SOURCE_DIR}/lawba/src/joints_limit_analyzer.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/joints_limit_analyzer.cpp ${CMAKE_CURRENT_SOURCE_DIR}/lawba/src/lawba_ik_solver.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/lawba_ik_solver.cpp ${CMAKE_CURRENT_SOURCE_DIR}/lawba/src/opt_psi_selector.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/opt_psi_selector.cpp
) )
target_include_directories(ik_solver PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) target_include_directories(ik_solver PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
@ -20,7 +19,7 @@ target_link_libraries(ik_solver PUBLIC
pinocchio_default pinocchio_default
pinocchio_parsers pinocchio_parsers
pinocchio_collision pinocchio_collision
cmvr_es::planner cmvr_es::base_motion
cmvr_es::common cmvr_es::common
) )
@ -32,15 +31,15 @@ install(TARGETS ik_solver LIBRARY DESTINATION lib)
# -------------------------------------------------------- # --------------------------------------------------------
add_executable(srs_ik_test add_executable(srs_ik_test
${CMAKE_CURRENT_SOURCE_DIR}/src/srs_ik_test.cpp ${CMAKE_CURRENT_SOURCE_DIR}/tests/src/srs_ik_test.cpp
) )
target_link_libraries(srs_ik_test target_link_libraries(srs_ik_test
PRIVATE PRIVATE
cmvr_es::utils
cmvr_es::ik_solver cmvr_es::ik_solver
cmvr_es::planner cmvr_es::base_motion
cmvr_es::arm_motion
cmvr_es::proto cmvr_es::proto
cmvr_es::mujoco_viewer cmvr_es::mujoco_viewer
gtest gtest
@ -54,7 +53,7 @@ target_link_libraries(srs_ik_test
add_executable(ik_test add_executable(ik_test
${CMAKE_CURRENT_SOURCE_DIR}/src/ik_test.cpp ${CMAKE_CURRENT_SOURCE_DIR}/tests/src/ik_test.cpp
) )
target_link_libraries(ik_test target_link_libraries(ik_test

View File

@ -3,8 +3,7 @@
// //
#pragma once #pragma once
#include <common/consts/constant.h> #include <common/base/constants.h>
#include "ik_solver.h"
#include <vector> #include <vector>
#include <string> #include <string>
#include <memory> #include <memory>

View File

@ -2,10 +2,9 @@
// Created by Codex on 2026/3/2. // Created by Codex on 2026/3/2.
// //
#include "ik_solver/include/ik_solver.h" #include "algorithms/kinematics/ik_solver/common/include/ik_solver.h"
#include "ik_solver/include/urdf_parser.h" #include "algorithms/kinematics/ik_solver/common/include/urdf_parser.h"
#include "common/base/logging/logger.h"
#include <iostream>
namespace cmvr { namespace cmvr {
@ -31,7 +30,7 @@ bool IKSolver::initUrdfChain(const std::string& urdf_path,
auto parser = std::make_shared<UrdfParser>(); auto parser = std::make_shared<UrdfParser>();
std::string err; std::string err;
if (!parser->loadModel(urdf_path, &err)) { if (!parser->loadModel(urdf_path, &err)) {
std::cerr << "[IKSolver] Failed to load URDF: " << err << "\n"; CMVR_LOG(ERROR) << "[IKSolver] Failed to load URDF: " << err;
return false; return false;
} }
return initUrdfChain(parser, base_frame_name, tip_frame_name); return initUrdfChain(parser, base_frame_name, tip_frame_name);
@ -41,18 +40,18 @@ bool IKSolver::initUrdfChain(const std::shared_ptr<const UrdfParser>& parser,
const std::string& base_frame_name, const std::string& base_frame_name,
const std::string& tip_frame_name) { const std::string& tip_frame_name) {
if (!parser) { if (!parser) {
std::cerr << "[IKSolver] initUrdfChain failed: parser is null\n"; CMVR_LOG(ERROR) << "[IKSolver] initUrdfChain failed: parser is null";
return false; return false;
} }
if (!parser->loaded()) { if (!parser->loaded()) {
std::cerr << "[IKSolver] initUrdfChain failed: parser model not loaded\n"; CMVR_LOG(ERROR) << "[IKSolver] initUrdfChain failed: parser model not loaded";
return false; return false;
} }
UrdfParser::ChainInfo chain_info; UrdfParser::ChainInfo chain_info;
std::string err; std::string err;
if (!parser->extractChain(base_frame_name, tip_frame_name, chain_info, &err)) { if (!parser->extractChain(base_frame_name, tip_frame_name, chain_info, &err)) {
std::cerr << "[IKSolver] Failed to extract chain: " << err << "\n"; CMVR_LOG(ERROR) << "[IKSolver] Failed to extract chain: " << err;
return false; return false;
} }

View File

@ -1,6 +1,6 @@
// Created by Codex on 2026/3/2. // Created by Codex on 2026/3/2.
#include "ik_solver/include/urdf_parser.h" #include "algorithms/kinematics/ik_solver/common/include/urdf_parser.h"
#include <algorithm> #include <algorithm>

View File

@ -0,0 +1,44 @@
#pragma once
#include <memory>
#include <stdexcept>
#include "algorithms/kinematics/ik_solver/common/include/ik_solver.h"
#include "algorithms/kinematics/ik_solver/lawba/include/lawba_ik_solver.h"
#include "algorithms/kinematics/ik_solver/pinocchio/include/pinocchio_dls_ik_solver.h"
#include "algorithms/kinematics/ik_solver/pinocchio/include/pinocchio_qp_ik_solver.h"
#include "algorithms/kinematics/ik_solver/srs/include/srs_ik_solver.h"
#include "cmvr/config/arm_config/arm_config.pb.h"
#include "common/base/logging/logger.h"
#include "common/config/config_files.h"
namespace cmvr {
class IKSolverFactory {
public:
static std::shared_ptr<IKSolver> create(const config::ArmKinematicsConfig& cfg)
{
switch (cfg.algorithm_case()) {
case config::ArmKinematicsConfig::kPinocchioDlsIkSolver: {
auto solver_cfg = cfg.pinocchio_dls_ik_solver();
solver_cfg.set_urdf_path(ConfigHelper::resolveResourceFile(solver_cfg.urdf_path()));
return std::make_shared<PinocchioDlsIKSolver>(solver_cfg);
}
case config::ArmKinematicsConfig::kPinocchioQpIkSolver: {
auto solver_cfg = cfg.pinocchio_qp_ik_solver();
solver_cfg.set_urdf_path(ConfigHelper::resolveResourceFile(solver_cfg.urdf_path()));
return std::make_shared<PinocchioQpIKSolver>(solver_cfg);
}
case config::ArmKinematicsConfig::kSrsIkSolver:
return std::make_shared<SrsIKSolver>(cfg.srs_ik_solver());
case config::ArmKinematicsConfig::kLawbaIkSolver:
return std::make_shared<LawbaIKSolver>(cfg.lawba_ik_solver());
case config::ArmKinematicsConfig::ALGORITHM_NOT_SET:
default:
CMVR_LOG(ERROR) << "[IKSolverFactory] missing arm kinematics algorithm config";
return nullptr;
}
}
};
} // namespace cmvr

View File

@ -7,7 +7,7 @@
#include <vector> #include <vector>
#include <Eigen/Core> #include <Eigen/Core>
#include "common/math/include/support_functions.h" #include "common/math/support_functions.h"
namespace cmvr { namespace cmvr {
class JointsLimitAnalyzer { class JointsLimitAnalyzer {

View File

@ -1,8 +1,8 @@
// //
// Created by lgv on 11/7/25. // Created by lgv on 11/7/25.
// //
#ifndef CMVR_ES_OPT_PSI_LIMIT_BIAS_SLOVER_H #ifndef CMVR_ES_LAWBA_IK_SOLVER_H
#define CMVR_ES_OPT_PSI_LIMIT_BIAS_SLOVER_H #define CMVR_ES_LAWBA_IK_SOLVER_H
#include <memory> #include <memory>
#include <vector> #include <vector>
@ -10,17 +10,17 @@
#include <Eigen/Dense> #include <Eigen/Dense>
#include "cmvr/msgs/can_card_parameter.grpc.pb.h" #include "cmvr/config/lawba_ik_config.pb.h"
#include "ik_solver/include/bias_srs_ik_slover.h" #include "algorithms/kinematics/ik_solver/lawba/include/joints_limit_analyzer.h"
#include "ik_solver/include/joints_limit_analyzer.h" #include "algorithms/kinematics/ik_solver/lawba/include/opt_psi_selector.h"
#include "ik_solver/include/opt_psi_selector.h" #include "algorithms/kinematics/ik_solver/srs/include/srs_ik_solver.h"
#include "ik_solver/include/ik_solver.h" #include "algorithms/kinematics/ik_solver/common/include/ik_solver.h"
namespace cmvr { namespace cmvr {
class LawbaIKSolver : public IKSolver { class LawbaIKSolver : public IKSolver {
public: public:
LawbaIKSolver(); explicit LawbaIKSolver(const config::LawbaIKConfig& cfg);
~LawbaIKSolver() override = default; ~LawbaIKSolver() override = default;
bool init() override; bool init() override;
@ -42,6 +42,8 @@ public:
} }
private: private:
config::LawbaIKConfig config_;
// 机械臂 URDF 中末端工具坐标系相对于 MDH 中末端法兰的变换矩阵。 // 机械臂 URDF 中末端工具坐标系相对于 MDH 中末端法兰的变换矩阵。
Eigen::Matrix4d T_tool_flange_{Eigen::Matrix4d::Identity()}; Eigen::Matrix4d T_tool_flange_{Eigen::Matrix4d::Identity()};
@ -51,7 +53,7 @@ private:
// 机械臂 URDF 中法兰姿态相对于 MDH 中法兰姿态的变换矩阵。 // 机械臂 URDF 中法兰姿态相对于 MDH 中法兰姿态的变换矩阵。
Eigen::Matrix4d T_flange_urdf_mdh_{Eigen::Matrix4d::Identity()}; Eigen::Matrix4d T_flange_urdf_mdh_{Eigen::Matrix4d::Identity()};
std::shared_ptr<BiasSRSIkSolver> bias_srs_ik_solver_{nullptr}; std::shared_ptr<SrsIKSolver> srs_ik_solver_{nullptr};
std::shared_ptr<JointsLimitAnalyzer> joints_limit_analyzer_{nullptr}; std::shared_ptr<JointsLimitAnalyzer> joints_limit_analyzer_{nullptr};
std::shared_ptr<OptPsiSelector> opt_psi_selector_{nullptr}; std::shared_ptr<OptPsiSelector> opt_psi_selector_{nullptr};
@ -88,14 +90,14 @@ private:
double &cost_out); double &cost_out);
// ConfigDirection ↔ 符号 // ConfigDirection ↔ 符号
static inline int sign_from_dir(BiasSRSIkSolver::ConfigDirection d) { static inline int sign_from_dir(SrsIKSolver::ConfigDirection d) {
return (d == BiasSRSIkSolver::ConfigDirection::OUTWARD) ? +1 : -1; return (d == SrsIKSolver::ConfigDirection::OUTWARD) ? +1 : -1;
} }
static inline BiasSRSIkSolver::ConfigDirection dir_from_sign(int s) { static inline SrsIKSolver::ConfigDirection dir_from_sign(int s) {
return (s >= 0) return (s >= 0)
? BiasSRSIkSolver::ConfigDirection::OUTWARD ? SrsIKSolver::ConfigDirection::OUTWARD
: BiasSRSIkSolver::ConfigDirection::INWARD; : SrsIKSolver::ConfigDirection::INWARD;
} }
// 符号 (-1/+1) → 索引 (1/0) // 符号 (-1/+1) → 索引 (1/0)
@ -115,4 +117,4 @@ private:
} // namespace cmvr } // namespace cmvr
#endif // CMVR_ES_OPT_PSI_LIMIT_BIAS_SLOVER_H #endif // CMVR_ES_LAWBA_IK_SOLVER_H

View File

@ -7,7 +7,7 @@
#include <cmath> #include <cmath>
#include <vector> #include <vector>
#include "common/math/include/support_functions.h" #include "common/math/support_functions.h"
namespace cmvr { namespace cmvr {
class OptPsiSelector { class OptPsiSelector {

View File

@ -2,7 +2,7 @@
// Created by lgv on 2025/11/3. // Created by lgv on 2025/11/3.
// //
#include "ik_solver/include/joints_limit_analyzer.h" #include "algorithms/kinematics/ik_solver/lawba/include/joints_limit_analyzer.h"
#include <algorithm> #include <algorithm>
#include <limits> #include <limits>

View File

@ -1,14 +1,17 @@
// //
// Created by lgv on 11/7/25. // Created by lgv on 11/7/25.
// //
#include "ik_solver/include/lawba_ik_solver.h" #include "algorithms/kinematics/ik_solver/lawba/include/lawba_ik_solver.h"
#include "common/math/include/support_functions.h" #include "common/math/support_functions.h"
using namespace cmvr; using namespace cmvr;
LawbaIKSolver::LawbaIKSolver() : IKSolver("", "", "") { LawbaIKSolver::LawbaIKSolver(const config::LawbaIKConfig& cfg)
bias_srs_ik_solver_ = std::make_shared<BiasSRSIkSolver>(); : IKSolver("", "", "")
, config_(cfg)
{
srs_ik_solver_ = std::make_shared<SrsIKSolver>(config_.srs_config());
joints_limit_analyzer_ = std::make_shared<JointsLimitAnalyzer>(); joints_limit_analyzer_ = std::make_shared<JointsLimitAnalyzer>();
opt_psi_selector_ = std::make_shared<OptPsiSelector>(); opt_psi_selector_ = std::make_shared<OptPsiSelector>();
@ -16,6 +19,10 @@ LawbaIKSolver::LawbaIKSolver() : IKSolver("", "", "") {
} }
bool LawbaIKSolver::init() { bool LawbaIKSolver::init() {
if (!srs_ik_solver_ || !srs_ik_solver_->init()) {
return false;
}
Eigen::Matrix4d T_tool_flange, T_arm_robot; Eigen::Matrix4d T_tool_flange, T_arm_robot;
T_tool_flange << 0, 1, 0, -0.284077, T_tool_flange << 0, 1, 0, -0.284077,
@ -36,14 +43,23 @@ bool LawbaIKSolver::init() {
T_tool_flange_ = T_tool_flange; T_tool_flange_ = T_tool_flange;
T_arm_robot_ = T_arm_robot; T_arm_robot_ = T_arm_robot;
// 臂角更新参数 const double opt_psi_update_alpha =
opt_psi_selector_->set_update_params(0.6, 5.0, -1, 1e-4); config_.opt_psi_update_alpha() > 0.0 ? config_.opt_psi_update_alpha() : 0.6;
const double opt_psi_max_delta =
config_.opt_psi_max_delta() > 0.0 ? config_.opt_psi_max_delta() : 5.0;
const double opt_psi_min =
config_.opt_psi_min() != 0.0 ? config_.opt_psi_min() : -1.0;
const double opt_psi_eps =
config_.opt_psi_eps() > 0.0 ? config_.opt_psi_eps() : 1e-4;
opt_psi_selector_->set_update_params(opt_psi_update_alpha,
opt_psi_max_delta,
opt_psi_min,
opt_psi_eps);
// 代价参数(可之后再通过 set_cost_params 调整) set_cost_params(config_.lambda_q() > 0.0 ? config_.lambda_q() : 5.5,
set_cost_params( config_.cur_branch_cost_threshold() > 0.0
5.5, // lambda_q_ ? config_.cur_branch_cost_threshold()
1e-3 // cur_branch_cost_threshold_ : 1e-3);
);
return true; return true;
} }
@ -51,7 +67,7 @@ bool LawbaIKSolver::init() {
bool LawbaIKSolver::fk(const std::vector<double> &joints_angle, bool LawbaIKSolver::fk(const std::vector<double> &joints_angle,
Eigen::Matrix4d &cur_pose, Eigen::Matrix4d &cur_pose,
bool is_tcp) { bool is_tcp) {
cur_pose = T_arm_robot_ * bias_srs_ik_solver_->calc_total_transform(joints_angle); cur_pose = T_arm_robot_ * srs_ik_solver_->calc_total_transform(joints_angle);
if (is_tcp) { if (is_tcp) {
cur_pose = cur_pose * T_tool_flange_; cur_pose = cur_pose * T_tool_flange_;
} else { } else {
@ -67,10 +83,10 @@ bool LawbaIKSolver::estimate_state_from_current_joints() {
// 1) 当前位姿 & 系数矩阵 // 1) 当前位姿 & 系数矩阵
Eigen::Matrix4d cur_pose = Eigen::Matrix4d cur_pose =
bias_srs_ik_solver_->calc_total_transform(cur_joints_angle_); srs_ik_solver_->calc_total_transform(cur_joints_angle_);
Eigen::MatrixXd s_mat(3, 9), w_mat(3, 9); Eigen::MatrixXd s_mat(3, 9), w_mat(3, 9);
bias_srs_ik_solver_->cal_coefficient_matrix(cur_pose, s_mat, w_mat); srs_ik_solver_->cal_coefficient_matrix(cur_pose, s_mat, w_mat);
// 2) 清空所有分支的缓存 // 2) 清空所有分支的缓存
for (int i = 0; i < 2; ++i) for (int i = 0; i < 2; ++i)
@ -83,14 +99,14 @@ bool LawbaIKSolver::estimate_state_from_current_joints() {
double best_score = std::numeric_limits<double>::infinity(); double best_score = std::numeric_limits<double>::infinity();
// 3) 枚举所有 (s,e,w) 分支组合 // 3) 枚举所有 (s,e,w) 分支组合
BiasSRSIkSolver::ConfigDirection dirs[2] = { SrsIKSolver::ConfigDirection dirs[2] = {
BiasSRSIkSolver::ConfigDirection::OUTWARD, SrsIKSolver::ConfigDirection::OUTWARD,
BiasSRSIkSolver::ConfigDirection::INWARD SrsIKSolver::ConfigDirection::INWARD
}; };
for (BiasSRSIkSolver::ConfigDirection s_dir: dirs) { for (SrsIKSolver::ConfigDirection s_dir: dirs) {
for (BiasSRSIkSolver::ConfigDirection e_dir: dirs) { for (SrsIKSolver::ConfigDirection e_dir: dirs) {
for (BiasSRSIkSolver::ConfigDirection w_dir: dirs) { for (SrsIKSolver::ConfigDirection w_dir: dirs) {
int s = sign_from_dir(s_dir); int s = sign_from_dir(s_dir);
int e = sign_from_dir(e_dir); int e = sign_from_dir(e_dir);
int w = sign_from_dir(w_dir); int w = sign_from_dir(w_dir);
@ -131,10 +147,9 @@ bool LawbaIKSolver::estimate_state_from_current_joints() {
return false; return false;
} }
// 4) 把求得的“最佳分支”应用到 bias_srs_ik_solver_保持一致 srs_ik_solver_->set_shoulder_config(dir_from_sign(best.s_conf));
bias_srs_ik_solver_->set_shoulder_config(dir_from_sign(best.s_conf)); srs_ik_solver_->set_elbow_config(dir_from_sign(best.e_conf));
bias_srs_ik_solver_->set_elbow_config(dir_from_sign(best.e_conf)); srs_ik_solver_->set_wrist_config(dir_from_sign(best.w_conf));
bias_srs_ik_solver_->set_wrist_config(dir_from_sign(best.w_conf));
branch_state_ = best; branch_state_ = best;
return true; return true;
@ -147,7 +162,7 @@ bool LawbaIKSolver::solve_on_branch(const Eigen::Matrix4d &target_cal_pose,
std::vector<double> &q_out, std::vector<double> &q_out,
double &psi_out, double &psi_out,
double &cost_out) { double &cost_out) {
auto joints_limits = bias_srs_ik_solver_->get_joints_limits(); auto joints_limits = srs_ik_solver_->get_joints_limits();
// 1) 这一分支下的 ψ 可行区间 // 1) 这一分支下的 ψ 可行区间
auto limits = joints_limit_analyzer_->calc_arm_angle_limits( auto limits = joints_limit_analyzer_->calc_arm_angle_limits(
@ -167,14 +182,13 @@ bool LawbaIKSolver::solve_on_branch(const Eigen::Matrix4d &target_cal_pose,
return false; return false;
} }
// 3) 把 bias_srs_ik_solver_ 的分支设置为当前 branch srs_ik_solver_->set_shoulder_config(dir_from_sign(branch.s_conf));
bias_srs_ik_solver_->set_shoulder_config(dir_from_sign(branch.s_conf)); srs_ik_solver_->set_elbow_config(dir_from_sign(branch.e_conf));
bias_srs_ik_solver_->set_elbow_config(dir_from_sign(branch.e_conf)); srs_ik_solver_->set_wrist_config(dir_from_sign(branch.w_conf));
bias_srs_ik_solver_->set_wrist_config(dir_from_sign(branch.w_conf));
// 4) 解析 IK // 4) 解析 IK
std::vector<double> q; std::vector<double> q;
if (!bias_srs_ik_solver_->inverse_kinematics(target_cal_pose, q, best_psi)) { if (!srs_ik_solver_->ikWithPsi(target_cal_pose, q, best_psi)) {
return false; return false;
} }
@ -217,17 +231,17 @@ bool LawbaIKSolver::ik(const Eigen::Matrix4d &target_pose,
// --- 1) 校验当前缓存的 branch_state_ 是否仍然对应 cur_joints_angle_ --- // --- 1) 校验当前缓存的 branch_state_ 是否仍然对应 cur_joints_angle_ ---
if (branch_state_.valid) { if (branch_state_.valid) {
Eigen::Matrix4d cur_pose = Eigen::Matrix4d cur_pose =
bias_srs_ik_solver_->calc_total_transform(cur_joints_angle_); srs_ik_solver_->calc_total_transform(cur_joints_angle_);
// 确保 solver 内部分支与 branch_state_ 一致 // 确保 solver 内部分支与 branch_state_ 一致
bias_srs_ik_solver_->set_shoulder_config(dir_from_sign(branch_state_.s_conf)); srs_ik_solver_->set_shoulder_config(dir_from_sign(branch_state_.s_conf));
bias_srs_ik_solver_->set_elbow_config(dir_from_sign(branch_state_.e_conf)); srs_ik_solver_->set_elbow_config(dir_from_sign(branch_state_.e_conf));
bias_srs_ik_solver_->set_wrist_config(dir_from_sign(branch_state_.w_conf)); srs_ik_solver_->set_wrist_config(dir_from_sign(branch_state_.w_conf));
std::vector<double> cur_joints_angle; std::vector<double> cur_joints_angle;
if (!bias_srs_ik_solver_->inverse_kinematics(cur_pose, cur_joints_angle, if (!srs_ik_solver_->ikWithPsi(cur_pose, cur_joints_angle,
branch_state_.psi)) { branch_state_.psi)) {
branch_state_.valid = false; branch_state_.valid = false;
} else { } else {
for (int i = 0; i < static_cast<int>(cur_joints_angle_.size()); ++i) { for (int i = 0; i < static_cast<int>(cur_joints_angle_.size()); ++i) {
@ -248,7 +262,7 @@ bool LawbaIKSolver::ik(const Eigen::Matrix4d &target_pose,
// --- 3) 只算一次 target 位姿的系数矩阵 --- // --- 3) 只算一次 target 位姿的系数矩阵 ---
Eigen::MatrixXd s_mat(3, 9), w_mat(3, 9); Eigen::MatrixXd s_mat(3, 9), w_mat(3, 9);
bias_srs_ik_solver_->cal_coefficient_matrix(target_cal_pose, s_mat, w_mat); srs_ik_solver_->cal_coefficient_matrix(target_cal_pose, s_mat, w_mat);
// --- 4) 先在“当前分支”上试一次 --- // --- 4) 先在“当前分支”上试一次 ---
std::vector<double> q_cur; std::vector<double> q_cur;
@ -284,14 +298,14 @@ bool LawbaIKSolver::ik(const Eigen::Matrix4d &target_pose,
best_state.valid = true; best_state.valid = true;
} }
BiasSRSIkSolver::ConfigDirection dirs[2] = { SrsIKSolver::ConfigDirection dirs[2] = {
BiasSRSIkSolver::ConfigDirection::OUTWARD, SrsIKSolver::ConfigDirection::OUTWARD,
BiasSRSIkSolver::ConfigDirection::INWARD SrsIKSolver::ConfigDirection::INWARD
}; };
for (BiasSRSIkSolver::ConfigDirection s_dir: dirs) { for (SrsIKSolver::ConfigDirection s_dir: dirs) {
for (BiasSRSIkSolver::ConfigDirection e_dir: dirs) { for (SrsIKSolver::ConfigDirection e_dir: dirs) {
for (BiasSRSIkSolver::ConfigDirection w_dir: dirs) { for (SrsIKSolver::ConfigDirection w_dir: dirs) {
int s = sign_from_dir(s_dir); int s = sign_from_dir(s_dir);
int e = sign_from_dir(e_dir); int e = sign_from_dir(e_dir);
int w = sign_from_dir(w_dir); int w = sign_from_dir(w_dir);
@ -353,9 +367,9 @@ bool LawbaIKSolver::ik(const Eigen::Matrix4d &target_pose,
// --- 6) 用最佳分支 + ψ 更新状态并返回 --- // --- 6) 用最佳分支 + ψ 更新状态并返回 ---
branch_state_ = best_state; branch_state_ = best_state;
bias_srs_ik_solver_->set_shoulder_config(dir_from_sign(branch_state_.s_conf)); srs_ik_solver_->set_shoulder_config(dir_from_sign(branch_state_.s_conf));
bias_srs_ik_solver_->set_elbow_config(dir_from_sign(branch_state_.e_conf)); srs_ik_solver_->set_elbow_config(dir_from_sign(branch_state_.e_conf));
bias_srs_ik_solver_->set_wrist_config(dir_from_sign(branch_state_.w_conf)); srs_ik_solver_->set_wrist_config(dir_from_sign(branch_state_.w_conf));
joints_angle = best_q; joints_angle = best_q;
cur_joints_angle_ = joints_angle; // 作为下一次的“当前姿态” cur_joints_angle_ = joints_angle; // 作为下一次的“当前姿态”

View File

@ -2,7 +2,7 @@
// Created by lgv on 11/7/25. // Created by lgv on 11/7/25.
// //
#include "ik_solver/include/opt_psi_selector.h" #include "algorithms/kinematics/ik_solver/lawba/include/opt_psi_selector.h"
using namespace cmvr; using namespace cmvr;

View File

@ -0,0 +1,95 @@
#pragma once
#include "algorithms/kinematics/ik_solver/pinocchio/include/pinocchio_ik_base.h"
#include <Eigen/Core>
#include <limits>
#include <memory>
#include <string>
#include <vector>
#include "cmvr/config/pinocchio_dls_ik_config.pb.h"
namespace cmvr {
/**
* @brief Pinocchio DLS
*
*
* - 姿 IK`ik`
* - FK`fk`
* - twist IK`ik`
*
* URDF `base_frame_name -> flange_frame_name`
* TCP frame
*/
class PinocchioDlsIKSolver : public PinocchioIKBase {
public:
explicit PinocchioDlsIKSolver(const config::PinocchioDlsIKConfig& cfg);
~PinocchioDlsIKSolver() override = default;
bool init() override;
bool ik(const Eigen::Matrix4d &target_pose,
std::vector<double> &joints_angle,
bool is_tcp = true) override;
bool ik(const std::string& base_link,
const std::string& ee_link,
const Eigen::Matrix4d& target_pose,
std::vector<double>& joints_angle);
bool ik(const std::string& base_link,
const std::string& ee_link,
const Eigen::Matrix<double,6,1>& target_vel,
std::vector<double>& joints_vel,
double damping = -1.0,
double qdot_abs_max = std::numeric_limits<double>::infinity());
bool solveVelocityBase(const Eigen::MatrixXd& jacobian_base,
const Eigen::Matrix<double,6,1>& target_twist_base,
const std::vector<double>& q_chain,
std::vector<double>& qdot_out,
double qdot_abs_max = std::numeric_limits<double>::infinity()) const override;
void setJointLimitAvoidance(bool enable,
double gain = 0.2,
double margin_ratio = 0.15,
double max_push = 0.25);
void setMaxIters(int iters) { max_iters_ = iters; }
void setDamping(double d) { damping_ = d; }
void setEps(double pos_eps, double rot_eps) { pos_eps_ = pos_eps; rot_eps_ = rot_eps; }
double damping() const { return damping_; }
private:
Eigen::MatrixXd dampedPseudoInverse(const Eigen::MatrixXd &J, double lambda);
bool refreshJointLimits_(const config::PinocchioDlsIKConfig& cfg);
Eigen::VectorXd computeJointLimitAvoidanceVelocity(const Eigen::VectorXd& q_chain) const;
Eigen::VectorXd projectToNullspace(const Eigen::MatrixXd& J_pinv,
const Eigen::MatrixXd& J,
const Eigen::VectorXd& secondary) const;
private:
bool limit_avoidance_enabled_{false};
double limit_avoidance_gain_{0.2};
double limit_avoidance_margin_ratio_{0.15};
double limit_avoidance_max_push_{0.25};
bool initialized_{false};
int max_iters_;
double pos_eps_;
double rot_eps_;
double damping_;
config::PinocchioDlsIKConfig config_;
};
} // namespace cmvr

View File

@ -1,8 +1,8 @@
// Created by Codex on 2026/3/3. // Created by Codex on 2026/3/3.
#pragma once #pragma once
#include "ik_solver/include/ik_solver.h" #include "algorithms/kinematics/ik_solver/common/include/ik_solver.h"
#include "ik_solver/include/urdf_parser.h" #include "algorithms/kinematics/ik_solver/common/include/urdf_parser.h"
#include <pinocchio/multibody/model.hpp> #include <pinocchio/multibody/model.hpp>
#include <pinocchio/multibody/data.hpp> #include <pinocchio/multibody/data.hpp>
@ -11,6 +11,7 @@
#include <Eigen/Core> #include <Eigen/Core>
#include <memory> #include <memory>
#include <limits>
#include <string> #include <string>
namespace cmvr { namespace cmvr {
@ -70,6 +71,39 @@ public:
const std::vector<double>& joints_angle, const std::vector<double>& joints_angle,
Eigen::Matrix4d& cur_pose); Eigen::Matrix4d& cur_pose);
int chainDof() const { return chain_q_dof_; }
int chainVelocityDof() const { return chain_v_dof_; }
bool computeJacobianBaseAtQ(const std::vector<double>& q_chain,
bool is_tcp,
Eigen::MatrixXd& jacobian_base,
Eigen::Matrix3d& base_R_ee);
bool computeJacobianBaseAtQ(const std::vector<double>& q_chain,
const std::string& ee_frame_name,
Eigen::MatrixXd& jacobian_base,
Eigen::Matrix3d& base_R_ee);
bool computeTwistBaseAtQ(const std::vector<double>& q_chain,
const std::vector<double>& qdot_chain,
bool is_tcp,
Eigen::Matrix<double, 6, 1>& twist_base,
Eigen::MatrixXd* jacobian_base_out = nullptr,
Eigen::Matrix3d* base_R_ee_out = nullptr);
bool computeTwistBaseAtQ(const std::vector<double>& q_chain,
const std::vector<double>& qdot_chain,
const std::string& ee_frame_name,
Eigen::Matrix<double, 6, 1>& twist_base,
Eigen::MatrixXd* jacobian_base_out = nullptr,
Eigen::Matrix3d* base_R_ee_out = nullptr);
virtual bool solveVelocityBase(const Eigen::MatrixXd& jacobian_base,
const Eigen::Matrix<double, 6, 1>& target_twist_base,
const std::vector<double>& q_chain,
std::vector<double>& qdot_out,
double qdot_abs_max = std::numeric_limits<double>::infinity()) const;
protected: protected:
/** /**
* @brief URDF Pinocchio frame * @brief URDF Pinocchio frame
@ -96,6 +130,29 @@ protected:
Eigen::VectorXd& q_full, Eigen::VectorXd& q_full,
const char* context) const; const char* context) const;
bool buildFullQFromChain(const Eigen::VectorXd& q_chain,
Eigen::VectorXd& q_full,
const char* context) const;
Eigen::MatrixXd extractChainJacobian(
const Eigen::Matrix<double, 6, Eigen::Dynamic>& jacobian_full) const;
const pinocchio::SE3& getBasePoseWorld() const;
bool buildJacobianBaseAtQ(const std::vector<double>& q_chain,
pinocchio::FrameIndex ee_id,
Eigen::MatrixXd& jacobian_base,
Eigen::Matrix3d& base_R_ee,
Eigen::VectorXd* q_full_out = nullptr);
bool computeMeasuredTwistBase(const std::vector<double>& q_chain,
const std::vector<double>& qdot_chain,
pinocchio::FrameIndex ee_id,
Eigen::Matrix<double, 6, 1>& twist_base,
Eigen::MatrixXd* jacobian_base_out = nullptr,
Eigen::Matrix3d* base_R_ee_out = nullptr,
Eigen::VectorXd* q_full_out = nullptr);
/** /**
* @brief `model_` FK frame placement * @brief `model_` FK frame placement
*/ */
@ -129,6 +186,10 @@ protected:
/** @brief 是否存在有效 TCP frame。 */ /** @brief 是否存在有效 TCP frame。 */
bool has_tcp_{false}; bool has_tcp_{false};
/** @brief 配置链路 base 在 world 下的中性位姿缓存。 */
bool base_pose_cached_{false};
pinocchio::SE3 oM_base_cached_;
/** @brief 当前链在 full-model `q` 中的起始索引。 */ /** @brief 当前链在 full-model `q` 中的起始索引。 */
int chain_q_start_{0}; int chain_q_start_{0};
/** @brief 当前链关节位置自由度数量。 */ /** @brief 当前链关节位置自由度数量。 */

View File

@ -7,41 +7,22 @@
#pragma once #pragma once
#include "ik_solver/include/pinocchio_ik_base.h" #include "algorithms/kinematics/ik_solver/pinocchio/include/pinocchio_ik_base.h"
#include "common/math/include/qp_solver.h" #include "common/math/qp_solver.h"
#include <Eigen/Core> #include <Eigen/Core>
#include <limits>
#include <memory> #include <memory>
#include <string> #include <string>
#include <vector> #include <vector>
#include "cmvr/config/pinocchio_qp_ik_solver_config.pb.h" #include "cmvr/config/pinocchio_qp_ik_config.pb.h"
namespace cmvr { namespace cmvr {
class PinocchioQpIKSolver : public PinocchioIKBase { class PinocchioQpIKSolver : public PinocchioIKBase {
public: public:
/// urdf_path : URDF 路径(可以是单臂,也可以是双臂整机) explicit PinocchioQpIKSolver(const config::PinocchioQpIKConfig& config);
/// base_frame_name : 作为 IK 基坐标系的 frame 名PELVIS_S
/// flange_frame_name : 末端法兰 frame 名L_FLANGE
/// tcp_frame_name : TCP frame 名(可为空;为空则 is_tcp=true 时也用法兰)
/// lambda : 阻尼cost 中的 √lambda * I
/// w_posrot : 位置 / 姿态误差权重0~1越大越重姿态
/// max_iters : 外层迭代上限
/// tol : 误差收敛阈值(||任务误差||
/// qp_time_limit : 每次 QP 的时间限制(秒)
PinocchioQpIKSolver(const std::string &urdf_path,
const std::string &base_frame_name,
const std::string &flange_frame_name,
const std::string &tcp_frame_name = std::string(),
double lambda = 1e-4,
double w_posrot = 0.5,
int max_iters = 100,
double tol = 1e-6,
double qp_time_limit = 1e-2);
PinocchioQpIKSolver();
PinocchioQpIKSolver(const config::PinocchioQpIKConfig &config);
~PinocchioQpIKSolver() override = default; ~PinocchioQpIKSolver() override = default;
@ -57,6 +38,12 @@ public:
std::vector<double> &joints_angle, std::vector<double> &joints_angle,
bool is_tcp = true) override; bool is_tcp = true) override;
bool solveVelocityBase(const Eigen::MatrixXd& jacobian_base,
const Eigen::Matrix<double, 6, 1>& target_twist_base,
const std::vector<double>& q_chain,
std::vector<double>& qdot_out,
double qdot_abs_max = std::numeric_limits<double>::infinity()) const override;
/// 如你有更严格的速度 / 加速度限位,可以覆盖默认值 /// 如你有更严格的速度 / 加速度限位,可以覆盖默认值
void setVelocityLimits(const Eigen::VectorXd &qd_max); void setVelocityLimits(const Eigen::VectorXd &qd_max);
void setAccelerationLimits(const Eigen::VectorXd &qdd_max); void setAccelerationLimits(const Eigen::VectorXd &qdd_max);

View File

@ -0,0 +1,473 @@
// Created by lgv on 11/28/25.
#include "algorithms/kinematics/ik_solver/pinocchio/include/pinocchio_dls_ik_solver.h"
#include "common/base/logging/logger.h"
#include "common/math/joint_limits.h"
#include "algorithms/kinematics/ik_solver/common/include/urdf_parser.h"
#include "common/config/config_files.h"
#include <pinocchio/algorithm/frames.hpp>
#include <pinocchio/algorithm/kinematics.hpp>
#include <pinocchio/algorithm/jacobian.hpp>
#include <pinocchio/spatial/explog.hpp>
#include <Eigen/SVD>
#include <algorithm>
#include <cmath>
#include <limits>
#include <unordered_map>
namespace cmvr {
using cmvr::common::config::positiveOr;
Eigen::MatrixXd PinocchioDlsIKSolver::dampedPseudoInverse(const Eigen::MatrixXd &J, double lambda) {
const int m = J.rows();
const int n = J.cols();
const double l2 = lambda * lambda;
if (m <= n) {
Eigen::MatrixXd I = Eigen::MatrixXd::Identity(m, m);
Eigen::MatrixXd JJt = J * J.transpose() + l2 * I;
return J.transpose() * JJt.inverse();
} else {
Eigen::MatrixXd I = Eigen::MatrixXd::Identity(n, n);
Eigen::MatrixXd JtJ = J.transpose() * J + l2 * I;
return JtJ.inverse() * J.transpose();
}
}
PinocchioDlsIKSolver::PinocchioDlsIKSolver(const config::PinocchioDlsIKConfig& cfg)
: PinocchioIKBase(cfg.urdf_path(),
cfg.base_frame_name(),
cfg.flange_frame_name(),
cfg.tcp_frame_name())
, max_iters_(cfg.max_iters() > 0 ? cfg.max_iters() : 100)
, pos_eps_(cfg.pos_eps() > 0.0 ? cfg.pos_eps() : 1e-6)
, rot_eps_(cfg.rot_eps() > 0.0 ? cfg.rot_eps() : 1e-6)
, damping_(cfg.damping() > 0.0 ? cfg.damping() : 1e-4)
, config_(cfg)
{
if (cfg.has_joint_limit_avoidance()) {
const auto& avoidance = cfg.joint_limit_avoidance();
setJointLimitAvoidance(
avoidance.enable(),
positiveOr(avoidance.gain(), limit_avoidance_gain_),
positiveOr(avoidance.margin_ratio(), limit_avoidance_margin_ratio_),
positiveOr(avoidance.max_push(), limit_avoidance_max_push_));
}
}
bool PinocchioDlsIKSolver::refreshJointLimits_(const config::PinocchioDlsIKConfig& cfg) {
const auto source = cfg.has_joint_limits()
? cfg.joint_limits().source()
: config::JOINT_LIMIT_SOURCE_URDF;
if (source == config::JOINT_LIMIT_SOURCE_UNKNOWN ||
source == config::JOINT_LIMIT_SOURCE_URDF) {
return true;
}
if (source != config::JOINT_LIMIT_SOURCE_CUSTOM) {
CMVR_LOG(ERROR) << "[PinocchioDlsIKSolver] unsupported joint limit source";
return false;
}
std::vector<std::string> joint_names;
if (!getChainJointNames(joint_names) || joint_names.empty()) {
CMVR_LOG(ERROR) << "[PinocchioDlsIKSolver] failed to get chain joint names for custom limits";
return false;
}
std::unordered_map<std::string, config::JointLimitConfig> custom_limits;
if (cfg.has_joint_limits()) {
custom_limits.reserve(static_cast<std::size_t>(cfg.joint_limits().joints_size()));
for (const auto& item : cfg.joint_limits().joints()) {
if (!item.joint_name().empty()) {
custom_limits[item.joint_name()] = item;
}
}
}
const auto dof = static_cast<Eigen::Index>(joint_names.size());
joint_pos_lower_limits_.resize(dof);
joint_pos_upper_limits_.resize(dof);
joint_vel_limits_.resize(dof);
for (Eigen::Index i = 0; i < dof; ++i) {
const auto& joint_name = joint_names[static_cast<std::size_t>(i)];
const auto it = custom_limits.find(joint_name);
if (it == custom_limits.end()) {
CMVR_LOG(ERROR) << "[PinocchioDlsIKSolver] missing custom joint limit for " << joint_name;
return false;
}
const auto& limit = it->second;
if (!std::isfinite(limit.lower()) || !std::isfinite(limit.upper()) ||
!std::isfinite(limit.velocity()) || limit.upper() <= limit.lower() ||
limit.velocity() <= 0.0) {
CMVR_LOG(ERROR) << "[PinocchioDlsIKSolver] invalid custom joint limit for "
<< limit.joint_name();
return false;
}
joint_pos_lower_limits_[i] = limit.lower();
joint_pos_upper_limits_[i] = limit.upper();
joint_vel_limits_[i] = std::abs(limit.velocity());
}
return true;
}
Eigen::VectorXd PinocchioDlsIKSolver::computeJointLimitAvoidanceVelocity(
const Eigen::VectorXd& q_chain) const {
if (!limit_avoidance_enabled_ ||
limit_avoidance_gain_ <= 0.0 ||
chain_v_dof_ != chain_q_dof_ ||
q_chain.size() != chain_q_dof_ ||
joint_pos_lower_limits_.size() != chain_q_dof_ ||
joint_pos_upper_limits_.size() != chain_q_dof_) {
return Eigen::VectorXd::Zero(chain_v_dof_);
}
return cmvr::kinematics::computeJointLimitAvoidanceVelocity(
q_chain,
joint_pos_lower_limits_,
joint_pos_upper_limits_,
limit_avoidance_enabled_,
limit_avoidance_gain_,
limit_avoidance_margin_ratio_,
limit_avoidance_max_push_);
}
Eigen::VectorXd PinocchioDlsIKSolver::projectToNullspace(const Eigen::MatrixXd& J_pinv,
const Eigen::MatrixXd& J,
const Eigen::VectorXd& secondary) const {
if (secondary.size() != J.cols()) {
return Eigen::VectorXd::Zero(J.cols());
}
const Eigen::MatrixXd N =
Eigen::MatrixXd::Identity(J.cols(), J.cols()) - J_pinv * J;
return N * secondary;
}
bool PinocchioDlsIKSolver::init() {
UrdfParser::ChainInfo chain_info;
std::string err;
if (!initPinocchioFromUrdfChain(&chain_info, &err)) {
CMVR_LOG(ERROR) << "[PinocchioDlsIKSolver] Failed to init pinocchio base: " << err;
return false;
}
if (!refreshJointLimits_(config_)) {
return false;
}
{
Eigen::VectorXd q0 = pinocchio::neutral(model_);
updateKinematics(q0);
oM_base_cached_ = data_->oMf[base_frame_id_];
base_pose_cached_ = true;
}
cur_joints_angle_.assign(chain_q_dof_, 0.0);
initialized_ = true;
CMVR_LOG(INFO) << "[PinocchioDlsIKSolver] Chain '" << chain_base_frame_name_ << "' -> '" << chain_tip_frame_name_
<< "': q_start=" << chain_q_start_ << " q_dof=" << chain_q_dof_
<< ", v_start=" << chain_v_start_ << " v_dof=" << chain_v_dof_
<< ", nq=" << model_.nq << " nv=" << model_.nv;
CMVR_LOG(INFO) << "[PinocchioDlsIKSolver] Chain joint position limits (rad):";
for (const auto& seg : chain_info.joints) {
const int q_idx = seg.q_index;
const int nq = seg.nq;
const std::string& jname = seg.name;
if (nq <= 0) continue;
for (int k = 0; k < nq; ++k) {
const int qi = q_idx - chain_q_start_ + k;
if (qi < 0 || qi >= chain_q_dof_) continue;
if (nq == 1) {
CMVR_LOG(INFO) << " - " << jname
<< ": [" << joint_pos_lower_limits_[qi] << ", " << joint_pos_upper_limits_[qi] << "]";
} else {
CMVR_LOG(INFO) << " - " << jname << "[" << k << "]"
<< ": [" << joint_pos_lower_limits_[qi] << ", " << joint_pos_upper_limits_[qi] << "]";
}
}
}
CMVR_LOG(INFO) << "[PinocchioDlsIKSolver] base pose cached (constant)";
return true;
}
bool PinocchioDlsIKSolver::ik(const Eigen::Matrix4d &target_pose_base,
std::vector<double> &joints_angle,
bool is_tcp)
{
const std::string& ee_link = (is_tcp && has_tcp_) ? tcp_frame_name_ : chain_tip_frame_name_;
return ik(chain_base_frame_name_, ee_link, target_pose_base, joints_angle);
}
bool PinocchioDlsIKSolver::ik(const std::string& base_link,
const std::string& ee_link,
const Eigen::Matrix4d& target_pose,
std::vector<double>& joints_angle) {
if (!initialized_) return false;
if ((int)cur_joints_angle_.size() != chain_q_dof_) return false;
if (!model_.existFrame(base_link)) {
CMVR_LOG(ERROR) << "[PinocchioDlsIKSolver] base frame not found: " << base_link;
return false;
}
if (!model_.existFrame(ee_link)) {
CMVR_LOG(ERROR) << "[PinocchioDlsIKSolver] ee frame not found: " << ee_link;
return false;
}
const pinocchio::FrameIndex base_frame_id = model_.getFrameId(base_link);
const pinocchio::FrameIndex ee_frame_id = model_.getFrameId(ee_link);
const pinocchio::JointIndex chain_base_joint = model_.frames[base_frame_id_].parent;
const pinocchio::JointIndex base_joint = model_.frames[base_frame_id].parent;
const pinocchio::JointIndex flange_joint = model_.frames[flange_frame_id_].parent;
const pinocchio::JointIndex ee_joint = model_.frames[ee_frame_id].parent;
auto jointOnParentPath = [this](pinocchio::JointIndex from,
pinocchio::JointIndex target) {
if (target == 0) return true;
pinocchio::JointIndex j = from;
while (j != 0) {
if (j == target) return true;
j = model_.parents[j];
}
return false;
};
auto jointOnConfiguredBranch = [&](pinocchio::JointIndex j) {
return jointOnParentPath(flange_joint, j) && jointOnParentPath(j, chain_base_joint);
};
const bool base_on_branch = jointOnConfiguredBranch(base_joint);
const bool ee_on_branch = jointOnConfiguredBranch(ee_joint);
const bool base_is_ancestor_of_ee =
jointOnParentPath(ee_joint, base_joint);
if (!base_on_branch || !ee_on_branch || !base_is_ancestor_of_ee) {
CMVR_LOG(ERROR) << "[PinocchioDlsIKSolver] base/ee must be on configured single chain and base must be ancestor of ee";
return false;
}
const pinocchio::SE3 base_M_target = matrix4ToSE3(target_pose);
Eigen::VectorXd q_chain = Eigen::Map<Eigen::VectorXd>(cur_joints_angle_.data(), chain_q_dof_);
bool success = false;
for (int iter = 0; iter < max_iters_; ++iter) {
Eigen::VectorXd q_full;
if (!buildFullQFromChain(q_chain, q_full, "ik(base,ee)")) {
return false;
}
updateKinematics(q_full);
const pinocchio::SE3& oM_base =
(base_frame_id == base_frame_id_) ? getBasePoseWorld() : data_->oMf[base_frame_id];
const pinocchio::SE3 oM_target = oM_base * base_M_target;
const pinocchio::SE3 &oM_cur = data_->oMf[ee_frame_id];
pinocchio::SE3 dM = oM_cur.inverse() * oM_target;
Eigen::Matrix<double,6,1> err = pinocchio::log6(dM).toVector();
if (err.head<3>().norm() < pos_eps_ && err.tail<3>().norm() < rot_eps_) {
success = true;
break;
}
Eigen::Matrix<double,6,Eigen::Dynamic> J_full(6, model_.nv);
pinocchio::computeFrameJacobian(model_, *data_, q_full,
ee_frame_id,
pinocchio::ReferenceFrame::LOCAL,
J_full);
Eigen::MatrixXd J = extractChainJacobian(J_full);
Eigen::MatrixXd J_pinv = dampedPseudoInverse(J, damping_);
Eigen::VectorXd dq = J_pinv * err;
q_chain += dq;
q_chain = cmvr::kinematics::clampToJointPositionLimits(
q_chain,
joint_pos_lower_limits_,
joint_pos_upper_limits_);
}
if (!success) {
CMVR_LOG(ERROR) << "[PinocchioDlsIKSolver] IK solve failed";
return false;
}
cur_joints_angle_.assign(q_chain.data(), q_chain.data() + q_chain.size());
joints_angle = cur_joints_angle_;
return true;
}
void PinocchioDlsIKSolver::setJointLimitAvoidance(bool enable,
double gain,
double margin_ratio,
double max_push) {
limit_avoidance_enabled_ = enable;
limit_avoidance_gain_ = std::max(0.0, gain);
limit_avoidance_margin_ratio_ = std::clamp(margin_ratio, 1e-3, 0.49);
limit_avoidance_max_push_ = max_push;
}
bool PinocchioDlsIKSolver::ik(const std::string& base_link,
const std::string& ee_link,
const Eigen::Matrix<double,6,1>& target_vel,
std::vector<double>& joints_vel,
double damping,
double qdot_abs_max)
{
if (!initialized_) return false;
if (!model_.existFrame(base_link)) {
CMVR_LOG(ERROR) << "[PinocchioDlsIKSolver] base frame not found: " << base_link;
return false;
}
if (ee_link.empty()) {
CMVR_LOG(ERROR) << "[PinocchioDlsIKSolver] ee_frame_name is empty";
return false;
}
if (!model_.existFrame(ee_link)) {
CMVR_LOG(ERROR) << "[PinocchioDlsIKSolver] frame not found: " << ee_link;
return false;
}
if (chain_v_dof_ <= 0) {
CMVR_LOG(ERROR) << "[PinocchioDlsIKSolver] invalid chain_v_dof";
return false;
}
if (static_cast<int>(cur_joints_angle_.size()) != chain_q_dof_) {
CMVR_LOG(ERROR) << "[PinocchioDlsIKSolver] ik(velocity) current joint state not initialized";
return false;
}
const pinocchio::FrameIndex base_id = model_.getFrameId(base_link);
const pinocchio::FrameIndex ee_id = model_.getFrameId(ee_link);
const pinocchio::JointIndex chain_base_joint = model_.frames[base_frame_id_].parent;
const pinocchio::JointIndex base_joint = model_.frames[base_id].parent;
const pinocchio::JointIndex flange_joint = model_.frames[flange_frame_id_].parent;
const pinocchio::JointIndex ee_joint = model_.frames[ee_id].parent;
auto jointOnParentPath = [this](pinocchio::JointIndex from,
pinocchio::JointIndex target) {
if (target == 0) return true;
pinocchio::JointIndex j = from;
while (j != 0) {
if (j == target) return true;
j = model_.parents[j];
}
return false;
};
auto jointOnConfiguredBranch = [&](pinocchio::JointIndex j) {
return jointOnParentPath(flange_joint, j) && jointOnParentPath(j, chain_base_joint);
};
const bool base_on_branch = jointOnConfiguredBranch(base_joint);
const bool ee_on_branch = jointOnConfiguredBranch(ee_joint);
const bool base_is_ancestor_of_ee = jointOnParentPath(ee_joint, base_joint);
if (!base_on_branch || !ee_on_branch || !base_is_ancestor_of_ee) {
CMVR_LOG(ERROR) << "[PinocchioDlsIKSolver] base/ee must be on configured single chain and base must be ancestor of ee";
return false;
}
const Eigen::Map<const Eigen::VectorXd> q_chain(cur_joints_angle_.data(), chain_q_dof_);
Eigen::VectorXd q_full;
if (!buildFullQFromChain(q_chain, q_full, "ik(velocity)")) {
return false;
}
updateKinematics(q_full);
const pinocchio::SE3 &oM_base =
(base_id == base_frame_id_) ? getBasePoseWorld() : data_->oMf[base_id];
const pinocchio::SE3 &oM_ee = data_->oMf[ee_id];
const pinocchio::SE3 base_M_ee = oM_base.inverse() * oM_ee;
const Eigen::Matrix3d R_be = base_M_ee.rotation();
Eigen::Matrix<double,6,1> twist_base;
twist_base.head<3>() = R_be * target_vel.head<3>();
twist_base.tail<3>() = R_be * target_vel.tail<3>();
Eigen::Matrix<double,6,Eigen::Dynamic> J_world(6, model_.nv);
pinocchio::computeFrameJacobian(model_, *data_, q_full,
ee_id,
pinocchio::ReferenceFrame::LOCAL_WORLD_ALIGNED,
J_world);
Eigen::MatrixXd J = extractChainJacobian(J_world);
const Eigen::Matrix3d R_bo = oM_base.rotation().transpose();
J.topRows(3) = R_bo * J.topRows(3);
J.bottomRows(3) = R_bo * J.bottomRows(3);
const double lambda = (damping > 0.0) ? damping : damping_;
Eigen::Matrix<double,6,6> A = J * J.transpose();
A.diagonal().array() += (lambda * lambda);
Eigen::LDLT<Eigen::Matrix<double,6,6>> ldlt(A);
if (ldlt.info() != Eigen::Success) {
CMVR_LOG(ERROR) << "[PinocchioDlsIKSolver] ik(velocity) LDLT failed";
return false;
}
Eigen::VectorXd qdot = J.transpose() * ldlt.solve(twist_base);
const Eigen::VectorXd qdot_avoid = computeJointLimitAvoidanceVelocity(q_chain);
if (qdot_avoid.size() == chain_v_dof_ && qdot_avoid.squaredNorm() > 1e-16) {
const Eigen::Matrix<double,6,6> A_inv =
ldlt.solve(Eigen::Matrix<double,6,6>::Identity());
const Eigen::MatrixXd J_pinv = J.transpose() * A_inv;
qdot += projectToNullspace(J_pinv, J, qdot_avoid);
}
joints_vel.resize(chain_v_dof_);
const Eigen::VectorXd qdot_limited =
cmvr::kinematics::scaleToVelocityLimits(qdot, joint_vel_limits_, qdot_abs_max);
for (int i = 0; i < chain_v_dof_; ++i) {
joints_vel[i] = qdot_limited[i];
}
return true;
}
bool PinocchioDlsIKSolver::solveVelocityBase(const Eigen::MatrixXd& jacobian_base,
const Eigen::Matrix<double,6,1>& target_twist_base,
const std::vector<double>& q_chain_std,
std::vector<double>& qdot_out,
const double qdot_abs_max) const
{
if (jacobian_base.rows() != 6 || jacobian_base.cols() != chain_v_dof_) {
CMVR_LOG(ERROR) << "[PinocchioDlsIKSolver] solveVelocityBase failed: jacobian size mismatch";
return false;
}
if (static_cast<int>(q_chain_std.size()) != chain_q_dof_) {
CMVR_LOG(ERROR) << "[PinocchioDlsIKSolver] solveVelocityBase failed: q size mismatch";
return false;
}
Eigen::Matrix<double,6,6> A = jacobian_base * jacobian_base.transpose();
A.diagonal().array() += (damping_ * damping_);
Eigen::LDLT<Eigen::Matrix<double,6,6>> ldlt(A);
if (ldlt.info() != Eigen::Success) {
CMVR_LOG(ERROR) << "[PinocchioDlsIKSolver] solveVelocityBase failed: LDLT failed";
return false;
}
Eigen::VectorXd qdot = jacobian_base.transpose() * ldlt.solve(target_twist_base);
const Eigen::Map<const Eigen::VectorXd> q_chain(q_chain_std.data(), chain_q_dof_);
const Eigen::VectorXd qdot_avoid = computeJointLimitAvoidanceVelocity(q_chain);
if (qdot_avoid.size() == chain_v_dof_ && qdot_avoid.squaredNorm() > 1e-16) {
const Eigen::Matrix<double,6,6> A_inv =
ldlt.solve(Eigen::Matrix<double,6,6>::Identity());
const Eigen::MatrixXd J_pinv = jacobian_base.transpose() * A_inv;
qdot += projectToNullspace(J_pinv, jacobian_base, qdot_avoid);
}
qdot = cmvr::kinematics::scaleToVelocityLimits(qdot, joint_vel_limits_, qdot_abs_max);
qdot_out.resize(chain_v_dof_);
for (int i = 0; i < chain_v_dof_; ++i) {
qdot_out[i] = qdot[i];
}
return true;
}
} // namespace cmvr

View File

@ -0,0 +1,330 @@
// Created by Codex on 2026/3/3.
#include "algorithms/kinematics/ik_solver/pinocchio/include/pinocchio_ik_base.h"
#include "common/base/logging/logger.h"
#include <pinocchio/algorithm/frames.hpp>
#include <pinocchio/algorithm/jacobian.hpp>
#include <pinocchio/algorithm/kinematics.hpp>
namespace cmvr {
PinocchioIKBase::PinocchioIKBase(const std::string& urdf_path,
const std::string& base_frame_name,
const std::string& flange_frame_name,
const std::string& tcp_frame_name)
: IKSolver(urdf_path, base_frame_name, flange_frame_name)
, tcp_frame_name_(tcp_frame_name) {
}
PinocchioIKBase::PinocchioIKBase(std::shared_ptr<const UrdfParser> parser,
const std::string& base_frame_name,
const std::string& flange_frame_name,
const std::string& tcp_frame_name)
: IKSolver(parser, base_frame_name, flange_frame_name)
, tcp_frame_name_(tcp_frame_name) {
}
bool PinocchioIKBase::initPinocchioFromUrdfChain(UrdfParser::ChainInfo* chain_info_out,
std::string* error) {
if (!urdf_parser_ || !urdf_parser_->loaded()) {
if (error) *error = "urdf parser not initialized";
return false;
}
if (chain_base_frame_name_.empty() || chain_tip_frame_name_.empty()) {
if (error) *error = "chain base/tip frame name is empty";
return false;
}
if (!urdf_chain_cached_) {
if (!initUrdfChain(urdf_parser_, chain_base_frame_name_, chain_tip_frame_name_)) {
if (error) *error = "initUrdfChain failed";
return false;
}
}
model_ = urdf_parser_->model();
data_ = std::make_unique<pinocchio::Data>(model_);
UrdfParser::ChainInfo chain_info;
std::string chain_err;
if (!urdf_parser_->extractChain(chain_base_frame_name_, chain_tip_frame_name_, chain_info, &chain_err)) {
if (error) *error = chain_err;
return false;
}
base_frame_id_ = chain_info.base_frame_id;
flange_frame_id_ = chain_info.tip_frame_id;
chain_q_start_ = chain_info.q_start;
chain_q_dof_ = chain_info.q_dof;
chain_v_start_ = chain_info.v_start;
chain_v_dof_ = chain_info.v_dof;
has_tcp_ = false;
tcp_frame_id_ = (pinocchio::FrameIndex)(-1);
if (!tcp_frame_name_.empty() && model_.existFrame(tcp_frame_name_)) {
tcp_frame_id_ = model_.getFrameId(tcp_frame_name_);
has_tcp_ = true;
}
if (chain_info_out != nullptr) {
*chain_info_out = chain_info;
}
return true;
}
bool PinocchioIKBase::buildFullQFromInput(const std::vector<double>& joints,
Eigen::VectorXd& q_full,
const char* context) const {
const int size = static_cast<int>(joints.size());
if (size != chain_q_dof_ && size != model_.nq) {
if (context != nullptr) {
CMVR_LOG(ERROR) << "[PinocchioIKBase] " << context << " joints size mismatch";
}
return false;
}
q_full = pinocchio::neutral(model_);
if (size == model_.nq) {
q_full = Eigen::Map<const Eigen::VectorXd>(joints.data(), model_.nq);
} else {
Eigen::Map<const Eigen::VectorXd> q_chain(joints.data(), chain_q_dof_);
q_full.segment(chain_q_start_, chain_q_dof_) = q_chain;
}
return true;
}
bool PinocchioIKBase::buildFullQFromChain(const Eigen::VectorXd& q_chain,
Eigen::VectorXd& q_full,
const char* context) const {
if (q_chain.size() != chain_q_dof_) {
if (context != nullptr) {
CMVR_LOG(ERROR) << "[PinocchioIKBase] " << context << " chain q size mismatch";
}
return false;
}
q_full = pinocchio::neutral(model_);
q_full.segment(chain_q_start_, chain_q_dof_) = q_chain;
return true;
}
Eigen::MatrixXd PinocchioIKBase::extractChainJacobian(
const Eigen::Matrix<double, 6, Eigen::Dynamic>& jacobian_full) const {
return jacobian_full.middleCols(chain_v_start_, chain_v_dof_);
}
const pinocchio::SE3& PinocchioIKBase::getBasePoseWorld() const {
return base_pose_cached_ ? oM_base_cached_ : data_->oMf[base_frame_id_];
}
void PinocchioIKBase::updateKinematics(const Eigen::VectorXd& q_full) {
pinocchio::forwardKinematics(model_, *data_, q_full);
pinocchio::updateFramePlacements(model_, *data_);
}
bool PinocchioIKBase::buildJacobianBaseAtQ(const std::vector<double>& q_chain_std,
const pinocchio::FrameIndex ee_id,
Eigen::MatrixXd& jacobian_base,
Eigen::Matrix3d& base_R_ee,
Eigen::VectorXd* q_full_out)
{
if (static_cast<int>(q_chain_std.size()) != chain_q_dof_) {
CMVR_LOG(ERROR) << "[PinocchioIKBase] buildJacobianBaseAtQ: q size mismatch";
return false;
}
const Eigen::Map<const Eigen::VectorXd> q_chain(q_chain_std.data(), chain_q_dof_);
Eigen::VectorXd q_full;
if (!buildFullQFromChain(q_chain, q_full, "buildJacobianBaseAtQ")) {
return false;
}
updateKinematics(q_full);
const pinocchio::SE3& oM_base = getBasePoseWorld();
const pinocchio::SE3& oM_ee = data_->oMf[ee_id];
const pinocchio::SE3 base_M_ee = oM_base.inverse() * oM_ee;
base_R_ee = base_M_ee.rotation();
Eigen::Matrix<double, 6, Eigen::Dynamic> jacobian_world(6, model_.nv);
pinocchio::computeFrameJacobian(model_, *data_, q_full,
ee_id,
pinocchio::ReferenceFrame::LOCAL_WORLD_ALIGNED,
jacobian_world);
jacobian_base = extractChainJacobian(jacobian_world);
const Eigen::Matrix3d R_bo = oM_base.rotation().transpose();
jacobian_base.topRows(3) = R_bo * jacobian_base.topRows(3);
jacobian_base.bottomRows(3) = R_bo * jacobian_base.bottomRows(3);
if (q_full_out != nullptr) {
*q_full_out = q_full;
}
return true;
}
bool PinocchioIKBase::computeJacobianBaseAtQ(const std::vector<double>& q_chain,
const bool is_tcp,
Eigen::MatrixXd& jacobian_base,
Eigen::Matrix3d& base_R_ee)
{
const pinocchio::FrameIndex ee_id = (is_tcp && has_tcp_) ? tcp_frame_id_ : flange_frame_id_;
return buildJacobianBaseAtQ(q_chain, ee_id, jacobian_base, base_R_ee, nullptr);
}
bool PinocchioIKBase::computeJacobianBaseAtQ(const std::vector<double>& q_chain,
const std::string& ee_frame_name,
Eigen::MatrixXd& jacobian_base,
Eigen::Matrix3d& base_R_ee)
{
if (!model_.existFrame(ee_frame_name)) {
CMVR_LOG(ERROR) << "[PinocchioIKBase] ee frame not found: " << ee_frame_name;
return false;
}
return buildJacobianBaseAtQ(q_chain,
model_.getFrameId(ee_frame_name),
jacobian_base,
base_R_ee,
nullptr);
}
bool PinocchioIKBase::computeMeasuredTwistBase(const std::vector<double>& q_chain,
const std::vector<double>& qdot_chain,
const pinocchio::FrameIndex ee_id,
Eigen::Matrix<double, 6, 1>& twist_base,
Eigen::MatrixXd* jacobian_base_out,
Eigen::Matrix3d* base_R_ee_out,
Eigen::VectorXd* q_full_out)
{
if (static_cast<int>(qdot_chain.size()) != chain_v_dof_) {
CMVR_LOG(ERROR) << "[PinocchioIKBase] computeMeasuredTwistBase: qdot size mismatch";
return false;
}
Eigen::MatrixXd jacobian_base;
Eigen::Matrix3d base_R_ee = Eigen::Matrix3d::Identity();
if (!buildJacobianBaseAtQ(q_chain, ee_id, jacobian_base, base_R_ee, q_full_out)) {
return false;
}
const Eigen::Map<const Eigen::VectorXd> qdot(qdot_chain.data(), chain_v_dof_);
twist_base = jacobian_base * qdot;
if (jacobian_base_out != nullptr) {
*jacobian_base_out = jacobian_base;
}
if (base_R_ee_out != nullptr) {
*base_R_ee_out = base_R_ee;
}
return true;
}
bool PinocchioIKBase::computeTwistBaseAtQ(const std::vector<double>& q_chain,
const std::vector<double>& qdot_chain,
const bool is_tcp,
Eigen::Matrix<double, 6, 1>& twist_base,
Eigen::MatrixXd* jacobian_base_out,
Eigen::Matrix3d* base_R_ee_out)
{
const pinocchio::FrameIndex ee_id = (is_tcp && has_tcp_) ? tcp_frame_id_ : flange_frame_id_;
return computeMeasuredTwistBase(q_chain,
qdot_chain,
ee_id,
twist_base,
jacobian_base_out,
base_R_ee_out,
nullptr);
}
bool PinocchioIKBase::computeTwistBaseAtQ(const std::vector<double>& q_chain,
const std::vector<double>& qdot_chain,
const std::string& ee_frame_name,
Eigen::Matrix<double, 6, 1>& twist_base,
Eigen::MatrixXd* jacobian_base_out,
Eigen::Matrix3d* base_R_ee_out)
{
if (!model_.existFrame(ee_frame_name)) {
CMVR_LOG(ERROR) << "[PinocchioIKBase] ee frame not found: " << ee_frame_name;
return false;
}
return computeMeasuredTwistBase(q_chain,
qdot_chain,
model_.getFrameId(ee_frame_name),
twist_base,
jacobian_base_out,
base_R_ee_out,
nullptr);
}
bool PinocchioIKBase::solveVelocityBase(const Eigen::MatrixXd& jacobian_base,
const Eigen::Matrix<double, 6, 1>& target_twist_base,
const std::vector<double>& q_chain,
std::vector<double>& qdot_out,
const double qdot_abs_max) const
{
(void)jacobian_base;
(void)target_twist_base;
(void)q_chain;
(void)qdot_out;
(void)qdot_abs_max;
CMVR_LOG(ERROR) << "[PinocchioIKBase] solveVelocityBase is not implemented by this solver";
return false;
}
bool PinocchioIKBase::fk(const std::vector<double>& joints_angle,
Eigen::Matrix4d& cur_pose,
bool is_tcp) {
const std::string& ee_link = (is_tcp && has_tcp_) ? tcp_frame_name_ : chain_tip_frame_name_;
return fk(chain_base_frame_name_, ee_link, joints_angle, cur_pose);
}
bool PinocchioIKBase::fk(const std::string& base_link,
const std::string& ee_link,
const std::vector<double>& joints_angle,
Eigen::Matrix4d& cur_pose) {
if (!data_) {
CMVR_LOG(ERROR) << "[PinocchioIKBase] fk called before pinocchio init";
return false;
}
if (!model_.existFrame(base_link)) {
CMVR_LOG(ERROR) << "[PinocchioIKBase] base frame not found: " << base_link;
return false;
}
if (!model_.existFrame(ee_link)) {
CMVR_LOG(ERROR) << "[PinocchioIKBase] ee frame not found: " << ee_link;
return false;
}
Eigen::VectorXd q_full;
if (!buildFullQFromInput(joints_angle, q_full, "fk")) {
return false;
}
updateKinematics(q_full);
const pinocchio::FrameIndex base_id = model_.getFrameId(base_link);
const pinocchio::FrameIndex ee_id = model_.getFrameId(ee_link);
const pinocchio::SE3& oM_base = data_->oMf[base_id];
const pinocchio::SE3& oM_ee = data_->oMf[ee_id];
const pinocchio::SE3 base_M_ee = oM_base.inverse() * oM_ee;
cur_pose = se3ToMatrix4(base_M_ee);
return true;
}
pinocchio::SE3 PinocchioIKBase::matrix4ToSE3(const Eigen::Matrix4d& T) {
pinocchio::SE3 M;
M.rotation() = T.block<3,3>(0,0);
M.translation() = T.block<3,1>(0,3);
return M;
}
Eigen::Matrix4d PinocchioIKBase::se3ToMatrix4(const pinocchio::SE3& M) {
Eigen::Matrix4d T = Eigen::Matrix4d::Identity();
T.block<3,3>(0,0) = M.rotation();
T.block<3,1>(0,3) = M.translation();
return T;
}
} // namespace cmvr

View File

@ -2,79 +2,40 @@
// Created by lgv on 12/2/25. // Created by lgv on 12/2/25.
// //
#include "ik_solver/include/pinocchio_qp_ik_solver.h" #include "algorithms/kinematics/ik_solver/pinocchio/include/pinocchio_qp_ik_solver.h"
#include "common/base/logging/logger.h"
#include "common/math/joint_limits.h"
#include "common/config/config_files.h"
#include <pinocchio/algorithm/frames.hpp> #include <pinocchio/algorithm/frames.hpp>
#include <pinocchio/algorithm/kinematics.hpp> #include <pinocchio/algorithm/kinematics.hpp>
#include <pinocchio/algorithm/jacobian.hpp> #include <pinocchio/algorithm/jacobian.hpp>
#include <pinocchio/spatial/explog.hpp> #include <pinocchio/spatial/explog.hpp>
#include <iostream>
#include <algorithm> // std::clamp, std::max, std::min #include <algorithm> // std::clamp, std::max, std::min
#include <cmath> // std::sqrt #include <cmath> // std::sqrt
#include "common/utils/config_helper/include/config_helper.h"
namespace cmvr { namespace cmvr {
using Eigen::Matrix4d; using Eigen::Matrix4d;
using Eigen::VectorXd; using Eigen::VectorXd;
using Eigen::MatrixXd; using Eigen::MatrixXd;
using cmvr::common::config::positiveIntOr;
PinocchioQpIKSolver::PinocchioQpIKSolver(const std::string &urdf_path, using cmvr::common::config::positiveOr;
const std::string &base_frame_name,
const std::string &flange_frame_name,
const std::string &tcp_frame_name,
double lambda,
double w_posrot,
int max_iters,
double tol,
double qp_time_limit)
: PinocchioIKBase(urdf_path, base_frame_name, flange_frame_name, tcp_frame_name)
, urdf_path_(urdf_path)
, lambda_(lambda)
, w_posrot_(w_posrot)
, max_iters_(max_iters)
, tol_(tol)
, qp_time_limit_(qp_time_limit)
, solver_() {
}
PinocchioQpIKSolver::PinocchioQpIKSolver()
: PinocchioIKBase("", "", "", "")
, solver_() {
config::PinocchioQpIKConfig config;
if (ConfigHelper::getPinocchioQpIkSolverConfig(config))
{
urdf_path_ = config.urdf_path();
chain_base_frame_name_ = config.base_frame_name();
chain_tip_frame_name_ = config.flange_frame_name();
tcp_frame_name_ = config.tcp_frame_name();
lambda_ = config.lambda();
w_posrot_ = config.w_posrot();
max_iters_ = config.max_iters();
tol_ = config.tol();
qp_time_limit_ = config.qp_time_limit();
if (!urdf_path_.empty() &&
!chain_base_frame_name_.empty() &&
!chain_tip_frame_name_.empty()) {
initUrdfChain(urdf_path_, chain_base_frame_name_, chain_tip_frame_name_);
}
}
}
PinocchioQpIKSolver::PinocchioQpIKSolver(const config::PinocchioQpIKConfig& config) PinocchioQpIKSolver::PinocchioQpIKSolver(const config::PinocchioQpIKConfig& config)
: PinocchioIKBase(config.urdf_path(), : PinocchioIKBase(config.urdf_path(),
config.base_frame_name(), config.base_frame_name(),
config.flange_frame_name(), config.flange_frame_name(),
config.tcp_frame_name()) config.tcp_frame_name())
, config_(config) , config_(config)
, solver_() , urdf_path_(config.urdf_path())
, lambda_(positiveOr(config.lambda(), 1e-4))
, w_posrot_(positiveOr(config.w_posrot(), 0.5))
, max_iters_(positiveIntOr(config.max_iters(), 100))
, tol_(positiveOr(config.tol(), 1e-6))
, qp_time_limit_(positiveOr(config.qp_time_limit(), 1e-2))
, solver_()
{ {
urdf_path_ = config.urdf_path();
lambda_ = config.lambda();
w_posrot_ = config.w_posrot();
max_iters_ = config.max_iters();
tol_ = config.tol();
qp_time_limit_ = config.qp_time_limit();
} }
bool PinocchioQpIKSolver::init() { bool PinocchioQpIKSolver::init() {
@ -82,11 +43,11 @@ namespace cmvr {
if (urdf_path_.empty() || if (urdf_path_.empty() ||
chain_base_frame_name_.empty() || chain_base_frame_name_.empty() ||
chain_tip_frame_name_.empty()) { chain_tip_frame_name_.empty()) {
std::cerr << "[PinocchioQpIKSolver] missing urdf/base/flange config before init().\n"; CMVR_LOG(ERROR) << "[PinocchioQpIKSolver] missing urdf/base/flange config before init().";
return false; return false;
} }
if (!initUrdfChain(urdf_path_, chain_base_frame_name_, chain_tip_frame_name_)) { if (!initUrdfChain(urdf_path_, chain_base_frame_name_, chain_tip_frame_name_)) {
std::cerr << "[PinocchioQpIKSolver] Failed to cache URDF chain metadata in IKSolver.\n"; CMVR_LOG(ERROR) << "[PinocchioQpIKSolver] Failed to cache URDF chain metadata in IKSolver.";
return false; return false;
} }
} }
@ -94,18 +55,18 @@ namespace cmvr {
UrdfParser::ChainInfo chain_info; UrdfParser::ChainInfo chain_info;
std::string err; std::string err;
if (!initPinocchioFromUrdfChain(&chain_info, &err)) { if (!initPinocchioFromUrdfChain(&chain_info, &err)) {
std::cerr << "[PinocchioQpIKSolver] Failed to init pinocchio base: " << err << std::endl; CMVR_LOG(ERROR) << "[PinocchioQpIKSolver] Failed to init pinocchio base: " << err;
return false; return false;
} }
if (chain_q_dof_ <= 0 || chain_v_dof_ <= 0) { if (chain_q_dof_ <= 0 || chain_v_dof_ <= 0) {
std::cerr << "[PinocchioQpIKSolver] Invalid chain dof: q=" << chain_q_dof_ CMVR_LOG(ERROR) << "[PinocchioQpIKSolver] Invalid chain dof: q=" << chain_q_dof_
<< " v=" << chain_v_dof_ << std::endl; << " v=" << chain_v_dof_;
return false; return false;
} }
if (chain_q_dof_ != chain_v_dof_) { if (chain_q_dof_ != chain_v_dof_) {
std::cerr << "[PinocchioQpIKSolver] requires q_dof == v_dof, got q=" CMVR_LOG(ERROR) << "[PinocchioQpIKSolver] requires q_dof == v_dof, got q="
<< chain_q_dof_ << " v=" << chain_v_dof_ << std::endl; << chain_q_dof_ << " v=" << chain_v_dof_;
return false; return false;
} }
@ -114,14 +75,14 @@ namespace cmvr {
int expected_v = chain_v_start_; int expected_v = chain_v_start_;
for (const auto& seg : chain_info.joints) { for (const auto& seg : chain_info.joints) {
if (seg.nq != 1 || seg.nv != 1) { if (seg.nq != 1 || seg.nv != 1) {
std::cerr << "[PinocchioQpIKSolver] only supports nq=1,nv=1 joints. joint=" CMVR_LOG(ERROR) << "[PinocchioQpIKSolver] only supports nq=1,nv=1 joints. joint="
<< seg.name << " nq=" << seg.nq << " nv=" << seg.nv << std::endl; << seg.name << " nq=" << seg.nq << " nv=" << seg.nv;
return false; return false;
} }
if (seg.q_index != expected_q || seg.v_index != expected_v) { if (seg.q_index != expected_q || seg.v_index != expected_v) {
std::cerr << "[PinocchioQpIKSolver] chain q/v index must be contiguous. joint=" CMVR_LOG(ERROR) << "[PinocchioQpIKSolver] chain q/v index must be contiguous. joint="
<< seg.name << " q_index=" << seg.q_index << " expect_q=" << expected_q << seg.name << " q_index=" << seg.q_index << " expect_q=" << expected_q
<< " v_index=" << seg.v_index << " expect_v=" << expected_v << std::endl; << " v_index=" << seg.v_index << " expect_v=" << expected_v;
return false; return false;
} }
expected_q += seg.nq; expected_q += seg.nq;
@ -130,7 +91,7 @@ namespace cmvr {
if (joint_pos_lower_limits_.size() != chain_q_dof_ || if (joint_pos_lower_limits_.size() != chain_q_dof_ ||
joint_pos_upper_limits_.size() != chain_q_dof_) { joint_pos_upper_limits_.size() != chain_q_dof_) {
std::cerr << "[PinocchioQpIKSolver] position limits size mismatch with q_dof." << std::endl; CMVR_LOG(ERROR) << "[PinocchioQpIKSolver] position limits size mismatch with q_dof.";
return false; return false;
} }
@ -148,13 +109,13 @@ namespace cmvr {
} }
initialized_ = true; initialized_ = true;
std::cout << "[PinocchioQpIKSolver] Init OK. full nq = " << model_.nq CMVR_LOG(INFO) << "[PinocchioQpIKSolver] Init OK. full nq = " << model_.nq
<< ", active dof = " << chain_q_dof_ << ", active dof = " << chain_q_dof_
<< ", q_start=" << chain_q_start_ << ", v_start=" << chain_v_start_ << ", q_start=" << chain_q_start_ << ", v_start=" << chain_v_start_
<< ", base frame = " << chain_base_frame_name_ << ", base frame = " << chain_base_frame_name_
<< ", flange frame = " << chain_tip_frame_name_ << ", flange frame = " << chain_tip_frame_name_
<< ", tcp frame = " << (has_tcp_ ? tcp_frame_name_ : "<none>") << ", tcp frame = " << (has_tcp_ ? tcp_frame_name_ : "<none>")
<< std::endl; ;
return true; return true;
} }
@ -162,8 +123,8 @@ namespace cmvr {
if (qd_max.size() == chain_v_dof_) { if (qd_max.size() == chain_v_dof_) {
joint_vel_limits_ = qd_max; joint_vel_limits_ = qd_max;
} else { } else {
std::cerr << "[PinocchioQpIKSolver] setVelocityLimits size mismatch. got " CMVR_LOG(ERROR) << "[PinocchioQpIKSolver] setVelocityLimits size mismatch. got "
<< qd_max.size() << ", expect " << chain_v_dof_ << std::endl; << qd_max.size() << ", expect " << chain_v_dof_;
} }
} }
@ -171,8 +132,8 @@ namespace cmvr {
if (qdd_max.size() == chain_q_dof_) { if (qdd_max.size() == chain_q_dof_) {
qdd_max_global_ = qdd_max; qdd_max_global_ = qdd_max;
} else { } else {
std::cerr << "[PinocchioQpIKSolver] setAccelerationLimits size mismatch. got " CMVR_LOG(ERROR) << "[PinocchioQpIKSolver] setAccelerationLimits size mismatch. got "
<< qdd_max.size() << ", expect " << chain_q_dof_ << std::endl; << qdd_max.size() << ", expect " << chain_q_dof_;
} }
} }
@ -180,12 +141,12 @@ namespace cmvr {
std::vector<double> &joints_angle, std::vector<double> &joints_angle,
bool is_tcp) { bool is_tcp) {
if (!initialized_) { if (!initialized_) {
std::cerr << "[PinocchioQpIKSolver] IK called before init()." << std::endl; CMVR_LOG(ERROR) << "[PinocchioQpIKSolver] IK called before init().";
return false; return false;
} }
if (cur_joints_angle_.size() != static_cast<std::size_t>(chain_q_dof_)) { if (cur_joints_angle_.size() != static_cast<std::size_t>(chain_q_dof_)) {
std::cerr << "[PinocchioQpIKSolver] cur_joints_angle_ size mismatch: " CMVR_LOG(ERROR) << "[PinocchioQpIKSolver] cur_joints_angle_ size mismatch: "
<< cur_joints_angle_.size() << " vs dof = " << chain_q_dof_ << std::endl; << cur_joints_angle_.size() << " vs dof = " << chain_q_dof_;
return false; return false;
} }
@ -195,9 +156,9 @@ namespace cmvr {
target_frame_id = tcp_frame_id_; target_frame_id = tcp_frame_id_;
} else { } else {
if (is_tcp && !has_tcp_) { if (is_tcp && !has_tcp_) {
std::cerr << "[PinocchioQpIKSolver] is_tcp=true but no TCP frame, " CMVR_LOG(WARNING) << "[PinocchioQpIKSolver] is_tcp=true but no TCP frame, "
"fallback to flange." "fallback to flange."
<< std::endl; ;
} }
target_frame_id = flange_frame_id_; target_frame_id = flange_frame_id_;
} }
@ -301,19 +262,17 @@ namespace cmvr {
try { try {
dq_local = solver_.Solve(); dq_local = solver_.Solve();
} catch (const QPSolverException &e) { } catch (const QPSolverException &e) {
std::cerr << "[PinocchioQpIKSolver] QP failed: " << e.what() CMVR_LOG(ERROR) << "[PinocchioQpIKSolver] QP failed: " << e.what()
<< " (code=" << e.code() << ")" << std::endl; << " (code=" << e.code() << ")";
return false; return false;
} catch (const std::exception &e) { } catch (const std::exception &e) {
std::cerr << "[PinocchioQpIKSolver] QP failed: " << e.what() << std::endl; CMVR_LOG(ERROR) << "[PinocchioQpIKSolver] QP failed: " << e.what();
return false; return false;
} }
// 5) 更新 q_local 并写回 q_full // 5) 更新 q_local 并写回 q_full
q_local += dq_local; q_local += dq_local;
for (int i = 0; i < chain_q_dof_; ++i) { q_local = cmvr::kinematics::clampToJointPositionLimits(q_local, q_min, q_max);
q_local(i) = std::clamp(q_local(i), q_min(i), q_max(i));
}
q_full.segment(chain_q_start_, chain_q_dof_) = q_local; q_full.segment(chain_q_start_, chain_q_dof_) = q_local;
// 6) 收敛检查 // 6) 收敛检查
@ -336,5 +295,96 @@ namespace cmvr {
return false; return false;
} }
bool PinocchioQpIKSolver::solveVelocityBase(
const MatrixXd& jacobian_base,
const Eigen::Matrix<double, 6, 1>& target_twist_base,
const std::vector<double>& q_chain_std,
std::vector<double>& qdot_out,
const double qdot_abs_max) const
{
if (!initialized_) {
CMVR_LOG(ERROR) << "[PinocchioQpIKSolver] solveVelocityBase called before init().";
return false;
}
if (jacobian_base.rows() != 6 || jacobian_base.cols() != chain_v_dof_) {
CMVR_LOG(ERROR) << "[PinocchioQpIKSolver] solveVelocityBase jacobian size mismatch.";
return false;
}
if (static_cast<int>(q_chain_std.size()) != chain_q_dof_ || chain_q_dof_ != chain_v_dof_) {
CMVR_LOG(ERROR) << "[PinocchioQpIKSolver] solveVelocityBase q size mismatch.";
return false;
}
const int dof = chain_v_dof_;
MatrixXd cost(6 + dof, dof);
VectorXd target(6 + dof);
cost.setZero();
target.setZero();
cost.topRows(6) = jacobian_base;
target.head(6) = target_twist_base;
cost.bottomRows(dof) = std::sqrt(lambda_) * MatrixXd::Identity(dof, dof);
VectorXd lower(dof);
VectorXd upper(dof);
const Eigen::Map<const VectorXd> q_chain(q_chain_std.data(), dof);
for (int i = 0; i < dof; ++i) {
double limit = std::numeric_limits<double>::infinity();
if (joint_vel_limits_.size() == dof) {
const double joint_limit = std::abs(joint_vel_limits_[i]);
if (std::isfinite(joint_limit) && joint_limit > 0.0) {
limit = std::min(limit, joint_limit);
}
}
if (std::isfinite(qdot_abs_max) && qdot_abs_max > 0.0) {
limit = std::min(limit, qdot_abs_max);
}
if (!std::isfinite(limit) || limit <= 0.0) {
limit = 1e6;
}
lower[i] = -limit;
upper[i] = limit;
if (joint_pos_lower_limits_.size() == dof &&
joint_pos_upper_limits_.size() == dof) {
const double q_min = joint_pos_lower_limits_[i];
const double q_max = joint_pos_upper_limits_[i];
if (std::isfinite(q_min) && std::isfinite(q_max) && q_max > q_min) {
constexpr double kLimitEps = 1e-6;
if (q_chain[i] <= q_min + kLimitEps) {
lower[i] = std::max(0.0, lower[i]);
}
if (q_chain[i] >= q_max - kLimitEps) {
upper[i] = std::min(0.0, upper[i]);
}
}
}
}
QPSolver solver;
solver.Setup(dof, dof, qp_time_limit_);
solver.SetCostFunction(cost, target);
solver.SetConstraintsFunction(MatrixXd::Identity(dof, dof), lower, upper);
VectorXd qdot;
try {
qdot = solver.Solve();
} catch (const QPSolverException& e) {
CMVR_LOG(ERROR) << "[PinocchioQpIKSolver] solveVelocityBase QP failed: "
<< e.what() << " (code=" << e.code() << ")";
return false;
} catch (const std::exception& e) {
CMVR_LOG(ERROR) << "[PinocchioQpIKSolver] solveVelocityBase QP failed: "
<< e.what();
return false;
}
if (qdot.size() != dof) {
return false;
}
qdot_out.assign(qdot.data(), qdot.data() + qdot.size());
return true;
}
} // namespace cmvr } // namespace cmvr

View File

@ -0,0 +1,84 @@
#pragma once
#include <utility>
#include <vector>
#include <Eigen/Dense>
#include "cmvr/config/srs_ik_config.pb.h"
#include "algorithms/kinematics/ik_solver/common/include/ik_solver.h"
namespace cmvr {
class SrsIKSolver : public IKSolver {
public:
enum ConfigDirection {
OUTWARD = 1,
INWARD = -1
};
explicit SrsIKSolver(const config::SrsIKConfig& cfg);
~SrsIKSolver() override = default;
bool init() override;
bool ik(const Eigen::Matrix4d& target_pose,
std::vector<double>& joints_angle,
bool is_tcp = true) override;
bool fk(const std::vector<double>& joints_angle,
Eigen::Matrix4d& cur_pose,
bool is_tcp = true) override;
bool ikWithPsi(const Eigen::Matrix4d& pose,
std::vector<double>& joints,
double psi);
Eigen::Matrix4d calc_total_transform(const std::vector<double>& joint_angles);
bool cal_coefficient_matrix(const Eigen::Matrix4d& pose,
Eigen::MatrixXd& s_mat,
Eigen::MatrixXd& w_mat);
void setPsi(double psi) { psi_ = psi; }
double psi() const { return psi_; }
void set_shoulder_config(ConfigDirection value) { shoulder_config_ = value; }
void set_elbow_config(ConfigDirection value) { elbow_config_ = value; }
void set_wrist_config(ConfigDirection value) { wrist_config_ = value; }
int get_shoulder_config() const { return static_cast<int>(shoulder_config_); }
int get_elbow_config() const { return static_cast<int>(elbow_config_); }
int get_wrist_config() const { return static_cast<int>(wrist_config_); }
std::vector<std::pair<double, double>> get_joints_limits() const { return joints_limits_; }
private:
static ConfigDirection toConfigDirection_(int value);
Eigen::Matrix3d reference_plane(const Eigen::Vector3d& S,
const Eigen::Vector3d& W);
Eigen::Matrix3d calc_rotation_matrix(const Eigen::Vector3d& rotation_axis,
double rotation_angle);
Eigen::Matrix4d calc_dh(double d, double alpha, double a, double theta);
private:
config::SrsIKConfig cfg_;
double psi_{0.0};
ConfigDirection shoulder_config_{OUTWARD};
ConfigDirection elbow_config_{OUTWARD};
ConfigDirection wrist_config_{OUTWARD};
Eigen::VectorXd link_lengths_;
Eigen::MatrixXd dh_params_;
double d_bs_{0.0};
double d_se_{0.0};
double d_ew_{0.0};
double d_wt_{0.0};
std::vector<std::pair<double, double>> joints_limits_{};
};
} // namespace cmvr

View File

@ -0,0 +1,339 @@
#include "algorithms/kinematics/ik_solver/srs/include/srs_ik_solver.h"
#include <algorithm>
#include <cmath>
#include "common/base/logging/logger.h"
#include "common/base/constants.h"
#include "common/math/support_functions.h"
namespace cmvr {
SrsIKSolver::SrsIKSolver(const config::SrsIKConfig& cfg)
: IKSolver("", "", "")
, cfg_(cfg)
{
}
bool SrsIKSolver::init()
{
psi_ = cfg_.initial_psi();
shoulder_config_ = toConfigDirection_(cfg_.shoulder_config());
elbow_config_ = toConfigDirection_(cfg_.elbow_config());
wrist_config_ = toConfigDirection_(cfg_.wrist_config());
link_lengths_ = Eigen::VectorXd(4);
if (cfg_.link_lengths_size() == 4) {
for (int i = 0; i < 4; ++i) {
link_lengths_[i] = cfg_.link_lengths(i);
}
} else {
link_lengths_ << 0.0945 + 0.0765, 0.1475 + 0.1025, 0.0965 + 0.1525, 0.03;
}
const double half_pi = M_PI / 2.0;
dh_params_ = Eigen::MatrixXd(7, 4);
dh_params_ << link_lengths_[0], -half_pi, 0.0, 0.0,
0.0, half_pi, 0.0, 0.0,
link_lengths_[1], -half_pi, 0.0, 0.0,
0.0, half_pi, 0.0, 0.0,
link_lengths_[2], -half_pi, 0.0, half_pi,
0.0, half_pi, 0.0, half_pi,
link_lengths_[3], 0.0, 0.0, 0.0;
d_bs_ = link_lengths_[0];
d_se_ = link_lengths_[1];
d_ew_ = link_lengths_[2];
d_wt_ = link_lengths_[3];
joints_limits_.clear();
if (cfg_.joint_lower_limits_size() == 7 && cfg_.joint_upper_limits_size() == 7) {
joints_limits_.reserve(7);
for (int i = 0; i < 7; ++i) {
joints_limits_.emplace_back(cfg_.joint_lower_limits(i), cfg_.joint_upper_limits(i));
}
} else {
joints_limits_ = {
{-M_PI, M_PI},
{-0.78, 1.57},
{-M_PI, M_PI},
{0.0, 2.05},
{-M_PI, M_PI},
{-0.78, 0.78},
{-0.26, 1.57},
};
}
return true;
}
bool SrsIKSolver::ik(const Eigen::Matrix4d& target_pose,
std::vector<double>& joints_angle,
bool is_tcp)
{
(void)is_tcp;
return ikWithPsi(target_pose, joints_angle, psi_);
}
bool SrsIKSolver::fk(const std::vector<double>& joints_angle,
Eigen::Matrix4d& cur_pose,
bool is_tcp)
{
(void)is_tcp;
if (joints_angle.size() < 7) {
return false;
}
cur_pose = calc_total_transform(joints_angle);
return true;
}
SrsIKSolver::ConfigDirection SrsIKSolver::toConfigDirection_(const int value)
{
return value < 0 ? INWARD : OUTWARD;
}
Eigen::Matrix3d SrsIKSolver::reference_plane(const Eigen::Vector3d& S,
const Eigen::Vector3d& W)
{
const double d_sw = (W - S).norm();
const Eigen::Vector3d v_sw = (W - S).normalized();
const double x = (d_sw * d_sw + d_se_ * d_se_ - d_ew_ * d_ew_) / (2.0 * d_sw);
const double r = std::sqrt(std::max(d_se_ * d_se_ - x * x, 0.0));
const Eigen::Vector3d F = S + x * v_sw;
Eigen::Vector3d FE;
if (v_sw.head<2>().cwiseAbs().maxCoeff() <= 1e-6) {
FE = Eigen::Vector3d(-1.0, 0.0, 0.0);
} else {
FE(0) = -v_sw(0) * v_sw(2) / (v_sw(0) * v_sw(0) + v_sw(1) * v_sw(1));
FE(1) = -v_sw(1) * v_sw(2) / (v_sw(0) * v_sw(0) + v_sw(1) * v_sw(1));
FE(2) = 1.0;
}
const Eigen::Vector3d E = F + elbow_config_ * r * FE.normalized();
const Eigen::Vector3d v_es = (S - E).normalized();
const Eigen::Vector3d v_ew = (W - E).normalized();
const Eigen::Vector3d R30_y = v_es;
Eigen::Vector3d R30_z = v_ew.cross(v_es);
if (elbow_config_ == INWARD) {
R30_z = -R30_z;
}
const double nz = R30_z.norm();
if (nz > 1e-12) {
R30_z /= nz;
} else {
R30_z = Eigen::Vector3d(-0.0, 1.0, 0.0);
}
const Eigen::Vector3d R30_x = R30_y.cross(R30_z);
Eigen::Matrix3d R30;
R30.col(0) = R30_x;
R30.col(1) = R30_y;
R30.col(2) = R30_z;
return R30;
}
bool SrsIKSolver::ikWithPsi(const Eigen::Matrix4d& pose,
std::vector<double>& joints,
double psi)
{
joints.resize(7, 0.0);
const Eigen::Vector3d P_target = pose.block<3, 1>(0, 3);
const Eigen::Vector3d S(0.0, 0.0, d_bs_);
const Eigen::Vector3d P67(0.0, 0.0, d_wt_);
const Eigen::Vector3d W = P_target - pose.block<3, 3>(0, 0) * P67;
const double d_sw = (W - S).norm();
const double r_max = d_se_ + d_ew_;
const double r_min = std::abs(d_se_ - d_ew_);
const double diff_max = d_sw - r_max;
const double diff_min = r_min - d_sw;
if (diff_max > EPS || diff_min > EPS) {
CMVR_LOG(ERROR) << "[SrsIKSolver] pose outside reachable workspace, IK solve failed";
return false;
}
double cos_elbow = (d_se_ * d_se_ + d_ew_ * d_ew_ - d_sw * d_sw) / (2.0 * d_se_ * d_ew_);
cos_elbow = std::clamp(cos_elbow, -1.0, 1.0);
joints[3] = elbow_config_ * (M_PI - std::acos(cos_elbow));
const Eigen::Matrix3d R30 = reference_plane(S, W);
const Eigen::Matrix3d R_axis = calc_rotation_matrix((W - S).normalized(), psi);
const Eigen::Matrix3d R3 = R_axis * R30;
double k = shoulder_config_;
const double c2 = std::clamp(-R3(2, 1), -1.0, 1.0);
constexpr double eps = 1e-8;
if (std::fabs(c2 - 1.0) < eps) {
joints[1] = 0.0 * k;
joints[0] = 0.0;
joints[2] = std::atan2(k * R3(1, 0), k * R3(0, 0));
} else if (std::fabs(c2 + 1.0) < eps) {
joints[1] = k * M_PI;
joints[2] = std::atan2(k * R3(1, 0), k * R3(1, 2));
joints[0] = 0.0;
} else {
joints[0] = std::atan2(-k * R3(1, 1), -k * R3(0, 1));
joints[1] = k * std::acos(c2);
joints[2] = std::atan2(k * R3(2, 2), -k * R3(2, 0));
}
Eigen::Matrix3d R04 = Eigen::Matrix3d::Identity();
for (int i = 0; i < 4; ++i) {
const Eigen::Vector4d dh = dh_params_.row(i);
R04 = R04 * calc_dh(dh[0], dh[1], dh[2], dh[3] + joints[i]).block<3, 3>(0, 0);
}
const Eigen::Matrix3d R47 = R04.transpose() * pose.block<3, 3>(0, 0);
k = wrist_config_;
const double c = std::clamp(R47(2, 2), -1.0, 1.0);
double theta_y = k * std::acos(c);
double phi_z = std::atan2(k * R47(1, 2), k * R47(0, 2));
double psi_z = std::atan2(k * R47(2, 1), -k * R47(2, 0));
if (std::fabs(c - 1.0) < eps) {
theta_y = 0.0;
phi_z = std::atan2(k * R47(1, 0), k * R47(0, 0));
psi_z = 0.0;
} else if (std::fabs(c + 1.0) < eps) {
theta_y = k * M_PI;
phi_z = std::atan2(-k * R47(1, 0), -k * R47(0, 0));
psi_z = 0.0;
}
joints[4] = SupportFunctions::normalize_angle(phi_z - M_PI / 2.0);
joints[5] = SupportFunctions::normalize_angle(theta_y - M_PI / 2.0);
joints[6] = SupportFunctions::normalize_angle(psi_z);
psi_ = psi;
return true;
}
Eigen::Matrix3d SrsIKSolver::calc_rotation_matrix(const Eigen::Vector3d& rotation_axis,
double rotation_angle)
{
const Eigen::Vector3d normalized_axis = rotation_axis.normalized();
const double ux = normalized_axis[0];
const double uy = normalized_axis[1];
const double uz = normalized_axis[2];
Eigen::Matrix3d u_hat;
u_hat << 0.0, -uz, uy,
uz, 0.0, -ux,
-uy, ux, 0.0;
return Eigen::Matrix3d::Identity()
+ std::sin(rotation_angle) * u_hat
+ (1.0 - std::cos(rotation_angle)) * (u_hat * u_hat);
}
Eigen::Matrix4d SrsIKSolver::calc_dh(double d, double alpha, double a, double theta)
{
const double ca = std::cos(alpha);
const double sa = std::sin(alpha);
const double ct = std::cos(theta);
const double st = std::sin(theta);
Eigen::Matrix4d T;
T << ct, -st * ca, st * sa, a * ct,
st, ct * ca, -ct * sa, a * st,
0.0, sa, ca, d,
0.0, 0.0, 0.0, 1.0;
return T;
}
Eigen::Matrix4d SrsIKSolver::calc_total_transform(const std::vector<double>& joint_angles)
{
Eigen::Matrix4d T_total = Eigen::Matrix4d::Identity();
if (joint_angles.size() < static_cast<std::size_t>(dh_params_.rows())) {
return T_total;
}
for (int i = 0; i < dh_params_.rows(); ++i) {
const double d = dh_params_(i, 0);
const double alpha = dh_params_(i, 1);
const double a = dh_params_(i, 2);
const double theta0 = dh_params_(i, 3);
T_total = T_total * calc_dh(d, alpha, a, theta0 + joint_angles[i]);
}
return T_total;
}
bool SrsIKSolver::cal_coefficient_matrix(const Eigen::Matrix4d& pose,
Eigen::MatrixXd& s_mat,
Eigen::MatrixXd& w_mat)
{
if (s_mat.rows() != 3 || s_mat.cols() != 9) {
s_mat.setZero(3, 9);
}
if (w_mat.rows() != 3 || w_mat.cols() != 9) {
w_mat.setZero(3, 9);
}
std::vector<double> joints(7, 0.0);
const Eigen::Vector3d P_target = pose.block<3, 1>(0, 3);
const Eigen::Vector3d S(0.0, 0.0, d_bs_);
const Eigen::Vector3d P67(0.0, 0.0, d_wt_);
const Eigen::Vector3d W = P_target - pose.block<3, 3>(0, 0) * P67;
const double d_sw = (W - S).norm();
const double r_max = d_se_ + d_ew_;
const double r_min = std::abs(d_se_ - d_ew_);
const double diff_max = d_sw - r_max;
const double diff_min = r_min - d_sw;
if (diff_max > EPS || diff_min > EPS) {
CMVR_LOG(ERROR) << "[SrsIKSolver] pose outside reachable workspace, IK solve failed";
return false;
}
double cos_elbow = (d_se_ * d_se_ + d_ew_ * d_ew_ - d_sw * d_sw) / (2.0 * d_se_ * d_ew_);
cos_elbow = std::clamp(cos_elbow, -1.0, 1.0);
joints[3] = elbow_config_ * (M_PI - std::acos(cos_elbow));
const Eigen::Matrix3d R30 = reference_plane(S, W);
const Eigen::Vector3d normalized_axis = (W - S).normalized();
const double ux = normalized_axis[0];
const double uy = normalized_axis[1];
const double uz = normalized_axis[2];
Eigen::Matrix3d u_hat;
u_hat << 0.0, -uz, uy,
uz, 0.0, -ux,
-uy, ux, 0.0;
const Eigen::MatrixXd A_s = u_hat * R30;
const Eigen::MatrixXd B_s = -u_hat * u_hat * R30;
const Eigen::MatrixXd C_s = (Eigen::MatrixXd::Identity(3, 3) + u_hat * u_hat) * R30;
const Eigen::MatrixXd T34 = calc_dh(dh_params_(3, 0),
dh_params_(3, 1),
dh_params_(3, 2),
dh_params_(3, 3) + joints[3]);
const Eigen::MatrixXd R34 = T34.block(0, 0, 3, 3);
const Eigen::MatrixXd A_w = R34.transpose() * A_s.transpose() * pose.block(0, 0, 3, 3);
const Eigen::MatrixXd B_w = R34.transpose() * B_s.transpose() * pose.block(0, 0, 3, 3);
const Eigen::MatrixXd C_w = R34.transpose() * C_s.transpose() * pose.block(0, 0, 3, 3);
s_mat.block<3, 3>(0, 0) = A_s;
s_mat.block<3, 3>(0, 3) = B_s;
s_mat.block<3, 3>(0, 6) = C_s;
w_mat.block<3, 3>(0, 0) = A_w;
w_mat.block<3, 3>(0, 3) = B_w;
w_mat.block<3, 3>(0, 6) = C_w;
return true;
}
} // namespace cmvr

View File

@ -13,12 +13,51 @@
#include "pinocchio/multibody/sample-models.hpp" #include "pinocchio/multibody/sample-models.hpp"
#include <Eigen/Dense> #include <Eigen/Dense>
#include "gtest/gtest.h" #include "gtest/gtest.h"
#include "ik_solver/include/pinocchio_dls_ik_solver.h" #include "algorithms/kinematics/ik_solver/pinocchio/include/pinocchio_dls_ik_solver.h"
#include "ik_solver/include/pinocchio_qp_ik_solver.h" #include "algorithms/kinematics/ik_solver/pinocchio/include/pinocchio_qp_ik_solver.h"
#include "ik_solver/include/lawba_ik_solver.h" #include "algorithms/kinematics/ik_solver/lawba/include/lawba_ik_solver.h"
#include "simulate/mujoco/mujoco_viewer/include/mujoco_viewer.h" #include "simulate/mujoco/mujoco_viewer/include/mujoco_viewer.h"
using namespace cmvr; using namespace cmvr;
namespace {
constexpr const char* kDefaultUrdf =
"/home/lgv/cmvr/0-workspace/cmvr-es/model/xiaoyan_description/dual_arm.urdf";
constexpr const char* kDefaultBaseFrame = "PELVIS_S";
constexpr const char* kDefaultFlangeFrame = "R_WRIST_R_S";
constexpr const char* kDefaultTcpFrame = "R_FINGER_TIP_FIXED";
config::PinocchioDlsIKConfig makeDlsConfig()
{
config::PinocchioDlsIKConfig cfg;
cfg.set_urdf_path(kDefaultUrdf);
cfg.set_base_frame_name(kDefaultBaseFrame);
cfg.set_flange_frame_name(kDefaultFlangeFrame);
cfg.set_tcp_frame_name(kDefaultTcpFrame);
cfg.set_max_iters(100);
cfg.set_pos_eps(1e-6);
cfg.set_rot_eps(1e-6);
cfg.set_damping(1e-4);
return cfg;
}
config::PinocchioQpIKConfig makeQpConfig()
{
config::PinocchioQpIKConfig cfg;
cfg.set_urdf_path(kDefaultUrdf);
cfg.set_base_frame_name(kDefaultBaseFrame);
cfg.set_flange_frame_name(kDefaultFlangeFrame);
cfg.set_tcp_frame_name(kDefaultTcpFrame);
cfg.set_lambda(1e-4);
cfg.set_w_posrot(0.5);
cfg.set_max_iters(100);
cfg.set_tol(1e-6);
cfg.set_qp_time_limit(1e-2);
return cfg;
}
} // namespace
class DualArmViewer : public MuJocoViewer { class DualArmViewer : public MuJocoViewer {
public: public:
@ -185,28 +224,18 @@ void benchmarkIkSolversRandomJoints(DualArmViewer &viewer)
{-0.26, 1.57}, {-0.26, 1.57},
}}; }};
constexpr int N_SAMPLES = 10; // 样本数1000 组随机关节角 constexpr int N_SAMPLES = 10;
// ========== 2. 创建三个求解器实例 ========== // ========== 2. 创建三个求解器实例 ==========
// 数值优化类 QP IK // 数值优化类 QP IK
PinocchioQpIKSolver qp_solver( PinocchioQpIKSolver qp_solver(makeQpConfig());
"/home/lgv/cmvr/0-workspace/cmvr-es/model/xiaoyan_description/dual_arm.urdf",
"PELVIS_S",
"R_WRIST_R_S", // 法兰 frame
"R_FINGER_TIP_FIXED" // TCP frame
);
// 基于广义逆雅可比矩阵的数值增量 IK // 基于广义逆雅可比矩阵的数值增量 IK
PinocchioDlsIKSolver pinv_solver( PinocchioDlsIKSolver pinv_solver(makeDlsConfig());
"/home/lgv/cmvr/0-workspace/cmvr-es/model/xiaoyan_description/dual_arm.urdf",
"PELVIS_S",
"R_WRIST_R_S", // 法兰 frame
"R_FINGER_TIP_FIXED" // TCP frame
);
// 你的专利方法:臂角 ψ + 可行域 + 势场 // 你的专利方法:臂角 ψ + 可行域 + 势场
LawbaIKSolver psi_solver; LawbaIKSolver psi_solver(config::LawbaIKConfig{});
// 统一容器,方便 for 循环 // 统一容器,方便 for 循环
std::vector<IKSolver*> solvers = { std::vector<IKSolver*> solvers = {
@ -236,6 +265,8 @@ void benchmarkIkSolversRandomJoints(DualArmViewer &viewer)
// ========== 5. 主循环:随机关节角 → FK → 三种 IK ========== // ========== 5. 主循环:随机关节角 → FK → 三种 IK ==========
// 这里用 PinocchioDlsIKSolver (pinv_solver) 的 FK 作为“真值” // 这里用 PinocchioDlsIKSolver (pinv_solver) 的 FK 作为“真值”
for (int i = 0; i < N_SAMPLES; ++i) { for (int i = 0; i < N_SAMPLES; ++i) {
std::cout << "[ik_compare] sample " << (i + 1) << "/" << N_SAMPLES << std::endl;
// 5.1 随机生成一组 q_true // 5.1 随机生成一组 q_true
std::vector<double> q_true(7); std::vector<double> q_true(7);
for (int j = 0; j < 7; ++j) { for (int j = 0; j < 7; ++j) {
@ -308,7 +339,7 @@ void benchmarkIkSolversRandomJoints(DualArmViewer &viewer)
} }
viewer.moveJ(q_sol); viewer.moveJ(q_sol);
std::this_thread::sleep_for(std::chrono::duration<double>(1)); std::this_thread::sleep_for(std::chrono::milliseconds(500));
double dt_ms = std::chrono::duration_cast< double dt_ms = std::chrono::duration_cast<
std::chrono::microseconds>(t1 - t0).count() / 1000.0; std::chrono::microseconds>(t1 - t0).count() / 1000.0;
@ -395,34 +426,24 @@ void benchmarkIkSolversRandomJoints()
}}; }};
constexpr int N_SAMPLES = 100; // 样本数1000 组随机关节角 constexpr int N_SAMPLES = 10;
// ========== 2. 创建三个求解器实例 ========== // ========== 2. 创建三个求解器实例 ==========
// 数值优化类 QP IK // 数值优化类 QP IK
PinocchioQpIKSolver qp_solver( PinocchioQpIKSolver qp_solver(makeQpConfig());
"/home/lgv/cmvr/0-workspace/cmvr-es/model/xiaoyan_description/dual_arm.urdf",
"PELVIS_S",
"R_WRIST_R_S", // 法兰 frame
"R_FINGER_TIP_FIXED" // TCP frame
);
// 基于广义逆雅可比矩阵的数值增量 IK // 基于广义逆雅可比矩阵的数值增量 IK
PinocchioDlsIKSolver pinv_solver( PinocchioDlsIKSolver pinv_solver(makeDlsConfig());
"/home/lgv/cmvr/0-workspace/cmvr-es/model/xiaoyan_description/dual_arm.urdf",
"PELVIS_S",
"R_WRIST_R_S", // 法兰 frame
"R_FINGER_TIP_FIXED" // TCP frame
);
// 你的专利方法:臂角 ψ + 可行域 + 势场 // 你的专利方法:臂角 ψ + 可行域 + 势场
LawbaIKSolver psi_solver; LawbaIKSolver psi_solver(config::LawbaIKConfig{});
// 统一容器,方便 for 循环 // 统一容器,方便 for 循环
std::vector<IKSolver*> solvers = { std::vector<IKSolver*> solvers = {
// &psi_solver, &psi_solver,
&pinv_solver, &pinv_solver,
// &qp_solver &qp_solver
}; };
@ -446,6 +467,8 @@ void benchmarkIkSolversRandomJoints()
// ========== 5. 主循环:随机关节角 → FK → 三种 IK ========== // ========== 5. 主循环:随机关节角 → FK → 三种 IK ==========
// 这里用 PinocchioDlsIKSolver (pinv_solver) 的 FK 作为“真值” // 这里用 PinocchioDlsIKSolver (pinv_solver) 的 FK 作为“真值”
for (int i = 0; i < N_SAMPLES; ++i) { for (int i = 0; i < N_SAMPLES; ++i) {
std::cout << "[ik_compare] sample " << (i + 1) << "/" << N_SAMPLES << std::endl;
// 5.1 随机生成一组 q_true // 5.1 随机生成一组 q_true
std::vector<double> q_true(7); std::vector<double> q_true(7);
for (int j = 0; j < 7; ++j) { for (int j = 0; j < 7; ++j) {
@ -586,30 +609,23 @@ void benchmarkIkSolversRandomJoints()
} }
TEST(ik_test,pinocchio_lib_test) { TEST(ik_test,pinocchio_lib_test) {
// PinocchioQpIKSolver solver( PinocchioQpIKSolver solver(makeQpConfig());
// "/home/lgv/cmvr/cmvr-es/config/robot_description/hc_description/dual_arm.urdf", // PinocchioDlsIKSolver solver(makeDlsConfig());
// "PELVIS_S", ASSERT_TRUE(solver.init());
// "R_WRIST_R_S", // 法兰 frame
// "R_FINGER_TIP_FIXED" // TCP frame
// );
LawbaIKSolver solver;
solver.init();
// 真实关节角(比如从控制器读回来) // 真实关节角(比如从控制器读回来)
std::vector<double> q_cur(7, 0.0); std::vector<double> q_cur(7, 0.0);
solver.update_joints_state(q_cur);
q_cur[5] = 0.236; q_cur[5] = 0.236;
q_cur[4] = -0.236; q_cur[4] = -0.236;
q_cur[3] = 0.156; q_cur[3] = 0.156;
q_cur[2] = 0.036; q_cur[2] = 0.036;
q_cur[1] = 0.236; q_cur[1] = 0.236;
solver.update_joints_state(q_cur);
// 1) 先求当前 TCP 位姿 // 1) 先求当前 TCP 位姿
Eigen::Matrix4d cur_tcp_pose; Eigen::Matrix4d cur_tcp_pose = Eigen::Matrix4d::Identity();
solver.fk(q_cur, cur_tcp_pose, false); // is_tcp = true ASSERT_TRUE(solver.fk(q_cur, cur_tcp_pose, false));
std::cout << cur_tcp_pose<< std::endl; std::cout << cur_tcp_pose<< std::endl;
@ -617,45 +633,29 @@ TEST(ik_test,pinocchio_lib_test) {
std::vector<double> q_target; std::vector<double> q_target;
bool ok = solver.ik(target_tcp_pose, q_target, false); // is_tcp = true ASSERT_TRUE(solver.ik(target_tcp_pose, q_target, false));
for (double q: q_target) { for (double q: q_target) {
std::cout << q << std::endl; std::cout << q << std::endl;
} }
Eigen::Matrix4d cur_flange_pose; Eigen::Matrix4d cur_flange_pose = Eigen::Matrix4d::Identity();
solver.fk(q_target, cur_flange_pose, false); // is_tcp = false ASSERT_TRUE(solver.fk(q_target, cur_flange_pose, false));
std::cout << cur_flange_pose<< std::endl; std::cout << cur_flange_pose<< std::endl;
} }
TEST(ik_test,ik_compare) { TEST(ik_test,ik_compare) {
// benchmarkIkSolversRandomJoints(); constexpr const char* model_path =
"/home/lgv/cmvr/0-workspace/cmvr-es/model/xiaoyan_description/dual_arm.xml";
const char *model_path =
"/home/lgv/cmvr/0-workspace/cmvr-es/model/xiaoyan_description/dual_arm.xml";
DualArmViewer viewer(model_path); DualArmViewer viewer(model_path);
std::this_thread::sleep_for(std::chrono::seconds(3)); std::thread benchmark_thread([&viewer]() {
// viewer.run() 在主线程创建仿真和渲染资源,稍后再开始发送关节目标。
// int viewer = 0; std::this_thread::sleep_for(std::chrono::seconds(1));
// 把所有 IK 运算 + moveJ 循环放到控制线程里
std::thread ctrl_thread([&viewer]() {
benchmarkIkSolversRandomJoints(viewer); benchmarkIkSolversRandomJoints(viewer);
std::cout << "[ik_compare] benchmark finished; close the viewer to exit." << std::endl;
//
// // 可视化:右臂关节位置控制
// viewer.moveJ(q); // 更新目标角
// std::this_thread::sleep_for(std::chrono::duration<double>(0.01));
}); });
viewer.run();
viewer.run(); // 阻塞,直到你关掉窗口 benchmark_thread.join();
ctrl_thread.join(); // 控制线程结束
} }

View File

@ -2,11 +2,11 @@
// Created by lgv on 2025/11/3. // Created by lgv on 2025/11/3.
// //
#include "ik_solver/include/pinocchio_dls_ik_solver.h" #include "algorithms/kinematics/ik_solver/pinocchio/include/pinocchio_dls_ik_solver.h"
#include "gtest/gtest.h" #include "gtest/gtest.h"
#include "ik_solver/include/bias_srs_ik_slover.h" #include "algorithms/kinematics/ik_solver/srs/include/srs_ik_solver.h"
#include "ik_solver/include/joints_limit_analyzer.h" #include "algorithms/kinematics/ik_solver/lawba/include/joints_limit_analyzer.h"
#include "ik_solver/include/opt_psi_selector.h" #include "algorithms/kinematics/ik_solver/lawba/include/opt_psi_selector.h"
#include <algorithm> #include <algorithm>
#include <array> #include <array>
#include <chrono> #include <chrono>
@ -16,19 +16,60 @@
#include <iomanip> #include <iomanip>
#include <iostream> #include <iostream>
#include <limits> #include <limits>
#include <memory>
#include <mutex> #include <mutex>
#include <thread> #include <thread>
#include <vector> #include <vector>
#include "ik_solver/include/lawba_ik_solver.h" #include "algorithms/kinematics/ik_solver/lawba/include/lawba_ik_solver.h"
#include "algorithms/motion_planner/arm_motion/cartesian_motion/pinocchio_dls/include/pinocchio_dls_cartesian_motion_planner.h"
#include "common/math/transform_math.h"
#include "simulate/mujoco/mujoco_viewer/include/mujoco_viewer.h" #include "simulate/mujoco/mujoco_viewer/include/mujoco_viewer.h"
#include "planner/joint_space_planner/include/toppra_bspline.h" #include "algorithms/motion_planner/base_motion/joint_trajectory/toppra/include/toppra_joint_trajectory_planner.h"
#include "planner/joint_space_planner/include/joint_space_planner_creator.h" #include "common/math/support_functions.h"
#include "common/math/include/support_functions.h" #include "algorithms/kinematics/ik_solver/pinocchio/include/pinocchio_qp_ik_solver.h"
#include "ik_solver/include/pinocchio_qp_ik_solver.h"
#include <matplot/matplot.h> #include <matplot/matplot.h>
using namespace cmvr; using namespace cmvr;
namespace {
constexpr const char* kDefaultUrdf =
"/home/lgv/cmvr/0-workspace/cmvr-es/model/xiaoyan_description/dual_arm.urdf";
constexpr const char* kDefaultBaseFrame = "PELVIS_S";
constexpr const char* kDefaultFlangeFrame = "R_WRIST_R_S";
constexpr const char* kDefaultTcpFrame = "R_FINGER_TIP_FIXED";
config::PinocchioDlsIKConfig makeDlsConfig()
{
config::PinocchioDlsIKConfig cfg;
cfg.set_urdf_path(kDefaultUrdf);
cfg.set_base_frame_name(kDefaultBaseFrame);
cfg.set_flange_frame_name(kDefaultFlangeFrame);
cfg.set_tcp_frame_name(kDefaultTcpFrame);
cfg.set_max_iters(100);
cfg.set_pos_eps(1e-6);
cfg.set_rot_eps(1e-6);
cfg.set_damping(1e-4);
return cfg;
}
config::PinocchioQpIKConfig makeQpConfig()
{
config::PinocchioQpIKConfig cfg;
cfg.set_urdf_path(kDefaultUrdf);
cfg.set_base_frame_name(kDefaultBaseFrame);
cfg.set_flange_frame_name(kDefaultFlangeFrame);
cfg.set_tcp_frame_name(kDefaultTcpFrame);
cfg.set_lambda(1e-4);
cfg.set_w_posrot(0.5);
cfg.set_max_iters(100);
cfg.set_tol(1e-6);
cfg.set_qp_time_limit(1e-2);
return cfg;
}
} // namespace
struct IkSample { struct IkSample {
double psi; double psi;
@ -347,11 +388,11 @@ private:
// int viewer = 0; // int viewer = 0;
// // 把所有 IK 运算 + moveJ 循环放到控制线程里 // // 把所有 IK 运算 + moveJ 循环放到控制线程里
// std::thread ctrl_thread([&viewer]() { // std::thread ctrl_thread([&viewer]() {
// BiasSRSIkSolver slover; // SrsIKSolver slover(config::SrsIKConfig{});
// std::vector<IkSample> samples; // std::vector<IkSample> samples;
// samples.reserve(4096); // samples.reserve(4096);
// //
// slover.set_shoulder_config(BiasSRSIkSolver::INWARD); // slover.set_shoulder_config(SrsIKSolver::INWARD);
// //
// std::vector<double> joint_angles(7, 0); // std::vector<double> joint_angles(7, 0);
// // joint_angles = {0.875, 0.22, 0.2644, M_PI / 2, 1.8, 1.99, 1.56}; // // joint_angles = {0.875, 0.22, 0.2644, M_PI / 2, 1.8, 1.99, 1.56};
@ -431,7 +472,7 @@ private:
// //
// double psi = psi_vals[idx]; // double psi = psi_vals[idx];
// //
// auto q = slover.inverse_kinematics(target_pose, psi); // auto q = slover.ikWithPsi(target_pose, psi);
// if (q.size() != 7 || std::any_of(q.begin(), q.end(), // if (q.size() != 7 || std::any_of(q.begin(), q.end(),
// [](double v) { return !std::isfinite(v); })) { // [](double v) { return !std::isfinite(v); })) {
// ++bad; // ++bad;
@ -522,7 +563,7 @@ private:
// //
// // std::cout << std::fixed << std::setprecision(7); // // std::cout << std::fixed << std::setprecision(7);
// //
// BiasSRSIkSolver slover; // SrsIKSolver slover(config::SrsIKConfig{});
// std::vector<IkSample> samples; // std::vector<IkSample> samples;
// samples.reserve(4096); // samples.reserve(4096);
// //
@ -572,7 +613,7 @@ private:
// }; // };
// //
// // IK 解 // // IK 解
// auto q = slover.inverse_kinematics(target_pose, best_psi); // auto q = slover.ikWithPsi(target_pose, best_psi);
// //
// for (double q1: q) { // for (double q1: q) {
// std::cout << q1 << " , "; // std::cout << q1 << " , ";
@ -836,7 +877,7 @@ TEST(SRS_IK_TEST, MOVE_L_PLANNER_TEST) {
// 等 MuJoCo / OpenGL 初始化好 // 等 MuJoCo / OpenGL 初始化好
std::this_thread::sleep_for(3s); std::this_thread::sleep_for(3s);
LawbaIKSolver solver; LawbaIKSolver solver(config::LawbaIKConfig{});
// =============== 1) 设置初始关节状态 =============== // =============== 1) 设置初始关节状态 ===============
std::vector<double> joint_angles(7, 0.0); std::vector<double> joint_angles(7, 0.0);
@ -937,8 +978,8 @@ TEST(SRS_IK_TEST, MOVE_L_PLANNER_TEST) {
return; return;
} }
// =============== 5) 使用 JointSpacePlanner 对 IK 路点做时间参数化 =============== // =============== 5) 使用 JointTrajectoryPlanner 对 IK 路点做时间参数化 ===============
auto planner = JointSpacePlannerCreator::create(JointSpacePlannerType::TOPPRA_BSPLINE); auto planner = std::make_shared<ToppraJointTrajectoryPlanner>();
planner->setPathType(PathType::Natural); planner->setPathType(PathType::Natural);
// 按自己实际的关节约束改 // 按自己实际的关节约束改
@ -993,7 +1034,7 @@ TEST(SRS_IK_TEST, TR_TEST) {
using std::endl; using std::endl;
PinocchioQpIKSolver solver; PinocchioDlsIKSolver solver(makeDlsConfig());
solver.init(); solver.init();
Eigen::Matrix4d T; Eigen::Matrix4d T;
T << 9.99998311e-01, -1.78940420e-03, 4.20611000e-04, 3.83367005e-02, T << 9.99998311e-01, -1.78940420e-03, 4.20611000e-04, 3.83367005e-02,
@ -1235,15 +1276,8 @@ TEST(SRS_IK_TEST, MOVEL_S_CURVE_LOCAL_RUN_MUJOCO) {
using namespace std::chrono_literals; using namespace std::chrono_literals;
std::this_thread::sleep_for(3s); std::this_thread::sleep_for(3s);
cmvr::PinocchioDlsIKSolver solver( auto solver = std::make_shared<cmvr::PinocchioDlsIKSolver>(makeDlsConfig());
"/home/lgv/cmvr/0-workspace/cmvr-es/model/xiaoyan_description/dual_arm.urdf", if (!solver->init()) return;
"PELVIS_S",
"R_WRIST_R_S",
"R_FINGER_TIP_FIXED"
);
LawbaIKSolver solver_1;
if (!solver.init()) return;
// 起点关节 // 起点关节
// std::vector<double> q_start = {-0.424743, 0.759386, 1.80129, 2.03728, -1.34668, 0.0560845, -0.26}; // std::vector<double> q_start = {-0.424743, 0.759386, 1.80129, 2.03728, -1.34668, 0.0560845, -0.26};
@ -1254,14 +1288,14 @@ TEST(SRS_IK_TEST, MOVEL_S_CURVE_LOCAL_RUN_MUJOCO) {
// 起点位姿base下 // 起点位姿base下
Eigen::Matrix4d T0; Eigen::Matrix4d T0;
if (!solver_1.fk(q_start, T0, true)) return; if (!solver->fk(q_start, T0, true)) return;
std::cout << T0 << std::endl; std::cout << T0 << std::endl;
// 目标位姿base X 方向走 0.25m,姿态保持起点 // 目标位姿base X 方向走 0.25m,姿态保持起点
Eigen::Matrix4d Tg = T0; Eigen::Matrix4d Tg = T0;
// Tg(0,3) += 0.2; // Tg(0,3) += 0.2;
Tg(2,3) += 0.2; Tg(1,3) -= 0.2;
Eigen::Vector3d dp_check = Tg.block<3,1>(0,3) - T0.block<3,1>(0,3); Eigen::Vector3d dp_check = Tg.block<3,1>(0,3) - T0.block<3,1>(0,3);
std::cerr << "dp(base)=" << dp_check.transpose() << "\n"; std::cerr << "dp(base)=" << dp_check.transpose() << "\n";
@ -1272,15 +1306,29 @@ TEST(SRS_IK_TEST, MOVEL_S_CURVE_LOCAL_RUN_MUJOCO) {
const double j_tcp = 100.00; const double j_tcp = 100.00;
std::vector<double> qd_max(7, 3.0); std::vector<double> qd_max(7, 3.0);
std::vector<std::vector<double>> q_traj; cmvr::device::PinocchioDlsCartesianMotionPlanner planner(solver);
std::vector<double> t_traj; cmvr::config::MoveLPlannerConfig move_l_config;
move_l_config.set_sample_period_s(dt_gen);
move_l_config.set_position_gain(4.0);
if (!solver.moveL_SCurveLocal(Tg, q_start, q_traj, t_traj, move_l_config.set_rotation_gain(4.0);
dt_gen, v_tcp, a_tcp, j_tcp, qd_max, true)) { if (!planner.configureMoveL(move_l_config)) {
std::cerr << "moveL_SCurveLocal failed\n"; std::cerr << "configureMoveL failed\n";
return; return;
} }
cmvr::device::CartesianJointTrajectory trajectory;
if (!planner.planMoveL(cmvr::common::math::matrixToPose(Tg),
q_start,
qd_max,
v_tcp,
a_tcp,
j_tcp,
cmvr::device::FrameType::Base,
trajectory)) {
std::cerr << "planMoveL failed\n";
return;
}
const auto& q_traj = trajectory.position;
const auto& t_traj = trajectory.time;
std::cerr << "traj gen samples=" << q_traj.size() std::cerr << "traj gen samples=" << q_traj.size()
<< " T=" << (t_traj.empty()?0.0:t_traj.back()) << "s\n"; << " T=" << (t_traj.empty()?0.0:t_traj.back()) << "s\n";
@ -1288,7 +1336,7 @@ TEST(SRS_IK_TEST, MOVEL_S_CURVE_LOCAL_RUN_MUJOCO) {
write_qtraj_csv(csv_path, t_traj, q_traj); write_qtraj_csv(csv_path, t_traj, q_traj);
std::vector<Eigen::Vector3d> p, v, a; std::vector<Eigen::Vector3d> p, v, a;
if (build_tcp_pva(solver, t_traj, q_traj, p, v, a, true)) { if (build_tcp_pva(*solver, t_traj, q_traj, p, v, a, true)) {
write_tcp_pva_csv(tcp_csv_path, t_traj, p, v, a); write_tcp_pva_csv(tcp_csv_path, t_traj, p, v, a);
std::cerr << "saved tcp csv: " << tcp_csv_path << "\n"; std::cerr << "saved tcp csv: " << tcp_csv_path << "\n";
} else { } else {
@ -1297,7 +1345,7 @@ TEST(SRS_IK_TEST, MOVEL_S_CURVE_LOCAL_RUN_MUJOCO) {
std::cerr << "saved csv: " << csv_path << "\n"; std::cerr << "saved csv: " << csv_path << "\n";
// 正确的 TCP 速度统计(用 t_traj // 正确的 TCP 速度统计(用 t_traj
print_tcp_speed_stats(solver, t_traj, q_traj, true); print_tcp_speed_stats(*solver, t_traj, q_traj, true);
// 直接按采样时刻逐点下发位置目标,不在 viewer 内做轨迹缓存和插值。 // 直接按采样时刻逐点下发位置目标,不在 viewer 内做轨迹缓存和插值。
const auto t0 = std::chrono::steady_clock::now(); const auto t0 = std::chrono::steady_clock::now();
@ -1425,13 +1473,10 @@ TEST(SRS_IK_TEST, SPEEDL_RUN_MUJOCO) {
std::cerr << msg << "\n"; std::cerr << msg << "\n";
}; };
cmvr::PinocchioDlsIKSolver solver( auto dls_cfg = makeDlsConfig();
urdf_path, dls_cfg.set_urdf_path(urdf_path);
"PELVIS_S", auto solver = std::make_shared<cmvr::PinocchioDlsIKSolver>(dls_cfg);
"R_WRIST_R_S", if (!solver->init()) {
"R_FINGER_TIP_FIXED"
);
if (!solver.init()) {
fail("speedL test: solver.init() failed"); fail("speedL test: solver.init() failed");
return; return;
} }
@ -1449,23 +1494,25 @@ TEST(SRS_IK_TEST, SPEEDL_RUN_MUJOCO) {
std::this_thread::sleep_for(1s); std::this_thread::sleep_for(1s);
const std::vector<double> q_init = viewer.getQ(); const std::vector<double> q_init = viewer.getQ();
solver.update_joints_state(q_init); solver->update_joints_state(q_init);
std::vector<double> q_ref = q_init; std::vector<double> q_ref = q_init;
cmvr::PinocchioDlsIKSolver::SpeedLConfig speedl_config; cmvr::config::SpeedLPlannerConfig speedl_config;
speedl_config.linear_velocity_max = 0.55; speedl_config.set_linear_velocity_max(0.55);
speedl_config.linear_acceleration_max = 0.80; speedl_config.set_linear_acceleration_max(0.80);
speedl_config.linear_jerk_max = 3.30; speedl_config.set_linear_jerk_max(3.30);
speedl_config.angular_velocity_max = 1.00; speedl_config.set_angular_velocity_max(1.00);
speedl_config.angular_acceleration_max = 3.00; speedl_config.set_angular_acceleration_max(3.00);
speedl_config.angular_jerk_max = 12.0; speedl_config.set_angular_jerk_max(12.0);
speedl_config.joint_acceleration_max = std::vector<double>(7, 8.0); for (int i = 0; i < 7; ++i) {
speedl_config.linear_target_replan_threshold = 1e-4; speedl_config.add_joint_acceleration_max(8.0);
speedl_config.angular_target_replan_threshold = 1e-4; }
speedl_config.linear_reverse_cos_threshold = -0.8660254037844386; speedl_config.set_linear_target_replan_threshold(1e-4);
speedl_config.linear_reverse_switch_speed_threshold = 1e-3; speedl_config.set_angular_target_replan_threshold(1e-4);
// speedl_config.qdot_measurement_lowpass_alpha = 0.5; speedl_config.set_linear_reverse_cos_threshold(-0.8660254037844386);
if (!solver.configureSpeedL(speedl_config)) { speedl_config.set_linear_reverse_switch_speed_threshold(1e-3);
cmvr::device::PinocchioDlsCartesianMotionPlanner planner(solver);
if (!planner.configureSpeedL(speedl_config, q_init.size())) {
fail("speedL test: configureSpeedL() failed"); fail("speedL test: configureSpeedL() failed");
return; return;
} }
@ -1487,11 +1534,17 @@ TEST(SRS_IK_TEST, SPEEDL_RUN_MUJOCO) {
tcp_omega_traj.reserve(reserve_count); tcp_omega_traj.reserve(reserve_count);
auto record_sample = [&](double t_sample, auto record_sample = [&](double t_sample,
const std::vector<double>& q_sample) -> bool { const std::vector<double>& q_sample,
const std::vector<double>& qd_sample) -> bool {
q_traj.push_back(q_sample); q_traj.push_back(q_sample);
t_traj.push_back(t_sample); t_traj.push_back(t_sample);
twist_cmd_traj.push_back(solver.getSpeedLCommandTwistBase()); twist_cmd_traj.push_back(
const auto twist_exec = solver.getSpeedLExecutedTwistBase(); cmvr::common::math::velocityToVector(planner.getSpeedLCommandTwistBase()));
Eigen::Matrix<double, 6, 1> twist_exec = Eigen::Matrix<double, 6, 1>::Zero();
if (!solver->computeTwistBaseAtQ(q_sample, qd_sample, true, twist_exec)) {
fail("speedL test: failed to compute executed TCP twist");
return false;
}
Eigen::Vector3d tcp_pos = Eigen::Vector3d::Zero(); Eigen::Vector3d tcp_pos = Eigen::Vector3d::Zero();
Eigen::Vector3d tcp_vel = Eigen::Vector3d::Zero(); Eigen::Vector3d tcp_vel = Eigen::Vector3d::Zero();
@ -1535,21 +1588,21 @@ TEST(SRS_IK_TEST, SPEEDL_RUN_MUJOCO) {
Eigen::Matrix<double,6,1>::Zero(); Eigen::Matrix<double,6,1>::Zero();
if (t < segment_time) { if (t < segment_time) {
target_twist[1] = linear_speed_cmd; target_twist[1] = linear_speed_cmd;
target_twist[2] = 0.1; target_twist[2] = 0;
} else if (t < 2.0 * segment_time) { } else if (t < 2.0 * segment_time) {
target_twist[1] = -linear_speed_cmd; target_twist[1] = -linear_speed_cmd;
target_twist[2] = -0.1; target_twist[2] = 0;
} else { } else {
target_twist.setZero(); target_twist.setZero();
} }
std::vector<double> qd_cmd; std::vector<double> qd_cmd;
if (!solver.speedLStep(target_twist, if (!planner.speedLStep(cmvr::common::math::vectorToVelocity(target_twist),
dt, dt,
q_meas, q_meas,
qd_cmd, qd_meas,
cmvr::CartesianFrame::Base, qd_cmd,
true)) { cmvr::device::FrameType::Base)) {
viewer.moveJ(q_meas); viewer.moveJ(q_meas);
fail("speedL test: speedLStep() failed"); fail("speedL test: speedLStep() failed");
return; return;
@ -1564,7 +1617,7 @@ TEST(SRS_IK_TEST, SPEEDL_RUN_MUJOCO) {
std::min(q_ref[j], q_meas[j] + max_q_ref_tracking_error)); std::min(q_ref[j], q_meas[j] + max_q_ref_tracking_error));
} }
viewer.moveJ(q_ref); viewer.moveJ(q_ref);
if (!record_sample(t, q_meas)) { if (!record_sample(t, q_meas, qd_meas)) {
return; return;
} }
} }
@ -1583,12 +1636,12 @@ TEST(SRS_IK_TEST, SPEEDL_RUN_MUJOCO) {
Eigen::Matrix<double,6,1>::Zero(); Eigen::Matrix<double,6,1>::Zero();
std::vector<double> qd_cmd; std::vector<double> qd_cmd;
if (!solver.speedLStep(target_twist, if (!planner.speedLStep(cmvr::common::math::vectorToVelocity(target_twist),
dt, dt,
q_meas, q_meas,
qd_cmd, qd_meas,
cmvr::CartesianFrame::Base, qd_cmd,
true)) { cmvr::device::FrameType::Base)) {
viewer.moveJ(q_meas); viewer.moveJ(q_meas);
fail("speedL test: speedLStep() failed during stop phase"); fail("speedL test: speedLStep() failed during stop phase");
return; return;
@ -1603,7 +1656,7 @@ TEST(SRS_IK_TEST, SPEEDL_RUN_MUJOCO) {
std::min(q_ref[j], q_meas[j] + max_q_ref_tracking_error)); std::min(q_ref[j], q_meas[j] + max_q_ref_tracking_error));
} }
viewer.moveJ(q_ref); viewer.moveJ(q_ref);
if (!record_sample(t, q_meas)) { if (!record_sample(t, q_meas, qd_meas)) {
return; return;
} }
} }

View File

@ -0,0 +1,2 @@
add_subdirectory(base_motion)
add_subdirectory(arm_motion)

View File

@ -0,0 +1,17 @@
add_library(arm_motion SHARED
cartesian_motion/pinocchio_dls/src/pinocchio_dls_cartesian_motion_planner.cpp
cartesian_motion/pinocchio_qp/src/pinocchio_qp_cartesian_motion_planner.cpp
joint_motion/toppra/src/toppra_joint_motion_planner.cpp
)
target_include_directories(arm_motion PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(arm_motion
PUBLIC
cmvr_es::ik_solver
cmvr_es::base_motion
)
add_library(cmvr_es::arm_motion ALIAS arm_motion)
add_library(cmvr_es::algorithms::arm_motion ALIAS arm_motion)
install(TARGETS arm_motion LIBRARY DESTINATION lib)

View File

@ -0,0 +1,47 @@
#ifndef CMVR_ES_CARTESIAN_MOTION_PLANNER_H
#define CMVR_ES_CARTESIAN_MOTION_PLANNER_H
#include <vector>
#include "cmvr/config/arm_config/arm_config.pb.h"
#include "common/types/arm/arm_types.h"
namespace cmvr::device {
struct CartesianJointTrajectory {
std::vector<std::vector<double>> position;
std::vector<std::vector<double>> velocity;
std::vector<double> time;
};
class CartesianMotionPlanner {
public:
virtual ~CartesianMotionPlanner() = default;
virtual bool configureSpeedL(const config::SpeedLPlannerConfig& config,
std::size_t dof) = 0;
virtual bool configureMoveL(const config::MoveLPlannerConfig& config) = 0;
virtual bool planMoveL(const CartesianPose& target,
const std::vector<double>& q_start,
const std::vector<double>& qd_max,
double velocity,
double acceleration,
double jerk,
FrameType frame,
CartesianJointTrajectory& trajectory) = 0;
virtual bool speedLStep(const CartesianVelocity& target_velocity,
double dt,
const std::vector<double>& q_measured,
const std::vector<double>& qd_measured,
std::vector<double>& qd_command,
FrameType frame) = 0;
virtual bool updateSpeedLAcceleration(double acceleration) = 0;
virtual CartesianVelocity getSpeedLCommandTwistBase() const = 0;
};
} // namespace cmvr::device
#endif // CMVR_ES_CARTESIAN_MOTION_PLANNER_H

View File

@ -0,0 +1,78 @@
#ifndef CMVR_ES_CARTESIAN_MOTION_PLANNER_FACTORY_H
#define CMVR_ES_CARTESIAN_MOTION_PLANNER_FACTORY_H
#include <memory>
#include "algorithms/kinematics/ik_solver/pinocchio/include/pinocchio_dls_ik_solver.h"
#include "algorithms/kinematics/ik_solver/pinocchio/include/pinocchio_ik_base.h"
#include "algorithms/motion_planner/arm_motion/cartesian_motion/pinocchio_dls/include/pinocchio_dls_cartesian_motion_planner.h"
#include "algorithms/motion_planner/arm_motion/cartesian_motion/pinocchio_qp/include/pinocchio_qp_cartesian_motion_planner.h"
#include "algorithms/motion_planner/arm_motion/cartesian_motion/cartesian_motion_planner.h"
#include "cmvr/config/arm_config/arm_config.pb.h"
namespace cmvr::device {
class CartesianMotionPlannerFactory {
public:
static std::shared_ptr<CartesianMotionPlanner> create(
const config::MoveLConfig& move_l,
const config::SpeedLConfig& speed_l,
const std::shared_ptr<cmvr::PinocchioIKBase>& solver)
{
if (!solver) {
return nullptr;
}
switch (move_l.algorithm_case()) {
case config::MoveLConfig::kPinocchioQpCartesianMotionPlanner:
if (speed_l.algorithm_case() !=
config::SpeedLConfig::kPinocchioQpCartesianMotionPlanner) {
return nullptr;
}
return std::make_shared<PinocchioQpCartesianMotionPlanner>(solver);
case config::MoveLConfig::kPinocchioDlsCartesianMotionPlanner:
if (speed_l.algorithm_case() !=
config::SpeedLConfig::kPinocchioDlsCartesianMotionPlanner) {
return nullptr;
}
if (auto dls_solver = std::dynamic_pointer_cast<cmvr::PinocchioDlsIKSolver>(solver)) {
return std::make_shared<PinocchioDlsCartesianMotionPlanner>(dls_solver);
}
return nullptr;
case config::MoveLConfig::ALGORITHM_NOT_SET:
default:
return nullptr;
}
}
static const config::SpeedLPlannerConfig* speedLConfig(
const config::SpeedLConfig& cfg)
{
switch (cfg.algorithm_case()) {
case config::SpeedLConfig::kPinocchioQpCartesianMotionPlanner:
return &cfg.pinocchio_qp_cartesian_motion_planner();
case config::SpeedLConfig::kPinocchioDlsCartesianMotionPlanner:
return &cfg.pinocchio_dls_cartesian_motion_planner();
case config::SpeedLConfig::ALGORITHM_NOT_SET:
default:
return nullptr;
}
}
static const config::MoveLPlannerConfig* moveLConfig(
const config::MoveLConfig& cfg)
{
switch (cfg.algorithm_case()) {
case config::MoveLConfig::kPinocchioQpCartesianMotionPlanner:
return &cfg.pinocchio_qp_cartesian_motion_planner();
case config::MoveLConfig::kPinocchioDlsCartesianMotionPlanner:
return &cfg.pinocchio_dls_cartesian_motion_planner();
case config::MoveLConfig::ALGORITHM_NOT_SET:
default:
return nullptr;
}
}
};
} // namespace cmvr::device
#endif // CMVR_ES_CARTESIAN_MOTION_PLANNER_FACTORY_H

View File

@ -0,0 +1,66 @@
#ifndef CMVR_ES_PINOCCHIO_DLS_CARTESIAN_MOTION_PLANNER_H
#define CMVR_ES_PINOCCHIO_DLS_CARTESIAN_MOTION_PLANNER_H
#include <Eigen/Core>
#include <memory>
#include <vector>
#include "../../cartesian_motion_planner.h"
#include "algorithms/kinematics/ik_solver/pinocchio/include/pinocchio_dls_ik_solver.h"
#include "algorithms/motion_planner/base_motion/cartesian_velocity/twist_limiter/include/cartesian_twist_limiter.h"
namespace cmvr::device {
class PinocchioDlsCartesianMotionPlanner final : public CartesianMotionPlanner {
public:
explicit PinocchioDlsCartesianMotionPlanner(std::shared_ptr<cmvr::PinocchioDlsIKSolver> solver);
bool configureSpeedL(const config::SpeedLPlannerConfig& config,
std::size_t dof) override;
bool configureMoveL(const config::MoveLPlannerConfig& config) override;
bool planMoveL(const CartesianPose& target,
const std::vector<double>& q_start,
const std::vector<double>& qd_max,
double velocity,
double acceleration,
double jerk,
FrameType frame,
CartesianJointTrajectory& trajectory) override;
bool speedLStep(const CartesianVelocity& target_velocity,
double dt,
const std::vector<double>& q_measured,
const std::vector<double>& qd_measured,
std::vector<double>& qd_command,
FrameType frame) override;
bool updateSpeedLAcceleration(double acceleration) override;
CartesianVelocity getSpeedLCommandTwistBase() const override;
private:
bool refreshJointLimits_();
Eigen::VectorXd applyJointVelocityLimits_(const Eigen::VectorXd& qdot) const;
Eigen::VectorXd applyJointSoftLimits_(const Eigen::VectorXd& q,
const Eigen::VectorXd& qdot);
Eigen::VectorXd applyJointAccelerationLimits_(const Eigen::VectorXd& qdot,
const Eigen::VectorXd& reference,
double dt) const;
std::shared_ptr<cmvr::PinocchioDlsIKSolver> solver_{nullptr};
config::MoveLPlannerConfig movel_config_{};
config::SpeedLPlannerConfig speedl_config_{};
cmvr::CartesianTwistLimiter twist_limiter_{};
Eigen::VectorXd joint_lower_limits_;
Eigen::VectorXd joint_upper_limits_;
Eigen::VectorXd joint_velocity_limits_;
std::vector<double> prev_qdot_command_;
Eigen::Matrix<double, 6, 1> speedl_command_twist_base_{Eigen::Matrix<double, 6, 1>::Zero()};
double speedl_applied_acceleration_{0.25};
bool speedl_configured_{false};
};
} // namespace cmvr::device
#endif // CMVR_ES_PINOCCHIO_DLS_CARTESIAN_MOTION_PLANNER_H

View File

@ -0,0 +1,408 @@
#include "algorithms/motion_planner/arm_motion/cartesian_motion/pinocchio_dls/include/pinocchio_dls_cartesian_motion_planner.h"
#include <Eigen/Geometry>
#include <algorithm>
#include <cmath>
#include <limits>
#include <utility>
#include "algorithms/motion_planner/arm_motion/common/include/twist_limiter_config.h"
#include "algorithms/motion_planner/base_motion/motion_profile/s_curve/include/s_curve.h"
#include "common/math/cartesian_motion_math.h"
#include "common/math/joint_limits.h"
#include "common/config/config_files.h"
#include "common/math/transform_math.h"
namespace cmvr::device {
using cmvr::common::config::positiveOr;
using cmvr::device::cartesian_motion::clamp;
using cmvr::device::cartesian_motion::directionDeviationDeg;
using cmvr::device::cartesian_motion::rotationVector;
using cmvr::device::cartesian_motion::toEigenVector;
using cmvr::device::cartesian_motion::toStdVector;
PinocchioDlsCartesianMotionPlanner::PinocchioDlsCartesianMotionPlanner(
std::shared_ptr<cmvr::PinocchioDlsIKSolver> solver)
: solver_(std::move(solver))
{
}
bool PinocchioDlsCartesianMotionPlanner::refreshJointLimits_()
{
if (!solver_) {
return false;
}
if (!solver_->getJointPositionLimits(joint_lower_limits_, joint_upper_limits_)) {
return false;
}
if (!solver_->getJointVelocityLimits(joint_velocity_limits_)) {
return false;
}
return true;
}
Eigen::VectorXd PinocchioDlsCartesianMotionPlanner::applyJointVelocityLimits_(
const Eigen::VectorXd& qdot) const
{
return cmvr::kinematics::scaleToVelocityLimits(qdot, joint_velocity_limits_);
}
Eigen::VectorXd PinocchioDlsCartesianMotionPlanner::applyJointSoftLimits_(
const Eigen::VectorXd& q,
const Eigen::VectorXd& qdot)
{
if (joint_lower_limits_.size() != q.size() ||
joint_upper_limits_.size() != q.size() ||
qdot.size() != q.size()) {
return qdot;
}
Eigen::VectorXd limited = qdot;
for (Eigen::Index i = 0; i < q.size(); ++i) {
const double lower = joint_lower_limits_[i];
const double upper = joint_upper_limits_[i];
if (!std::isfinite(lower) || !std::isfinite(upper) || upper <= lower) {
continue;
}
const double span = upper - lower;
const double margin = std::max(0.02, 0.08 * span);
if (limited[i] < 0.0 && q[i] < lower + margin) {
const double ratio = clamp((q[i] - lower) / margin, 0.0, 1.0);
limited[i] *= ratio;
if (q[i] <= lower) {
limited[i] = std::max(0.0, limited[i]);
}
} else if (limited[i] > 0.0 && q[i] > upper - margin) {
const double ratio = clamp((upper - q[i]) / margin, 0.0, 1.0);
limited[i] *= ratio;
if (q[i] >= upper) {
limited[i] = std::min(0.0, limited[i]);
}
}
}
return limited;
}
Eigen::VectorXd PinocchioDlsCartesianMotionPlanner::applyJointAccelerationLimits_(
const Eigen::VectorXd& qdot,
const Eigen::VectorXd& reference,
const double dt) const
{
if (reference.size() != qdot.size() || dt <= 0.0) {
return qdot;
}
Eigen::VectorXd limited = qdot;
for (Eigen::Index i = 0; i < qdot.size(); ++i) {
double acc_limit = 8.0;
if (i < speedl_config_.joint_acceleration_max_size() &&
speedl_config_.joint_acceleration_max(static_cast<int>(i)) > 0.0) {
acc_limit = speedl_config_.joint_acceleration_max(static_cast<int>(i));
}
const double delta_max = acc_limit * dt;
const double delta = clamp(qdot[i] - reference[i], -delta_max, delta_max);
limited[i] = reference[i] + delta;
}
return limited;
}
bool PinocchioDlsCartesianMotionPlanner::configureSpeedL(const config::SpeedLPlannerConfig& config,
const std::size_t dof)
{
if (!solver_ || dof == 0) {
return false;
}
const auto solver_dof = static_cast<std::size_t>(std::max(0, solver_->chainVelocityDof()));
if (solver_dof != 0 && solver_dof != dof) {
return false;
}
speedl_config_ = config;
if (!refreshJointLimits_()) {
return false;
}
cartesian_motion::configureTwistLimiterFromSpeedLConfig(twist_limiter_, speedl_config_);
prev_qdot_command_.assign(dof, 0.0);
speedl_command_twist_base_.setZero();
speedl_applied_acceleration_ = positiveOr(speedl_config_.linear_acceleration_max(), 5.0);
speedl_configured_ = true;
return true;
}
bool PinocchioDlsCartesianMotionPlanner::configureMoveL(
const config::MoveLPlannerConfig& config)
{
if (!std::isfinite(config.sample_period_s()) ||
!std::isfinite(config.position_gain()) ||
!std::isfinite(config.rotation_gain())) {
return false;
}
movel_config_ = config;
return true;
}
bool PinocchioDlsCartesianMotionPlanner::planMoveL(const CartesianPose& target,
const std::vector<double>& q_start,
const std::vector<double>& qd_max,
const double velocity,
const double acceleration,
const double jerk,
const FrameType frame,
CartesianJointTrajectory& trajectory)
{
trajectory = {};
const double dt = positiveOr(movel_config_.sample_period_s(), 0.001);
if (!solver_ || q_start.empty() ||
velocity <= 0.0 || acceleration <= 0.0 || jerk <= 0.0) {
return false;
}
if (static_cast<int>(q_start.size()) != solver_->chainDof()) {
return false;
}
if (!qd_max.empty() && qd_max.size() != q_start.size()) {
return false;
}
if (!refreshJointLimits_()) {
return false;
}
Eigen::Matrix4d start_pose_base = Eigen::Matrix4d::Identity();
if (!solver_->fk(q_start, start_pose_base, true)) {
return false;
}
const Eigen::Matrix4d target_pose_input = common::math::poseToMatrix(target);
const Eigen::Matrix4d target_pose_base =
frame == FrameType::Tool ? start_pose_base * target_pose_input : target_pose_input;
const Eigen::Vector3d p_start = start_pose_base.block<3, 1>(0, 3);
const Eigen::Vector3d p_target = target_pose_base.block<3, 1>(0, 3);
const Eigen::Vector3d dp = p_target - p_start;
const double linear_distance = dp.norm();
const Eigen::Matrix3d R_start = start_pose_base.block<3, 3>(0, 0);
const Eigen::Matrix3d R_target = target_pose_base.block<3, 3>(0, 0);
const Eigen::Vector3d total_rotation_vector = rotationVector(R_target * R_start.transpose());
const double angular_distance = total_rotation_vector.norm();
const double path_length = linear_distance > 1e-9 ? linear_distance : angular_distance;
trajectory.position.push_back(q_start);
trajectory.velocity.push_back(std::vector<double>(q_start.size(), 0.0));
trajectory.time.push_back(0.0);
if (path_length <= 1e-9) {
return true;
}
cmvr::SCurve curve(velocity, acceleration, jerk);
const cmvr::SCurveProfile profile = curve.calculateProfile(0.0, path_length, 0.0, 0.0);
if (profile.total_time <= 0.0) {
return false;
}
Eigen::VectorXd q_current = toEigenVector(q_start);
Eigen::Vector3d linear_direction = Eigen::Vector3d::Zero();
if (linear_distance > 1e-9) {
linear_direction = dp / linear_distance;
}
const Eigen::Quaterniond q_start_rot(R_start);
const Eigen::Quaterniond q_target_rot(R_target);
const double position_gain = positiveOr(movel_config_.position_gain(), 4.0);
const double rotation_gain = positiveOr(movel_config_.rotation_gain(), 4.0);
double previous_time = 0.0;
for (double t = std::min(dt, profile.total_time);
t <= profile.total_time + 1e-9;
t = std::min(t + dt, profile.total_time)) {
const double step_dt = std::max(1e-6, t - previous_time);
previous_time = t;
const double s = clamp(curve.getPositionAtTime(profile, t), 0.0, path_length);
const double sd = std::max(0.0, curve.getVelocityAtTime(profile, t));
const double ratio = clamp(s / path_length, 0.0, 1.0);
const std::vector<double> q_std = toStdVector(q_current);
Eigen::Matrix4d current_pose_base = Eigen::Matrix4d::Identity();
if (!solver_->fk(q_std, current_pose_base, true)) {
return false;
}
const Eigen::Vector3d p_current = current_pose_base.block<3, 1>(0, 3);
const Eigen::Vector3d p_desired = p_start + ratio * dp;
Eigen::Matrix<double, 6, 1> target_twist_base = Eigen::Matrix<double, 6, 1>::Zero();
target_twist_base.head<3>() =
linear_direction * sd + position_gain * (p_desired - p_current);
if (angular_distance > 1e-9) {
const Eigen::Matrix3d R_current = current_pose_base.block<3, 3>(0, 0);
const Eigen::Matrix3d R_desired =
q_start_rot.slerp(ratio, q_target_rot).toRotationMatrix();
const Eigen::Vector3d rotation_error = rotationVector(R_desired * R_current.transpose());
target_twist_base.tail<3>() =
(total_rotation_vector / path_length) * sd + rotation_gain * rotation_error;
}
Eigen::MatrixXd jacobian_base;
Eigen::Matrix3d base_R_tool;
if (!solver_->computeJacobianBaseAtQ(q_std, true, jacobian_base, base_R_tool)) {
return false;
}
std::vector<double> qdot_std;
if (!solver_->solveVelocityBase(jacobian_base,
target_twist_base,
q_std,
qdot_std,
std::numeric_limits<double>::infinity())) {
return false;
}
Eigen::VectorXd qdot = applyJointVelocityLimits_(toEigenVector(qdot_std));
if (!qd_max.empty()) {
double scale = 1.0;
for (Eigen::Index i = 0; i < qdot.size(); ++i) {
const double limit = std::abs(qd_max[static_cast<std::size_t>(i)]);
if (limit <= 0.0 || !std::isfinite(limit)) {
continue;
}
const double value = std::abs(qdot[i]);
if (value > limit) {
scale = std::min(scale, limit / value);
}
}
qdot *= scale;
}
qdot = applyJointSoftLimits_(q_current, qdot);
q_current += qdot * step_dt;
if (joint_lower_limits_.size() == q_current.size() &&
joint_upper_limits_.size() == q_current.size()) {
q_current = q_current.cwiseMax(joint_lower_limits_).cwiseMin(joint_upper_limits_);
}
trajectory.position.push_back(toStdVector(q_current));
trajectory.velocity.push_back(toStdVector(qdot));
trajectory.time.push_back(t);
if (t >= profile.total_time - 1e-9) {
break;
}
}
return true;
}
bool PinocchioDlsCartesianMotionPlanner::speedLStep(const CartesianVelocity& target_velocity,
const double dt,
const std::vector<double>& q_measured,
const std::vector<double>& qd_measured,
std::vector<double>& qd_command,
const FrameType frame)
{
qd_command.clear();
if (!solver_ || !speedl_configured_ || dt <= 0.0) {
return false;
}
if (static_cast<int>(q_measured.size()) != solver_->chainDof() ||
static_cast<int>(qd_measured.size()) != solver_->chainVelocityDof()) {
return false;
}
Eigen::Matrix<double, 6, 1> measured_twist_base = Eigen::Matrix<double, 6, 1>::Zero();
Eigen::MatrixXd jacobian_base;
Eigen::Matrix3d base_R_tool = Eigen::Matrix3d::Identity();
if (!solver_->computeTwistBaseAtQ(q_measured,
qd_measured,
true,
measured_twist_base,
&jacobian_base,
&base_R_tool)) {
return false;
}
const Eigen::Matrix<double, 6, 1> target_twist = common::math::velocityToVector(target_velocity);
if (target_twist.squaredNorm() <= 1e-12) {
twist_limiter_.synchronize(measured_twist_base, dt, true);
} else if (speedl_command_twist_base_.squaredNorm() <= 1e-12) {
twist_limiter_.initialize(Eigen::Matrix<double, 6, 1>::Zero());
}
twist_limiter_.setTargetTwist(target_twist, common::math::toPlannerFrame(frame));
speedl_command_twist_base_ = twist_limiter_.update(dt, base_R_tool);
std::vector<double> qdot_std;
if (!solver_->solveVelocityBase(jacobian_base,
speedl_command_twist_base_,
q_measured,
qdot_std,
std::numeric_limits<double>::infinity())) {
return false;
}
Eigen::VectorXd qdot = applyJointVelocityLimits_(toEigenVector(qdot_std));
qdot = applyJointSoftLimits_(toEigenVector(q_measured), qdot);
Eigen::VectorXd reference = toEigenVector(qd_measured);
if (prev_qdot_command_.size() == qdot.size()) {
reference = toEigenVector(prev_qdot_command_);
}
qdot = applyJointAccelerationLimits_(qdot, reference, dt);
const Eigen::Matrix<double, 6, 1> achieved_twist_base = jacobian_base * qdot;
const Eigen::Vector3d desired_linear = speedl_command_twist_base_.head<3>();
const Eigen::Vector3d achieved_linear = achieved_twist_base.head<3>();
const double desired_linear_norm = desired_linear.norm();
const double achieved_linear_norm = achieved_linear.norm();
const double direction_check_min_speed =
std::max(1e-4, positiveOr(speedl_config_.linear_reverse_switch_speed_threshold(), 1e-3));
if (desired_linear_norm > direction_check_min_speed) {
const double linear_min_speed_ratio =
clamp(positiveOr(speedl_config_.linear_min_speed_ratio(), 0.2), 0.0, 1.0);
const double speed_ratio = achieved_linear_norm / desired_linear_norm;
if (speed_ratio < linear_min_speed_ratio) {
return false;
}
if (achieved_linear_norm > direction_check_min_speed) {
const double deviation_deg = directionDeviationDeg(desired_linear, achieved_linear);
const double severe_direction_deviation_deg =
positiveOr(speedl_config_.severe_direction_deviation_deg(), 45.0);
if (deviation_deg >= severe_direction_deviation_deg) {
return false;
}
}
}
qd_command = toStdVector(qdot);
prev_qdot_command_ = qd_command;
return true;
}
bool PinocchioDlsCartesianMotionPlanner::updateSpeedLAcceleration(const double acceleration)
{
if (!speedl_configured_ || acceleration <= 0.0) {
return false;
}
if (std::abs(speedl_applied_acceleration_ - acceleration) <= 1e-9) {
return true;
}
cartesian_motion::updateTwistLimiterAcceleration(
twist_limiter_,
speedl_config_,
acceleration);
speedl_applied_acceleration_ = acceleration;
return true;
}
CartesianVelocity PinocchioDlsCartesianMotionPlanner::getSpeedLCommandTwistBase() const
{
return common::math::vectorToVelocity(speedl_command_twist_base_);
}
} // namespace cmvr::device

View File

@ -0,0 +1,76 @@
#ifndef CMVR_ES_PINOCCHIO_QP_CARTESIAN_MOTION_PLANNER_H
#define CMVR_ES_PINOCCHIO_QP_CARTESIAN_MOTION_PLANNER_H
#include <Eigen/Core>
#include <memory>
#include <vector>
#include "algorithms/kinematics/ik_solver/pinocchio/include/pinocchio_ik_base.h"
#include "../../cartesian_motion_planner.h"
#include "algorithms/motion_planner/base_motion/cartesian_velocity/twist_limiter/include/cartesian_twist_limiter.h"
#include "common/math/qp_solver.h"
namespace cmvr::device {
class PinocchioQpCartesianMotionPlanner final : public CartesianMotionPlanner {
public:
explicit PinocchioQpCartesianMotionPlanner(std::shared_ptr<cmvr::PinocchioIKBase> solver);
bool configureSpeedL(const config::SpeedLPlannerConfig& config,
std::size_t dof) override;
bool configureMoveL(const config::MoveLPlannerConfig& config) override;
bool planMoveL(const CartesianPose& target,
const std::vector<double>& q_start,
const std::vector<double>& qd_max,
double velocity,
double acceleration,
double jerk,
FrameType frame,
CartesianJointTrajectory& trajectory) override;
bool speedLStep(const CartesianVelocity& target_velocity,
double dt,
const std::vector<double>& q_measured,
const std::vector<double>& qd_measured,
std::vector<double>& qd_command,
FrameType frame) override;
bool updateSpeedLAcceleration(double acceleration) override;
CartesianVelocity getSpeedLCommandTwistBase() const override;
private:
bool refreshJointLimits_(const config::CartesianVelocityQpConfig& config);
bool configureQpSolver_(Eigen::Index dof, double solver_eps);
bool solveVelocityQp_(const Eigen::MatrixXd& jacobian_base,
const Eigen::Matrix<double, 6, 1>& target_twist_base,
const Eigen::VectorXd& q_measured,
const Eigen::VectorXd& qd_reference,
double dt,
const std::vector<double>& qd_max,
bool enforce_acceleration_limits,
const config::CartesianVelocityQpConfig& qp_config,
Eigen::VectorXd& qdot);
bool validateAchievedLinearTwist_(const Eigen::MatrixXd& jacobian_base,
const Eigen::VectorXd& qdot) const;
std::shared_ptr<cmvr::PinocchioIKBase> solver_{nullptr};
config::MoveLPlannerConfig movel_config_{};
config::SpeedLPlannerConfig speedl_config_{};
cmvr::CartesianTwistLimiter twist_limiter_{};
cmvr::QPSolver qp_solver_;
int qp_solver_dof_{0};
double qp_solver_eps_{0.0};
Eigen::VectorXd joint_lower_limits_;
Eigen::VectorXd joint_upper_limits_;
Eigen::VectorXd joint_velocity_limits_;
std::vector<double> prev_qdot_command_;
Eigen::Matrix<double, 6, 1> speedl_command_twist_base_{Eigen::Matrix<double, 6, 1>::Zero()};
double speedl_applied_acceleration_{0.25};
bool speedl_configured_{false};
};
} // namespace cmvr::device
#endif // CMVR_ES_PINOCCHIO_QP_CARTESIAN_MOTION_PLANNER_H

View File

@ -0,0 +1,590 @@
#include "algorithms/motion_planner/arm_motion/cartesian_motion/pinocchio_qp/include/pinocchio_qp_cartesian_motion_planner.h"
#include <algorithm>
#include <cmath>
#include <Eigen/Geometry>
#include <limits>
#include <unordered_map>
#include <utility>
#include "algorithms/motion_planner/arm_motion/common/include/twist_limiter_config.h"
#include "algorithms/motion_planner/base_motion/motion_profile/s_curve/include/s_curve.h"
#include "common/base/logging/logger.h"
#include "common/math/cartesian_motion_math.h"
#include "common/math/joint_limits.h"
#include "common/config/config_files.h"
#include "common/math/proto_geometry.h"
#include "common/math/transform_math.h"
namespace cmvr::device {
using cmvr::common::config::positiveOr;
using cmvr::device::cartesian_motion::clamp;
using cmvr::device::cartesian_motion::directionDeviationDeg;
using cmvr::device::cartesian_motion::rotationVector;
using cmvr::device::cartesian_motion::toEigenVector;
using cmvr::device::cartesian_motion::toStdVector;
namespace {
const config::CartesianVelocityQpConfig& qpConfigOrDefault(
const config::CartesianVelocityQpConfig& config)
{
static const config::CartesianVelocityQpConfig defaults;
return config.ByteSizeLong() > 0 ? config : defaults;
}
Eigen::Matrix<double, 6, 1> twistTrackingWeightOrDefault(
const cmvr::common::Vec6& value)
{
Eigen::Matrix<double, 6, 1> defaults;
defaults << 1.0, 1.0, 1.0, 0.5, 0.5, 0.5;
Eigen::Matrix<double, 6, 1> weight =
cmvr::common::math::toEigenVec6(value, defaults);
for (int i = 0; i < weight.size(); ++i) {
if (!std::isfinite(weight[i]) || weight[i] <= 0.0) {
weight[i] = defaults[i];
}
}
return weight;
}
} // namespace
PinocchioQpCartesianMotionPlanner::PinocchioQpCartesianMotionPlanner(
std::shared_ptr<cmvr::PinocchioIKBase> solver)
: solver_(std::move(solver))
{
}
bool PinocchioQpCartesianMotionPlanner::refreshJointLimits_(
const config::CartesianVelocityQpConfig& config)
{
if (!solver_) {
return false;
}
const auto source = config.has_joint_limits()
? config.joint_limits().source()
: config::JOINT_LIMIT_SOURCE_URDF;
if (source == config::JOINT_LIMIT_SOURCE_CUSTOM) {
std::vector<std::string> joint_names;
if (!solver_->getChainJointNames(joint_names) || joint_names.empty()) {
return false;
}
std::unordered_map<std::string, config::JointLimitConfig> custom_limits;
if (config.has_joint_limits()) {
custom_limits.reserve(
static_cast<std::size_t>(config.joint_limits().joints_size()));
for (const auto& item : config.joint_limits().joints()) {
if (!item.joint_name().empty()) {
custom_limits[item.joint_name()] = item;
}
}
}
const auto dof = static_cast<Eigen::Index>(joint_names.size());
joint_lower_limits_.resize(dof);
joint_upper_limits_.resize(dof);
joint_velocity_limits_.resize(dof);
for (Eigen::Index i = 0; i < dof; ++i) {
const auto it = custom_limits.find(joint_names[static_cast<std::size_t>(i)]);
if (it == custom_limits.end()) {
CMVR_LOG(ERROR) << "[PinocchioQpCartesianMotionPlanner] missing custom joint limit for "
<< joint_names[static_cast<std::size_t>(i)];
return false;
}
const auto& limit = it->second;
if (!std::isfinite(limit.lower()) || !std::isfinite(limit.upper()) ||
!std::isfinite(limit.velocity()) || limit.upper() <= limit.lower() ||
limit.velocity() <= 0.0) {
CMVR_LOG(ERROR) << "[PinocchioQpCartesianMotionPlanner] invalid custom joint limit for "
<< limit.joint_name();
return false;
}
joint_lower_limits_[i] = limit.lower();
joint_upper_limits_[i] = limit.upper();
joint_velocity_limits_[i] = std::abs(limit.velocity());
}
return true;
}
if (!solver_->getJointPositionLimits(joint_lower_limits_, joint_upper_limits_)) {
return false;
}
if (!solver_->getJointVelocityLimits(joint_velocity_limits_)) {
return false;
}
return true;
}
bool PinocchioQpCartesianMotionPlanner::configureQpSolver_(const Eigen::Index dof,
const double solver_eps)
{
if (dof <= 0) {
return false;
}
const double eps = solver_eps > 0.0 ? solver_eps : 1e-3;
if (qp_solver_dof_ != static_cast<int>(dof) || std::abs(qp_solver_eps_ - eps) > 1e-12) {
qp_solver_.Setup(static_cast<int>(dof), static_cast<int>(dof), eps);
qp_solver_.ResetIsFirst();
qp_solver_dof_ = static_cast<int>(dof);
qp_solver_eps_ = eps;
}
return true;
}
bool PinocchioQpCartesianMotionPlanner::configureSpeedL(const config::SpeedLPlannerConfig& config,
const std::size_t dof)
{
if (!solver_ || dof == 0) {
return false;
}
const auto solver_dof = static_cast<std::size_t>(std::max(0, solver_->chainVelocityDof()));
if (solver_dof != 0 && solver_dof != dof) {
return false;
}
speedl_config_ = config;
const auto& qp_config = qpConfigOrDefault(speedl_config_.qp());
if (!refreshJointLimits_(qp_config)) {
return false;
}
cartesian_motion::configureTwistLimiterFromSpeedLConfig(twist_limiter_, speedl_config_);
prev_qdot_command_.assign(dof, 0.0);
speedl_command_twist_base_.setZero();
speedl_applied_acceleration_ = positiveOr(speedl_config_.linear_acceleration_max(), 5.0);
if (!configureQpSolver_(static_cast<Eigen::Index>(dof),
positiveOr(qp_config.solver_eps(), 1e-3))) {
return false;
}
speedl_configured_ = true;
return true;
}
bool PinocchioQpCartesianMotionPlanner::configureMoveL(
const config::MoveLPlannerConfig& config)
{
if (!std::isfinite(config.sample_period_s()) ||
!std::isfinite(config.position_gain()) ||
!std::isfinite(config.rotation_gain())) {
return false;
}
movel_config_ = config;
return true;
}
bool PinocchioQpCartesianMotionPlanner::planMoveL(const CartesianPose& target,
const std::vector<double>& q_start,
const std::vector<double>& qd_max,
const double velocity,
const double acceleration,
const double jerk,
const FrameType frame,
CartesianJointTrajectory& trajectory)
{
trajectory = {};
const double dt = positiveOr(movel_config_.sample_period_s(), 0.001);
if (!solver_ || q_start.empty() ||
velocity <= 0.0 || acceleration <= 0.0 || jerk <= 0.0) {
return false;
}
if (static_cast<int>(q_start.size()) != solver_->chainDof()) {
return false;
}
if (!qd_max.empty() && qd_max.size() != q_start.size()) {
return false;
}
const auto& qp_config = qpConfigOrDefault(movel_config_.qp());
if (!refreshJointLimits_(qp_config)) {
return false;
}
Eigen::Matrix4d start_pose_base = Eigen::Matrix4d::Identity();
if (!solver_->fk(q_start, start_pose_base, true)) {
return false;
}
const Eigen::Matrix4d target_pose_input = common::math::poseToMatrix(target);
const Eigen::Matrix4d target_pose_base =
frame == FrameType::Tool ? start_pose_base * target_pose_input : target_pose_input;
const Eigen::Vector3d p_start = start_pose_base.block<3, 1>(0, 3);
const Eigen::Vector3d p_target = target_pose_base.block<3, 1>(0, 3);
const Eigen::Vector3d dp = p_target - p_start;
const double linear_distance = dp.norm();
const Eigen::Matrix3d R_start = start_pose_base.block<3, 3>(0, 0);
const Eigen::Matrix3d R_target = target_pose_base.block<3, 3>(0, 0);
const Eigen::Vector3d total_rotation_vector = rotationVector(R_target * R_start.transpose());
const double angular_distance = total_rotation_vector.norm();
const double path_length = linear_distance > 1e-9 ? linear_distance : angular_distance;
trajectory.position.push_back(q_start);
trajectory.velocity.push_back(std::vector<double>(q_start.size(), 0.0));
trajectory.time.push_back(0.0);
if (path_length <= 1e-9) {
return true;
}
cmvr::SCurve curve(velocity, acceleration, jerk);
const cmvr::SCurveProfile profile = curve.calculateProfile(0.0, path_length, 0.0, 0.0);
if (profile.total_time <= 0.0) {
return false;
}
Eigen::VectorXd q_current = toEigenVector(q_start);
Eigen::VectorXd qdot_previous = Eigen::VectorXd::Zero(static_cast<Eigen::Index>(q_start.size()));
Eigen::Vector3d linear_direction = Eigen::Vector3d::Zero();
if (linear_distance > 1e-9) {
linear_direction = dp / linear_distance;
}
const Eigen::Quaterniond q_start_rot(R_start);
const Eigen::Quaterniond q_target_rot(R_target);
const double position_gain = positiveOr(movel_config_.position_gain(), 4.0);
const double rotation_gain = positiveOr(movel_config_.rotation_gain(), 4.0);
qp_solver_.ResetIsFirst();
double previous_time = 0.0;
for (double t = std::min(dt, profile.total_time);
t <= profile.total_time + 1e-9;
t = std::min(t + dt, profile.total_time)) {
const double step_dt = std::max(1e-6, t - previous_time);
previous_time = t;
const double s = clamp(curve.getPositionAtTime(profile, t), 0.0, path_length);
const double sd = std::max(0.0, curve.getVelocityAtTime(profile, t));
const double ratio = clamp(s / path_length, 0.0, 1.0);
const std::vector<double> q_std = toStdVector(q_current);
Eigen::Matrix4d current_pose_base = Eigen::Matrix4d::Identity();
if (!solver_->fk(q_std, current_pose_base, true)) {
return false;
}
const Eigen::Vector3d p_current = current_pose_base.block<3, 1>(0, 3);
const Eigen::Vector3d p_desired = p_start + ratio * dp;
Eigen::Matrix<double, 6, 1> target_twist_base = Eigen::Matrix<double, 6, 1>::Zero();
target_twist_base.head<3>() =
linear_direction * sd + position_gain * (p_desired - p_current);
if (angular_distance > 1e-9) {
const Eigen::Matrix3d R_current = current_pose_base.block<3, 3>(0, 0);
const Eigen::Matrix3d R_desired =
q_start_rot.slerp(ratio, q_target_rot).toRotationMatrix();
const Eigen::Vector3d rotation_error =
rotationVector(R_desired * R_current.transpose());
target_twist_base.tail<3>() =
(total_rotation_vector / path_length) * sd + rotation_gain * rotation_error;
}
Eigen::MatrixXd jacobian_base;
Eigen::Matrix3d base_R_tool = Eigen::Matrix3d::Identity();
if (!solver_->computeJacobianBaseAtQ(q_std, true, jacobian_base, base_R_tool)) {
return false;
}
Eigen::VectorXd qdot;
if (!solveVelocityQp_(jacobian_base,
target_twist_base,
q_current,
qdot_previous,
step_dt,
qd_max,
false,
qp_config,
qdot)) {
return false;
}
q_current += qdot * step_dt;
if (joint_lower_limits_.size() == q_current.size() &&
joint_upper_limits_.size() == q_current.size()) {
q_current = q_current.cwiseMax(joint_lower_limits_).cwiseMin(joint_upper_limits_);
}
trajectory.position.push_back(toStdVector(q_current));
trajectory.velocity.push_back(toStdVector(qdot));
trajectory.time.push_back(t);
qdot_previous = qdot;
if (t >= profile.total_time - 1e-9) {
break;
}
}
return true;
}
bool PinocchioQpCartesianMotionPlanner::solveVelocityQp_(
const Eigen::MatrixXd& jacobian_base,
const Eigen::Matrix<double, 6, 1>& target_twist_base,
const Eigen::VectorXd& q_measured,
const Eigen::VectorXd& qd_reference,
const double dt,
const std::vector<double>& qd_max,
const bool enforce_acceleration_limits,
const config::CartesianVelocityQpConfig& qp_config,
Eigen::VectorXd& qdot)
{
const Eigen::Index dof = q_measured.size();
if (jacobian_base.rows() != 6 || jacobian_base.cols() != dof ||
qd_reference.size() != dof || dt <= 0.0) {
return false;
}
if (!configureQpSolver_(dof, positiveOr(qp_config.solver_eps(), 1e-3))) {
return false;
}
const Eigen::Matrix<double, 6, 1> twist_weight =
twistTrackingWeightOrDefault(qp_config.twist_tracking_weight());
Eigen::Matrix<double, 6, 6> task_weight = Eigen::Matrix<double, 6, 6>::Identity();
for (int i = 0; i < 6; ++i) {
task_weight(i, i) = twist_weight[i];
}
const double qdot_regularization =
positiveOr(qp_config.qdot_regularization(), 1e-4);
const double prev_qdot_regularization =
positiveOr(qp_config.prev_qdot_regularization(),
enforce_acceleration_limits ? 2e-2 : 1e-4);
const bool use_joint_limit_avoidance =
qp_config.has_joint_limit_avoidance() &&
qp_config.joint_limit_avoidance().enable() &&
qp_config.joint_limit_avoidance().weight() > 0.0;
const int avoidance_rows = use_joint_limit_avoidance ? static_cast<int>(dof) : 0;
Eigen::MatrixXd cost(6 + 2 * dof + avoidance_rows, dof);
Eigen::VectorXd target(6 + 2 * dof + avoidance_rows);
cost.topRows(6) = task_weight * jacobian_base;
target.head(6) = task_weight * target_twist_base;
cost.middleRows(6, dof) = std::sqrt(qdot_regularization) * Eigen::MatrixXd::Identity(dof, dof);
target.segment(6, dof).setZero();
cost.middleRows(6 + dof, dof) =
std::sqrt(prev_qdot_regularization) * Eigen::MatrixXd::Identity(dof, dof);
target.segment(6 + dof, dof) = std::sqrt(prev_qdot_regularization) * qd_reference;
if (use_joint_limit_avoidance) {
const auto& avoidance = qp_config.joint_limit_avoidance();
const Eigen::VectorXd qdot_avoid =
cmvr::kinematics::computeJointLimitAvoidanceVelocity(
q_measured,
joint_lower_limits_,
joint_upper_limits_,
true,
positiveOr(avoidance.gain(), 0.2),
positiveOr(avoidance.margin_ratio(), 0.15),
positiveOr(avoidance.max_push(), 0.25));
const double sqrt_weight = std::sqrt(
positiveOr(qp_config.joint_limit_avoidance().weight(), 0.05));
cost.middleRows(6 + 2 * dof, dof) =
sqrt_weight * Eigen::MatrixXd::Identity(dof, dof);
target.segment(6 + 2 * dof, dof) = sqrt_weight * qdot_avoid;
}
Eigen::VectorXd lower(dof);
Eigen::VectorXd upper(dof);
for (Eigen::Index i = 0; i < dof; ++i) {
double velocity_limit = std::numeric_limits<double>::infinity();
if (joint_velocity_limits_.size() == dof && joint_velocity_limits_[i] > 0.0) {
velocity_limit = std::abs(joint_velocity_limits_[i]);
}
if (qd_max.size() == static_cast<std::size_t>(dof)) {
const double requested_limit = std::abs(qd_max[static_cast<std::size_t>(i)]);
if (std::isfinite(requested_limit) && requested_limit > 0.0) {
velocity_limit = std::min(velocity_limit, requested_limit);
}
}
double lb = -velocity_limit;
double ub = velocity_limit;
if (enforce_acceleration_limits) {
double acc_limit = 8.0;
if (i < speedl_config_.joint_acceleration_max_size() &&
speedl_config_.joint_acceleration_max(static_cast<int>(i)) > 0.0) {
acc_limit = speedl_config_.joint_acceleration_max(static_cast<int>(i));
}
lb = std::max(lb, qd_reference[i] - acc_limit * dt);
ub = std::min(ub, qd_reference[i] + acc_limit * dt);
}
if (joint_lower_limits_.size() == dof && joint_upper_limits_.size() == dof) {
lb = std::max(lb, (joint_lower_limits_[i] - q_measured[i]) / dt);
ub = std::min(ub, (joint_upper_limits_[i] - q_measured[i]) / dt);
}
if (lb > ub) {
CMVR_LOG(ERROR) << "[PinocchioQpCartesianMotionPlanner][speedL] velocity bound infeasible at joint "
<< i << ": lb=" << lb << ", ub=" << ub
<< ", q=" << q_measured[i]
<< ", qd_ref=" << qd_reference[i]
<< ", dt=" << dt;
return false;
}
lower[i] = lb;
upper[i] = ub;
}
qp_solver_.SetCostFunction(cost, target);
qp_solver_.SetConstraintsFunction(Eigen::MatrixXd::Identity(dof, dof), lower, upper);
qp_solver_.SetPrimalVariable(qd_reference);
try {
qdot = qp_solver_.Solve();
} catch (const cmvr::QPSolverException& error) {
CMVR_LOG(ERROR) << "[PinocchioQpCartesianMotionPlanner] QP failed: "
<< error.what() << " (code=" << error.code() << ")";
return false;
} catch (const std::exception& error) {
CMVR_LOG(ERROR) << "[PinocchioQpCartesianMotionPlanner] QP failed: "
<< error.what();
return false;
}
return qdot.size() == dof;
}
bool PinocchioQpCartesianMotionPlanner::validateAchievedLinearTwist_(
const Eigen::MatrixXd& jacobian_base,
const Eigen::VectorXd& qdot) const
{
const Eigen::Matrix<double, 6, 1> achieved_twist_base = jacobian_base * qdot;
const Eigen::Vector3d desired_linear = speedl_command_twist_base_.head<3>();
const Eigen::Vector3d achieved_linear = achieved_twist_base.head<3>();
const double desired_linear_norm = desired_linear.norm();
const double achieved_linear_norm = achieved_linear.norm();
const double direction_check_min_speed =
std::max(1e-4, positiveOr(speedl_config_.linear_reverse_switch_speed_threshold(), 1e-3));
if (desired_linear_norm <= direction_check_min_speed) {
return true;
}
const double linear_min_speed_ratio =
clamp(positiveOr(speedl_config_.linear_min_speed_ratio(), 0.2), 0.0, 1.0);
const double speed_ratio = achieved_linear_norm / desired_linear_norm;
if (speed_ratio < linear_min_speed_ratio) {
CMVR_LOG(ERROR) << "[PinocchioQpCartesianMotionPlanner][speedL] achieved speed too low: desired_linear=["
<< desired_linear.x() << ", " << desired_linear.y() << ", " << desired_linear.z()
<< "], achieved_linear=[" << achieved_linear.x() << ", "
<< achieved_linear.y() << ", " << achieved_linear.z()
<< "], desired_norm=" << desired_linear_norm
<< ", achieved_norm=" << achieved_linear_norm
<< ", speed_ratio=" << speed_ratio
<< ", min_ratio=" << linear_min_speed_ratio
<< ", direction_check_min_speed=" << direction_check_min_speed;
return false;
}
if (achieved_linear_norm <= direction_check_min_speed) {
return true;
}
const double deviation_deg = directionDeviationDeg(desired_linear, achieved_linear);
const double severe_direction_deviation_deg =
positiveOr(speedl_config_.severe_direction_deviation_deg(), 45.0);
if (deviation_deg >= severe_direction_deviation_deg) {
CMVR_LOG(ERROR) << "[PinocchioQpCartesianMotionPlanner][speedL] direction deviation too large: desired_linear=["
<< desired_linear.x() << ", " << desired_linear.y() << ", " << desired_linear.z()
<< "], achieved_linear=[" << achieved_linear.x() << ", "
<< achieved_linear.y() << ", " << achieved_linear.z()
<< "], deviation_deg=" << deviation_deg
<< ", severe_threshold_deg=" << severe_direction_deviation_deg
<< ", direction_check_min_speed=" << direction_check_min_speed;
return false;
}
return true;
}
bool PinocchioQpCartesianMotionPlanner::speedLStep(const CartesianVelocity& target_velocity,
const double dt,
const std::vector<double>& q_measured,
const std::vector<double>& qd_measured,
std::vector<double>& qd_command,
const FrameType frame)
{
qd_command.clear();
if (!solver_ || !speedl_configured_ || dt <= 0.0) {
CMVR_LOG(ERROR) << "[PinocchioQpCartesianMotionPlanner][speedL] invalid state: solver="
<< (solver_ ? 1 : 0)
<< ", configured=" << (speedl_configured_ ? 1 : 0)
<< ", dt=" << dt;
return false;
}
if (static_cast<int>(q_measured.size()) != solver_->chainDof() ||
static_cast<int>(qd_measured.size()) != solver_->chainVelocityDof()) {
CMVR_LOG(ERROR) << "[PinocchioQpCartesianMotionPlanner][speedL] state size mismatch: q="
<< q_measured.size() << "/" << solver_->chainDof()
<< ", qd=" << qd_measured.size() << "/" << solver_->chainVelocityDof();
return false;
}
Eigen::Matrix<double, 6, 1> measured_twist_base = Eigen::Matrix<double, 6, 1>::Zero();
Eigen::MatrixXd jacobian_base;
Eigen::Matrix3d base_R_tool = Eigen::Matrix3d::Identity();
if (!solver_->computeTwistBaseAtQ(q_measured,
qd_measured,
true,
measured_twist_base,
&jacobian_base,
&base_R_tool)) {
CMVR_LOG(ERROR) << "[PinocchioQpCartesianMotionPlanner][speedL] computeTwistBaseAtQ failed";
return false;
}
const Eigen::Matrix<double, 6, 1> target_twist = common::math::velocityToVector(target_velocity);
if (target_twist.squaredNorm() <= 1e-12) {
twist_limiter_.synchronize(measured_twist_base, dt, true);
} else if (speedl_command_twist_base_.squaredNorm() <= 1e-12) {
twist_limiter_.initialize(Eigen::Matrix<double, 6, 1>::Zero());
}
twist_limiter_.setTargetTwist(target_twist, common::math::toPlannerFrame(frame));
speedl_command_twist_base_ = twist_limiter_.update(dt, base_R_tool);
Eigen::VectorXd reference = toEigenVector(qd_measured);
if (prev_qdot_command_.size() == q_measured.size()) {
reference = toEigenVector(prev_qdot_command_);
}
Eigen::VectorXd qdot;
if (!solveVelocityQp_(jacobian_base,
speedl_command_twist_base_,
toEigenVector(q_measured),
reference,
dt,
{},
true,
qpConfigOrDefault(speedl_config_.qp()),
qdot)) {
CMVR_LOG(ERROR) << "[PinocchioQpCartesianMotionPlanner][speedL] solveVelocityQp failed";
return false;
}
if (!validateAchievedLinearTwist_(jacobian_base, qdot)) {
CMVR_LOG(ERROR) << "[PinocchioQpCartesianMotionPlanner][speedL] validateAchievedLinearTwist failed";
return false;
}
qd_command = toStdVector(qdot);
prev_qdot_command_ = qd_command;
return true;
}
bool PinocchioQpCartesianMotionPlanner::updateSpeedLAcceleration(const double acceleration)
{
if (!speedl_configured_ || acceleration <= 0.0) {
return false;
}
if (std::abs(speedl_applied_acceleration_ - acceleration) <= 1e-9) {
return true;
}
cartesian_motion::updateTwistLimiterAcceleration(
twist_limiter_,
speedl_config_,
acceleration);
speedl_applied_acceleration_ = acceleration;
return true;
}
CartesianVelocity PinocchioQpCartesianMotionPlanner::getSpeedLCommandTwistBase() const
{
return common::math::vectorToVelocity(speedl_command_twist_base_);
}
} // namespace cmvr::device

View File

@ -0,0 +1,51 @@
#ifndef CMVR_ES_TWIST_LIMITER_CONFIG_H
#define CMVR_ES_TWIST_LIMITER_CONFIG_H
#include "algorithms/motion_planner/base_motion/cartesian_velocity/twist_limiter/include/cartesian_twist_limiter.h"
#include "cmvr/config/arm_config/arm_config.pb.h"
#include "common/config/config_files.h"
namespace cmvr::device::cartesian_motion {
inline void configureTwistLimiterFromSpeedLConfig(
cmvr::CartesianTwistLimiter& limiter,
const config::SpeedLPlannerConfig& config)
{
using cmvr::common::config::positiveOr;
limiter.setLinearConstraints(positiveOr(config.linear_velocity_max(), 0.55),
positiveOr(config.linear_acceleration_max(), 5.0),
positiveOr(config.linear_jerk_max(), 10.0));
limiter.setAngularConstraints(positiveOr(config.angular_velocity_max(), 1.0),
positiveOr(config.angular_acceleration_max(), 5.0),
positiveOr(config.angular_jerk_max(), 12.0));
limiter.setLinearTargetReplanThreshold(
positiveOr(config.linear_target_replan_threshold(), 1e-4));
limiter.setAngularTargetReplanThreshold(
positiveOr(config.angular_target_replan_threshold(), 1e-4));
limiter.setLinearReverseSwitchPolicy(
config.linear_reverse_cos_threshold() != 0.0
? config.linear_reverse_cos_threshold()
: -0.8660254037844386,
positiveOr(config.linear_reverse_switch_speed_threshold(), 1e-3));
limiter.initialize(Eigen::Matrix<double, 6, 1>::Zero());
}
inline void updateTwistLimiterAcceleration(
cmvr::CartesianTwistLimiter& limiter,
const config::SpeedLPlannerConfig& config,
const double acceleration)
{
using cmvr::common::config::positiveOr;
limiter.setLinearConstraints(positiveOr(config.linear_velocity_max(), 0.55),
acceleration,
positiveOr(config.linear_jerk_max(), 10.0));
limiter.setAngularConstraints(positiveOr(config.angular_velocity_max(), 1.0),
acceleration,
positiveOr(config.angular_jerk_max(), 12.0));
}
} // namespace cmvr::device::cartesian_motion
#endif // CMVR_ES_TWIST_LIMITER_CONFIG_H

View File

@ -0,0 +1,31 @@
#ifndef CMVR_ES_JOINT_MOTION_PLANNER_H
#define CMVR_ES_JOINT_MOTION_PLANNER_H
#include <vector>
#include "common/types/arm/arm_types.h"
namespace cmvr::device {
struct JointTrajectorySample {
double t{0.0};
std::vector<double> position;
std::vector<double> velocity;
};
class JointMotionPlanner {
public:
virtual ~JointMotionPlanner() = default;
virtual bool init() = 0;
virtual bool planMoveJ(const std::vector<double>& start,
const JointPositionCommand& target,
const MotionOptions& options,
double speed_scaling,
std::vector<JointTrajectorySample>& samples) = 0;
};
} // namespace cmvr::device
#endif // CMVR_ES_JOINT_MOTION_PLANNER_H

View File

@ -0,0 +1,55 @@
#ifndef CMVR_ES_JOINT_MOTION_PLANNER_FACTORY_H
#define CMVR_ES_JOINT_MOTION_PLANNER_FACTORY_H
#include <memory>
#include "algorithms/motion_planner/arm_motion/joint_motion/joint_motion_planner.h"
#include "algorithms/motion_planner/arm_motion/joint_motion/toppra/include/toppra_joint_motion_planner.h"
#include "cmvr/config/arm_config/arm_config.pb.h"
namespace cmvr::device {
class JointMotionPlannerFactory {
public:
static std::shared_ptr<JointMotionPlanner> create(
const config::MoveJConfig& cfg)
{
if (!cfg.has_toppra_joint_motion_planner()) {
return nullptr;
}
cmvr::PathType path_type;
switch (cfg.toppra_joint_motion_planner().path_type()) {
case config::TOPPRA_PATH_TYPE_LINEAR:
path_type = cmvr::PathType::Linear;
break;
case config::TOPPRA_PATH_TYPE_CUBIC_HERMITE:
path_type = cmvr::PathType::CubicHermite;
break;
case config::TOPPRA_PATH_TYPE_QUINTIC:
path_type = cmvr::PathType::Quintic;
break;
case config::TOPPRA_PATH_TYPE_NATURAL:
path_type = cmvr::PathType::Natural;
break;
case config::TOPPRA_PATH_TYPE_UNKNOWN:
default:
return nullptr;
}
const auto& toppra = cfg.toppra_joint_motion_planner();
auto planner = std::make_shared<ToppraJointMotionPlanner>(
path_type,
toppra.sample_period_s(),
toppra.grid_size(),
toppra.high_grid_size());
if (!planner->init()) {
return nullptr;
}
return planner;
}
};
} // namespace cmvr::device
#endif // CMVR_ES_JOINT_MOTION_PLANNER_FACTORY_H

View File

@ -0,0 +1,36 @@
#ifndef CMVR_ES_TOPPRA_JOINT_MOTION_PLANNER_H
#define CMVR_ES_TOPPRA_JOINT_MOTION_PLANNER_H
#include <memory>
#include "../../joint_motion_planner.h"
#include "../../../../base_motion/joint_trajectory/joint_trajectory_planner.h"
namespace cmvr::device {
class ToppraJointMotionPlanner final : public JointMotionPlanner {
public:
ToppraJointMotionPlanner(cmvr::PathType path_type,
double sample_period_s,
int grid_size,
int high_grid_size);
bool init() override;
bool planMoveJ(const std::vector<double>& start,
const JointPositionCommand& target,
const MotionOptions& options,
double speed_scaling,
std::vector<JointTrajectorySample>& samples) override;
private:
std::shared_ptr<cmvr::JointTrajectoryPlanner> planner_;
cmvr::PathType path_type_{cmvr::PathType::Quintic};
double sample_period_s_{0.001};
int grid_size_{150};
int high_grid_size_{300};
};
} // namespace cmvr::device
#endif // CMVR_ES_TOPPRA_JOINT_MOTION_PLANNER_H

View File

@ -0,0 +1,76 @@
#include "algorithms/motion_planner/arm_motion/joint_motion/toppra/include/toppra_joint_motion_planner.h"
#include "algorithms/motion_planner/base_motion/joint_trajectory/toppra/include/toppra_joint_trajectory_planner.h"
namespace cmvr::device {
namespace {
std::vector<double> toStdVector(const Eigen::VectorXd& value)
{
std::vector<double> result;
result.reserve(static_cast<std::size_t>(value.size()));
for (int i = 0; i < value.size(); ++i) {
result.push_back(value[i]);
}
return result;
}
} // namespace
ToppraJointMotionPlanner::ToppraJointMotionPlanner(const cmvr::PathType path_type,
const double sample_period_s,
const int grid_size,
const int high_grid_size)
: path_type_(path_type)
, sample_period_s_(sample_period_s)
, grid_size_(grid_size)
, high_grid_size_(high_grid_size)
{
}
bool ToppraJointMotionPlanner::init()
{
if (sample_period_s_ <= 0.0 || grid_size_ <= 0 || high_grid_size_ < grid_size_) {
return false;
}
planner_ = std::make_shared<cmvr::ToppraJointTrajectoryPlanner>(path_type_);
planner_->setGridSizes(grid_size_, high_grid_size_);
return true;
}
bool ToppraJointMotionPlanner::planMoveJ(const std::vector<double>& start,
const JointPositionCommand& target,
const MotionOptions& options,
const double speed_scaling,
std::vector<JointTrajectorySample>& samples)
{
samples.clear();
if (!planner_ || start.empty() || start.size() != target.position.size() ||
options.velocity <= 0.0 || options.acceleration <= 0.0) {
return false;
}
cmvr::TrajPtr trajectory;
planner_->setPathType(path_type_);
planner_->setGridSizes(grid_size_, high_grid_size_);
planner_->setSymmetricLimits(
std::vector<double>(start.size(), options.velocity * speed_scaling),
std::vector<double>(start.size(), options.acceleration));
if (!planner_->plan(start, target.position, trajectory)) {
return false;
}
const auto raw_samples = planner_->sampleTrajectory(trajectory, sample_period_s_);
samples.reserve(raw_samples.size());
for (const auto& sample : raw_samples) {
JointTrajectorySample dst;
dst.t = sample.t;
dst.position = toStdVector(sample.q);
dst.velocity = toStdVector(sample.qd);
samples.push_back(std::move(dst));
}
return true;
}
} // namespace cmvr::device

View File

@ -0,0 +1,65 @@
add_library(base_motion SHARED
joint_trajectory/toppra/src/toppra_joint_trajectory_planner.cpp
motion_profile/s_curve/src/s_curve.cpp
motion_profile/s_curve/src/s_curve_position_planner.cpp
motion_profile/s_curve/src/s_curve_velocity_planner.cpp
cartesian_velocity/twist_limiter/src/cartesian_twist_limiter.cpp
)
target_include_directories(base_motion PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(base_motion PUBLIC
OsqpEigen
tinyxml2
fcl
toppra
cmvr_es::common
)
add_library(cmvr_es::base_motion ALIAS base_motion)
install(TARGETS base_motion LIBRARY DESTINATION lib)
# --------------------------------------------------------
# Unit test
# --------------------------------------------------------
add_executable(toppra_joint_trajectory_planner_test
${CMAKE_CURRENT_SOURCE_DIR}/joint_trajectory/toppra/src/toppra_joint_trajectory_planner_test.cpp
)
target_link_libraries(toppra_joint_trajectory_planner_test
PRIVATE
cmvr_es::base_motion
gtest
gtest_main
pthread
glog
cmvr_es::proto
ccd
fcl
OsqpEigen
)
add_executable(cartesian_twist_limiter_test
${CMAKE_CURRENT_SOURCE_DIR}/cartesian_velocity/twist_limiter/src/cartesian_twist_limiter_test.cpp
)
target_link_libraries(cartesian_twist_limiter_test
PRIVATE
cmvr_es::base_motion
gtest
gtest_main
pthread
glog
cmvr_es::proto
matplot
)

View File

@ -2,7 +2,7 @@
#include <Eigen/Core> #include <Eigen/Core>
#include <Eigen/Geometry> #include <Eigen/Geometry>
#include "planner/s_curve_planner/include/s_curve_velocity_planner.h" #include "algorithms/motion_planner/base_motion/motion_profile/s_curve/include/s_curve_velocity_planner.h"
namespace cmvr namespace cmvr
{ {

View File

@ -7,7 +7,7 @@
#include <Eigen/Core> #include <Eigen/Core>
#include <matplot/matplot.h> #include <matplot/matplot.h>
#include "planner/cartesian_space_planner/include/cartesian_twist_limiter.h" #include "algorithms/motion_planner/base_motion/cartesian_velocity/twist_limiter/include/cartesian_twist_limiter.h"
namespace cmvr namespace cmvr
{ {

View File

@ -4,7 +4,11 @@
#pragma once #pragma once
#include <memory>
#include <string>
#include <vector> #include <vector>
#include <Eigen/Dense>
#include <toppra/geometric_path/piecewise_poly_path.hpp> #include <toppra/geometric_path/piecewise_poly_path.hpp>
#include <toppra/parametrizer/const_accel.hpp> #include <toppra/parametrizer/const_accel.hpp>
#include <toppra/parametrizer/spline.hpp> #include <toppra/parametrizer/spline.hpp>
@ -33,12 +37,12 @@ namespace cmvr {
Quintic, Quintic,
// 三次B样条 // 三次B样条
Natural }; Natural };
class JointSpacePlanner { class JointTrajectoryPlanner {
public: public:
explicit JointSpacePlanner(PathType p):path_type_(p){}; explicit JointTrajectoryPlanner(PathType p):path_type_(p){};
JointSpacePlanner()=default; JointTrajectoryPlanner()=default;
virtual ~JointSpacePlanner() = default; virtual ~JointTrajectoryPlanner() = default;
virtual bool plan(const std::vector<double>& start_joints, virtual bool plan(const std::vector<double>& start_joints,
const std::vector<double>& goal_joints, const std::vector<double>& goal_joints,
@ -62,14 +66,20 @@ namespace cmvr {
} }
// 对称限:[-v_max, v_max]、[-a_max, a_max] // 对称限:[-v_max, v_max]、[-a_max, a_max]
virtual void setSymmetricLimits(const std::vector<double> &v_max, virtual void setSymmetricLimits(const std::vector<double> &v_max,const std::vector<double> &a_max){
const std::vector<double> &a_max); v_max_ = v_max;
a_max_ = a_max;
}
// 可调网格密度(默认 150/300 // 可调网格密度(默认 150/300
virtual void setGridSizes(int N, int N_high); virtual void setGridSizes(int N, int N_high)
{
N_grid_ = N;
N_grid_high_ = N_high;
}
// 切换几何路径类型 // 切换几何路径类型
virtual void setPathType(PathType p); virtual void setPathType(PathType p){path_type_ = p;}
protected: protected:
PathType path_type_{PathType::Quintic}; PathType path_type_{PathType::Quintic};
int N_grid_ = 150; int N_grid_ = 150;

View File

@ -4,7 +4,7 @@
#include <vector> #include <vector>
#include <Eigen/Dense> #include <Eigen/Dense>
#include "planner/joint_space_planner/include/joint_space_planner.h" #include "../../joint_trajectory_planner.h"
#include <toppra/geometric_path/piecewise_poly_path.hpp> #include <toppra/geometric_path/piecewise_poly_path.hpp>
#include <toppra/parametrizer/const_accel.hpp> #include <toppra/parametrizer/const_accel.hpp>
#include <toppra/parametrizer/spline.hpp> #include <toppra/parametrizer/spline.hpp>
@ -47,9 +47,9 @@ namespace cmvr {
}; };
// 具体规划器:一次/三次/五次可切换ConstAccel 校验失败自动回退 Spline // 具体规划器:一次/三次/五次可切换ConstAccel 校验失败自动回退 Spline
class ToppraBSpline : public JointSpacePlanner { class ToppraJointTrajectoryPlanner : public JointTrajectoryPlanner {
public: public:
explicit ToppraBSpline(PathType type = PathType::Quintic); explicit ToppraJointTrajectoryPlanner(PathType type = PathType::Quintic);
// 统一入口:两点/多点皆可 // 统一入口:两点/多点皆可

View File

@ -3,7 +3,7 @@
#include <toppra/constraint/linear_joint_acceleration.hpp> #include <toppra/constraint/linear_joint_acceleration.hpp>
#include <toppra/solver/seidel.hpp> #include <toppra/solver/seidel.hpp>
#include <toppra/toppra.hpp> #include <toppra/toppra.hpp>
#include "planner/joint_space_planner/include/toppra_bspline.h" #include "algorithms/motion_planner/base_motion/joint_trajectory/toppra/include/toppra_joint_trajectory_planner.h"
#include <fstream> #include <fstream>
#include <iomanip> #include <iomanip>
@ -31,19 +31,19 @@ namespace cmvr {
Eigen::VectorXd SplineTraj::qd(double t) const { return impl_.eval_single(t, 1); } Eigen::VectorXd SplineTraj::qd(double t) const { return impl_.eval_single(t, 1); }
Eigen::VectorXd SplineTraj::qdd(double t) const { return impl_.eval_single(t, 2); } Eigen::VectorXd SplineTraj::qdd(double t) const { return impl_.eval_single(t, 2); }
// ===== ToppraBSpline ===== // ===== ToppraJointTrajectoryPlanner =====
ToppraBSpline::ToppraBSpline(PathType type) :JointSpacePlanner(type) { ToppraJointTrajectoryPlanner::ToppraJointTrajectoryPlanner(PathType type) :JointTrajectoryPlanner(type) {
} }
bool ToppraBSpline::ensureLimitsSized(std::size_t DoF) { bool ToppraJointTrajectoryPlanner::ensureLimitsSized(std::size_t DoF) {
if (v_max_.size() != DoF) v_max_.assign(DoF, 1.5); if (v_max_.size() != DoF) v_max_.assign(DoF, 1.5);
if (a_max_.size() != DoF) a_max_.assign(DoF, 3.0); if (a_max_.size() != DoF) a_max_.assign(DoF, 3.0);
return true; return true;
} }
void ToppraBSpline::sanitizeVsq(toppra::Vector &v) { void ToppraJointTrajectoryPlanner::sanitizeVsq(toppra::Vector &v) {
for (int i = 0; i < v.size(); ++i) if (v[i] < 0 && v[i] > -1e-10) v[i] = 0; for (int i = 0; i < v.size(); ++i) if (v[i] < 0 && v[i] > -1e-10) v[i] = 0;
if (v.size() > 0) { if (v.size() > 0) {
v[0] = 0; v[0] = 0;
@ -51,7 +51,7 @@ namespace cmvr {
} }
} }
// ===== 统一入口:两点/多点 ===== // ===== 统一入口:两点/多点 =====
bool ToppraBSpline::plan(const std::vector<std::vector<double>>& waypoints, bool ToppraJointTrajectoryPlanner::plan(const std::vector<std::vector<double>>& waypoints,
TrajPtr& traj_out) { TrajPtr& traj_out) {
traj_out.reset(); traj_out.reset();
const size_t M = waypoints.size(); const size_t M = waypoints.size();
@ -115,7 +115,7 @@ namespace cmvr {
} }
bool ToppraBSpline::plan(const std::vector<double>& start_joints, bool ToppraJointTrajectoryPlanner::plan(const std::vector<double>& start_joints,
const std::vector<double>& goal_joints, const std::vector<double>& goal_joints,
TrajPtr& traj_out) { TrajPtr& traj_out) {
if (start_joints.empty() || start_joints.size()!=goal_joints.size()) return false; if (start_joints.empty() || start_joints.size()!=goal_joints.size()) return false;
@ -125,7 +125,7 @@ namespace cmvr {
std::shared_ptr<toppra::PiecewisePolyPath> std::shared_ptr<toppra::PiecewisePolyPath>
ToppraBSpline::buildPathUnified(const std::vector<Eigen::VectorXd>& q, ToppraJointTrajectoryPlanner::buildPathUnified(const std::vector<Eigen::VectorXd>& q,
const std::vector<toppra::value_type>& S) { const std::vector<toppra::value_type>& S) {
const size_t M = q.size(); const size_t M = q.size();
if (M == 2) return buildTwoPointPath(q[0], q[1]); if (M == 2) return buildTwoPointPath(q[0], q[1]);
@ -140,7 +140,7 @@ namespace cmvr {
std::shared_ptr<toppra::PiecewisePolyPath> std::shared_ptr<toppra::PiecewisePolyPath>
ToppraBSpline::buildTwoPointPath(const Eigen::VectorXd& q0, const Eigen::VectorXd& q1) { ToppraJointTrajectoryPlanner::buildTwoPointPath(const Eigen::VectorXd& q0, const Eigen::VectorXd& q1) {
switch (path_type_) { switch (path_type_) {
case PathType::Linear: return buildLinearTwo(q0, q1); case PathType::Linear: return buildLinearTwo(q0, q1);
case PathType::CubicHermite: return buildCubicHermiteTwo(q0, q1); case PathType::CubicHermite: return buildCubicHermiteTwo(q0, q1);
@ -152,7 +152,7 @@ namespace cmvr {
// 二点Linear —— // 二点Linear ——
std::shared_ptr<toppra::PiecewisePolyPath> std::shared_ptr<toppra::PiecewisePolyPath>
ToppraBSpline::buildLinearTwo(const Eigen::VectorXd& q0, const Eigen::VectorXd& q1) { ToppraJointTrajectoryPlanner::buildLinearTwo(const Eigen::VectorXd& q0, const Eigen::VectorXd& q1) {
const size_t DoF = static_cast<size_t>(q0.size()); const size_t DoF = static_cast<size_t>(q0.size());
toppra::Matrix seg(2, DoF); toppra::Matrix seg(2, DoF);
seg.row(0) = (q1 - q0).transpose(); seg.row(0) = (q1 - q0).transpose();
@ -163,7 +163,7 @@ namespace cmvr {
// 二点Cubic Hermite端点速度 0 —— // 二点Cubic Hermite端点速度 0 ——
std::shared_ptr<toppra::PiecewisePolyPath> std::shared_ptr<toppra::PiecewisePolyPath>
ToppraBSpline::buildCubicHermiteTwo(const Eigen::VectorXd& q0, const Eigen::VectorXd& q1) { ToppraJointTrajectoryPlanner::buildCubicHermiteTwo(const Eigen::VectorXd& q0, const Eigen::VectorXd& q1) {
toppra::Vectors pos{q0, q1}; toppra::Vectors pos{q0, q1};
toppra::Vectors vel{Eigen::VectorXd::Zero(q0.size()), toppra::Vectors vel{Eigen::VectorXd::Zero(q0.size()),
Eigen::VectorXd::Zero(q1.size())}; Eigen::VectorXd::Zero(q1.size())};
@ -174,7 +174,7 @@ namespace cmvr {
// 二点Quintic rest-to-rest —— // 二点Quintic rest-to-rest ——
std::shared_ptr<toppra::PiecewisePolyPath> std::shared_ptr<toppra::PiecewisePolyPath>
ToppraBSpline::buildQuinticRestToRestTwo(const Eigen::VectorXd& q0, const Eigen::VectorXd& q1) { ToppraJointTrajectoryPlanner::buildQuinticRestToRestTwo(const Eigen::VectorXd& q0, const Eigen::VectorXd& q1) {
const size_t DoF = static_cast<size_t>(q0.size()); const size_t DoF = static_cast<size_t>(q0.size());
const Eigen::VectorXd dq = q1 - q0; const Eigen::VectorXd dq = q1 - q0;
toppra::Matrix seg(6, DoF); // x^5..x^0 toppra::Matrix seg(6, DoF); // x^5..x^0
@ -188,7 +188,7 @@ namespace cmvr {
} }
std::shared_ptr<toppra::PiecewisePolyPath> std::shared_ptr<toppra::PiecewisePolyPath>
ToppraBSpline::buildNaturalTwo(const Eigen::VectorXd& q0, const Eigen::VectorXd& q1) { ToppraJointTrajectoryPlanner::buildNaturalTwo(const Eigen::VectorXd& q0, const Eigen::VectorXd& q1) {
using PWP = toppra::PiecewisePolyPath; using PWP = toppra::PiecewisePolyPath;
// positions两点 // positions两点
@ -217,7 +217,7 @@ namespace cmvr {
// —— 多点Linear —— // —— 多点Linear ——
std::shared_ptr<toppra::PiecewisePolyPath> std::shared_ptr<toppra::PiecewisePolyPath>
ToppraBSpline::buildLinearMulti(const std::vector<Eigen::VectorXd>& q, ToppraJointTrajectoryPlanner::buildLinearMulti(const std::vector<Eigen::VectorXd>& q,
const std::vector<toppra::value_type>& S) { const std::vector<toppra::value_type>& S) {
const size_t M = q.size(), DoF = q[0].size(); const size_t M = q.size(), DoF = q[0].size();
toppra::Matrices segs; segs.reserve(M-1); toppra::Matrices segs; segs.reserve(M-1);
@ -234,7 +234,7 @@ namespace cmvr {
// —— 多点Cubic Hermite —— // —— 多点Cubic Hermite ——
std::shared_ptr<toppra::PiecewisePolyPath> std::shared_ptr<toppra::PiecewisePolyPath>
ToppraBSpline::buildCubicHermiteMulti(const std::vector<Eigen::VectorXd>& q, ToppraJointTrajectoryPlanner::buildCubicHermiteMulti(const std::vector<Eigen::VectorXd>& q,
const std::vector<toppra::value_type>& S) { const std::vector<toppra::value_type>& S) {
auto v = estimateVelsCatmull(q, S); auto v = estimateVelsCatmull(q, S);
clampNodeVels(v, q, /*k=*/1.0); clampNodeVels(v, q, /*k=*/1.0);
@ -246,7 +246,7 @@ namespace cmvr {
std::shared_ptr<toppra::PiecewisePolyPath> std::shared_ptr<toppra::PiecewisePolyPath>
ToppraBSpline::buildNaturalMulti(const std::vector<Eigen::VectorXd>& q, ToppraJointTrajectoryPlanner::buildNaturalMulti(const std::vector<Eigen::VectorXd>& q,
const std::vector<toppra::value_type>& S) { const std::vector<toppra::value_type>& S) {
using PWP = toppra::PiecewisePolyPath; using PWP = toppra::PiecewisePolyPath;
@ -278,7 +278,7 @@ namespace cmvr {
// —— 多点Quintic C² —— // —— 多点Quintic C² ——
std::shared_ptr<toppra::PiecewisePolyPath> std::shared_ptr<toppra::PiecewisePolyPath>
ToppraBSpline::buildQuinticC2Multi(const std::vector<Eigen::VectorXd>& q, ToppraJointTrajectoryPlanner::buildQuinticC2Multi(const std::vector<Eigen::VectorXd>& q,
const std::vector<toppra::value_type>& S) { const std::vector<toppra::value_type>& S) {
const size_t M = q.size(), DoF = q[0].size(); const size_t M = q.size(), DoF = q[0].size();
auto v = estimateVelsCatmull(q, S); auto v = estimateVelsCatmull(q, S);
@ -312,7 +312,7 @@ namespace cmvr {
return std::make_shared<toppra::PiecewisePolyPath>(segs, std::vector<double>(S.begin(), S.end())); return std::make_shared<toppra::PiecewisePolyPath>(segs, std::vector<double>(S.begin(), S.end()));
} }
std::vector<TrajSample> ToppraBSpline::sampleTrajectory(const TrajPtr &traj, double dt) { std::vector<TrajSample> ToppraJointTrajectoryPlanner::sampleTrajectory(const TrajPtr &traj, double dt) {
std::vector<TrajSample> out; std::vector<TrajSample> out;
if (!traj) return out; if (!traj) return out;
@ -357,7 +357,7 @@ namespace cmvr {
} }
bool ToppraBSpline::writeTrajectoryCsv(const std::string &filename, const std::vector<TrajSample> &samples) { bool ToppraJointTrajectoryPlanner::writeTrajectoryCsv(const std::string &filename, const std::vector<TrajSample> &samples) {
if (samples.empty()) return false; if (samples.empty()) return false;
const int dof = static_cast<int>(samples.front().q.size()); const int dof = static_cast<int>(samples.front().q.size());

View File

@ -3,12 +3,14 @@
// //
#include "gtest/gtest.h" #include "gtest/gtest.h"
#include "planner/joint_space_planner/include/toppra_bspline.h" #include "algorithms/motion_planner/base_motion/joint_trajectory/toppra/include/toppra_joint_trajectory_planner.h"
#include "planner/joint_space_planner/include/joint_space_planner_creator.h"
using namespace cmvr;
TEST(JOINT_SPACE_PLANNER_TEST,TOPPRA_TEST) {
auto planner = JointSpacePlannerCreator::create(JointSpacePlannerType::TOPPRA_BSPLINE); #include <memory>
using namespace cmvr;
TEST(TOPPRA_JOINT_TRAJECTORY_PLANNER_TEST,TOPPRA_TEST) {
auto planner = std::make_shared<ToppraJointTrajectoryPlanner>();
planner->setPathType(PathType::Quintic); planner->setPathType(PathType::Quintic);
planner->setSymmetricLimits(std::vector<double>(7, 1.5), planner->setSymmetricLimits(std::vector<double>(7, 1.5),
@ -30,9 +32,9 @@ TEST(JOINT_SPACE_PLANNER_TEST,TOPPRA_TEST) {
} }
TEST(JOINT_SPACE_PLANNER_TEST, TOPPRA_WAYPOINTS_TEST) { TEST(TOPPRA_JOINT_TRAJECTORY_PLANNER_TEST, TOPPRA_WAYPOINTS_TEST) {
auto planner = JointSpacePlannerCreator::create(JointSpacePlannerType::TOPPRA_BSPLINE); auto planner = std::make_shared<ToppraJointTrajectoryPlanner>();
planner->setPathType(PathType::Quintic); planner->setPathType(PathType::Quintic);
// 7 自由度对称速度 / 加速度约束 // 7 自由度对称速度 / 加速度约束

View File

@ -8,7 +8,7 @@
* @brief S 线 * @brief S 线
*/ */
#include "common/curve/include/s_curve.h" #include "algorithms/motion_planner/base_motion/motion_profile/s_curve/include/s_curve.h"
#include <cmath> #include <cmath>
#include <algorithm> #include <algorithm>
#include <stdexcept> #include <stdexcept>
@ -187,7 +187,7 @@ SCurveProfile SCurve::calculateProfile(double start_position, double end_positio
void SCurve::calculateShortProfile(SCurveProfile& profile) const void SCurve::calculateShortProfile(SCurveProfile& profile) const
{ {
// 短距离无巡航段t4 = 0,与 moveL_SCurveLocal 中的 SCurveProfile1D 保持一致 // 短距离无巡航段t4 = 0
const double j = profile.j_max; const double j = profile.j_max;
const double a = profile.a_max; const double a = profile.a_max;
const double v = profile.v_max; const double v = profile.v_max;

View File

@ -2,8 +2,8 @@
find_package(OpenCV REQUIRED) find_package(OpenCV REQUIRED)
add_library(perception SHARED add_library(perception SHARED
src/tag_relative_target_3d.cpp apriltag/src/tag_relative_target_3d.cpp
src/apriltag_perception.cpp apriltag/src/apriltag_perception.cpp
) )
target_include_directories(perception PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) target_include_directories(perception PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
@ -33,7 +33,7 @@ add_library(cmvr_es::perception ALIAS perception)
install(TARGETS perception LIBRARY DESTINATION lib) install(TARGETS perception LIBRARY DESTINATION lib)
add_executable(tag_relative_target_3d_test add_executable(tag_relative_target_3d_test
src/tag_relative_target_3d_test.cpp apriltag/src/tag_relative_target_3d_test.cpp
) )
target_link_libraries(tag_relative_target_3d_test target_link_libraries(tag_relative_target_3d_test

View File

@ -12,7 +12,7 @@
#include <Eigen/Dense> #include <Eigen/Dense>
#include <opencv2/opencv.hpp> #include <opencv2/opencv.hpp>
#include "perception/include/apriltag_perception.h" #include "algorithms/perception/apriltag/include/apriltag_perception.h"
namespace cmvr::perception { namespace cmvr::perception {

View File

@ -2,7 +2,7 @@
// Created by lgv on 2026/3/5. // Created by lgv on 2026/3/5.
// //
#include "perception/include/apriltag_perception.h" #include "algorithms/perception/apriltag/include/apriltag_perception.h"
#include <algorithm> #include <algorithm>
#include <cmath> #include <cmath>

View File

@ -1,11 +1,11 @@
#include "perception/include/tag_relative_target_3d.h" #include "algorithms/perception/apriltag/include/tag_relative_target_3d.h"
#include <algorithm> #include <algorithm>
#include <cmath> #include <cmath>
#include <cstring> #include <cstring>
#include <limits> #include <limits>
#include "common/utils/image/image_process.h" #include "common/vision/image_projection.h"
namespace cmvr::perception { namespace cmvr::perception {
namespace { namespace {

View File

@ -2,7 +2,7 @@
// Created by lgv on 2026/2/26. // Created by lgv on 2026/2/26.
// //
#include "perception/include/tag_relative_target_3d.h" #include "algorithms/perception/apriltag/include/tag_relative_target_3d.h"
#include "devices/camera/realsense_camera/include/realsense_camera.h" #include "devices/camera/realsense_camera/include/realsense_camera.h"
#include <gtest/gtest.h> #include <gtest/gtest.h>
@ -301,7 +301,6 @@ TEST(TagRelativeTarget3DRealSenseTest, PrintTargetPointInDetectedTags) {
cam_cfg.set_align_mode(cmvr::config::ALIGN_MODE_COLOR); cam_cfg.set_align_mode(cmvr::config::ALIGN_MODE_COLOR);
cam_cfg.set_buffer_size(30); cam_cfg.set_buffer_size(30);
cam_cfg.set_sync(true); cam_cfg.set_sync(true);
cam_cfg.set_enable(true);
auto camera = std::make_shared<cmvr::device::RealsenseCamera>(cam_cfg); auto camera = std::make_shared<cmvr::device::RealsenseCamera>(cam_cfg);
@ -623,7 +622,6 @@ TEST(TagRelativeTarget3DRealSenseTest, PrintTargetPointInDetectedTags) {
// cam_cfg.set_align_mode(cmvr::config::ALIGN_MODE_COLOR); // cam_cfg.set_align_mode(cmvr::config::ALIGN_MODE_COLOR);
// cam_cfg.set_buffer_size(30); // cam_cfg.set_buffer_size(30);
// cam_cfg.set_sync(true); // cam_cfg.set_sync(true);
// cam_cfg.set_enable(true);
// //
// auto camera = std::make_shared<cmvr::device::RealsenseCamera>(cam_cfg); // auto camera = std::make_shared<cmvr::device::RealsenseCamera>(cam_cfg);
// cmvr::perception::TagRelativeTarget3D tracker(camera); // cmvr::perception::TagRelativeTarget3D tracker(camera);

View File

@ -1,31 +0,0 @@
add_library(applications
src/touch_screen_app.cpp
)
target_include_directories(applications PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(applications
PUBLIC
cmvr_es::controller
cmvr_es::common
PRIVATE
cmvr_es::device_manager
)
add_library(cmvr_es::applications ALIAS applications)
install(TARGETS applications LIBRARY DESTINATION lib)
add_executable(touch_screen_app_test
src/touch_screen_app_test.cpp
)
target_link_libraries(touch_screen_app_test PRIVATE
cmvr_es::applications
cmvr_es::device_manager
cmvr_es::service
cmvr_es::monitor_manager
gtest
gtest_main
pthread
glog
)

View File

@ -1,329 +0,0 @@
#pragma once
#ifndef CMVR_ES_TOUCH_SCREEN_APP_H
#define CMVR_ES_TOUCH_SCREEN_APP_H
#include <array>
#include <chrono>
#include <memory>
#include <string>
#include <vector>
#include <Eigen/Dense>
#include "cmvr/config/touch_screen_app_config/touch_screen_app_config.pb.h"
#include "controller/include/ibvs_controller.h"
#include "devices/camera/abstract_camera.h"
#include "devices/dexhand/abstract_dexhand.h"
#include "devices/robot/abstract_robot.h"
#include "perception/include/apriltag_perception.h"
#include "perception/include/tag_relative_target_3d.h"
namespace cmvr::app {
class TouchScreenApp {
public:
enum class Phase {
IDLE = 0, // 空闲,尚未开始任务。
ALIGNING, // 视觉对准阶段:持续 IBVS 对齐目标点。
ALIGN_REACHED, // 视觉对准已达到阈值,等待进入下一阶段。
TOUCHING, // 前进触控阶段:沿设定方向向屏幕推进。
DWELLING, // 已检测到接触,保持当前位置短暂停留。
RETRACTING, // 回退阶段:沿设定回退方向离开屏幕。
DONE, // 整个流程成功完成。
FAILED // 流程失败并已停止。
};
enum class Status {
IDLE = 0, // 空闲状态。
NOT_INITIALIZED, // 尚未调用 init() 完成初始化。
INVALID_CONFIG, // 配置非法,无法启动或应用参数。
CONTROL_JOINT_MISMATCH, // 控制关节顺序与 IK 链不一致。
ALIGN_WAITING_PERCEPTION, // 对准阶段等待相机/AprilTag 感知结果。
ALIGN_WAITING_TRACK, // 对准阶段等待目标点跟踪恢复成功。
ALIGN_TARGET_SETUP_FAILED,// 视觉目标设置失败setTargetFromPointInTag 失败。
ALIGN_COMPUTE_FAILED, // 对准阶段 IBVS 或 IK 计算失败。
ALIGN_TIMEOUT, // 对准阶段超时仍未收敛。
ALIGNING, // 正在执行视觉对准。
ALIGN_REACHED, // 视觉对准完成。
TOUCHING, // 正在向前触控。
TACTILE_UNAVAILABLE, // 触觉数据不可用。
TOUCH_TRIGGERED, // 已检测到接触触发。
TOUCH_FORWARD_TIMEOUT, // 前进触控时间到,但未触发接触。
RETRACTING, // 正在回退离开屏幕。
DONE, // 流程成功完成。
STOPPED, // 被外部 stop() 主动停止。
ROBOT_STATE_FAILED, // 读取机器人状态失败。
ROBOT_COMMAND_FAILED // 向机器人下发控制命令失败。
};
enum class AlignMode {
POSE_AND_POSITION = 0, // 使用配置里的固定 rx/ry/rz 与位置一起对齐。
RX_RY_AND_POSITION, // 使用配置里的 rx/ry保留锁定时看到的 tag 平面内 yaw再与位置一起对齐。
POSITION_ONLY // 保留锁定时看到的完整 tag 姿态,只按位置对齐。
};
enum class TactileCriterion {
FZ = 0,
MAGNITUDE
};
struct Config {
// 是否在触控流程开始前先回到指定初始关节位姿。
bool move_to_init_position_before_start{false};
// 是否在触控流程结束DONE/FAILED后回到指定初始关节位姿。
bool move_to_init_position{false};
// 初始关节位姿目标,在前置回位或结束后回位开启时使用。
std::vector<device::JointPoint> init_joint_positions{};
// 回到初始位姿时的 moveJ 主导速度,单位 rad/s。
double init_movej_vel{1.0};
// 回到初始位姿时的 moveJ 主导加速度,单位 rad/s^2。
double init_movej_acc{2.0};
// IBVS / IK 初始化参数。
// URDF 文件路径,用于初始化 IbvsController 内部 IK 求解器。
std::string urdf_path;
// IK 链基座 link 名称。
std::string base_link{"PELVIS_S"};
// IK 链末端法兰 link 名称。
std::string flange_link{"R_WRIST_R_S"};
// URDF 中相机 link 名称。
std::string camera_link;
// 视觉感知参数。
// AprilTag 实际边长,单位米。
double tag_size_m{0.12};
// 感知更新时如何使用深度图:不用 / 尽量用 / 必须用。
perception::AprilTagPerception::DepthPolicy depth_policy{
perception::AprilTagPerception::DepthPolicy::NONE};
// 从像素恢复目标点时采用 tag 平面求交,还是深度图反投影。
perception::TagRelativeTarget3D::TargetPointMethod target_point_method{
perception::TagRelativeTarget3D::TargetPointMethod::TAG_PLANE};
// 视觉阶段目标:触控点在相机坐标系中的 hover 位置。
// 目标点在相机坐标系中的期望位置,单位米。
Eigen::Vector3d hover_target_in_camera{0.0, 0.0, 0.40};
// 目标 tag 姿态旋转参数,直接传给 vpRotationMatrix::buildFrom。
double target_rx{3.14159265358979323846};
// 目标 tag 姿态旋转参数,直接传给 vpRotationMatrix::buildFrom。
double target_ry{0.0};
// 目标 tag 姿态旋转参数,直接传给 vpRotationMatrix::buildFrom。
double target_rz{0.0};
// 对齐模式1) 固定姿态+位置2) 固定 rx/ry + 锁定时 yaw + 位置3) 仅位置。
AlignMode align_mode{AlignMode::POSE_AND_POSITION};
// IBVS 参数。
// 视觉伺服增益 lambda。
double ibvs_lambda{0.6};
// DLS IK 阻尼系数 mu。
double ibvs_mu{0.1};
// 单关节最大速度,单位 rad/s。
double ibvs_qdot_max{0.15};
// 相机 twist 六维限幅 `[vx, vy, vz, wx, wy, wz]`。
std::array<double, 6> ibvs_vmax6{{0.15, 0.15, 0.20, 0.6, 0.6, 0.6}};
// 相机 twist 六维加速度限幅 `[ax, ay, az, alphax, alphay, alphaz]`
// 分量小于等于 0 表示该维度不启用加速度限幅。
std::array<double, 6> ibvs_amax6{{0.0, 0.0, 0.0, 0.0, 0.0, 0.0}};
// 相机 twist 一阶低通滤波系数;取值在 (0, 1) 时启用低通,默认 1.0 表示不过滤。
double ibvs_twist_filter_alpha{1.0};
// 是否启用关节限位回避。
bool enable_joint_limit_avoidance{true};
// 关节限位回避增益。
double joint_limit_avoidance_gain{0.2};
// 距离关节限位多近时开始回避,按关节范围比例计算。
double joint_limit_avoidance_margin_ratio{0.15};
// 单关节限位回避最大推回速度。
double joint_limit_avoidance_max_push{0.25};
// `AbstractCamera` 相机坐标系到 ViSP 相机坐标系的旋转矩阵。
Eigen::Matrix3d R_camera_to_visp{Eigen::Matrix3d::Identity()};
// `AbstractCamera` 相机坐标系到 URDF 相机坐标系的旋转矩阵。
Eigen::Matrix3d R_camera_to_urdf{Eigen::Matrix3d::Identity()};
// 关节控制链,默认右臂 7 轴。
// 顺序必须与 IbvsController 内部 IK 链顺序一致。
std::vector<std::string> control_joint_names{
"R_SHOULDER_P", "R_SHOULDER_R", "R_SHOULDER_Y",
"R_ELBOW_R", "R_WRIST_P", "R_WRIST_Y", "R_WRIST_R"};
// 视觉对准收敛判据 `[x, y, z, rx, ry, rz]`。
// 其中位置误差单位米,旋转误差单位弧度;旋转部分使用目标姿态误差 rotvec 的三个分量分别比较。
std::array<double, 6> align_error_threshold6{{0.003, 0.003, 0.010,
0.08726646259971647,
0.08726646259971647,
0.08726646259971647}};
// 连续多少帧都满足阈值,才认为对准完成。
int align_stable_frames{5};
// 对准阶段超时时间,单位秒。
double align_timeout_s{10.0};
// 为 true 时对准完成后暂停,不自动进入触控阶段。
bool pause_after_align_reached{false};
// 触控阶段前进方向。当前按末端 Tool 坐标系解释,字段名保留兼容。
// 为 true 时TOUCHING 阶段使用 speedL为 false 时使用 moveL。
// 当前 moveL 路径为同步前进,执行完成后直接结束流程,并按配置决定是否回初始位姿。
bool touch_use_speedl{true};
// 6 维速度命令 `[vx, vy, vz, wx, wy, wz]`,单位 m/s 和 rad/s。
// 当前机器人上 `[0, -0.08, 0, 0, 0, 0]` 表示沿 Tool -Y 方向向前触屏。
// 当 TOUCHING 使用 moveL 时,会取其线速度方向并按 touch_forward_l 构造位移目标。
Eigen::Matrix<double, 6, 1> touch_twist_base{
(Eigen::Matrix<double, 6, 1>() << 0.0, -0.08, 0.0, 0.0, 0.0, 0.0).finished()};
// 触控阶段 speedL 的加速度参数。
double touch_speedl_acceleration{3.0};
// 触控阶段使用 moveL 时,沿 touch_twist_base 线速度方向前进的距离,单位米。
double touch_forward_l{0.08};
// 触控阶段使用 moveL 时的末端速度,单位 m/s。
double touch_movel_speed{0.25};
// 触控阶段使用 moveL 时的末端加速度,单位 m/s^2。
double touch_movel_acceleration{1.2};
// 触控阶段使用 moveL 时的末端 jerk单位 m/s^3。
double touch_movel_jerk{5.0};
// 触控阶段使用 moveL 时的关节速度上限;为空时退回 robot->moveL 默认值。
std::vector<double> touch_movel_qd_max{2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5};
// 前进触控阶段的最大累计位移,单位米。
// 该距离仅在 TOUCHING 使用 speedL 时生效,由 TouchScreenApp 根据末端相对触控起点的累计位移判断。
// 大于 0 时,达到该距离后无论压力是否达阈值,都会立即进入回退阶段。
// 小于等于 0 时,表示不启用这条限制。
double touch_speedl_forward_l{0.08};
// 接触后停留与回退。当前按末端 Tool 坐标系解释。
// 检测到接触后在当前位置停留的时间,单位秒。
// 当该值小于 0 时,表示不做停留,直接把 speedL 切换为回退。
double dwell_time_s{0.05};
// 回退阶段的 6 维速度命令 `[vx, vy, vz, wx, wy, wz]`。
// 当前机器人上 `[0, +0.08, 0, 0, 0, 0]` 表示沿 Tool +Y 方向向后离屏。
// 回退阶段统一使用 speedL。
Eigen::Matrix<double, 6, 1> retract_twist_base{
(Eigen::Matrix<double, 6, 1>() << 0.0, 0.08,0.0, 0.0, 0.0, 0.0).finished()};
// 回退阶段 speedL 的加速度参数。
double retract_acceleration{3.0};
// 回退阶段 speedL 持续时间,单位秒。
double retract_duration_s{0.8};
// 指尖触觉判据。
// 使用哪根手指的触觉阵列判断是否接触。
device::AbstractDexHand::FingerType tactile_finger{device::AbstractDexHand::FingerType::INDEX};
// 使用该手指的哪个触觉区域。
device::AbstractDexHand::TactileRegion tactile_region{
device::AbstractDexHand::TactileRegion::TIP};
// 三维合力标量化方式:直接使用法向 fz或使用三维力模长。
TactileCriterion tactile_criterion{TactileCriterion::FZ};
// 触觉区域三维合力按 tactile_criterion 标量化后的阈值;超过该值认为已经接触。
double tactile_pressure_sum_threshold{100.0};
// 保留兼容的旧字段;当前合力判定逻辑不再使用非零点数量阈值。
int tactile_nonzero_count_threshold{1};
};
TouchScreenApp();
~TouchScreenApp() = default;
bool init();
bool init(const std::shared_ptr<device::AbstractRobot>& robot,
const std::shared_ptr<device::AbstractDexHand>& dexhand,
const std::shared_ptr<device::AbstractCamera>& camera);
bool init(const std::shared_ptr<device::AbstractRobot>& robot,
const std::shared_ptr<device::AbstractDexHand>& dexhand,
const std::shared_ptr<device::AbstractCamera>& camera,
const cmvr::config::TouchScreenAppConfig& config);
bool setConfigFromProto();
bool setConfigFromProto(const cmvr::config::TouchScreenAppConfig& config);
bool setTouchSpeedlForwardL(double forward_l);
bool startFromPixel(int u, int v);
bool step(double dt);
void stop();
Phase phase() const { return phase_; }
Status lastStatus() const { return last_status_; }
static const char* phaseToString(Phase phase);
static const char* statusToString(Status status);
bool isBusy() const { return phase_ == Phase::ALIGNING || phase_ == Phase::ALIGN_REACHED ||
phase_ == Phase::TOUCHING || phase_ == Phase::DWELLING ||
phase_ == Phase::RETRACTING; }
bool isFinished() const { return phase_ == Phase::DONE; }
bool isFailed() const { return phase_ == Phase::FAILED; }
int targetU() const { return target_u_; }
int targetV() const { return target_v_; }
double lastTouchPressureSum() const { return last_touch_pressure_sum_; }
int lastTouchNonzeroCount() const { return last_touch_nonzero_count_; }
int lastActiveTagId() const { return last_active_tag_id_; }
const Eigen::Vector3d& lastAlignErrorCamera() const { return last_align_error_camera_; }
const std::shared_ptr<perception::AprilTagPerception>& perception() const { return perception_; }
const perception::TagRelativeTarget3D& tracker() const { return tracker_; }
const IbvsController& ibvs() const { return ibvs_; }
private:
using Clock = std::chrono::steady_clock;
static bool configFromProto(const cmvr::config::TouchScreenAppConfig& proto_config,
Config& config_out);
void setConfig(const Config& config);
bool applyConfig();
bool validateControlJointNames() const;
bool stepAligning(double dt);
bool stepTouching();
bool stepDwelling();
bool stepRetracting();
bool readControlledJointPositions(std::vector<double>& q_out) const;
bool sendJointVelocity(const std::vector<double>& qdot) const;
bool sendZeroJointVelocity() const;
void hardStopIbvsMotion();
bool holdCurrentControlledPosition() const;
bool moveToInitPositionIfEnabled() const;
bool readCurrentTouchPointPositionBase(Eigen::Vector3d& p_out) const;
void logTouchingSpeedLState() const;
bool startTouchPhase();
bool handleTouchTriggered(bool stop_forward_motion);
bool startRetractPhase(Phase next_phase_after_retract, Status final_status_after_retract);
void enterFailed(Status status);
bool updateTouchPressure();
private:
std::shared_ptr<device::AbstractRobot> robot_{nullptr};
std::shared_ptr<device::AbstractDexHand> dexhand_{nullptr};
std::shared_ptr<device::AbstractCamera> camera_{nullptr};
std::shared_ptr<perception::AprilTagPerception> perception_{nullptr};
perception::TagRelativeTarget3D tracker_;
IbvsController ibvs_;
Config config_{};
Phase phase_{Phase::IDLE};
Phase phase_after_retract_{Phase::DONE};
Status last_status_{Status::NOT_INITIALIZED};
bool initialized_{false};
bool target_locked_{false};
bool ibvs_target_initialized_{false};
bool touch_command_started_{false};
bool retract_command_started_{false};
int target_u_{-1};
int target_v_{-1};
int align_stable_count_{0};
int last_active_tag_id_{-1};
double last_touch_pressure_sum_{0.0};
int last_touch_nonzero_count_{0};
Eigen::Vector3d last_align_error_camera_{Eigen::Vector3d::Zero()};
bool locked_target_rotation_valid_{false};
Eigen::Matrix3d locked_target_rotation_{Eigen::Matrix3d::Identity()};
bool touch_start_position_valid_{false};
Eigen::Vector3d touch_start_position_base_{Eigen::Vector3d::Zero()};
Clock::time_point phase_start_time_{};
Status final_status_after_retract_{Status::DONE};
};
using touch_screen_app = TouchScreenApp;
} // namespace cmvr::app
#endif // CMVR_ES_TOUCH_SCREEN_APP_H

File diff suppressed because it is too large Load Diff

View File

@ -1,105 +0,0 @@
#include "gtest/gtest.h"
#include <algorithm>
#include <chrono>
#include <iostream>
#include <thread>
#include "applications/include/touch_screen_app.h"
#include "include/device_manager.h"
#include "service/grpc/include/server_runner.h"
namespace {
constexpr const char* kConfigPath =
"/home/lgv/cmvr/cmvr-es/cmvr-es/common/config/cabin_robot.xml";
constexpr int kTargetU = 1280 / 2.0;
constexpr int kTargetV = 720 / 2.0;
void run_touch_once(int u, int v) {
const XmlNode config(kConfigPath);
// auto& dm = cmvr::device::DeviceManager::getInstance();
cmvr::device::DeviceManager::getInstance(config.getChild("DeviceManager"));
// cmvr::service::ServerRunner runner;
// runner.start(config);
ASSERT_TRUE(config.hasChild("DeviceManager")) << "DeviceManager node not found";
cmvr::app::TouchScreenApp app;
ASSERT_TRUE(app.init())
<< "TouchScreenApp init failed";
ASSERT_TRUE(app.startFromPixel(u, v)) << "startFromPixel failed";
bool align_reached = false;
bool touch_triggered = false;
auto last_step_time = std::chrono::steady_clock::now();
bool first_step = true;
while (app.isBusy()) {
const auto now = std::chrono::steady_clock::now();
double dt = 0.02;
if (!first_step) {
dt = std::chrono::duration<double>(now - last_step_time).count();
dt = std::clamp(dt, 0.005, 0.05);
}
last_step_time = now;
first_step = false;
const bool step_ok = app.step(dt);
const auto& p_c_target = app.tracker().lastTargetInCamera();
std::cout << "phase=" << cmvr::app::TouchScreenApp::phaseToString(app.phase())
<< ", status=" << cmvr::app::TouchScreenApp::statusToString(app.lastStatus())
<< ", active_tag=" << app.lastActiveTagId()
<< ", target_c=[" << p_c_target.x() << ", "
<< p_c_target.y() << ", "
<< p_c_target.z() << "]"
<< ", nonzero_count=" << app.lastTouchNonzeroCount()
<< ", pressure_sum=" << app.lastTouchPressureSum()
<< ", err_c=[" << app.lastAlignErrorCamera().x() << ", "
<< app.lastAlignErrorCamera().y() << ", "
<< app.lastAlignErrorCamera().z() << "]\n";
if (app.lastStatus() == cmvr::app::TouchScreenApp::Status::ALIGN_REACHED) {
std::cout << "align reached, target_c=[" << p_c_target.x() << ", "
<< p_c_target.y() << ", "
<< p_c_target.z() << "]\n";
align_reached = true;
}
if (app.lastStatus() == cmvr::app::TouchScreenApp::Status::TOUCH_TRIGGERED) {
std::cout << "touch triggered, nonzero_count=" << app.lastTouchNonzeroCount()
<< ", pressure_sum=" << app.lastTouchPressureSum() << "\n";
touch_triggered = true;
}
if (!step_ok) {
const auto failed_status = app.lastStatus();
app.stop();
FAIL() << "touch flow failed, status="
<< cmvr::app::TouchScreenApp::statusToString(failed_status);
}
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
const auto final_status = app.lastStatus();
ASSERT_TRUE(align_reached)
<< "align was never reached, final status="
<< cmvr::app::TouchScreenApp::statusToString(final_status);
ASSERT_TRUE(touch_triggered)
<< "touch was never triggered, final status="
<< cmvr::app::TouchScreenApp::statusToString(final_status);
ASSERT_TRUE(app.isFinished())
<< "touch did not finish successfully, final status="
<< cmvr::app::TouchScreenApp::statusToString(final_status);
std::cout << "touch done, final status="
<< cmvr::app::TouchScreenApp::statusToString(final_status) << "\n";
app.stop();
}
} // namespace
TEST(TouchScreenAppTest, RunTouchOnceOnRealRobot) {
run_touch_once(kTargetU, kTargetV);
}

View File

@ -1,19 +1,12 @@
#find_package(protobuf REQUIRED) #find_package(protobuf REQUIRED)
add_library(common SHARED
file(GLOB SRC ${CMAKE_CURRENT_SOURCE_DIR}/media/ffmpeg/camera_capture.cpp
${CMAKE_CURRENT_SOURCE_DIR}/utils/config_helper/src/config_setting.cpp ${CMAKE_CURRENT_SOURCE_DIR}/media/ffmpeg/realsense_capture.cpp
${CMAKE_CURRENT_SOURCE_DIR}/utils/ffmpeg/src/CameraCapture.cpp ${CMAKE_CURRENT_SOURCE_DIR}/media/ffmpeg/video_frame_encoder.cpp
${CMAKE_CURRENT_SOURCE_DIR}/utils/ffmpeg/src/RealSenseCapture.cpp ${CMAKE_CURRENT_SOURCE_DIR}/media/ffmpeg/video_writer.cpp
${CMAKE_CURRENT_SOURCE_DIR}/utils/ffmpeg/src/VideoFrameEncoder.cpp
${CMAKE_CURRENT_SOURCE_DIR}/utils/ffmpeg/src/VideoWriter.cpp
${CMAKE_CURRENT_SOURCE_DIR}/curve/src/s_curve.cpp
) )
add_library(common SHARED ${SRC})
target_include_directories(common PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) target_include_directories(common PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(common PUBLIC target_link_libraries(common PUBLIC
@ -52,17 +45,3 @@ install(TARGETS common LIBRARY DESTINATION lib)
# pthread # pthread
#) #)
# #
#add_executable(s_curve_test
# curve/src/s_curve_test.cpp
#)
#
#target_link_libraries(s_curve_test
# PRIVATE
# cmvr_es::common
# cmvr_es::proto
# glog
# gtest
# gtest_main
# pthread
# Matplot++::matplot
#)

View File

@ -5,6 +5,8 @@
#ifndef GRPC_UTILS_H #ifndef GRPC_UTILS_H
#define GRPC_UTILS_H #define GRPC_UTILS_H
#include <chrono>
#include <grpcpp/grpcpp.h> #include <grpcpp/grpcpp.h>
inline void setCurrentTimestamp(google::protobuf::Timestamp* ts) { inline void setCurrentTimestamp(google::protobuf::Timestamp* ts) {

View File

@ -0,0 +1,15 @@
add_library(logging STATIC
logger.cpp
)
target_include_directories(logging PUBLIC
${PROJECT_SOURCE_DIR}/cmvr-es
)
target_link_libraries(logging PUBLIC
cmvr_es::proto
)
add_library(cmvr_es::logging ALIAS logging)
install(TARGETS logging ARCHIVE DESTINATION lib)

View File

@ -0,0 +1,365 @@
#include "common/base/logging/logger.h"
#include <cstdlib>
#include <ctime>
#include <iomanip>
#include <iostream>
#include <system_error>
#include <thread>
namespace cmvr::logging {
namespace {
std::string formatTimestamp()
{
const auto now = std::chrono::system_clock::now();
const auto time = std::chrono::system_clock::to_time_t(now);
const auto milliseconds = std::chrono::duration_cast<std::chrono::milliseconds>(
now.time_since_epoch()) % 1000;
std::tm local_time{};
localtime_r(&time, &local_time);
std::ostringstream output;
output << std::put_time(&local_time, "%Y-%m-%d %H:%M:%S")
<< '.' << std::setfill('0') << std::setw(3) << milliseconds.count();
return output.str();
}
const char* baseName(const char* path)
{
if (path == nullptr) {
return "unknown";
}
const char* result = path;
for (const char* current = path; *current != '\0'; ++current) {
if (*current == '/' || *current == '\\') {
result = current + 1;
}
}
return result;
}
const char* terminalColor(const Level level)
{
switch (level) {
case Level::WARNING:
return "\033[33m";
case Level::ERROR:
case Level::FATAL:
return "\033[31m";
case Level::DEBUG:
case Level::INFO:
case Level::COUNT:
break;
}
return "";
}
} // namespace
Logger& Logger::instance()
{
static Logger logger;
return logger;
}
Logger::Logger()
{
routes_[index(Level::INFO)].terminal = true;
routes_[index(Level::WARNING)].terminal = true;
routes_[index(Level::ERROR)].terminal = true;
routes_[index(Level::FATAL)].terminal = true;
}
bool Logger::initialize(const config::LoggerConfig& config,
const std::string& application_name,
const std::filesystem::path& executable_directory)
{
std::lock_guard<std::mutex> lock(mutex_);
std::array<Route, static_cast<std::size_t>(Level::COUNT)> new_routes{};
std::array<bool, static_cast<std::size_t>(Level::COUNT)> configured{};
bool any_file_route = false;
Level new_minimum_level;
if (!convertLevel(config.minimum_level(), new_minimum_level)) {
std::cerr << "Invalid minimum logging level" << std::endl;
return false;
}
for (const auto& route_config : config.routes()) {
Level level;
if (!convertLevel(route_config.level(), level)) {
std::cerr << "Invalid logging level in route" << std::endl;
return false;
}
const auto route_index = index(level);
if (configured[route_index]) {
std::cerr << "Duplicate logging route for " << levelName(level) << std::endl;
return false;
}
configured[route_index] = true;
new_routes[route_index] = {route_config.terminal(), route_config.file()};
any_file_route = any_file_route || route_config.file();
}
if (config.routes().empty()) {
std::cerr << "No logging routes configured" << std::endl;
return false;
}
std::ofstream new_log_file;
std::filesystem::path new_log_path;
if (any_file_route) {
std::filesystem::path directory = config.directory().empty()
? std::filesystem::path("../log")
: std::filesystem::path(config.directory());
if (directory.is_relative()) {
directory = executable_directory / directory;
}
directory = directory.lexically_normal();
std::error_code error;
std::filesystem::create_directories(directory, error);
if (error) {
std::cerr << "Failed to create log directory: " << directory
<< ": " << error.message() << std::endl;
return false;
}
const std::string file_name = application_name.empty() ? "cmvr_es.log" : application_name + ".log";
new_log_path = directory / file_name;
new_log_file.open(new_log_path, std::ios::out | std::ios::app);
if (!new_log_file.is_open()) {
std::cerr << "Failed to open log file: " << new_log_path << std::endl;
return false;
}
}
if (log_file_.is_open()) {
log_file_.flush();
log_file_.close();
}
routes_ = new_routes;
format_ = parseFormat_(config);
minimum_level_ = new_minimum_level;
log_file_path_ = std::move(new_log_path);
log_file_ = std::move(new_log_file);
max_file_size_bytes_ = static_cast<std::uintmax_t>(
config.max_file_size_mb() > 0 ? config.max_file_size_mb() : 100) * 1024U * 1024U;
flush_interval_ = std::chrono::seconds(
config.flush_interval_seconds() > 0 ? config.flush_interval_seconds() : 1);
last_flush_ = std::chrono::steady_clock::now();
initialized_ = true;
return true;
}
void Logger::shutdown()
{
std::lock_guard<std::mutex> lock(mutex_);
if (log_file_.is_open()) {
log_file_.flush();
log_file_.close();
}
initialized_ = false;
}
bool Logger::enabled(const Level level) const
{
std::lock_guard<std::mutex> lock(mutex_);
if (level != Level::FATAL && level < minimum_level_) {
return false;
}
const auto& route = routes_[index(level)];
return route.terminal || route.file || level == Level::FATAL;
}
void Logger::write(const Level level,
const char* source_file,
const int source_line,
const std::string& message)
{
std::lock_guard<std::mutex> lock(mutex_);
if (level != Level::FATAL && level < minimum_level_) {
return;
}
const Route route = routes_[index(level)];
if (!route.terminal && !route.file && level != Level::FATAL) {
return;
}
const std::string line = formatLine_(level, source_file, source_line, message);
if (route.terminal || level == Level::FATAL) {
writeTerminal_(level, line);
}
if (route.file && log_file_.is_open()) {
rotateIfNeeded_();
log_file_ << line << '\n';
const auto now = std::chrono::steady_clock::now();
if (level == Level::ERROR || level == Level::FATAL || now - last_flush_ >= flush_interval_) {
log_file_.flush();
last_flush_ = now;
}
}
}
std::size_t Logger::index(const Level level)
{
return static_cast<std::size_t>(level);
}
const char* Logger::levelName(const Level level)
{
switch (level) {
case Level::DEBUG: return "DEBUG";
case Level::INFO: return "INFO";
case Level::WARNING: return "WARNING";
case Level::ERROR: return "ERROR";
case Level::FATAL: return "FATAL";
case Level::COUNT: break;
}
return "UNKNOWN";
}
bool Logger::convertLevel(const config::LogLevel input, Level& output)
{
switch (input) {
case config::LOG_LEVEL_DEBUG: output = Level::DEBUG; return true;
case config::LOG_LEVEL_INFO: output = Level::INFO; return true;
case config::LOG_LEVEL_WARNING: output = Level::WARNING; return true;
case config::LOG_LEVEL_ERROR: output = Level::ERROR; return true;
case config::LOG_LEVEL_FATAL: output = Level::FATAL; return true;
case config::LOG_LEVEL_UNSPECIFIED: break;
}
return false;
}
Format Logger::parseFormat_(const config::LoggerConfig& config)
{
Format format;
if (!config.has_format()) {
return format;
}
const auto& format_config = config.format();
if (format_config.has_show_time()) {
format.show_time = format_config.show_time();
}
if (format_config.has_show_level()) {
format.show_level = format_config.show_level();
}
if (format_config.has_show_thread_id()) {
format.show_thread_id = format_config.show_thread_id();
}
if (format_config.has_show_source_location()) {
format.show_source_location = format_config.show_source_location();
}
return format;
}
std::string Logger::formatLine_(const Level level,
const char* source_file,
const int source_line,
const std::string& message) const
{
std::ostringstream formatted;
bool has_prefix = false;
const auto append_prefix_part = [&formatted, &has_prefix](const std::string& part) {
if (has_prefix) {
formatted << ' ';
}
formatted << part;
has_prefix = true;
};
if (format_.show_time) {
append_prefix_part(formatTimestamp());
}
if (format_.show_level) {
append_prefix_part(std::string("[") + levelName(level) + "]");
}
if (format_.show_thread_id) {
std::ostringstream thread_id;
thread_id << '[' << std::this_thread::get_id() << ']';
append_prefix_part(thread_id.str());
}
if (format_.show_source_location) {
std::ostringstream source_location;
source_location << '[' << baseName(source_file) << ':' << source_line << ']';
append_prefix_part(source_location.str());
}
if (has_prefix) {
formatted << ' ';
}
formatted << message;
return formatted.str();
}
void Logger::rotateIfNeeded_()
{
if (log_file_path_.empty() || !log_file_.is_open()) {
return;
}
std::error_code error;
const auto size = std::filesystem::file_size(log_file_path_, error);
if (error || size < max_file_size_bytes_) {
return;
}
log_file_.flush();
log_file_.close();
const auto backup_path = log_file_path_.string() + ".1";
std::filesystem::remove(backup_path, error);
error.clear();
std::filesystem::rename(log_file_path_, backup_path, error);
if (error) {
std::cerr << "Failed to rotate log file: " << error.message() << std::endl;
}
log_file_.open(log_file_path_, std::ios::out | std::ios::trunc);
}
void Logger::writeTerminal_(const Level level, const std::string& line)
{
const char* color = terminalColor(level);
if (color[0] == '\0') {
std::cout << line << std::endl;
return;
}
std::cout << color << line << "\033[0m" << std::endl;
}
LogMessage::LogMessage(const Level level, const char* source_file, const int source_line)
: level_(level), source_file_(source_file), source_line_(source_line)
{
}
LogMessage::~LogMessage()
{
Logger::instance().write(level_, source_file_, source_line_, stream_.str());
if (level_ == Level::FATAL) {
Logger::instance().shutdown();
std::abort();
}
}
bool initLogging(const config::LoggerConfig& config,
const std::string& application_name,
const std::filesystem::path& executable_directory)
{
return Logger::instance().initialize(config, application_name, executable_directory);
}
void shutdownLogging()
{
Logger::instance().shutdown();
}
} // namespace cmvr::logging

View File

@ -0,0 +1,116 @@
#pragma once
#include <atomic>
#include <array>
#include <chrono>
#include <filesystem>
#include <fstream>
#include <mutex>
#include <ostream>
#include <sstream>
#include <string>
#include "cmvr/config/logger_config/logger_config.pb.h"
namespace cmvr::logging {
enum class Level {
DEBUG = 0,
INFO,
WARNING,
ERROR,
FATAL,
COUNT
};
struct Route {
bool terminal{false};
bool file{false};
};
struct Format {
bool show_time{true};
bool show_level{true};
bool show_thread_id{true};
bool show_source_location{true};
};
class Logger {
public:
static Logger& instance();
bool initialize(const config::LoggerConfig& config,
const std::string& application_name,
const std::filesystem::path& executable_directory);
void shutdown();
bool enabled(Level level) const;
void write(Level level, const char* source_file, int source_line, const std::string& message);
private:
Logger();
static std::size_t index(Level level);
static const char* levelName(Level level);
static bool convertLevel(config::LogLevel input, Level& output);
static Format parseFormat_(const config::LoggerConfig& config);
std::string formatLine_(Level level, const char* source_file, int source_line, const std::string& message) const;
void rotateIfNeeded_();
void writeTerminal_(Level level, const std::string& line);
mutable std::mutex mutex_;
std::array<Route, static_cast<std::size_t>(Level::COUNT)> routes_{};
Format format_{};
Level minimum_level_{Level::INFO};
std::filesystem::path log_file_path_;
std::ofstream log_file_;
std::uintmax_t max_file_size_bytes_{100U * 1024U * 1024U};
std::chrono::seconds flush_interval_{1};
std::chrono::steady_clock::time_point last_flush_{};
bool initialized_{false};
};
class LogMessage {
public:
LogMessage(Level level, const char* source_file, int source_line);
~LogMessage();
std::ostream& stream() { return stream_; }
private:
Level level_;
const char* source_file_;
int source_line_;
std::ostringstream stream_;
};
class LogMessageVoidify {
public:
void operator&(std::ostream&) const {}
};
bool initLogging(const config::LoggerConfig& config,
const std::string& application_name,
const std::filesystem::path& executable_directory);
void shutdownLogging();
} // namespace cmvr::logging
#define CMVR_LOG(level) \
!::cmvr::logging::Logger::instance().enabled(::cmvr::logging::Level::level) \
? static_cast<void>(0) \
: ::cmvr::logging::LogMessageVoidify() & \
::cmvr::logging::LogMessage( \
::cmvr::logging::Level::level, __FILE__, __LINE__).stream()
#define CMVR_LOG_EVERY_N(level, n) \
if (![]() { \
static std::atomic<unsigned long> counter{0}; \
return counter.fetch_add(1, std::memory_order_relaxed) % (n) == 0; \
}()) {} else CMVR_LOG(level)
#define CMVR_LOG_IF_EVERY_N(level, condition, n) \
if (!(condition) || ![]() { \
static std::atomic<unsigned long> counter{0}; \
return counter.fetch_add(1, std::memory_order_relaxed) % (n) == 0; \
}()) {} else CMVR_LOG(level)

View File

@ -1,152 +0,0 @@
<CMVR-ES>
<Constants rootDir="/home/xtkuang/projects/cmvr-es"/>
<Logger dir="../log" level="info" bufSize="5" logSize="1024"/>
<DeviceManager name="cmvr_es" ver="0.1" description="cmvr edge system version 0.1">
<Devices>
<AGV>
</AGV>
<Battery>
</Battery>
<Camera>
<!-- <UVCCamera id="cam1" serial="/dev/video6" w="640" h="480" fps="30" mode="video" codec="H265"/>-->
<!-- <UVCCamera id="cam2" serial="/dev/video14" w="640" h="480" fps="30" mode="video" codec="H265"/>-->
<!-- <RealsenseCamera id="cam3" serial="243122072252" w="640" h="480" fps="30" mode="video" stream_mode="rgbd" align_mode="color" codec="H265"/>-->
<!-- <RealsenseCamera id="cam4" serial="243122075614" w="1280" h="720" fps="30" mode="video" stream_mode="rgbd" align_mode="color" codec="H265"/>-->
<!-- <MechMind id="cam5" ip="10.148.108.111" align="true" _2dtype="color"/>-->
<!-- <RealsenseCamera id="cam6" serial="243122075389" w="640" h="480" fps="30" mode="video" stream_mode="rgbd" align_mode="color" codec="H265"/>-->
</Camera>
<DexHand>
<!-- <RH56DFTP id="hand1" default_force="500" default_speed="500" ip_address="192.168.1.224" port="6000">-->
<!-- <Freedom order="01" default_force="500" default_speed="500" />-->
<!-- </RH56DFTP>-->
<!-- <RH56DFTP id="hand2" default_force="500" default_speed="500" ip_address="192.168.1.224" port="6000">-->
<!-- <Freedom order="01" default_force="500" default_speed="500" />-->
<!-- </RH56DFTP>-->
</DexHand>
<Robot>
<!-- <LeftArm id="left_arm" devtype="ti5Robot" />-->
<!-- <RightArm />-->
<!-- <Neck/>-->
<Humanoid id="hc01" dof="14"
urdf="/home/lgv/cmvr/cmvr-es/model/xiaoyan_description/dual_arm.urdf"
baseLink="PELVIS_S"
jointNames="L_SHOULDER_P,L_SHOULDER_R,L_SHOULDER_Y,L_ELBOW_R,L_WRIST_P,L_WRIST_Y,L_WRIST_R,R_SHOULDER_P,R_SHOULDER_R,R_SHOULDER_Y,R_ELBOW_R,R_WRIST_P,R_WRIST_Y,R_WRIST_R"
linkNames="PELVIS_S,L_SHOULDER_P_S,L_SHOULDER_R_S,L_SHOULDER_Y_S,L_ELBOW_R_S,L_WRIST_P_S,L_WRIST_Y_S,L_WRIST_R_S,R_SHOULDER_P_S,R_SHOULDER_R_S,R_SHOULDER_Y_S,R_ELBOW_R_S,R_WRIST_P_S,R_WRIST_Y_S,R_WRIST_R_S,R_FINGER_TIP,R_CAM"
bufferSize="50"
verbose="false">
<CanManger id="" devId="">
<LeftArmCan id = " " devId = " " channelId ="0" enable="false" toolFrame="L_FINGER_TIP">
<Motor id="23" jointName="L_SHOULDER_P" limitQLb="3.14" limitQUb="3.14" limitQd="3.0"/>
<Motor id="24" jointName="L_SHOULDER_R" limitQLb="3.14" limitQUb="3.14" limitQd="3.0"/>
<Motor id="25" jointName="L_SHOULDER_Y" limitQLb="3.14" limitQUb="3.14" limitQd="3.0"/>
<Motor id="26" jointName="L_ELBOW_R" limitQLb="3.14" limitQUb="3.14" limitQd="3.0"/>
<Motor id="27" jointName="L_WRIST_P" limitQLb="3.14" limitQUb="3.14" limitQd="3.0"/>
<Motor id="28" jointName="L_WRIST_Y" limitQLb="3.14" limitQUb="3.14" limitQd="3.0"/>
<Motor id="29" jointName="L_WRIST_R" limitQLb="3.14" limitQUb="3.14" limitQd="3.0"/>
</LeftArmCan>
<RightArmCan id = " " devId = " " channelId ="1" enable="true" toolFrame="R_FINGER_TIP">
<Motor id="16" jointName="R_SHOULDER_P" limitQLb="3.14" limitQUb="3.14" limitQd="3.0"/>
<Motor id="17" jointName="R_SHOULDER_R" limitQLb="3.14" limitQUb="3.14" limitQd="3.0"/>
<Motor id="18" jointName="R_SHOULDER_Y" limitQLb="3.14" limitQUb="3.14" limitQd="3.0"/>
<Motor id="19" jointName="R_ELBOW_R" limitQLb="3.14" limitQUb="3.14" limitQd="3.0"/>
<Motor id="20" jointName="R_WRIST_P" limitQLb="-3.14" limitQUb="3.14" limitQd="3.0"/>
<Motor id="21" jointName="R_WRIST_Y" limitQLb="-1.102" limitQUb="1.02" limitQd="3.0"/>
<Motor id="22" jointName="R_WRIST_R" limitQLb="-0.293" limitQUb="1.57079" limitQd="3.0"/>
</RightArmCan>
<HeadCan id = " " devId = " " channelId ="2" enable="false">
<Motor id="32" jointName="HEAD_Y" limitQLb="3.14" limitQUb="3.14" limitQd="3.0"/>
<Motor id="30" jointName="HEAD_P" limitQLb="3.14" limitQUb="3.14" limitQd="3.0"/>
<Motor id="31" jointName="HEAD_R" limitQLb="3.14" limitQUb="3.14" limitQd="3.0"/>
</HeadCan>
<WaistCan id = " " devId = " " channelId ="3" enable="false">
<Motor id="4" jointName="WAIST_Y" limitQLb="3.14" limitQUb="3.14" limitQd="3.0"/>
<Motor id="15" jointName="WAIST_P" limitQLb="3.14" limitQUb="3.14" limitQd="3.0"/>
</WaistCan>
</CanManger>
</Humanoid>
</Robot>
<BioHead>
<esp32 id="bio_head" serial="/dev/ttyUSB0" ctrlFreq="50">
<!-- 眉毛 -->
<EyeBrow serial="64:0~3"
offest="90 90 90 90"
jLmtUp="90 170 155 110"
jLmtLow="20 77 90 20"/>
<!-- 眼睛 -->
<Eye serial="64:4~9"
offest="90 90 90 90 90 90"
jLmtUp="90 150 165 90 120 115"
jLmtLow="20 90 90 25 70 75"/>
<!-- 嘴巴 -->
<Mouth serial="65:0~9"
offest="90 90 90 90 90 90 90 90 90 90"
jLmtUp="150 110 130 140 100 105 110 125 90 95"
jLmtLow="70 30 80 80 65 55 45 80 85 90"/>
</esp32>
</BioHead >
<Microphone>
<!-- <ffmpegMicPhone id="mic1" alsa="hw:0" channels="2" sampleRate="44100" volume="80"/>-->
<!-- <ffmpegMicPhone id="mic2" alsa="hw:1" channels="1" sampleRate="44100" volume="80"/>-->
</Microphone>
<Speaker>
<ffmpegSpeaker id="spk1" serial="" alas="default" channels="2" sampleRate="44100" softResample="1" latency="50000" volume="100"/>
</Speaker>
<Canbus>
<!-- <rightArmCan id="can1" brand="SOCKET_CAN_RAW" type="USB_CARD" channel_id="CHANNEL_ID_ZERO" interface="NATIVE" baudrate="BCAN_BAUDRATE_500K"/>-->
</Canbus>
</Devices>
<HighLevelController>
<BioHeadExpre headId="bio_head" />
<CartesianWBC urdf="" />
<ScreenTouch robotID="" DexhandID="" />
</HighLevelController>
</DeviceManager>
<MonitorManager>
<DiskMonitor id="file_monitor" freq="1">
<!-- <Folder fileDir="/home/share/assets/audio" maxVolume="1000"/>-->
<!-- <Folder fileDir="/home/share/assets/image" maxVolume="1000"/>-->
<!-- <Folder fileDir="/home/share/assets/video" maxVolume="1000"/>-->
<!-- <Folder fileDir="../log" maxVolume="1000"/>-->
</DiskMonitor>
<JointMonitor id="robot_joint_monitor" freq="200">
<RobotJoint robotID="left_arm" motorID="0" maxTemp="80" maxCurrent="5" maxVel="2"/>
<RobotJoint robotID="left_arm" motorID="1" maxTemp="80" maxCurrent="5" maxVel="2"/>
<RobotJoint robotID="left_arm" motorID="2" maxTemp="80" maxCurrent="5" maxVel="2"/>
<RobotJoint robotID="left_arm" motorID="3" maxTemp="80" maxCurrent="5" maxVel="2"/>
<RobotJoint robotID="left_arm" motorID="4" maxTemp="80" maxCurrent="5" maxVel="2"/>
<RobotJoint robotID="left_arm" motorID="6" maxTemp="80" maxCurrent="5" maxVel="2"/>
<RobotJoint robotID="left_arm" motorID="7" maxTemp="80" maxCurrent="5" maxVel="2"/>
<RobotJoint robotID="right_arm" motorID="0" maxTemp="80" maxCurrent="5" maxVel="2"/>
<RobotJoint robotID="right_arm" motorID="1" maxTemp="80" maxCurrent="5" maxVel="2"/>
<RobotJoint robotID="right_arm" motorID="2" maxTemp="80" maxCurrent="5" maxVel="2"/>
<RobotJoint robotID="right_arm" motorID="3" maxTemp="80" maxCurrent="5" maxVel="2"/>
<RobotJoint robotID="right_arm" motorID="4" maxTemp="80" maxCurrent="5" maxVel="2"/>
<RobotJoint robotID="right_arm" motorID="6" maxTemp="80" maxCurrent="5" maxVel="2"/>
<RobotJoint robotID="right_arm" motorID="7" maxTemp="80" maxCurrent="5" maxVel="2"/>
</JointMonitor>
</MonitorManager>
<gRPCServer port="50052">
</gRPCServer>
</CMVR-ES>

View File

@ -1,74 +0,0 @@
realsense_cameras {
id: "cam1"
serialNumber: "243122074587"
width: 640
height: 480
fps: 30
codec: "H265"
camera_mode: CAMERA_MODE_VIDEO
stream_mode: STREAM_MODE_RGBD
align_mode: ALIGN_MODE_COLOR
buffer_size: 30
sync: false
enable: false
}
realsense_cameras {
id: "right_hand_cam"
serialNumber: "243122072252"
width: 1280
height: 720
encode_width: 640
encode_height: 360
fps: 30
codec: "H264"
camera_mode: CAMERA_MODE_VIDEO
stream_mode: STREAM_MODE_RGB
align_mode: ALIGN_MODE_COLOR
buffer_size: 30
sync: false
enable: true
}
realsense_cameras {
id: "cam3"
serialNumber: "243122075614"
width: 640
height: 480
fps: 30
codec: "H265"
camera_mode: CAMERA_MODE_VIDEO
stream_mode: STREAM_MODE_RGBD
align_mode: ALIGN_MODE_COLOR
buffer_size: 30
sync: false
enable: false
}
uvc_cameras {
id: "left_eye_cam"
usb: "/dev/uvc_left_camera"
width: 640
height: 480
fps: 30
codec: "H265"
camera_mode: CAMERA_MODE_VIDEO
stream_mode: STREAM_MODE_RGB
buffer_size: 30
enable: false
}
realsense_cameras {
id: "cam5"
serialNumber: "243122075389"
width: 640
height: 480
fps: 30
codec: "H265"
camera_mode: CAMERA_MODE_VIDEO
stream_mode: STREAM_MODE_RGBD
align_mode: ALIGN_MODE_COLOR
buffer_size: 30
sync: false
enable: false
}

View File

@ -0,0 +1,152 @@
#pragma once
#include <cmath>
#include <filesystem>
#include <string>
#include "common/base/logging/logger.h"
#include "common/io/proto_file_io.h"
namespace cmvr {
class ConfigHelper
{
public:
static void setConfigRootFromFile(const std::string& file_name)
{
if (file_name.empty()) {
return;
}
const auto parent = std::filesystem::path(file_name).lexically_normal().parent_path();
if (!parent.empty()) {
configRoot() = parent;
}
}
static std::string resolveConfigFile(const std::string& file_name)
{
if (file_name.empty()) {
return {};
}
const std::filesystem::path path(file_name);
if (path.is_absolute()) {
return path.lexically_normal().string();
}
const auto& root = configRoot();
if (!root.empty()) {
return (root / path).lexically_normal().string();
}
return path.lexically_normal().string();
}
static std::string resolveResourceFile(const std::string& file_name)
{
if (file_name.empty()) {
return {};
}
const std::filesystem::path path(file_name);
if (path.is_absolute()) {
return path.lexically_normal().string();
}
const auto& root = configRoot();
if (root.empty()) {
return path.lexically_normal().string();
}
const std::filesystem::path candidates[] = {
root / path,
root.parent_path() / path,
root.parent_path().parent_path() / path,
root.parent_path().parent_path().parent_path() / path,
};
for (const auto& candidate : candidates) {
const auto normalized = candidate.lexically_normal();
if (std::filesystem::exists(normalized)) {
return normalized.string();
}
}
return (root / path).lexically_normal().string();
}
template <class T>
static bool loadConfigFile(const std::string& file_name, T& message)
{
return getConfig(resolveConfigFile(file_name), message, true);
}
template <class T>
static bool loadConfigFileSilent(const std::string& file_name, T& message)
{
return getConfig(resolveConfigFile(file_name), message, false);
}
template <class T>
static bool saveConfigFile(const std::string& file_name, const T& message)
{
return setConfig(message, resolveConfigFile(file_name));
}
private:
static std::filesystem::path& configRoot()
{
static std::filesystem::path root;
return root;
}
template <class T>
static bool setConfig(const T& message, const std::string& file_name)
{
CMVR_LOG(INFO) << "file_name = " << file_name;
if (file_name.empty()) {
CMVR_LOG(ERROR) << "Empty file name.";
return false;
}
const bool ok = ProtoMessageIo::setProtoToAsciiFile(message, file_name);
if (!ok) {
CMVR_LOG(ERROR) << "Failed to write ASCII proto config to: " << file_name;
return false;
}
return true;
}
template <class T>
static bool getConfig(const std::string& file_name, T& message, const bool log_success)
{
if (file_name.empty()) {
CMVR_LOG(ERROR) << "Empty file name.";
return false;
}
if (log_success) {
CMVR_LOG(INFO) << "[ConfigHelper] Load config file: " << file_name;
}
const bool ok = ProtoMessageIo::getProtoFromAsciiFile(file_name, &message);
if (!ok) {
CMVR_LOG(ERROR) << "Failed to load ASCII proto config from: " << file_name;
return false;
}
return true;
}
};
namespace common::config {
inline double positiveOr(const double value, const double fallback)
{
return std::isfinite(value) && value > 0.0 ? value : fallback;
}
inline int positiveIntOr(const int value, const int fallback)
{
return value > 0 ? value : fallback;
}
} // namespace common::config
} // namespace cmvr

View File

@ -1,36 +0,0 @@
rh56dftp_dexhands {
id: "hand1"
ip: "192.168.1.213"
port: 6000
poll_interval_ms: 10
enable: false
}
rh56dftp_dexhands {
id: "hand2"
ip: "192.168.1.224"
port: 6000
poll_interval_ms: 10
enable: true
}
px_6ax_gen3 {
id: "paxini_tip_1"
serial_port: "/dev/ttyACM1"
sensor_model: "S1813_core"
module_id: 2
baud_rate: 921600
distributed_length: 153
resultant_length: 3
poll_interval_ms: 5
response_timeout_ms: 200
response_header_bytes: 14
tactile_rows: 1
tactile_cols: 51
tactile_finger: "INDEX"
tactile_region: "TIP"
sensor_name: "Paxini Gen3末端压力"
polling_read_mode: PX_6AX_GEN3_POLLING_READ_MODE_RESULTANT_FORCE
auto_calibrate: false
enable: true
}

View File

@ -1,9 +0,0 @@
urdf_path: "/home/lgv/cmvr/dual_arm.urdf"
base_frame_name: "PELVIS_S"
flange_frame_name: "R_FLANGE"
tcp_frame_name: "R_TCP"
max_iters: 100
pos_eps: 1e-6
rot_eps: 1e-6
damping: 1e-6

View File

@ -1,10 +0,0 @@
urdf_path: "/home/cmvr/Projects/cmvr-es/model/xiaoyan_description/dual_arm.urdf"
base_frame_name: "PELVIS_S"
flange_frame_name: "R_WRIST_R_S"
tcp_frame_name: "R_FINGER_TIP_FIXED"
lambda: 0.0001
w_posrot: 0.5
max_iters: 80
tol: 1e-6
qp_time_limit: 0.005

View File

@ -1,8 +0,0 @@
ffmpeg_microphones {
id: "mic1"
channels: 2
sampleRate: 44100
volume: 100
enable: false
input_device: "default"
}

View File

@ -1,4 +0,0 @@
ffmpeg_speakers {
id: "spk1"
enable: false
}

View File

@ -1,151 +0,0 @@
robot_id: "hc01"
dexhand_id: "paxini_tip_1"
camera_id: "right_hand_cam"
move_to_init_position_before_start: true
move_to_init_position: true
init_joint_positions {
joint_name: "R_SHOULDER_P"
rad: -0.3678
}
init_joint_positions {
joint_name: "R_SHOULDER_R"
rad: 1.1127
}
init_joint_positions {
joint_name: "R_SHOULDER_Y"
rad: 1.6084
}
init_joint_positions {
joint_name: "R_ELBOW_R"
rad: 1.61
}
init_joint_positions {
joint_name: "R_WRIST_P"
rad: -2.5718
}
init_joint_positions {
joint_name: "R_WRIST_Y"
rad: 0.1276
}
init_joint_positions {
joint_name: "R_WRIST_R"
rad: 0.1297
}
init_movej_vel: 1.0
init_movej_acc: 2.0
urdf_path: "/home/lgv/cmvr/cmvr-es/model/xiaoyan_description/dual_arm.urdf"
base_link: "PELVIS_S"
flange_link: "R_WRIST_R_S"
camera_link: "R_CAM"
tag_size_m: 0.012
depth_policy: TOUCH_SCREEN_DEPTH_POLICY_NONE
target_point_method: TOUCH_SCREEN_TARGET_POINT_METHOD_TAG_PLANE
hover_target_in_camera {
x: -0.001
y: 0.08
z: 0.15
}
target_rx: 3.14159265358979323846
target_ry: 0.0
target_rz: 0.0
align_mode: TOUCH_SCREEN_ALIGN_MODE_RX_RY_AND_POSITION
ibvs_lambda: 0.4
ibvs_mu: 0.1
ibvs_qdot_max: 1.0
ibvs_vmax6 {
vx: 1.0
vy: 1.0
vz: 1.0
wx: 0.6
wy: 0.6
wz: 0.6
}
ibvs_amax6 {
vx: 2.4
vy: 2.4
vz: 4.5
wx: 2.5
wy: 2.5
wz: 2.5
}
ibvs_twist_filter_alpha: 1.0
enable_joint_limit_avoidance: true
joint_limit_avoidance_gain: 0.2
joint_limit_avoidance_margin_ratio: 0.15
joint_limit_avoidance_max_push: 0.25
r_camera_to_visp {
m00: 1.0
m11: 1.0
m22: 1.0
}
r_camera_to_urdf {
m00: 1.0
m11: 1.0
m22: 1.0
}
control_joint_names: "R_SHOULDER_P"
control_joint_names: "R_SHOULDER_R"
control_joint_names: "R_SHOULDER_Y"
control_joint_names: "R_ELBOW_R"
control_joint_names: "R_WRIST_P"
control_joint_names: "R_WRIST_Y"
control_joint_names: "R_WRIST_R"
align_error_threshold6 {
x: 0.005
y: 0.005
z: 0.01
rx: 0.1026646259971647
ry: 0.1026646259971647
rz: 0.1026646259971647
}
align_stable_frames: 2
align_timeout_s: 20.0
pause_after_align_reached: false
touch_twist_base {
vx: 0.0
vy: -0.04
vz: 0.0
wx: 0.0
wy: 0.0
wz: 0.0
}
touch_use_speedl: true
touch_speedl_acceleration: 6.0
touch_forward_l: 0.064
touch_movel_speed: 0.1
touch_movel_acceleration: 5.0
touch_movel_jerk: 5.0
touch_movel_qd_max: 2.5
touch_movel_qd_max: 2.5
touch_movel_qd_max: 2.5
touch_movel_qd_max: 2.5
touch_movel_qd_max: 2.5
touch_movel_qd_max: 2.5
touch_movel_qd_max: 2.5
touch_speedl_forward_l: 0.035
dwell_time_s: -1
retract_twist_base {
vx: 0.0
vy: 0.08
vz: 0.0
wx: 0.0
wy: 0.0
wz: 0.0
}
retract_acceleration: 8.0
retract_duration_s: 0.45
tactile_finger: TOUCH_SCREEN_FINGER_TYPE_INDEX
tactile_region: TOUCH_SCREEN_TACTILE_REGION_TIP
tactile_criterion: TOUCH_SCREEN_TACTILE_CRITERION_FZ
tactile_pressure_sum_threshold: 1.0
tactile_nonzero_count_threshold: 1

View File

@ -1,10 +1,12 @@
#pragma once #pragma once
#include <glog/logging.h> #include "common/base/logging/logger.h"
#include "google/protobuf/io/zero_copy_stream_impl.h" #include "google/protobuf/io/zero_copy_stream_impl.h"
#include "google/protobuf/text_format.h" #include "google/protobuf/text_format.h"
#include <cerrno>
#include <cstring>
#include <fcntl.h> #include <fcntl.h>
#include <fstream> #include <fstream>
#include <string> #include <string>
@ -21,17 +23,17 @@ public:
std::ofstream output(fileName, std::ios::out | std::ios::trunc | std::ios::binary); std::ofstream output(fileName, std::ios::out | std::ios::trunc | std::ios::binary);
if (!output.good()) if (!output.good())
{ {
LOG(WARNING) << "Failed to open file for binary write: " << fileName; CMVR_LOG(WARNING) << "Failed to open file for binary write: " << fileName;
return false; return false;
} }
if (!message.SerializePartialToOstream(&output)) if (!message.SerializePartialToOstream(&output))
{ {
LOG(WARNING) << "Failed to serialize proto to binary file: " << fileName; CMVR_LOG(WARNING) << "Failed to serialize proto to binary file: " << fileName;
return false; return false;
} }
LOG(INFO) << "Successfully wrote binary proto file: " << fileName; CMVR_LOG(INFO) << "Successfully wrote binary proto file: " << fileName;
return true; return true;
} }
@ -40,20 +42,20 @@ public:
{ {
if (message == nullptr) if (message == nullptr)
{ {
LOG(ERROR) << "Null message pointer when reading binary proto file: " << fileName; CMVR_LOG(ERROR) << "Null message pointer when reading binary proto file: " << fileName;
return false; return false;
} }
std::ifstream input(fileName, std::ios::in | std::ios::binary); std::ifstream input(fileName, std::ios::in | std::ios::binary);
if (!input.good()) if (!input.good())
{ {
LOG(WARNING) << "Failed to open file for binary read: " << fileName; CMVR_LOG(WARNING) << "Failed to open file for binary read: " << fileName;
return false; return false;
} }
if (!message->ParseFromIstream(&input)) if (!message->ParseFromIstream(&input))
{ {
LOG(WARNING) << "Failed to parse binary proto file: " << fileName; CMVR_LOG(WARNING) << "Failed to parse binary proto file: " << fileName;
return false; return false;
} }
@ -68,15 +70,16 @@ public:
const int fd = ::open(fileName.c_str(), O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR); const int fd = ::open(fileName.c_str(), O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
if (fd < 0) if (fd < 0)
{ {
PLOG(WARNING) << "Failed to open file for ASCII write: " << fileName; CMVR_LOG(WARNING) << "Failed to open file for ASCII write: " << fileName
<< ": " << std::strerror(errno);
return false; return false;
} }
const bool ok = setProtoToAsciiFile(message, fd); const bool ok = setProtoToAsciiFile(message, fd);
if (ok) if (ok)
LOG(INFO) << "Successfully wrote ASCII proto file: " << fileName; CMVR_LOG(INFO) << "Successfully wrote ASCII proto file: " << fileName;
else else
LOG(WARNING) << "Failed to write ASCII proto file: " << fileName; CMVR_LOG(WARNING) << "Failed to write ASCII proto file: " << fileName;
return ok; return ok;
} }
@ -89,7 +92,7 @@ public:
if (fileDescriptor < 0) if (fileDescriptor < 0)
{ {
LOG(WARNING) << "Invalid file descriptor for ASCII write."; CMVR_LOG(WARNING) << "Invalid file descriptor for ASCII write.";
return false; return false;
} }
@ -110,17 +113,20 @@ public:
if (message == nullptr) if (message == nullptr)
{ {
LOG(ERROR) << "Null message pointer when reading ASCII proto file: " << fileName; CMVR_LOG(ERROR) << "Null message pointer when reading ASCII proto file: " << fileName;
return false; return false;
} }
const int fd = ::open(fileName.c_str(), O_RDONLY); const int fd = ::open(fileName.c_str(), O_RDONLY);
if (fd < 0) if (fd < 0)
{ {
if (isOptional) if (isOptional) {
PLOG(INFO) << "Optional ASCII proto file not found/openable: " << fileName; CMVR_LOG(INFO) << "Optional ASCII proto file not found/openable: " << fileName
else << ": " << std::strerror(errno);
PLOG(ERROR) << "Failed to open ASCII proto file: " << fileName; } else {
CMVR_LOG(ERROR) << "Failed to open ASCII proto file: " << fileName
<< ": " << std::strerror(errno);
}
return false; return false;
} }
@ -131,9 +137,9 @@ public:
if (!ok) if (!ok)
{ {
if (isOptional) if (isOptional)
LOG(INFO) << "Failed to parse optional ASCII proto file: " << fileName; CMVR_LOG(INFO) << "Failed to parse optional ASCII proto file: " << fileName;
else else
LOG(ERROR) << "Failed to parse ASCII proto file: " << fileName; CMVR_LOG(ERROR) << "Failed to parse ASCII proto file: " << fileName;
} }
return ok; return ok;
} }

View File

@ -0,0 +1,58 @@
#ifndef CMVR_ES_CARTESIAN_MOTION_MATH_H
#define CMVR_ES_CARTESIAN_MOTION_MATH_H
#include <Eigen/Core>
#include <Eigen/Geometry>
#include <algorithm>
#include <cmath>
#include <vector>
namespace cmvr::device::cartesian_motion {
inline double clamp(const double value, const double lo, const double hi)
{
return std::max(lo, std::min(hi, value));
}
inline Eigen::VectorXd toEigenVector(const std::vector<double>& values)
{
if (values.empty()) {
return {};
}
return Eigen::Map<const Eigen::VectorXd>(values.data(),
static_cast<Eigen::Index>(values.size()));
}
inline std::vector<double> toStdVector(const Eigen::VectorXd& values)
{
return {values.data(), values.data() + values.size()};
}
inline double directionDeviationDeg(const Eigen::Vector3d& desired,
const Eigen::Vector3d& actual)
{
const double desired_norm = desired.norm();
const double actual_norm = actual.norm();
if (desired_norm <= 1e-9 || actual_norm <= 1e-9) {
return 0.0;
}
const double direction_cos =
clamp(desired.dot(actual) / (desired_norm * actual_norm), -1.0, 1.0);
constexpr double rad_to_deg = 180.0 / 3.14159265358979323846;
return std::acos(direction_cos) * rad_to_deg;
}
inline Eigen::Vector3d rotationVector(const Eigen::Matrix3d& rotation)
{
Eigen::AngleAxisd angle_axis(rotation);
const double angle = angle_axis.angle();
if (std::abs(angle) <= 1e-9) {
return Eigen::Vector3d::Zero();
}
return angle_axis.axis() * angle;
}
} // namespace cmvr::device::cartesian_motion
#endif // CMVR_ES_CARTESIAN_MOTION_MATH_H

View File

@ -0,0 +1,101 @@
#ifndef CMVR_ES_JOINT_LIMITS_H
#define CMVR_ES_JOINT_LIMITS_H
#include <Eigen/Core>
#include <algorithm>
#include <cmath>
#include <limits>
namespace cmvr::kinematics {
inline Eigen::VectorXd clampToJointPositionLimits(
const Eigen::VectorXd& q,
const Eigen::VectorXd& lower,
const Eigen::VectorXd& upper)
{
if (lower.size() != q.size() || upper.size() != q.size()) {
return q;
}
return q.cwiseMax(lower).cwiseMin(upper);
}
inline double velocityLimitScale(const Eigen::VectorXd& qdot,
const Eigen::VectorXd& velocity_limits,
const double abs_max = std::numeric_limits<double>::infinity())
{
double scale = 1.0;
for (Eigen::Index i = 0; i < qdot.size(); ++i) {
double limit = std::numeric_limits<double>::infinity();
if (std::isfinite(abs_max) && abs_max > 0.0) {
limit = std::min(limit, abs_max);
}
if (velocity_limits.size() == qdot.size()) {
const double joint_limit = std::abs(velocity_limits[i]);
if (std::isfinite(joint_limit) && joint_limit > 0.0) {
limit = std::min(limit, joint_limit);
}
}
const double value = std::abs(qdot[i]);
if (std::isfinite(limit) && limit > 0.0 && value > limit) {
scale = std::min(scale, limit / value);
}
}
return scale;
}
inline Eigen::VectorXd scaleToVelocityLimits(
const Eigen::VectorXd& qdot,
const Eigen::VectorXd& velocity_limits,
const double abs_max = std::numeric_limits<double>::infinity())
{
return velocityLimitScale(qdot, velocity_limits, abs_max) * qdot;
}
inline Eigen::VectorXd computeJointLimitAvoidanceVelocity(
const Eigen::VectorXd& q,
const Eigen::VectorXd& lower,
const Eigen::VectorXd& upper,
const bool enable,
const double gain,
const double margin_ratio,
const double max_push)
{
const Eigen::Index dof = q.size();
if (!enable || gain <= 0.0 || dof <= 0 ||
lower.size() != dof || upper.size() != dof) {
return Eigen::VectorXd::Zero(dof);
}
Eigen::VectorXd qdot_avoid = Eigen::VectorXd::Zero(dof);
for (Eigen::Index i = 0; i < dof; ++i) {
const double lo = lower[i];
const double hi = upper[i];
if (!std::isfinite(lo) || !std::isfinite(hi) || hi <= lo) {
continue;
}
const double span = hi - lo;
const double margin = std::max(1e-4, margin_ratio * span);
double push = 0.0;
if (q[i] < lo + margin) {
const double s = (lo + margin - q[i]) / margin;
push += gain * s * s;
} else if (q[i] > hi - margin) {
const double s = (q[i] - (hi - margin)) / margin;
push -= gain * s * s;
}
if (max_push > 0.0) {
push = std::max(-max_push, std::min(max_push, push));
}
qdot_avoid[i] = push;
}
return qdot_avoid;
}
} // namespace cmvr::kinematics
#endif // CMVR_ES_JOINT_LIMITS_H

View File

@ -0,0 +1,115 @@
#ifndef CMVR_ES_COMMON_MATH_PROTO_GEOMETRY_H
#define CMVR_ES_COMMON_MATH_PROTO_GEOMETRY_H
#include <Eigen/Dense>
#include "cmvr/common/geometry.pb.h"
namespace cmvr::common::math {
inline Eigen::Vector3d toEigenVec3(const cmvr::common::Vec3& src,
Eigen::Vector3d defaults)
{
if (src.has_x()) {
defaults.x() = src.x();
}
if (src.has_y()) {
defaults.y() = src.y();
}
if (src.has_z()) {
defaults.z() = src.z();
}
return defaults;
}
inline Eigen::Vector3d toEigenVec3(const cmvr::common::Vec3& src)
{
return toEigenVec3(src, Eigen::Vector3d::Zero());
}
inline Eigen::Matrix<double, 6, 1> toEigenVec6(
const cmvr::common::Vec6& src,
Eigen::Matrix<double, 6, 1> defaults)
{
if (src.has_x()) {
defaults[0] = src.x();
}
if (src.has_y()) {
defaults[1] = src.y();
}
if (src.has_z()) {
defaults[2] = src.z();
}
if (src.has_rx()) {
defaults[3] = src.rx();
}
if (src.has_ry()) {
defaults[4] = src.ry();
}
if (src.has_rz()) {
defaults[5] = src.rz();
}
return defaults;
}
inline Eigen::Matrix<double, 6, 1> toEigenVec6(const cmvr::common::Vec6& src)
{
return toEigenVec6(src, Eigen::Matrix<double, 6, 1>::Zero());
}
inline Eigen::Matrix3d toEigenMat3(const cmvr::common::Mat3& src,
Eigen::Matrix3d defaults)
{
if (src.has_m00()) {
defaults(0, 0) = src.m00();
}
if (src.has_m01()) {
defaults(0, 1) = src.m01();
}
if (src.has_m02()) {
defaults(0, 2) = src.m02();
}
if (src.has_m10()) {
defaults(1, 0) = src.m10();
}
if (src.has_m11()) {
defaults(1, 1) = src.m11();
}
if (src.has_m12()) {
defaults(1, 2) = src.m12();
}
if (src.has_m20()) {
defaults(2, 0) = src.m20();
}
if (src.has_m21()) {
defaults(2, 1) = src.m21();
}
if (src.has_m22()) {
defaults(2, 2) = src.m22();
}
return defaults;
}
inline Eigen::Matrix3d toEigenMat3(const cmvr::common::Mat3& src)
{
return toEigenMat3(src, Eigen::Matrix3d::Identity());
}
} // namespace cmvr::common::math
inline bool hasVec3(const cmvr::common::Vec3& value) {
return value.has_x() && value.has_y() && value.has_z();
}
inline bool hasVec6(const cmvr::common::Vec6& value) {
return value.has_x() && value.has_y() && value.has_z() &&
value.has_rx() && value.has_ry() && value.has_rz();
}
inline bool hasMat3(const cmvr::common::Mat3& value) {
return value.has_m00() && value.has_m01() && value.has_m02() &&
value.has_m10() && value.has_m11() && value.has_m12() &&
value.has_m20() && value.has_m21() && value.has_m22();
}
#endif // CMVR_ES_COMMON_MATH_PROTO_GEOMETRY_H

View File

@ -11,7 +11,7 @@
#include <memory> #include <memory>
#include <optional> #include <optional>
#include <unordered_map> #include <unordered_map>
#include <iostream> #include "common/base/logging/logger.h"
#include <Eigen/Core> #include <Eigen/Core>
#include <OsqpEigen/OsqpEigen.h> #include <OsqpEigen/OsqpEigen.h>
@ -171,13 +171,13 @@ inline void QPSolverImpl::InitFunctionImpl() {
inline void QPSolverImpl::AddCostFunctionImpl(const Eigen::MatrixXd &A, const Eigen::VectorXd &b) { inline void QPSolverImpl::AddCostFunctionImpl(const Eigen::MatrixXd &A, const Eigen::VectorXd &b) {
if (A.rows() != b.rows()) { if (A.rows() != b.rows()) {
std::cerr << "OSQP Solver Add cost function failed: Size issue Ax = b (A.rows(): " CMVR_LOG(ERROR) << "OSQP Solver Add cost function failed: Size issue Ax = b (A.rows(): "
<< A.rows() << ", b.rows(): " << b.rows() << ")" << std::endl; << A.rows() << ", b.rows(): " << b.rows() << ")";
return; return;
} }
if (b.cols() != 1) { if (b.cols() != 1) {
std::cerr << "OSQP Solver Add cost function failed: Size issue: b (Nx1)" << std::endl; CMVR_LOG(ERROR) << "OSQP Solver Add cost function failed: Size issue: b (Nx1)";
return; return;
} }

View File

@ -6,7 +6,8 @@
#include <cmath> #include <cmath>
#include <vector> #include <vector>
#include <algorithm> #include <algorithm>
#include <iostream> #include <sstream>
#include "common/base/logging/logger.h"
#include <Eigen/Core> #include <Eigen/Core>
class SupportFunctions { class SupportFunctions {
@ -124,10 +125,13 @@ public:
} }
static void print_intervals(const std::vector<std::pair<double, double> > &intervals) { static void print_intervals(const std::vector<std::pair<double, double> > &intervals) {
std::ostringstream output;
for (const auto &interval: intervals) { for (const auto &interval: intervals) {
std::cout << "[" << interval.first << ", " << interval.second << "] "; output << "[" << interval.first << ", " << interval.second << "] ";
}
if (!output.str().empty()) {
CMVR_LOG(INFO) << output.str();
} }
std::cout << std::endl;
} }

View File

@ -0,0 +1,94 @@
#ifndef CMVR_ES_COMMON_MATH_TRANSFORM_MATH_H
#define CMVR_ES_COMMON_MATH_TRANSFORM_MATH_H
#include <cmath>
#include <Eigen/Dense>
#include "algorithms/motion_planner/base_motion/cartesian_velocity/twist_limiter/include/cartesian_twist_limiter.h"
#include "common/types/arm/arm_types.h"
namespace cmvr::common::math {
inline Eigen::Matrix3d eulerZYXToRotationMatrix(const double rx,
const double ry,
const double rz)
{
Eigen::Matrix3d r_x;
r_x << 1.0, 0.0, 0.0,
0.0, std::cos(rx), -std::sin(rx),
0.0, std::sin(rx), std::cos(rx);
Eigen::Matrix3d r_y;
r_y << std::cos(ry), 0.0, std::sin(ry),
0.0, 1.0, 0.0,
-std::sin(ry), 0.0, std::cos(ry);
Eigen::Matrix3d r_z;
r_z << std::cos(rz), -std::sin(rz), 0.0,
std::sin(rz), std::cos(rz), 0.0,
0.0, 0.0, 1.0;
return r_x * r_y * r_z;
}
inline Eigen::Vector3d rotationMatrixToEulerZYX(const Eigen::Matrix3d& rotation)
{
const double ry = std::asin(rotation(0, 2));
const double cy = std::cos(ry);
double rx = 0.0;
double rz = 0.0;
if (std::abs(cy) > 1e-6) {
rx = std::atan2(-rotation(1, 2), rotation(2, 2));
rz = std::atan2(-rotation(0, 1), rotation(0, 0));
} else {
rz = ry > 0.0 ? std::atan2(rotation(1, 0), rotation(1, 1))
: std::atan2(-rotation(1, 0), rotation(1, 1));
}
return {rx, ry, rz};
}
inline Eigen::Matrix4d poseToMatrix(const device::CartesianPose& pose)
{
Eigen::Matrix4d transform = Eigen::Matrix4d::Identity();
transform.block<3, 3>(0, 0) = eulerZYXToRotationMatrix(pose.rx, pose.ry, pose.rz);
transform(0, 3) = pose.x;
transform(1, 3) = pose.y;
transform(2, 3) = pose.z;
return transform;
}
inline device::CartesianPose matrixToPose(const Eigen::Matrix4d& transform)
{
device::CartesianPose pose;
pose.x = transform(0, 3);
pose.y = transform(1, 3);
pose.z = transform(2, 3);
const Eigen::Vector3d euler = rotationMatrixToEulerZYX(transform.block<3, 3>(0, 0));
pose.rx = euler(0);
pose.ry = euler(1);
pose.rz = euler(2);
return pose;
}
inline Eigen::Matrix<double, 6, 1> velocityToVector(const device::CartesianVelocity& velocity)
{
Eigen::Matrix<double, 6, 1> value;
value << velocity.vx, velocity.vy, velocity.vz, velocity.wx, velocity.wy, velocity.wz;
return value;
}
inline device::CartesianVelocity vectorToVelocity(const Eigen::Matrix<double, 6, 1>& velocity)
{
return {velocity[0], velocity[1], velocity[2], velocity[3], velocity[4], velocity[5]};
}
inline cmvr::CartesianFrame toPlannerFrame(const device::FrameType frame)
{
return frame == device::FrameType::Tool ? cmvr::CartesianFrame::Tool
: cmvr::CartesianFrame::Base;
}
} // namespace cmvr::common::math
#endif // CMVR_ES_COMMON_MATH_TRANSFORM_MATH_H

View File

@ -1,6 +1,6 @@
// CameraCapture.cpp // CameraCapture.cpp
#include "../include/CameraCapture.h" #include "common/media/ffmpeg/camera_capture.h"
#include <iostream> #include "common/base/logging/logger.h"
#include <thread> #include <thread>
#include <chrono> #include <chrono>
@ -28,21 +28,21 @@ int CameraCapture::initialize(const Config& config) {
// 初始化设备 // 初始化设备
int ret = init_device(); int ret = init_device();
if (ret < 0) { if (ret < 0) {
std::cerr << "初始化设备失败" << std::endl; CMVR_LOG(ERROR) << "初始化设备失败";
return ret; return ret;
} }
// 初始化解码器 // 初始化解码器
ret = init_decoder(); ret = init_decoder();
if (ret < 0) { if (ret < 0) {
std::cerr << "初始化解码器失败" << std::endl; CMVR_LOG(ERROR) << "初始化解码器失败";
return ret; return ret;
} }
// 初始化SWS上下文 // 初始化SWS上下文
ret = init_sws_context(); ret = init_sws_context();
if (ret < 0) { if (ret < 0) {
std::cerr << "初始化SWS上下文失败" << std::endl; CMVR_LOG(ERROR) << "初始化SWS上下文失败";
return ret; return ret;
} }
@ -52,12 +52,12 @@ int CameraCapture::initialize(const Config& config) {
rgb_frame_->format = AV_PIX_FMT_BGR24; // OpenCV使用BGR格式 rgb_frame_->format = AV_PIX_FMT_BGR24; // OpenCV使用BGR格式
ret = av_frame_get_buffer(rgb_frame_, 0); ret = av_frame_get_buffer(rgb_frame_, 0);
if (ret < 0) { if (ret < 0) {
std::cerr << "分配RGB帧缓冲区失败" << std::endl; CMVR_LOG(ERROR) << "分配RGB帧缓冲区失败";
return ret; return ret;
} }
std::cout << "摄像头初始化成功: " << config_.width << "x" << config_.height CMVR_LOG(INFO) << "摄像头初始化成功: " << config_.width << "x" << config_.height
<< "@" << config_.fps << "fps" << std::endl; << "@" << config_.fps << "fps";
return 0; return 0;
} }
@ -74,7 +74,7 @@ int CameraCapture::init_device() {
#endif #endif
if (!input_fmt) { if (!input_fmt) {
std::cerr << "找不到输入格式" << std::endl; CMVR_LOG(ERROR) << "找不到输入格式";
return -1; return -1;
} }
@ -90,21 +90,21 @@ int CameraCapture::init_device() {
if (ret < 0) { if (ret < 0) {
char err_buf[1024]; char err_buf[1024];
av_strerror(ret, err_buf, sizeof(err_buf)); av_strerror(ret, err_buf, sizeof(err_buf));
std::cerr << "打开摄像头失败: " << err_buf << std::endl; CMVR_LOG(ERROR) << "打开摄像头失败: " << err_buf;
return ret; return ret;
} }
// 查找流信息 // 查找流信息
ret = avformat_find_stream_info(fmt_ctx_, nullptr); ret = avformat_find_stream_info(fmt_ctx_, nullptr);
if (ret < 0) { if (ret < 0) {
std::cerr << "查找流信息失败" << std::endl; CMVR_LOG(ERROR) << "查找流信息失败";
return ret; return ret;
} }
// 查找视频流 // 查找视频流
video_stream_index_ = av_find_best_stream(fmt_ctx_, AVMEDIA_TYPE_VIDEO, -1, -1, nullptr, 0); video_stream_index_ = av_find_best_stream(fmt_ctx_, AVMEDIA_TYPE_VIDEO, -1, -1, nullptr, 0);
if (video_stream_index_ < 0) { if (video_stream_index_ < 0) {
std::cerr << "找不到视频流" << std::endl; CMVR_LOG(ERROR) << "找不到视频流";
return video_stream_index_; return video_stream_index_;
} }
@ -116,27 +116,27 @@ int CameraCapture::init_decoder() {
const AVCodec* decoder = avcodec_find_decoder(stream->codecpar->codec_id); const AVCodec* decoder = avcodec_find_decoder(stream->codecpar->codec_id);
if (!decoder) { if (!decoder) {
std::cerr << "找不到解码器" << std::endl; CMVR_LOG(ERROR) << "找不到解码器";
return -1; return -1;
} }
decoder_ctx_ = avcodec_alloc_context3(decoder); decoder_ctx_ = avcodec_alloc_context3(decoder);
if (!decoder_ctx_) { if (!decoder_ctx_) {
std::cerr << "分配解码器上下文失败" << std::endl; CMVR_LOG(ERROR) << "分配解码器上下文失败";
return -1; return -1;
} }
// 复制参数到解码器上下文 // 复制参数到解码器上下文
int ret = avcodec_parameters_to_context(decoder_ctx_, stream->codecpar); int ret = avcodec_parameters_to_context(decoder_ctx_, stream->codecpar);
if (ret < 0) { if (ret < 0) {
std::cerr << "复制解码器参数失败" << std::endl; CMVR_LOG(ERROR) << "复制解码器参数失败";
return ret; return ret;
} }
// 打开解码器 // 打开解码器
ret = avcodec_open2(decoder_ctx_, decoder, nullptr); ret = avcodec_open2(decoder_ctx_, decoder, nullptr);
if (ret < 0) { if (ret < 0) {
std::cerr << "打开解码器失败" << std::endl; CMVR_LOG(ERROR) << "打开解码器失败";
return ret; return ret;
} }
@ -154,7 +154,7 @@ int CameraCapture::init_sws_context() {
); );
if (!sws_ctx_) { if (!sws_ctx_) {
std::cerr << "创建SWS上下文失败" << std::endl; CMVR_LOG(ERROR) << "创建SWS上下文失败";
return -1; return -1;
} }
@ -185,14 +185,14 @@ cv::Mat CameraCapture::avframe_to_cvmat(AVFrame* frame) {
int CameraCapture::start_capture(FrameCallback callback) { int CameraCapture::start_capture(FrameCallback callback) {
if (!callback || !fmt_ctx_ || !decoder_ctx_) { if (!callback || !fmt_ctx_ || !decoder_ctx_) {
std::cerr << "参数无效或未初始化" << std::endl; CMVR_LOG(ERROR) << "参数无效或未初始化";
return -1; return -1;
} }
is_capturing_ = true; is_capturing_ = true;
frame_count_ = 0; frame_count_ = 0;
std::cout << "开始采集..." << std::endl; CMVR_LOG(INFO) << "开始采集...";
while (is_capturing_) { while (is_capturing_) {
// 读取数据包 // 读取数据包
@ -202,7 +202,7 @@ int CameraCapture::start_capture(FrameCallback callback) {
std::this_thread::sleep_for(std::chrono::milliseconds(1)); std::this_thread::sleep_for(std::chrono::milliseconds(1));
continue; continue;
} }
std::cerr << "读取帧失败: " << ret << std::endl; CMVR_LOG(ERROR) << "读取帧失败: " << ret;
break; break;
} }
@ -211,7 +211,7 @@ int CameraCapture::start_capture(FrameCallback callback) {
// 发送数据包到解码器 // 发送数据包到解码器
ret = avcodec_send_packet(decoder_ctx_, packet_); ret = avcodec_send_packet(decoder_ctx_, packet_);
if (ret < 0 && ret != AVERROR(EAGAIN)) { if (ret < 0 && ret != AVERROR(EAGAIN)) {
std::cerr << "发送数据包到解码器失败: " << ret << std::endl; CMVR_LOG(ERROR) << "发送数据包到解码器失败: " << ret;
av_packet_unref(packet_); av_packet_unref(packet_);
continue; continue;
} }
@ -222,7 +222,7 @@ int CameraCapture::start_capture(FrameCallback callback) {
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
break; break;
} else if (ret < 0) { } else if (ret < 0) {
std::cerr << "接收解码帧失败: " << ret << std::endl; CMVR_LOG(ERROR) << "接收解码帧失败: " << ret;
break; break;
} }

View File

@ -1,6 +1,6 @@
// RealSenseCapture.cpp // RealSenseCapture.cpp
#include "../include/RealSenseCapture.h" #include "common/media/ffmpeg/realsense_capture.h"
#include <iostream> #include "common/base/logging/logger.h"
#include <chrono> #include <chrono>
namespace ffmpeg { namespace ffmpeg {
@ -26,7 +26,7 @@ int RealSenseCapture::initialize(const Config& config) {
// 初始化设备 // 初始化设备
int ret = init_device(); int ret = init_device();
if (ret < 0) { if (ret < 0) {
std::cerr << "初始化RealSense设备失败" << std::endl; CMVR_LOG(ERROR) << "初始化RealSense设备失败";
return ret; return ret;
} }
@ -38,20 +38,20 @@ int RealSenseCapture::initialize(const Config& config) {
rgb_frame_ = create_avframe(config_.width, config_.height, AV_PIX_FMT_BGR24); rgb_frame_ = create_avframe(config_.width, config_.height, AV_PIX_FMT_BGR24);
if (!frame_ || !rgb_frame_) { if (!frame_ || !rgb_frame_) {
std::cerr << "创建AVFrame失败" << std::endl; CMVR_LOG(ERROR) << "创建AVFrame失败";
return -1; return -1;
} }
std::cout << "RealSense摄像头初始化成功: " CMVR_LOG(INFO) << "RealSense摄像头初始化成功: "
<< config_.width << "x" << config_.height << "@" << config_.fps << "fps" << std::endl; << config_.width << "x" << config_.height << "@" << config_.fps << "fps";
return 0; return 0;
} catch (const rs2::error& e) { } catch (const rs2::error& e) {
std::cerr << "RealSense错误: " << e.what() << std::endl; CMVR_LOG(ERROR) << "RealSense错误: " << e.what();
return -1; return -1;
} catch (const std::exception& e) { } catch (const std::exception& e) {
std::cerr << "常规错误: " << e.what() << std::endl; CMVR_LOG(ERROR) << "常规错误: " << e.what();
return -1; return -1;
} }
} }
@ -63,11 +63,11 @@ int RealSenseCapture::init_device() {
size_t device_count = devices.size(); size_t device_count = devices.size();
if (device_count == 0) { if (device_count == 0) {
std::cerr << "未检测到RealSense设备" << std::endl; CMVR_LOG(ERROR) << "未检测到RealSense设备";
return -1; return -1;
} }
std::cout << "检测到 " << device_count << " 个RealSense设备" << std::endl; CMVR_LOG(INFO) << "检测到 " << device_count << " 个RealSense设备";
// 如果指定了序列号,查找对应设备 // 如果指定了序列号,查找对应设备
if (!config_.serial_number.empty()) { if (!config_.serial_number.empty()) {
@ -77,7 +77,7 @@ int RealSenseCapture::init_device() {
std::string serial = dev.get_info(RS2_CAMERA_INFO_SERIAL_NUMBER); std::string serial = dev.get_info(RS2_CAMERA_INFO_SERIAL_NUMBER);
if (serial == config_.serial_number) { if (serial == config_.serial_number) {
std::cout << "找到指定序列号的设备: " << serial << std::endl; CMVR_LOG(INFO) << "找到指定序列号的设备: " << serial;
found = true; found = true;
rs_cfg_.enable_device(serial); rs_cfg_.enable_device(serial);
break; break;
@ -85,15 +85,15 @@ int RealSenseCapture::init_device() {
} }
if (!found) { if (!found) {
std::cerr << "未找到序列号为 " << config_.serial_number << " 的设备" << std::endl; CMVR_LOG(ERROR) << "未找到序列号为 " << config_.serial_number << " 的设备";
return -1; return -1;
} }
} else { } else {
// 使用第一个设备 // 使用第一个设备
rs2::device dev = devices[0]; rs2::device dev = devices[0];
std::string serial = dev.get_info(RS2_CAMERA_INFO_SERIAL_NUMBER); std::string serial = dev.get_info(RS2_CAMERA_INFO_SERIAL_NUMBER);
std::cout << "使用第一个设备: " << dev.get_info(RS2_CAMERA_INFO_NAME) CMVR_LOG(INFO) << "使用第一个设备: " << dev.get_info(RS2_CAMERA_INFO_NAME)
<< " (序列号: " << serial << ")" << std::endl; << " (序列号: " << serial << ")";
rs_cfg_.enable_device(serial); rs_cfg_.enable_device(serial);
} }
@ -107,7 +107,7 @@ int RealSenseCapture::init_device() {
return 0; return 0;
} catch (const rs2::error& e) { } catch (const rs2::error& e) {
std::cerr << "初始化设备失败: " << e.what() << std::endl; CMVR_LOG(ERROR) << "初始化设备失败: " << e.what();
return -1; return -1;
} }
} }
@ -170,12 +170,12 @@ int RealSenseCapture::start_capture(FrameCallback callback) {
std::lock_guard<std::mutex> lock(mutex_); std::lock_guard<std::mutex> lock(mutex_);
if (!callback) { if (!callback) {
std::cerr << "回调函数为空" << std::endl; CMVR_LOG(ERROR) << "回调函数为空";
return -1; return -1;
} }
if (is_capturing_) { if (is_capturing_) {
std::cerr << "已经在捕获中" << std::endl; CMVR_LOG(ERROR) << "已经在捕获中";
return -1; return -1;
} }
@ -190,7 +190,7 @@ int RealSenseCapture::start_capture(FrameCallback callback) {
// 等待第一帧,确保设备正常工作 // 等待第一帧,确保设备正常工作
rs2::frameset frames = pipe_.wait_for_frames(2000); // 2秒超时 rs2::frameset frames = pipe_.wait_for_frames(2000); // 2秒超时
if (!frames.get_color_frame()) { if (!frames.get_color_frame()) {
std::cerr << "无法获取第一帧" << std::endl; CMVR_LOG(ERROR) << "无法获取第一帧";
pipe_.stop(); pipe_.stop();
return -1; return -1;
} }
@ -202,11 +202,11 @@ int RealSenseCapture::start_capture(FrameCallback callback) {
capture_thread_ = std::make_unique<std::thread>(&RealSenseCapture::capture_thread_func, capture_thread_ = std::make_unique<std::thread>(&RealSenseCapture::capture_thread_func,
this, callback); this, callback);
std::cout << "RealSense开始采集" << std::endl; CMVR_LOG(INFO) << "RealSense开始采集";
return 0; return 0;
} catch (const rs2::error& e) { } catch (const rs2::error& e) {
std::cerr << "启动采集失败: " << e.what() << std::endl; CMVR_LOG(ERROR) << "启动采集失败: " << e.what();
return -1; return -1;
} }
} }
@ -238,7 +238,7 @@ void RealSenseCapture::capture_thread_func(FrameCallback callback) {
callback(av_frame, rgb_image, frame_count_++); callback(av_frame, rgb_image, frame_count_++);
} catch (const rs2::error& e) { } catch (const rs2::error& e) {
std::cerr << "采集错误: " << e.what() << std::endl; CMVR_LOG(ERROR) << "采集错误: " << e.what();
if (!is_capturing_) break; if (!is_capturing_) break;
} }
@ -270,10 +270,10 @@ void RealSenseCapture::stop_capture() {
try { try {
pipe_.stop(); pipe_.stop();
} catch (const std::exception& e) { } catch (const std::exception& e) {
std::cerr << "停止pipeline时出错: " << e.what() << std::endl; CMVR_LOG(ERROR) << "停止pipeline时出错: " << e.what();
} }
std::cout << "RealSense停止采集" << std::endl; CMVR_LOG(INFO) << "RealSense停止采集";
} }
std::string RealSenseCapture::get_device_info() const { std::string RealSenseCapture::get_device_info() const {

View File

@ -1,6 +1,6 @@
// VideoFrameEncoder.cpp // VideoFrameEncoder.cpp
#include "../include/VideoFrameEncoder.h" #include "common/media/ffmpeg/video_frame_encoder.h"
#include <iostream> #include "common/base/logging/logger.h"
#include <chrono> #include <chrono>
namespace ffmpeg { namespace ffmpeg {
@ -30,14 +30,14 @@ int VideoFrameEncoder::initialize(const Config& config) {
//const AVCodec* encoder = avcodec_find_encoder(AV_CODEC_ID_H264); //const AVCodec* encoder = avcodec_find_encoder(AV_CODEC_ID_H264);
const AVCodec* encoder = avcodec_find_encoder_by_name(config.codec.c_str()); const AVCodec* encoder = avcodec_find_encoder_by_name(config.codec.c_str());
if (!encoder) { if (!encoder) {
std::cerr << "找不到H.264编码器" << std::endl; CMVR_LOG(ERROR) << "找不到H.264编码器";
return -1; return -1;
} }
// 分配编码器上下文 // 分配编码器上下文
encoder_ctx_ = avcodec_alloc_context3(encoder); encoder_ctx_ = avcodec_alloc_context3(encoder);
if (!encoder_ctx_) { if (!encoder_ctx_) {
std::cerr << "分配编码器上下文失败" << std::endl; CMVR_LOG(ERROR) << "分配编码器上下文失败";
return -1; return -1;
} }
@ -70,7 +70,7 @@ int VideoFrameEncoder::initialize(const Config& config) {
if (ret < 0) { if (ret < 0) {
char err_buf[1024]; char err_buf[1024];
av_strerror(ret, err_buf, sizeof(err_buf)); av_strerror(ret, err_buf, sizeof(err_buf));
std::cerr << "打开编码器失败: " << err_buf << std::endl; CMVR_LOG(ERROR) << "打开编码器失败: " << err_buf;
return ret; return ret;
} }
@ -81,7 +81,7 @@ int VideoFrameEncoder::initialize(const Config& config) {
ret = av_frame_get_buffer(converted_frame_, 0); ret = av_frame_get_buffer(converted_frame_, 0);
if (ret < 0) { if (ret < 0) {
std::cerr << "分配帧缓冲区失败" << std::endl; CMVR_LOG(ERROR) << "分配帧缓冲区失败";
return ret; return ret;
} }
@ -90,7 +90,7 @@ int VideoFrameEncoder::initialize(const Config& config) {
int VideoFrameEncoder::init_sws_context(AVFrame* frame) { int VideoFrameEncoder::init_sws_context(AVFrame* frame) {
if (!frame) { if (!frame) {
std::cerr << "输入帧为空" << std::endl; CMVR_LOG(ERROR) << "输入帧为空";
return -1; return -1;
} }
@ -110,7 +110,7 @@ int VideoFrameEncoder::init_sws_context(AVFrame* frame) {
); );
if (!sws_ctx_) { if (!sws_ctx_) {
std::cerr << "创建SWS上下文失败" << std::endl; CMVR_LOG(ERROR) << "创建SWS上下文失败";
return -1; return -1;
} }
@ -119,7 +119,7 @@ int VideoFrameEncoder::init_sws_context(AVFrame* frame) {
int VideoFrameEncoder::encode_frame(AVFrame* frame) { int VideoFrameEncoder::encode_frame(AVFrame* frame) {
if (!encoder_ctx_ || !frame) { if (!encoder_ctx_ || !frame) {
std::cerr << "编码器未初始化或输入帧为空" << std::endl; CMVR_LOG(ERROR) << "编码器未初始化或输入帧为空";
return -1; return -1;
} }
@ -151,7 +151,7 @@ int VideoFrameEncoder::encode_frame(AVFrame* frame) {
if (ret < 0) { if (ret < 0) {
char err_buf[1024]; char err_buf[1024];
av_strerror(ret, err_buf, sizeof(err_buf)); av_strerror(ret, err_buf, sizeof(err_buf));
std::cerr << "发送帧到编码器失败: " << err_buf << std::endl; CMVR_LOG(ERROR) << "发送帧到编码器失败: " << err_buf;
return ret; return ret;
} }
@ -162,7 +162,7 @@ int VideoFrameEncoder::encode_frame(AVFrame* frame) {
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
break; break;
} else if (ret < 0) { } else if (ret < 0) {
std::cerr << "接收编码数据包失败" << std::endl; CMVR_LOG(ERROR) << "接收编码数据包失败";
av_packet_free(&packet); av_packet_free(&packet);
return ret; return ret;
} }
@ -191,7 +191,7 @@ int VideoFrameEncoder::flush() {
// 发送空帧刷新编码器 // 发送空帧刷新编码器
int ret = avcodec_send_frame(encoder_ctx_, nullptr); int ret = avcodec_send_frame(encoder_ctx_, nullptr);
if (ret < 0) { if (ret < 0) {
std::cerr << "发送刷新帧失败" << std::endl; CMVR_LOG(ERROR) << "发送刷新帧失败";
return ret; return ret;
} }
@ -202,7 +202,7 @@ int VideoFrameEncoder::flush() {
if (ret == AVERROR_EOF) { if (ret == AVERROR_EOF) {
break; break;
} else if (ret < 0) { } else if (ret < 0) {
std::cerr << "接收刷新数据包失败" << std::endl; CMVR_LOG(ERROR) << "接收刷新数据包失败";
av_packet_free(&packet); av_packet_free(&packet);
return ret; return ret;
} }

Some files were not shown because too many files have changed in this diff Show More