/*********************************************************************
* 机械臂 自动动作切换 + 笛卡尔空间控制 Demo
* 功能:
* - 自动序列模式(原有速度动作循环)
* - 笛卡尔目标模式(通过逆解 + 速度闭环控制末端位姿)
* - 实时打印当前关节角和末端笛卡尔位姿
*********************************************************************/
#define _USE_MATH_DEFINES // 确保 M_PI 可用
#include <rclcpp/rclcpp.hpp>
#include <control_msgs/msg/joint_jog.hpp>
#include <sensor_msgs/msg/joint_state.hpp>
#include <signal.h>
#include <vector>
#include <map>
#include <thread>
#include <chrono>
#include <cmath>
#include <mutex>
#include <algorithm> // for std::find
#include "ArmKinematics.hpp"
// 话题名称
const std::string JOINT_TOPIC = "/servo_demo_node/delta_joint_cmds";
const std::string JOINT_STATES_TOPIC = "/joint_states";
const size_t ROS_QUEUE_SIZE = 10;
using JointAction = std::map<std::string, double>;
void quit(int sig) {
(void)sig;
rclcpp::shutdown();
exit(0);
}
class ArmAutoDemo : public rclcpp::Node {
public:
ArmAutoDemo() : Node("arm_auto_demo"), kin_() {
// ---- 创建发布器 ----
joint_pub_ = this->create_publisher<control_msgs::msg::JointJog>(JOINT_TOPIC, ROS_QUEUE_SIZE);
// ---- 订阅关节状态 ----
joint_state_sub_ = this->create_subscription<sensor_msgs::msg::JointState>(
JOINT_STATES_TOPIC, 10,
std::bind(&ArmAutoDemo::jointStateCallback, this, std::placeholders::_1));
// ---- 预设关节空间动作序列(与之前相同) ----
actions_.clear();
actions_.push_back({{"JM0", 0.15/3.0}, {"JM1-2", 0.25/3.0}, {"JM5", 0.10/3.0}});
actions_.push_back({{"JM0", -0.30/3.0}, {"JM1-2", -0.05/3.0}, {"JM4-3", 0.10/3.0}, {"JM4", 0.05/3.0}, {"JM5", 0.05/3.0}});
actions_.push_back({{"JM0", 0.25/3.0}, {"JM1-2", -0.30/3.0}, {"JM4-3", -0.15/3.0}, {"JM4", 0.05/3.0}, {"JM5", -0.20/3.0}, {"YB", 0.05/3.0}});
actions_.push_back({{"JM0", -0.10/3.0}, {"JM1-2", 0.10/3.0}, {"JM4-3", 0.05/3.0}, {"JM4", -0.10/3.0}, {"JM5", 0.05/3.0}, {"YB", -0.05/3.0}});
actions_.push_back({}); // 停驻(索引4)
action_duration_ = 3.0;
current_action_idx_ = 0;
action_start_time_ = this->now();
last_print_time_ = 0.0;
// ---- 笛卡尔目标模式相关 ----
cartesian_mode_active_ = false;
target_joints_.assign(6, 0.0);
target_pose_.assign(6, 0.0); // 新增:存储目标笛卡尔位姿
current_joints_.assign(6, 0.0);
joint_states_received_ = false;
RCLCPP_INFO(this->get_logger(), "===== 机械臂自动动作 + 笛卡尔控制 Demo 启动 =====");
RCLCPP_INFO(this->get_logger(), "关节空间序列共 %zu 组动作,每组 %.1f 秒", actions_.size(), action_duration_);
RCLCPP_INFO(this->get_logger(), "笛卡尔控制模式默认关闭,可通过 setCartesianTarget() 激活");
// ---- 启动线程 ----
std::thread(&ArmAutoDemo::publishLoop, this).detach();
std::thread(&ArmAutoDemo::actionSwitchLoop, this).detach();
// ---- 可选:演示示例,10秒后自动切换到一个笛卡尔目标(可取消注释测试) ----
// std::thread(this() {
// std::this_thread::sleep_for(std::chrono::seconds(10));
// this->setCartesianTarget(0.3, 0.1, 0.4, 0.0, 0.0, 0.0);
// }).detach();
}
/**
* @brief 设置笛卡尔空间目标,内部自动调用 ik 并切换至笛卡尔控制模式
*/
void setCartesianTarget(double x, double y, double z,
double roll, double pitch, double yaw) {
std::lock_guard<std::mutex> lock(action_mutex_);
// 调用逆运动学
target_joints_ = kin_.ik(x, y, z, roll, pitch, yaw);
target_pose_ = {x, y, z, roll, pitch, yaw}; // 保存目标位姿以便打印
cartesian_mode_active_ = true;
RCLCPP_INFO(this->get_logger(),
"🔷 切换到笛卡尔控制模式,目标位姿: (%.3f, %.3f, %.3f) RPY(%.2f, %.2f, %.2f)",
x, y, z, roll, pitch, yaw);
RCLCPP_INFO(this->get_logger(),
" 对应关节目标: %.3f, %.3f, %.3f, %.3f, %.3f, %.3f",
target_joints_0, target_joints_1, target_joints_2,
target_joints_3, target_joints_4, target_joints_5);
// 重置时间戳,使打印从0开始
action_start_time_ = this->now();
last_print_time_ = 0.0;
}
/**
* @brief 退出笛卡尔模式,回到自动关节序列
*/
void disableCartesianMode() {
std::lock_guard<std::mutex> lock(action_mutex_);
cartesian_mode_active_ = false;
RCLCPP_INFO(this->get_logger(), "🔶 退出笛卡尔模式,恢复关节序列");
action_start_time_ = this->now();
last_print_time_ = 0.0;
}
private:
// ----- 成员变量 -----
rclcpp::Publisher<control_msgs::msg::JointJog>::SharedPtr joint_pub_;
rclcpp::Subscription<sensor_msgs::msg::JointState>::SharedPtr joint_state_sub_;
std::vector<JointAction> actions_;
int current_action_idx_;
double action_duration_;
std::mutex action_mutex_;
rclcpp::Time action_start_time_;
double last_print_time_ = 0.0;
// 运动学对象
ArmKinematics kin_;
// 关节状态与目标
std::vector<double> current_joints_; // 当前实际关节角
std::vector<double> target_joints_; // 目标关节角(笛卡尔模式)
std::vector<double> target_pose_; // 目标笛卡尔位姿 x,y,z,roll,pitch,yaw
bool joint_states_received_ = false;
bool cartesian_mode_active_ = false;
// ----- 关节状态回调(按名称匹配,增强鲁棒性) -----
void jointStateCallback(const sensor_msgs::msg::JointState::SharedPtr msg) {
std::lock_guard<std::mutex> lock(action_mutex_);
// 定义期望的关节顺序
std::vector<std::string> expected_names = {"JM0", "JM1-2", "JM4-3", "JM4", "JM5", "YB"};
for (size_t i = 0; i < expected_names.size(); ++i) {
auto it = std::find(msg->name.begin(), msg->name.end(), expected_namesi);
if (it != msg->name.end()) {
size_t idx = std::distance(msg->name.begin(), it);
current_joints_i = msg->positionidx;
} else {
RCLCPP_WARN_THROTTLE(this->get_logger(), *this->get_clock(), 5000,
"关节 %s 未在 joint_states 中找到", expected_namesi.c_str());
}
}
joint_states_received_ = true;
}
// ----- 发布循环(100 Hz) -----
void publishLoop() {
rclcpp::Rate rate(100);
while (rclcpp::ok()) {
std::lock_guard<std::mutex> lock(action_mutex_);
auto msg = control_msgs::msg::JointJog();
msg.header.stamp = this->now();
if (cartesian_mode_active_ && joint_states_received_) {
// ---------- 笛卡尔模式:速度 P 控制 ----------
const double Kp = 0.5; // 比例增益
const double VEL_LIMIT = 0.5; // 关节速度限幅 (rad/s)
msg.joint_names = {"JM0", "JM1-2", "JM4-3", "JM4", "JM5", "YB"};
bool reached = true;
for (size_t i = 0; i < 6; ++i) {
double error = target_joints_i - current_joints_i;
if (std::fabs(error) > 0.005) reached = false;
double vel = Kp * error;
vel = std::max(-VEL_LIMIT, std::min(VEL_LIMIT, vel));
msg.velocities.push_back(vel);
}
// 如果到达目标,自动退出笛卡尔模式并切换到停驻动作
if (reached) {
RCLCPP_INFO(this->get_logger(), "✅ 笛卡尔目标已到达,自动退出模式并进入停驻");
cartesian_mode_active_ = false;
// 将序列索引设置为停驻动作(最后一个)
current_action_idx_ = static_cast<int>(actions_.size()) - 1;
// 发送零速度停止
msg.velocities.assign(6, 0.0);
action_start_time_ = this->now();
last_print_time_ = 0.0;
}
// ----- 打印笛卡尔模式下的进度 -----
double now = this->now().seconds();
double elapsed = now - action_start_time_.seconds();
if (elapsed - last_print_time_ >= 0.2 || elapsed <= 0.05) {
last_print_time_ = elapsed;
auto current_pose = kin_.fk(current_joints_);
RCLCPP_INFO(this->get_logger(),
"📍 笛卡尔模式 | 当前末端: (%.3f, %.3f, %.3f) | 目标: (%.3f, %.3f, %.3f)",
current_pose0, current_pose1, current_pose2,
target_pose_0, target_pose_1, target_pose_2);
}
} else {
// ---------- 关节空间自动序列模式 ----------
for (const auto& joint : actions_current_action_idx_) {
msg.joint_names.push_back(joint.first);
msg.velocities.push_back(joint.second);
}
// ----- 打印序列模式信息(与原逻辑相同)-----
double now = this->now().seconds();
double elapsed = now - action_start_time_.seconds();
if (elapsed - last_print_time_ >= 0.2 || elapsed <= 0.05) {
last_print_time_ = elapsed;
RCLCPP_INFO(this->get_logger(),
"→ 动作 %d | 已运行: %.1f/%.1f s",
current_action_idx_ + 1, elapsed, action_duration_);
for (const auto& joint : actions_current_action_idx_) {
double angle_deg = joint.second * elapsed * 180 / M_PI;
RCLCPP_INFO(this->get_logger(),
" └─ %s : 速度 %.2f rad/s | 累计角度: %.2f°",
joint.first.c_str(), joint.second, angle_deg);
}
if (actions_current_action_idx_.empty())
RCLCPP_INFO(this->get_logger(), " └─ 所有关节停止");
}
}
joint_pub_->publish(msg);
rate.sleep();
}
}
// ----- 动作切换线程(仅序列模式有效) -----
void actionSwitchLoop() {
while (rclcpp::ok()) {
std::this_thread::sleep_for(std::chrono::milliseconds((int)(action_duration_ * 1000)));
std::lock_guard<std::mutex> lock(action_mutex_);
// 如果处于笛卡尔模式,不切换序列索引
if (!cartesian_mode_active_) {
current_action_idx_ = (current_action_idx_ + 1) % actions_.size();
action_start_time_ = this->now();
last_print_time_ = 0.0;
}
}
}
};
int main(int argc, char** argv) {
rclcpp::init(argc, argv);
signal(SIGINT, quit);
auto node = std::make_shared<ArmAutoDemo>();
rclcpp::spin(node);
rclcpp::shutdown();
return 0;
}