dual arm motion control optimization

This commit is contained in:
Your Name
2026-02-26 09:17:34 +08:00
parent a721198fe0
commit 64ee485100
11 changed files with 821 additions and 1238 deletions

View File

@@ -1,6 +1,6 @@
cmake_minimum_required(VERSION 3.16) # 提升最低版本以匹配ROS 2推荐版本
project(robot_control)
set(ROS2_HUMBLE_PATH /home/nvidia/disk/ros/humble)
set(ROS2_HUMBLE_PATH /opt/ros/humble)
# 手动指定 eigen_stl_containers 的cmake配置文件路径
set(eigen_stl_containers_DIR ${ROS2_HUMBLE_PATH}/share/eigen_stl_containers/cmake)
# 手动指定 eigen3 相关依赖路径 (moveit_core同时依赖这个)
@@ -57,7 +57,6 @@ set(SOURCES
src/services/motor_service.cpp
src/core/motion_parameters.cpp
src/actions/action_manager.cpp
src/actions/follow_jt_executor.cpp
src/actions/move_home_action.cpp
src/actions/move_leg_action.cpp
src/actions/move_waist_action.cpp

View File

@@ -1,257 +0,0 @@
# 重构指南
## 已完成的工作
1. ✅ 创建了新的目录结构include/actions, include/core, include/controllers, include/utils
2. ✅ 创建了 ActionManager 类(头文件和实现文件)
3. ✅ 创建了 ControllerFactory 类(头文件和实现文件)
## 待完成的工作
### 步骤 1: 移动文件到新目录结构
由于文件依赖关系复杂,建议按以下顺序移动:
#### 1.1 移动工具类文件utils
这些文件依赖最少:
```bash
# 移动到 utils 目录
mv include/trapezoidal_trajectory.hpp include/utils/
mv src/trapezoidal_trajectory.cpp src/utils/
mv include/robot_model.hpp include/utils/
mv src/robot_model.cpp src/utils/
mv include/robot_kinematics.hpp include/utils/
mv src/robot_kinematics.cpp src/utils/
mv include/urdf_parser.hpp include/utils/
mv src/urdf_parser.cpp src/utils/
mv include/extended_kalman_filter.hpp include/utils/
mv src/extended_kalman_filter.cpp src/utils/
mv include/deceleration_planner.hpp include/utils/
```
#### 1.2 移动核心文件core
```bash
# 移动核心枚举和参数
mv include/common_enum.hpp include/core/
mv include/motion_parameters.hpp include/core/
# 移动管理器
mv include/robot_control_manager.hpp include/core/
mv src/robot_control_manager.cpp src/core/
# 移动节点(稍后修改)
# mv include/robot_control_node.hpp include/core/
# mv src/robot_control_node.cpp src/core/
```
#### 1.3 移动控制器文件controllers
```bash
# 移动控制器基类
mv include/control_base.hpp include/controllers/
mv src/control_base.cpp src/controllers/
# 移动具体控制器
mv include/leg_control.hpp include/controllers/
mv src/leg_control.cpp src/controllers/
mv include/arm_control.hpp include/controllers/
mv src/arm_control.cpp src/controllers/
mv include/wheel_control.hpp include/controllers/
mv src/wheel_control.cpp src/controllers/
mv include/waist_control.hpp include/controllers/
mv src/waist_control.cpp src/controllers/
```
### 步骤 2: 更新包含路径
所有文件移动后,需要更新包含路径。使用以下脚本批量替换:
```bash
# 在项目根目录执行
find include src -type f \( -name "*.hpp" -o -name "*.cpp" \) -exec sed -i 's|"control_base\.hpp"|"controllers/control_base.hpp"|g' {} +
find include src -type f \( -name "*.hpp" -o -name "*.cpp" \) -exec sed -i 's|"leg_control\.hpp"|"controllers/leg_control.hpp"|g' {} +
find include src -type f \( -name "*.hpp" -o -name "*.cpp" \) -exec sed -i 's|"arm_control\.hpp"|"controllers/arm_control.hpp"|g' {} +
find include src -type f \( -name "*.hpp" -o -name "*.cpp" \) -exec sed -i 's|"wheel_control\.hpp"|"controllers/wheel_control.hpp"|g' {} +
find include src -type f \( -name "*.hpp" -o -name "*.cpp" \) -exec sed -i 's|"waist_control\.hpp"|"controllers/waist_control.hpp"|g' {} +
find include src -type f \( -name "*.hpp" -o -name "*.cpp" \) -exec sed -i 's|"motion_parameters\.hpp"|"core/motion_parameters.hpp"|g' {} +
find include src -type f \( -name "*.hpp" -o -name "*.cpp" \) -exec sed -i 's|"common_enum\.hpp"|"core/common_enum.hpp"|g' {} +
find include src -type f \( -name "*.hpp" -o -name "*.cpp" \) -exec sed -i 's|"robot_control_manager\.hpp"|"core/robot_control_manager.hpp"|g' {} +
find include src -type f \( -name "*.hpp" -o -name "*.cpp" \) -exec sed -i 's|"trapezoidal_trajectory\.hpp"|"utils/trapezoidal_trajectory.hpp"|g' {} +
find include src -type f \( -name "*.hpp" -o -name "*.cpp" \) -exec sed -i 's|"robot_model\.hpp"|"utils/robot_model.hpp"|g' {} +
```
### 步骤 3: 修改 RobotControlManager 支持动态控制器加载
`include/core/robot_control_manager.hpp` 中:
1. 添加控制器工厂的包含:
```cpp
#include "core/controller_factory.hpp"
```
2. 修改私有成员,使用智能指针和可选控制器:
```cpp
private:
// 控制器(使用智能指针,支持可选加载)
std::unique_ptr<LegControl> leg_controller_;
std::unique_ptr<WheelControl> wheel_controller_;
std::unique_ptr<WaistControl> waist_controller_;
std::unique_ptr<ArmControl> arm_controller_; // 可选
// 控制器启用标志
bool leg_controller_enabled_;
bool wheel_controller_enabled_;
bool waist_controller_enabled_;
bool arm_controller_enabled_;
```
3. 修改构造函数,添加控制器配置参数:
```cpp
RobotControlManager(const std::vector<std::string>& enabled_controllers = {"leg", "wheel", "waist"});
```
4.`init()` 函数中使用 ControllerFactory 创建控制器:
```cpp
void RobotControlManager::init(const std::vector<std::string>& enabled_controllers)
{
// ... 其他初始化代码 ...
// 动态加载控制器
if (ControllerFactory::is_controller_enabled(ControllerType::LEG, enabled_controllers))
{
leg_controller_ = std::unique_ptr<LegControl>(
static_cast<LegControl*>(ControllerFactory::create_controller(
ControllerType::LEG, motionParams_).release()));
leg_controller_enabled_ = true;
}
if (ControllerFactory::is_controller_enabled(ControllerType::WHEEL, enabled_controllers))
{
wheel_controller_ = std::unique_ptr<WheelControl>(
static_cast<WheelControl*>(ControllerFactory::create_controller(
ControllerType::WHEEL, motionParams_).release()));
wheel_controller_enabled_ = true;
}
// ... 类似地处理其他控制器 ...
}
```
### 步骤 4: 修改 RobotControlNode 使用 ActionManager
`include/core/robot_control_node.hpp` 中:
1. 添加 ActionManager 包含:
```cpp
#include "actions/action_manager.hpp"
```
2. 移除所有 action 相关的成员变量和方法声明
3. 添加 ActionManager 成员:
```cpp
private:
std::unique_ptr<ActionManager> action_manager_;
```
4. 在构造函数中初始化 ActionManager
```cpp
RobotControlNode::RobotControlNode() : Node("robot_control_node")
{
// ... 其他初始化代码 ...
// 初始化 ActionManager
auto is_jog_mode_func = [this]() -> bool { return isJogMode_; };
action_manager_ = std::make_unique<ActionManager>(
this,
robotControlManager_,
is_jog_mode_func,
motorCmdPub_,
motorParamClient_);
action_manager_->initialize();
}
```
5. 在 ControlLoop 中使用 ActionManager 的状态:
```cpp
void RobotControlNode::ControlLoop()
{
// ...
if (action_manager_->is_move_home_executing())
{
if(robotControlManager_.GoHome(dt_sec))
{
robotControlManager_.SetJogWheel(false);
action_manager_->set_move_home_executing(false);
// Note: WheelReset/ImuReset were removed during refactor; state is handled internally now.
}
}
// ... 类似地处理其他 action ...
}
```
### 步骤 5: 更新 CMakeLists.txt
1. 更新源文件路径:
```cmake
set(SOURCES
src/core/robot_control_node.cpp
src/core/robot_control_manager.cpp
src/core/controller_factory.cpp
src/actions/action_manager.cpp
src/controllers/control_base.cpp
src/controllers/leg_control.cpp
src/controllers/arm_control.cpp
src/controllers/wheel_control.cpp
src/controllers/waist_control.cpp
src/utils/trapezoidal_trajectory.cpp
src/main.cpp
)
```
2. 更新头文件包含目录(如果需要):
```cmake
include_directories(
include
${EIGEN3_INCLUDE_DIRS}
)
```
### 步骤 6: 测试和调试
1. 编译项目:
```bash
cd /home/demo/hive_core_workspace/hivecore_robot_os1
colcon build --packages-select robot_control
```
2. 修复编译错误(主要是包含路径问题)
3. 运行测试确保功能正常
## 注意事项
1. **文件移动顺序很重要**:先移动依赖少的文件,再移动依赖多的文件
2. **包含路径更新**:移动文件后必须更新所有包含路径
3. **控制器动态加载**:确保在使用控制器前检查是否启用
4. **测试**:每完成一步都要测试编译,避免错误累积
## 建议的重构顺序
1. 移动 utils 文件并更新路径
2. 移动 controllers 文件并更新路径
3. 移动 core 文件(除了 robot_control_node并更新路径
4. 修改 RobotControlManager 支持动态加载
5. 移动和修改 robot_control_node
6. 更新 CMakeLists.txt
7. 测试编译和运行

View File

@@ -1,6 +1,8 @@
#pragma once
#include <atomic>
#include <chrono>
#include <functional>
#include <memory>
#include <string>
#include <vector>
@@ -10,7 +12,6 @@
#include "trajectory_msgs/msg/joint_trajectory.hpp"
#include "actions/action_context.hpp"
#include "actions/follow_jt_executor.hpp"
namespace robot_control
{
@@ -22,6 +23,7 @@ public:
using DualArm = interfaces::action::DualArm;
using GoalHandleDualArm = rclcpp_action::ServerGoalHandle<DualArm>;
using FollowJTA = control_msgs::action::FollowJointTrajectory;
using GoalHandleFollowJTA = rclcpp_action::ClientGoalHandle<FollowJTA>;
explicit DualArmAction(const ActionContext& ctx);
@@ -56,17 +58,48 @@ private:
double* total_time_sec,
std::string* error_msg) const;
void smooth_trajectory_start_end_(
trajectory_msgs::msg::JointTrajectory* traj,
double max_acceleration = 2.0,
double smooth_duration = 0.15) const;
void publish_planning_feedback_(
const std::shared_ptr<GoalHandleDualArm>& goal_handle,
const std::shared_ptr<DualArm::Feedback>& feedback) const;
void fill_feedback_from_fjt_(
const FollowJTA::Feedback& fjt_fb,
double trajectory_total_time_sec_(
const trajectory_msgs::msg::JointTrajectory& traj) const;
void split_dual_arm_trajectory_(
const MotionParameters& motion_params,
size_t arm_dof,
const trajectory_msgs::msg::JointTrajectory& traj,
trajectory_msgs::msg::JointTrajectory* left,
trajectory_msgs::msg::JointTrajectory* right) const;
void execute_single_arm_(
const std::shared_ptr<GoalHandleDualArm>& goal_handle,
const std::shared_ptr<const DualArm::Goal>& goal,
const std::shared_ptr<DualArm::Feedback>& feedback,
const std::shared_ptr<DualArm::Result>& result,
double acceleration_scaling);
void execute_dual_arm_(
const std::shared_ptr<GoalHandleDualArm>& goal_handle,
const std::shared_ptr<const DualArm::Goal>& goal,
const std::shared_ptr<DualArm::Feedback>& feedback,
const std::shared_ptr<DualArm::Result>& result,
const MotionParameters& motion_params,
double acceleration_scaling);
bool wait_for_execution_(
const std::shared_ptr<GoalHandleDualArm>& goal_handle,
const std::shared_ptr<DualArm::Feedback>& feedback,
const std::shared_ptr<DualArm::Result>& result,
double total_time_sec,
DualArm::Feedback* out) const;
const std::function<bool()>& is_done,
const std::function<void()>& cancel_actions,
const std::string& cancel_message,
bool* canceled) const;
void save_trajectory_to_file_(
const trajectory_msgs::msg::JointTrajectory& traj,
const std::string& tag) const;
ActionContext ctx_;
// 整体 DualArm 执行标志(用于双臂协同运动场景)
@@ -83,8 +116,9 @@ private:
rclcpp_action::Client<FollowJTA>::SharedPtr left_fjt_client_;
rclcpp_action::Client<FollowJTA>::SharedPtr right_fjt_client_;
// 如需更复杂的执行逻辑(软停止等),可以复用 FollowJTExecutor;当前实现未使用。
std::unique_ptr<FollowJTExecutor> follow_executor_;
bool save_trajectory_enabled_{true};
bool trajectory_execution_enabled_{false};
};
} // namespace robot_control

View File

@@ -1,66 +0,0 @@
#pragma once
#include <atomic>
#include <functional>
#include <mutex>
#include <optional>
#include <string>
#include <vector>
#include "rclcpp/rclcpp.hpp"
#include "rclcpp_action/rclcpp_action.hpp"
#include "control_msgs/action/follow_joint_trajectory.hpp"
#include "trajectory_msgs/msg/joint_trajectory.hpp"
namespace robot_control
{
class RobotControlManager;
class FollowJTExecutor
{
public:
using FollowJTA = control_msgs::action::FollowJointTrajectory;
using GoalHandleFollowJTA = rclcpp_action::ClientGoalHandle<FollowJTA>;
struct ExecResult
{
bool ok{false};
std::string message;
};
FollowJTExecutor(
rclcpp::Node* node,
RobotControlManager& robot_control_manager,
rclcpp_action::Client<FollowJTA>::SharedPtr follow_jt_client);
ExecResult send_and_wait(
const trajectory_msgs::msg::JointTrajectory& traj,
const std::function<bool()>& is_cancel_requested,
const std::function<void(const FollowJTA::Feedback&)>& on_feedback);
private:
double compute_soft_stop_duration_sec_(
const std::vector<double>& v0,
const std::vector<double>& amax) const;
trajectory_msgs::msg::JointTrajectory build_soft_stop_trajectory_(
const FollowJTA::Feedback& fb) const;
void save_trajectory_to_file_(
const trajectory_msgs::msg::JointTrajectory& traj) const;
rclcpp::Node* node_{nullptr};
RobotControlManager& robot_control_manager_;
rclcpp_action::Client<FollowJTA>::SharedPtr follow_jt_client_;
// Latest feedback cache for smooth stop on cancel (protected by mutex)
mutable std::mutex last_feedback_mutex_;
std::optional<FollowJTA::Feedback> last_feedback_;
// Trajectory saving switch
bool save_trajectory_enabled_{true};
};
} // namespace robot_control

View File

@@ -15,6 +15,7 @@
#include "geometry_msgs/msg/pose.hpp"
#include "moveit/move_group_interface/move_group_interface.h"
#include "trajectory_msgs/msg/joint_trajectory.hpp"
#include "utils/s_curve_trajectory.hpp"
// 前向声明
namespace rclcpp {
@@ -249,11 +250,26 @@ namespace robot_control
*/
bool timeParameterizeAndStore_(
uint8_t arm_id,
moveit::planning_interface::MoveGroupInterface& move_group,
moveit::planning_interface::MoveGroupInterface::Plan& plan,
double velocity_scaling,
double acceleration_scaling);
/**
* @brief 基于S型曲线对 MoveIt 轨迹进行时间参数化并重采样
* @param arm_id 手臂ID
* @param in_traj 输入轨迹(仅位置)
* @param out_traj 输出轨迹(包含 time/pos/vel/acc
* @param velocity_scaling 速度缩放因子
* @param acceleration_scaling 加速度缩放因子
* @return true 成功生成 S 型参数化轨迹
*/
bool sCurveTimeParameterize_(
uint8_t arm_id,
const trajectory_msgs::msg::JointTrajectory& in_traj,
trajectory_msgs::msg::JointTrajectory& out_traj,
double velocity_scaling,
double acceleration_scaling) const;
/**
* @brief 辅助函数:更新 joints_info_map_ 从单个轨迹点
* @param arm_id 手臂ID0=左臂1=右臂)

View File

@@ -55,7 +55,8 @@ namespace robot_control
private:
// ==================== ROS 2 通信接口 ====================
rclcpp::Publisher<control_msgs::msg::DynamicInterfaceGroupValues>::SharedPtr gpioPub_; ///< GPIO发布器
rclcpp::Publisher<control_msgs::msg::DynamicInterfaceGroupValues>::SharedPtr left_gpio_pub_; ///< 左臂GPIO发布器
rclcpp::Publisher<control_msgs::msg::DynamicInterfaceGroupValues>::SharedPtr right_gpio_pub_; ///< 右臂GPIO发布器
rclcpp::Subscription<std_msgs::msg::String>::SharedPtr joyCommandSub_; ///< 手柄命令订阅器
rclcpp::Subscription<sensor_msgs::msg::JointState>::SharedPtr jointStatesSub_; ///< 关节状态订阅器
rclcpp::Subscription<MotorPos>::SharedPtr wheelStatesSub_; ///< 轮子状态订阅器
@@ -118,17 +119,42 @@ namespace robot_control
/**
* @brief 设置电机 fault参考 test_motor.cpp
* @param id 电机/关节序号0=全部;否则按 all_joint_names_ 的 index+1 语义)
* @param id 关节序号0=全部;否则按 dual_arm_joint_names_ 的 index+1 语义)
* @param fault fault 值(通常 1 表示复位脉冲0 表示释放)
*/
void motor_fault(int id, double fault);
/**
* @brief 设置电机 enable参考 test_motor.cpp
* @param id 电机/关节序号0=全部;否则按 all_joint_names_ 的 index+1 语义)
* @param id 关节序号0=全部;否则按 dual_arm_joint_names_ 的 index+1 语义)
* @param enable 使能值1=使能0=禁用)
*/
void motor_enable(int id, double enable);
/**
* @brief 根据 dual_arm_joint_names_ 拆分左右臂关节名称
*/
bool split_arm_joint_names_(
const MotionParameters& motion_params,
std::vector<std::string>& left,
std::vector<std::string>& right) const;
/**
* @brief 发布GPIO命令fault/enable
* @param pub 目标GPIO控制器发布器
* @param joints 关节名称列表(对应 interface_groups
* @param interface_name 接口名称fault 或 enable
* @param id 关节序号0=全部)
* @param value 指令值
* @param offset 该列表在全局双臂数组中的起始偏移
*/
void publish_gpio_command_(
const rclcpp::Publisher<control_msgs::msg::DynamicInterfaceGroupValues>::SharedPtr& pub,
const std::vector<std::string>& joints,
const std::string& interface_name,
int id,
double value,
int offset) const;
};
}

View File

@@ -2,7 +2,14 @@
#include <thread>
#include <unordered_map>
#include <chrono>
#include <ctime>
#include <algorithm>
#include <fstream>
#include <iomanip>
#include <sstream>
#include "rclcpp/rclcpp.hpp"
#include "core/robot_control_manager.hpp"
#include "core/common_enum.hpp"
@@ -23,6 +30,28 @@ void DualArmAction::initialize()
ctx_.node, "/left_arm_controller/follow_joint_trajectory");
right_fjt_client_ = rclcpp_action::create_client<FollowJTA>(
ctx_.node, "/right_arm_controller/follow_joint_trajectory");
const std::string save_param = "save_trajectory_enabled";
try {
ctx_.node->declare_parameter<bool>(save_param, save_trajectory_enabled_);
} catch (const rclcpp::exceptions::ParameterAlreadyDeclaredException&) {
// Parameter already declared elsewhere; keep going.
}
if (ctx_.node->get_parameter(save_param, save_trajectory_enabled_)) {
RCLCPP_INFO(ctx_.node->get_logger(),
"save_trajectory_enabled=%s", save_trajectory_enabled_ ? "true" : "false");
}
const std::string exec_param = "arm_trajectory_execution_enabled";
try {
ctx_.node->declare_parameter<bool>(exec_param, trajectory_execution_enabled_);
} catch (const rclcpp::exceptions::ParameterAlreadyDeclaredException&) {
// Parameter already declared elsewhere; keep going.
}
if (ctx_.node->get_parameter(exec_param, trajectory_execution_enabled_)) {
RCLCPP_INFO(ctx_.node->get_logger(),
"arm_trajectory_execution_enabled=%s", trajectory_execution_enabled_ ? "true" : "false");
}
}
server_ = rclcpp_action::create_server<DualArm>(
@@ -285,406 +314,271 @@ bool DualArmAction::export_and_normalize_trajectory_(
return true;
}
void DualArmAction::smooth_trajectory_start_end_(
trajectory_msgs::msg::JointTrajectory* traj,
double max_acceleration,
double smooth_duration) const
void DualArmAction::publish_planning_feedback_(
const std::shared_ptr<GoalHandleDualArm>& goal_handle,
const std::shared_ptr<DualArm::Feedback>& feedback) const
{
if (!traj || traj->points.empty() || traj->joint_names.empty()) {
return;
}
const size_t num_joints = traj->joint_names.size();
const double dt = 0.01; // 采样时间间隔
std::vector<trajectory_msgs::msg::JointTrajectoryPoint> smoothed_points;
// ==================== 处理启动平滑 ====================
const auto& first_point = traj->points.front();
// 检查第一个点是否有非零速度或加速度
bool need_start_smooth = false;
for (size_t i = 0; i < num_joints; ++i) {
double v0 = (i < first_point.velocities.size()) ? first_point.velocities[i] : 0.0;
double a0 = (i < first_point.accelerations.size()) ? first_point.accelerations[i] : 0.0;
if (std::abs(v0) > 1e-6 || std::abs(a0) > 1e-6) {
need_start_smooth = true;
break;
}
}
if (need_start_smooth) {
// 计算从静止加速到第一个点速度所需的时间
double max_v0 = 0.0;
for (size_t i = 0; i < num_joints; ++i) {
double v0 = (i < first_point.velocities.size()) ? std::abs(first_point.velocities[i]) : 0.0;
if (v0 > max_v0) max_v0 = v0;
}
double t_accel = (max_v0 > 1e-6) ? std::min(smooth_duration, max_v0 / max_acceleration) : smooth_duration;
if (t_accel < dt) t_accel = dt;
// 生成启动段(从静止加速到第一个点的速度)
size_t n_start = static_cast<size_t>(std::ceil(t_accel / dt));
for (size_t k = 0; k < n_start; ++k) {
double t = static_cast<double>(k) * dt;
if (t > t_accel) t = t_accel;
trajectory_msgs::msg::JointTrajectoryPoint p;
p.positions.resize(num_joints, 0.0);
p.velocities.resize(num_joints, 0.0);
p.accelerations.resize(num_joints, 0.0);
for (size_t i = 0; i < num_joints; ++i) {
double v_target = (i < first_point.velocities.size()) ? first_point.velocities[i] : 0.0;
double a_target = (i < first_point.accelerations.size()) ? first_point.accelerations[i] : 0.0;
// 使用匀加速模型v(t) = a*t, s(t) = 0.5*a*t^2
double accel_dir = (std::abs(v_target) > 1e-6) ? ((v_target > 0) ? max_acceleration : -max_acceleration) : 0.0;
double alpha = (t_accel > 1e-6) ? (t / t_accel) : 1.0;
alpha = std::min(1.0, alpha);
p.accelerations[i] = accel_dir * (1.0 - alpha) + a_target * alpha;
p.velocities[i] = accel_dir * t;
p.positions[i] = first_point.positions[i] - 0.5 * accel_dir * t_accel * t_accel + 0.5 * accel_dir * t * t;
}
p.time_from_start = rclcpp::Duration::from_seconds(t);
smoothed_points.push_back(std::move(p));
}
}
// ==================== 添加原始轨迹点(调整时间偏移) ====================
double time_offset = 0.0;
if (need_start_smooth && !smoothed_points.empty()) {
const auto& last_smooth = smoothed_points.back();
time_offset =
static_cast<double>(last_smooth.time_from_start.sec) +
static_cast<double>(last_smooth.time_from_start.nanosec) * 1e-9;
}
for (size_t k = 0; k < traj->points.size(); ++k) {
const auto& orig = traj->points[k];
trajectory_msgs::msg::JointTrajectoryPoint p = orig;
double orig_time =
static_cast<double>(orig.time_from_start.sec) +
static_cast<double>(orig.time_from_start.nanosec) * 1e-9;
// 如果是第一个点且已添加启动段,跳过(避免重复)
if (k == 0 && need_start_smooth) {
continue;
}
double new_time = orig_time + time_offset;
p.time_from_start = rclcpp::Duration::from_seconds(new_time);
smoothed_points.push_back(std::move(p));
}
// ==================== 处理停止平滑 ====================
if (!smoothed_points.empty()) {
const auto& last_point = smoothed_points.back();
// 检查最后一个点是否有非零速度或加速度
bool need_stop_smooth = false;
for (size_t i = 0; i < num_joints; ++i) {
double v_end = (i < last_point.velocities.size()) ? last_point.velocities[i] : 0.0;
double a_end = (i < last_point.accelerations.size()) ? last_point.accelerations[i] : 0.0;
if (std::abs(v_end) > 1e-6 || std::abs(a_end) > 1e-6) {
need_stop_smooth = true;
break;
}
}
if (need_stop_smooth) {
// 计算从最后一个点速度减速到静止所需的时间
double max_v_end = 0.0;
for (size_t i = 0; i < num_joints; ++i) {
double v_end = (i < last_point.velocities.size()) ? std::abs(last_point.velocities[i]) : 0.0;
if (v_end > max_v_end) max_v_end = v_end;
}
double t_decel = (max_v_end > 1e-6) ? std::min(smooth_duration, max_v_end / max_acceleration) : smooth_duration;
if (t_decel < dt) t_decel = dt;
// 获取最后一个点的时间
double last_time =
static_cast<double>(last_point.time_from_start.sec) +
static_cast<double>(last_point.time_from_start.nanosec) * 1e-9;
// 生成停止段(从最后一个点的速度减速到静止)
size_t n_stop = static_cast<size_t>(std::ceil(t_decel / dt));
for (size_t k = 1; k <= n_stop; ++k) {
double t = static_cast<double>(k) * dt;
if (t > t_decel) t = t_decel;
trajectory_msgs::msg::JointTrajectoryPoint p;
p.positions.resize(num_joints, 0.0);
p.velocities.resize(num_joints, 0.0);
p.accelerations.resize(num_joints, 0.0);
for (size_t i = 0; i < num_joints; ++i) {
double v_start = (i < last_point.velocities.size()) ? last_point.velocities[i] : 0.0;
double pos_start = last_point.positions[i];
// 使用匀减速模型v(t) = v0 - a*t, s(t) = s0 + v0*t - 0.5*a*t^2
double accel_dir = (std::abs(v_start) > 1e-6) ? ((v_start > 0) ? -max_acceleration : max_acceleration) : 0.0;
double alpha = (t_decel > 1e-6) ? (t / t_decel) : 1.0;
alpha = std::min(1.0, alpha);
p.velocities[i] = v_start + accel_dir * t;
p.accelerations[i] = accel_dir;
p.positions[i] = pos_start + v_start * t + 0.5 * accel_dir * t * t;
// 确保最终速度为0
if (std::abs(p.velocities[i]) < 1e-6) {
p.velocities[i] = 0.0;
}
}
p.time_from_start = rclcpp::Duration::from_seconds(last_time + t);
smoothed_points.push_back(std::move(p));
}
}
}
// 替换原始轨迹点
if (!smoothed_points.empty()) {
traj->points = std::move(smoothed_points);
}
}
void DualArmAction::fill_feedback_from_fjt_(
const FollowJTA::Feedback& fjt_fb,
const MotionParameters& motion_params,
size_t arm_dof,
double total_time_sec,
DualArm::Feedback* out) const
{
if (!out) return;
out->status = 1;
const double t_des =
static_cast<double>(fjt_fb.desired.time_from_start.sec) +
static_cast<double>(fjt_fb.desired.time_from_start.nanosec) * 1e-9;
if (total_time_sec > 1e-9) {
double p = t_des / total_time_sec;
if (p < 0.0) p = 0.0;
if (p > 1.0) p = 1.0;
out->progress = p;
}
out->joints_left.assign(arm_dof, 0.0);
out->joints_right.assign(arm_dof, 0.0);
std::unordered_map<std::string, size_t> dual_idx;
dual_idx.reserve(motion_params.dual_arm_joint_names_.size());
for (size_t i = 0; i < motion_params.dual_arm_joint_names_.size(); ++i) {
dual_idx[motion_params.dual_arm_joint_names_[i]] = i;
}
for (size_t k = 0; k < fjt_fb.joint_names.size() && k < fjt_fb.actual.positions.size(); ++k) {
auto it = dual_idx.find(fjt_fb.joint_names[k]);
if (it == dual_idx.end()) continue;
const size_t idx = it->second;
if (idx < arm_dof) {
out->joints_left[idx] = fjt_fb.actual.positions[k];
} else if (idx < 2 * arm_dof) {
out->joints_right[idx - arm_dof] = fjt_fb.actual.positions[k];
}
}
}
void DualArmAction::execute_(const std::shared_ptr<GoalHandleDualArm> goal_handle)
{
if (!ctx_.robot_control_manager) {
auto result = std::make_shared<DualArm::Result>();
result->success = false;
result->message = "DualArm not initialized (missing robot_control_manager)";
goal_handle->abort(result);
return;
}
const auto goal = goal_handle->get_goal();
auto feedback = std::make_shared<DualArm::Feedback>();
auto result = std::make_shared<DualArm::Result>();
result->success = false;
result->message = "DualArm action failed";
const auto& motionParams = ctx_.robot_control_manager->GetMotionParameters();
const size_t ARM_DOF = motionParams.arm_dof_;
const double acceleration_scaling = 0.5;
// planning feedback
feedback->status = 0;
feedback->progress = 0.0;
feedback->joints_left.clear();
feedback->joints_right.clear();
goal_handle->publish_feedback(feedback);
}
// ==================== 单臂模式 ====================
const size_t arm_count = goal->arm_motion_params.size();
if (arm_count == 1) {
const auto& arm_param = goal->arm_motion_params[0];
const uint8_t arm_id = arm_param.arm_id;
if (arm_id != ArmMotionParams::ARM_LEFT && arm_id != ArmMotionParams::ARM_RIGHT) {
result->success = false;
result->message = "Invalid arm_id for single-arm goal";
goal_handle->abort(result);
return;
double DualArmAction::trajectory_total_time_sec_(
const trajectory_msgs::msg::JointTrajectory& traj) const
{
if (traj.points.empty()) {
return 0.0;
}
const auto& last_point = traj.points.back();
return static_cast<double>(last_point.time_from_start.sec) +
static_cast<double>(last_point.time_from_start.nanosec) * 1e-9;
}
void DualArmAction::split_dual_arm_trajectory_(
const MotionParameters& motion_params,
size_t arm_dof,
const trajectory_msgs::msg::JointTrajectory& traj,
trajectory_msgs::msg::JointTrajectory* left,
trajectory_msgs::msg::JointTrajectory* right) const
{
if (!left || !right) return;
left->header = traj.header;
right->header = traj.header;
left->joint_names.assign(
motion_params.dual_arm_joint_names_.begin(),
motion_params.dual_arm_joint_names_.begin() + arm_dof);
right->joint_names.assign(
motion_params.dual_arm_joint_names_.begin() + arm_dof,
motion_params.dual_arm_joint_names_.begin() + 2 * arm_dof);
left->points.clear();
right->points.clear();
left->points.reserve(traj.points.size());
right->points.reserve(traj.points.size());
for (const auto& p : traj.points) {
trajectory_msgs::msg::JointTrajectoryPoint lp;
trajectory_msgs::msg::JointTrajectoryPoint rp;
if (p.positions.size() >= 2 * arm_dof) {
lp.positions.assign(p.positions.begin(), p.positions.begin() + arm_dof);
rp.positions.assign(p.positions.begin() + arm_dof, p.positions.begin() + 2 * arm_dof);
}
if ((arm_id == 0 && !left_fjt_client_) || (arm_id == 1 && !right_fjt_client_)) {
result->success = false;
result->message = "FJT client for target arm is null";
goal_handle->abort(result);
return;
if (p.velocities.size() >= 2 * arm_dof) {
lp.velocities.assign(p.velocities.begin(), p.velocities.begin() + arm_dof);
rp.velocities.assign(p.velocities.begin() + arm_dof, p.velocities.begin() + 2 * arm_dof);
}
// 规划器占用检查(非阻塞)
bool expected = false;
if (!planner_busy_.compare_exchange_strong(expected, true)) {
result->success = false;
result->message = "MoveIt planner is busy, cannot plan another arm motion";
goal_handle->abort(result);
return;
if (p.accelerations.size() >= 2 * arm_dof) {
lp.accelerations.assign(p.accelerations.begin(), p.accelerations.begin() + arm_dof);
rp.accelerations.assign(p.accelerations.begin() + arm_dof, p.accelerations.begin() + 2 * arm_dof);
}
lp.time_from_start = p.time_from_start;
rp.time_from_start = p.time_from_start;
// 使用单臂规划接口
ArmMotionParams arm_params = arm_param;
left->points.push_back(std::move(lp));
right->points.push_back(std::move(rp));
}
}
if (!ctx_.robot_control_manager->PlanArmMotion(
arm_params,
goal->velocity_scaling,
acceleration_scaling)) {
planner_busy_.store(false);
result->success = false;
result->message = "PlanArmMotion failed for single arm";
goal_handle->abort(result);
return;
}
planner_busy_.store(false);
trajectory_msgs::msg::JointTrajectory traj_single;
if (!ctx_.robot_control_manager->ExportArmPlannedTrajectory(arm_id, traj_single)) {
result->success = false;
result->message = "ExportArmPlannedTrajectory failed";
goal_handle->abort(result);
return;
}
auto& fjt_client = (arm_id == 0) ? left_fjt_client_ : right_fjt_client_;
if (!fjt_client->wait_for_action_server(std::chrono::seconds(3))) {
result->success = false;
result->message = "FollowJointTrajectory action server not available for target arm";
goal_handle->abort(result);
return;
}
FollowJTA::Goal goal_msg;
goal_msg.trajectory = traj_single;
auto send_future = fjt_client->async_send_goal(goal_msg);
if (send_future.wait_for(std::chrono::seconds(5)) != std::future_status::ready) {
result->success = false;
result->message = "Timeout waiting FollowJointTrajectory goal acceptance for target arm";
goal_handle->abort(result);
return;
}
auto goal_handle_arm = send_future.get();
if (!goal_handle_arm) {
result->success = false;
result->message = "FollowJointTrajectory goal rejected for target arm";
goal_handle->abort(result);
return;
}
auto res_future = fjt_client->async_get_result(goal_handle_arm);
// 标记对应手臂正在执行
if (arm_id == 0) executing_left_.store(true);
else executing_right_.store(true);
// 使用时间进度估计执行完成
double total_time = 0.0;
if (!traj_single.points.empty()) {
const auto& last_point = traj_single.points.back();
total_time =
static_cast<double>(last_point.time_from_start.sec) +
static_cast<double>(last_point.time_from_start.nanosec) * 1e-9;
}
double total_time_sec = total_time;
rclcpp::Rate loop_rate(50.0);
auto start_time = ctx_.node->now();
while (rclcpp::ok()) {
if (goal_handle->is_canceling()) {
(void)fjt_client->async_cancel_goal(goal_handle_arm);
if (arm_id == 0) executing_left_.store(false);
else executing_right_.store(false);
result->success = false;
result->message = "Single-arm goal canceled";
goal_handle->canceled(result);
return;
}
auto now = ctx_.node->now();
const double elapsed =
(now.seconds() - start_time.seconds());
double prog = 1.0;
if (total_time_sec > 1e-6) {
prog = std::min(1.0, std::max(0.0, elapsed / total_time_sec));
}
feedback->status = 1;
feedback->progress = prog;
goal_handle->publish_feedback(feedback);
bool done = (res_future.wait_for(std::chrono::milliseconds(1)) == std::future_status::ready);
if (elapsed >= total_time_sec && done) {
break;
}
loop_rate.sleep();
}
if (arm_id == 0) executing_left_.store(false);
else executing_right_.store(false);
if (!rclcpp::ok()) {
result->success = false;
result->message = "ROS shutdown during single-arm execution";
goal_handle->abort(result);
return;
}
auto wrapped = res_future.get();
if (wrapped.code != rclcpp_action::ResultCode::SUCCEEDED) {
result->success = false;
result->message = "FollowJointTrajectory failed for target arm";
goal_handle->abort(result);
return;
}
feedback->status = 2;
feedback->progress = 1.0;
goal_handle->publish_feedback(feedback);
result->success = true;
result->message = "Single-arm motion succeeded";
result->final_progress = 1.0;
goal_handle->succeed(result);
void DualArmAction::save_trajectory_to_file_(
const trajectory_msgs::msg::JointTrajectory& traj,
const std::string& tag) const
{
if (!save_trajectory_enabled_) {
return;
}
if (traj.points.empty() || traj.joint_names.empty()) {
return;
}
// ==================== 双臂模式 ====================
if (arm_count != 2) {
auto now = std::chrono::system_clock::now();
auto time_t = std::chrono::system_clock::to_time_t(now);
std::stringstream ss;
ss << "/tmp/trajectory_" << tag << "_"
<< std::put_time(std::localtime(&time_t), "%Y%m%d_%H%M%S") << ".csv";
const std::string filename = ss.str();
std::ofstream file(filename);
if (!file.is_open()) {
RCLCPP_WARN(ctx_.node->get_logger(), "Failed to open file for trajectory saving: %s", filename.c_str());
return;
}
file << "time";
for (const auto& joint_name : traj.joint_names) {
file << "," << joint_name << "_position";
}
file << "\n";
for (const auto& point : traj.points) {
const double time_sec = point.time_from_start.sec + point.time_from_start.nanosec * 1e-9;
file << std::fixed << std::setprecision(6) << time_sec;
for (size_t i = 0; i < traj.joint_names.size(); ++i) {
const double pos = (i < point.positions.size()) ? point.positions[i] : 0.0;
file << "," << std::fixed << std::setprecision(8) << pos;
}
file << "\n";
}
file.close();
RCLCPP_INFO(ctx_.node->get_logger(), "Trajectory saved to file: %s", filename.c_str());
}
void DualArmAction::execute_single_arm_(
const std::shared_ptr<GoalHandleDualArm>& goal_handle,
const std::shared_ptr<const DualArm::Goal>& goal,
const std::shared_ptr<DualArm::Feedback>& feedback,
const std::shared_ptr<DualArm::Result>& result,
double acceleration_scaling)
{
const auto& arm_param = goal->arm_motion_params[0];
const uint8_t arm_id = arm_param.arm_id;
if (arm_id != ArmMotionParams::ARM_LEFT && arm_id != ArmMotionParams::ARM_RIGHT) {
result->success = false;
result->message = "Dual-arm mode requires two ArmMotionParams";
result->message = "Invalid arm_id for single-arm goal";
goal_handle->abort(result);
return;
}
if ((arm_id == 0 && !left_fjt_client_) || (arm_id == 1 && !right_fjt_client_)) {
result->success = false;
result->message = "FJT client for target arm is null";
goal_handle->abort(result);
return;
}
// 规划器占用检查(非阻塞)
bool expected = false;
if (!planner_busy_.compare_exchange_strong(expected, true)) {
result->success = false;
result->message = "MoveIt planner is busy, cannot plan another arm motion";
goal_handle->abort(result);
return;
}
// 使用单臂规划接口
ArmMotionParams arm_params = arm_param;
if (!ctx_.robot_control_manager->PlanArmMotion(
arm_params,
goal->velocity_scaling,
acceleration_scaling)) {
planner_busy_.store(false);
result->success = false;
result->message = "PlanArmMotion failed for single arm";
goal_handle->abort(result);
return;
}
planner_busy_.store(false);
trajectory_msgs::msg::JointTrajectory traj_single;
if (!ctx_.robot_control_manager->ExportArmPlannedTrajectory(arm_id, traj_single)) {
result->success = false;
result->message = "ExportArmPlannedTrajectory failed";
goal_handle->abort(result);
return;
}
save_trajectory_to_file_(traj_single, (arm_id == 0) ? "left_arm" : "right_arm");
if (!trajectory_execution_enabled_) {
result->success = false;
result->message = "Arm trajectory execution disabled by parameter: arm_trajectory_execution_enabled";
goal_handle->abort(result);
return;
}
auto& fjt_client = (arm_id == 0) ? left_fjt_client_ : right_fjt_client_;
if (!fjt_client->wait_for_action_server(std::chrono::seconds(3))) {
result->success = false;
result->message = "FollowJointTrajectory action server not available for target arm";
goal_handle->abort(result);
return;
}
FollowJTA::Goal goal_msg;
goal_msg.trajectory = traj_single;
auto send_future = fjt_client->async_send_goal(goal_msg);
if (send_future.wait_for(std::chrono::seconds(5)) != std::future_status::ready) {
result->success = false;
result->message = "Timeout waiting FollowJointTrajectory goal acceptance for target arm";
goal_handle->abort(result);
return;
}
auto goal_handle_arm = send_future.get();
if (!goal_handle_arm) {
result->success = false;
result->message = "FollowJointTrajectory goal rejected for target arm";
goal_handle->abort(result);
return;
}
auto res_future = fjt_client->async_get_result(goal_handle_arm);
// 标记对应手臂正在执行
if (arm_id == 0) executing_left_.store(true);
else executing_right_.store(true);
const double total_time_sec = trajectory_total_time_sec_(traj_single);
bool canceled = false;
if (!wait_for_execution_(
goal_handle,
feedback,
result,
total_time_sec,
[&res_future]() {
return res_future.wait_for(std::chrono::milliseconds(1)) == std::future_status::ready;
},
[&fjt_client, &goal_handle_arm, &arm_id, this]() {
(void)fjt_client->async_cancel_goal(goal_handle_arm);
if (arm_id == 0) executing_left_.store(false);
else executing_right_.store(false);
},
"Single-arm goal canceled",
&canceled)) {
if (canceled) return;
result->success = false;
result->message = "ROS shutdown during single-arm execution";
goal_handle->abort(result);
return;
}
if (arm_id == 0) executing_left_.store(false);
else executing_right_.store(false);
auto wrapped = res_future.get();
if (wrapped.code != rclcpp_action::ResultCode::SUCCEEDED) {
result->success = false;
result->message = "FollowJointTrajectory failed for target arm";
goal_handle->abort(result);
return;
}
feedback->status = 2;
feedback->progress = 1.0;
goal_handle->publish_feedback(feedback);
result->success = true;
result->message = "Single-arm motion succeeded";
result->final_progress = 1.0;
goal_handle->succeed(result);
}
void DualArmAction::execute_dual_arm_(
const std::shared_ptr<GoalHandleDualArm>& goal_handle,
const std::shared_ptr<const DualArm::Goal>& goal,
const std::shared_ptr<DualArm::Feedback>& feedback,
const std::shared_ptr<DualArm::Result>& result,
const MotionParameters& motion_params,
double acceleration_scaling)
{
const size_t ARM_DOF = motion_params.arm_dof_;
if (!left_fjt_client_ || !right_fjt_client_) {
result->success = false;
result->message = "FJT clients for left/right arm are null";
@@ -738,48 +632,23 @@ void DualArmAction::execute_(const std::shared_ptr<GoalHandleDualArm> goal_handl
trajectory_msgs::msg::JointTrajectory traj;
double total_time = 0.0;
if (!export_and_normalize_trajectory_(motionParams, &traj, &total_time, &err)) {
if (!export_and_normalize_trajectory_(motion_params, &traj, &total_time, &err)) {
result->success = false;
result->message = std::string("Export/normalize trajectory failed: ") + err;
goal_handle->abort(result);
return;
}
// 将 12 关节轨迹拆分为左右臂两条 6 关节轨迹
trajectory_msgs::msg::JointTrajectory traj_left;
trajectory_msgs::msg::JointTrajectory traj_right;
traj_left.header = traj.header;
traj_right.header = traj.header;
traj_left.joint_names.assign(
motionParams.dual_arm_joint_names_.begin(),
motionParams.dual_arm_joint_names_.begin() + ARM_DOF);
traj_right.joint_names.assign(
motionParams.dual_arm_joint_names_.begin() + ARM_DOF,
motionParams.dual_arm_joint_names_.begin() + 2 * ARM_DOF);
for (const auto& p : traj.points) {
trajectory_msgs::msg::JointTrajectoryPoint lp;
trajectory_msgs::msg::JointTrajectoryPoint rp;
if (p.positions.size() >= 2 * ARM_DOF) {
lp.positions.assign(p.positions.begin(), p.positions.begin() + ARM_DOF);
rp.positions.assign(p.positions.begin() + ARM_DOF, p.positions.begin() + 2 * ARM_DOF);
}
if (p.velocities.size() >= 2 * ARM_DOF) {
lp.velocities.assign(p.velocities.begin(), p.velocities.begin() + ARM_DOF);
rp.velocities.assign(p.velocities.begin() + ARM_DOF, p.velocities.begin() + 2 * ARM_DOF);
}
if (p.accelerations.size() >= 2 * ARM_DOF) {
lp.accelerations.assign(p.accelerations.begin(), p.accelerations.begin() + ARM_DOF);
rp.accelerations.assign(p.accelerations.begin() + ARM_DOF, p.accelerations.begin() + 2 * ARM_DOF);
}
lp.time_from_start = p.time_from_start;
rp.time_from_start = p.time_from_start;
traj_left.points.push_back(std::move(lp));
traj_right.points.push_back(std::move(rp));
split_dual_arm_trajectory_(motion_params, ARM_DOF, traj, &traj_left, &traj_right);
save_trajectory_to_file_(traj_left, "dual_left");
save_trajectory_to_file_(traj_right, "dual_right");
if (!trajectory_execution_enabled_) {
result->success = false;
result->message = "Arm trajectory execution disabled by parameter: arm_trajectory_execution_enabled";
goal_handle->abort(result);
return;
}
// 通过左右臂各自的 FollowJointTrajectory action 下发
@@ -830,56 +699,39 @@ void DualArmAction::execute_(const std::shared_ptr<GoalHandleDualArm> goal_handl
executing_right_.store(true);
const double total_time_sec = total_time;
rclcpp::Rate loop_rate(50.0);
auto start_time = ctx_.node->now();
while (rclcpp::ok()) {
if (goal_handle->is_canceling()) {
(void)left_fjt_client_->async_cancel_goal(left_handle);
(void)right_fjt_client_->async_cancel_goal(right_handle);
executing_.store(false);
executing_left_.store(false);
executing_right_.store(false);
result->success = false;
result->message = "DualArm goal canceled";
goal_handle->canceled(result);
return;
}
auto now = ctx_.node->now();
const double elapsed =
(now.seconds() - start_time.seconds());
double prog = 1.0;
if (total_time_sec > 1e-6) {
prog = std::min(1.0, std::max(0.0, elapsed / total_time_sec));
}
feedback->status = 1;
feedback->progress = prog;
goal_handle->publish_feedback(feedback);
// 同时检查左右臂 action 是否都完成
bool left_done = (left_res_future.wait_for(std::chrono::milliseconds(1)) == std::future_status::ready);
bool right_done = (right_res_future.wait_for(std::chrono::milliseconds(1)) == std::future_status::ready);
if (elapsed >= total_time_sec && left_done && right_done) {
break;
}
loop_rate.sleep();
}
executing_.store(false);
executing_left_.store(false);
executing_right_.store(false);
// 获取左右臂执行结果
if (!rclcpp::ok()) {
bool canceled = false;
if (!wait_for_execution_(
goal_handle,
feedback,
result,
total_time_sec,
[&left_res_future, &right_res_future]() {
const bool left_done =
(left_res_future.wait_for(std::chrono::milliseconds(1)) == std::future_status::ready);
const bool right_done =
(right_res_future.wait_for(std::chrono::milliseconds(1)) == std::future_status::ready);
return left_done && right_done;
},
[&left_handle, &right_handle, this]() {
(void)left_fjt_client_->async_cancel_goal(left_handle);
(void)right_fjt_client_->async_cancel_goal(right_handle);
executing_.store(false);
executing_left_.store(false);
executing_right_.store(false);
},
"DualArm goal canceled",
&canceled)) {
if (canceled) return;
result->success = false;
result->message = "ROS shutdown during DualArm execution";
goal_handle->abort(result);
return;
}
executing_.store(false);
executing_left_.store(false);
executing_right_.store(false);
auto left_wrapped = left_res_future.get();
auto right_wrapped = right_res_future.get();
if (left_wrapped.code != rclcpp_action::ResultCode::SUCCEEDED ||
@@ -899,6 +751,91 @@ void DualArmAction::execute_(const std::shared_ptr<GoalHandleDualArm> goal_handl
result->final_progress = 1.0;
goal_handle->succeed(result);
}
bool DualArmAction::wait_for_execution_(
const std::shared_ptr<GoalHandleDualArm>& goal_handle,
const std::shared_ptr<DualArm::Feedback>& feedback,
const std::shared_ptr<DualArm::Result>& result,
double total_time_sec,
const std::function<bool()>& is_done,
const std::function<void()>& cancel_actions,
const std::string& cancel_message,
bool* canceled) const
{
if (canceled) *canceled = false;
rclcpp::Rate loop_rate(50.0);
auto start_time = ctx_.node->now();
while (rclcpp::ok()) {
if (goal_handle->is_canceling()) {
if (cancel_actions) {
cancel_actions();
}
if (canceled) *canceled = true;
result->success = false;
result->message = cancel_message;
goal_handle->canceled(result);
return false;
}
auto now = ctx_.node->now();
const double elapsed =
(now.seconds() - start_time.seconds());
double prog = 1.0;
if (total_time_sec > 1e-6) {
prog = std::min(1.0, std::max(0.0, elapsed / total_time_sec));
}
feedback->status = 1;
feedback->progress = prog;
goal_handle->publish_feedback(feedback);
if (elapsed >= total_time_sec && is_done && is_done()) {
break;
}
loop_rate.sleep();
}
return rclcpp::ok();
}
void DualArmAction::execute_(const std::shared_ptr<GoalHandleDualArm> goal_handle)
{
if (!ctx_.robot_control_manager) {
auto result = std::make_shared<DualArm::Result>();
result->success = false;
result->message = "DualArm not initialized (missing robot_control_manager)";
goal_handle->abort(result);
return;
}
const auto goal = goal_handle->get_goal();
auto feedback = std::make_shared<DualArm::Feedback>();
auto result = std::make_shared<DualArm::Result>();
result->success = false;
result->message = "DualArm action failed";
publish_planning_feedback_(goal_handle, feedback);
const auto& motionParams = ctx_.robot_control_manager->GetMotionParameters();
const double acceleration_scaling = 0.5;
const size_t arm_count = goal->arm_motion_params.size();
if (arm_count == 1) {
execute_single_arm_(goal_handle, goal, feedback, result, acceleration_scaling);
return;
}
if (arm_count == 2) {
execute_dual_arm_(goal_handle, goal, feedback, result, motionParams, acceleration_scaling);
return;
}
result->success = false;
result->message = "Dual-arm mode requires two ArmMotionParams";
goal_handle->abort(result);
}
} // namespace robot_control

View File

@@ -1,255 +0,0 @@
#include "actions/follow_jt_executor.hpp"
#include <algorithm>
#include <chrono>
#include <cmath>
#include <ctime>
#include <future>
#include <fstream>
#include <iomanip>
#include <sstream>
#include "core/robot_control_manager.hpp"
namespace robot_control
{
FollowJTExecutor::FollowJTExecutor(
rclcpp::Node* node,
RobotControlManager& robot_control_manager,
rclcpp_action::Client<FollowJTA>::SharedPtr follow_jt_client)
: node_(node)
, robot_control_manager_(robot_control_manager)
, follow_jt_client_(std::move(follow_jt_client))
{
}
FollowJTExecutor::ExecResult FollowJTExecutor::send_and_wait(
const trajectory_msgs::msg::JointTrajectory& traj,
const std::function<bool()>& is_cancel_requested,
const std::function<void(const FollowJTA::Feedback&)>& on_feedback)
{
ExecResult out;
if (!follow_jt_client_) {
out.ok = false;
out.message = "follow_jt_client is null";
return out;
}
if (!follow_jt_client_->wait_for_action_server(std::chrono::seconds(3))) {
out.ok = false;
out.message = "FollowJointTrajectory action server not available: /trajectory_controller/follow_joint_trajectory";
return out;
}
FollowJTA::Goal goal;
goal.trajectory = traj;
// Save trajectory to file if enabled
if (save_trajectory_enabled_) {
save_trajectory_to_file_(goal.trajectory);
}
typename rclcpp_action::Client<FollowJTA>::SendGoalOptions options;
options.feedback_callback =
[this, on_feedback](GoalHandleFollowJTA::SharedPtr,
const std::shared_ptr<const FollowJTA::Feedback> feedback) {
if (feedback) {
std::lock_guard<std::mutex> lk(last_feedback_mutex_);
last_feedback_ = *feedback;
}
if (on_feedback && feedback) on_feedback(*feedback);
};
auto goal_handle_future = follow_jt_client_->async_send_goal(goal, options);
if (goal_handle_future.wait_for(std::chrono::seconds(5)) != std::future_status::ready) {
out.ok = false;
out.message = "Timeout waiting FollowJointTrajectory goal acceptance";
return out;
}
GoalHandleFollowJTA::SharedPtr goal_handle = goal_handle_future.get();
if (!goal_handle) {
out.ok = false;
out.message = "FollowJointTrajectory goal rejected";
return out;
}
auto result_future = follow_jt_client_->async_get_result(goal_handle);
while (rclcpp::ok()) {
if (is_cancel_requested && is_cancel_requested()) {
(void)follow_jt_client_->async_cancel_goal(goal_handle);
// Send a short deceleration trajectory to avoid an abrupt hold “impact”
std::optional<FollowJTA::Feedback> fb;
{
std::lock_guard<std::mutex> lk(last_feedback_mutex_);
fb = last_feedback_;
}
if (fb) {
auto stop_traj = build_soft_stop_trajectory_(*fb);
FollowJTA::Goal stop_goal;
stop_goal.trajectory = stop_traj;
(void)follow_jt_client_->async_send_goal(stop_goal);
}
out.ok = false;
out.message = "Canceled by outer action";
return out;
}
auto st = result_future.wait_for(std::chrono::milliseconds(50));
if (st == std::future_status::ready) break;
}
if (!rclcpp::ok()) {
out.ok = false;
out.message = "ROS shutdown";
return out;
}
auto wrapped = result_future.get();
if (wrapped.code != rclcpp_action::ResultCode::SUCCEEDED) {
out.ok = false;
out.message = "FollowJointTrajectory failed, code=" + std::to_string(static_cast<int>(wrapped.code));
return out;
}
out.ok = true;
out.message = "OK";
return out;
}
double FollowJTExecutor::compute_soft_stop_duration_sec_(
const std::vector<double>& v0,
const std::vector<double>& amax) const
{
const size_t dof = std::min(v0.size(), amax.size());
double T = 0.1;
for (size_t i = 0; i < dof; ++i) {
const double a = std::max(0.1, amax[i]) * 0.5;
if (a > 1e-6) {
T = std::max(T, std::abs(v0[i]) / a);
}
}
return std::clamp(T, 0.1, 0.5);
}
trajectory_msgs::msg::JointTrajectory FollowJTExecutor::build_soft_stop_trajectory_(
const FollowJTA::Feedback& fb) const
{
trajectory_msgs::msg::JointTrajectory stop;
stop.header.stamp.sec = 0;
stop.header.stamp.nanosec = 0;
stop.joint_names = fb.joint_names;
const auto& mp = robot_control_manager_.GetMotionParameters();
const size_t dof = fb.joint_names.size();
std::vector<double> q0(dof, 0.0), v0(dof, 0.0), amax(dof, 1.0);
for (size_t i = 0; i < dof; ++i) {
if (i < fb.actual.positions.size()) q0[i] = fb.actual.positions[i];
if (i < fb.actual.velocities.size()) v0[i] = fb.actual.velocities[i];
auto it = mp.joints_info_map_.find(fb.joint_names[i]);
if (it != mp.joints_info_map_.end()) {
amax[i] = std::max(0.1, std::abs(it->second.max_acceleration));
} else {
amax[i] = 1.0;
}
}
const double T = compute_soft_stop_duration_sec_(v0, amax);
const double dt = 0.01;
const size_t N = static_cast<size_t>(std::ceil(T / dt));
stop.points.reserve(std::max<size_t>(N, 1));
for (size_t k = 1; k <= std::max<size_t>(N, 1); ++k) {
const double t = std::min(T, static_cast<double>(k) * dt);
trajectory_msgs::msg::JointTrajectoryPoint p;
p.positions.resize(dof, 0.0);
p.velocities.resize(dof, 0.0);
p.accelerations.resize(dof, 0.0);
for (size_t i = 0; i < dof; ++i) {
const double s = (v0[i] >= 0.0) ? 1.0 : -1.0;
const double a = std::max(0.1, amax[i]) * 0.5;
const double t_stop = (a > 1e-6) ? (std::abs(v0[i]) / a) : 0.0;
if (t_stop <= 1e-6 || std::abs(v0[i]) <= 1e-6) {
p.positions[i] = q0[i];
p.velocities[i] = 0.0;
p.accelerations[i] = 0.0;
continue;
}
if (t < t_stop) {
p.positions[i] = q0[i] + v0[i] * t - 0.5 * s * a * t * t;
p.velocities[i] = v0[i] - s * a * t;
p.accelerations[i] = -s * a;
} else {
p.positions[i] = q0[i] + 0.5 * v0[i] * t_stop;
p.velocities[i] = 0.0;
p.accelerations[i] = 0.0;
}
}
p.time_from_start = rclcpp::Duration::from_seconds(t);
stop.points.push_back(std::move(p));
}
return stop;
}
void FollowJTExecutor::save_trajectory_to_file_(
const trajectory_msgs::msg::JointTrajectory& traj) const
{
if (traj.points.empty() || traj.joint_names.empty()) {
return;
}
// Generate filename with timestamp
auto now = std::chrono::system_clock::now();
auto time_t = std::chrono::system_clock::to_time_t(now);
std::stringstream ss;
ss << "/tmp/trajectory_" << std::put_time(std::localtime(&time_t), "%Y%m%d_%H%M%S") << ".csv";
std::string filename = ss.str();
std::ofstream file(filename);
if (!file.is_open()) {
RCLCPP_WARN(node_->get_logger(), "Failed to open file for trajectory saving: %s", filename.c_str());
return;
}
// Write header
file << "time";
for (const auto& joint_name : traj.joint_names) {
file << "," << joint_name << "_position";
}
file << "\n";
// Write trajectory points
for (const auto& point : traj.points) {
// Convert time_from_start to seconds
double time_sec = point.time_from_start.sec + point.time_from_start.nanosec * 1e-9;
file << std::fixed << std::setprecision(6) << time_sec;
// Write positions for each joint
for (size_t i = 0; i < traj.joint_names.size(); ++i) {
double pos = (i < point.positions.size()) ? point.positions[i] : 0.0;
file << "," << std::fixed << std::setprecision(8) << pos;
}
file << "\n";
file.flush();
}
file.close();
RCLCPP_INFO(node_->get_logger(), "Trajectory saved to file: %s", filename.c_str());
}
} // namespace robot_control

View File

@@ -4,13 +4,10 @@
#include <memory>
#include <cmath>
#include <algorithm>
#include <limits>
#include "rclcpp/rclcpp.hpp"
#include "moveit/move_group_interface/move_group_interface.h"
#include "moveit/planning_interface/planning_interface.h"
#include "moveit_msgs/msg/move_it_error_codes.hpp"
#include "moveit/robot_trajectory/robot_trajectory.h"
#include "moveit/robot_state/robot_state.h"
#include "moveit/trajectory_processing/iterative_time_parameterization.h"
#ifndef M_PI
#define M_PI 3.14159265358979323846
@@ -201,7 +198,7 @@ bool ArmControl::PlanJointMotion(
return false;
}
return timeParameterizeAndStore_(
arm_id, *move_group, plan, velocity_scaling, acceleration_scaling);
arm_id, plan, velocity_scaling, acceleration_scaling);
} catch (const std::exception& e) {
if (node_) {
RCLCPP_ERROR(node_->get_logger(), "PlanJointMotion exception: %s", e.what());
@@ -246,7 +243,7 @@ bool ArmControl::PlanCartesianMotion(
return false;
}
return timeParameterizeAndStore_(
arm_id, *move_group, plan, velocity_scaling, acceleration_scaling);
arm_id, plan, velocity_scaling, acceleration_scaling);
} catch (const std::exception& e) {
if (node_) {
RCLCPP_ERROR(node_->get_logger(), "PlanCartesianMotion exception: %s", e.what());
@@ -257,39 +254,34 @@ bool ArmControl::PlanCartesianMotion(
bool ArmControl::timeParameterizeAndStore_(
uint8_t arm_id,
moveit::planning_interface::MoveGroupInterface& move_group,
moveit::planning_interface::MoveGroupInterface::Plan& plan,
double velocity_scaling,
double acceleration_scaling)
{
// 1) RobotTrajectory从 plan.trajectory_ 构建
robot_trajectory::RobotTrajectory original_traj(
move_group.getRobotModel(), move_group.getName());
original_traj.setRobotTrajectoryMsg(*move_group.getCurrentState(), plan.trajectory_);
// 2) 时间参数化:为每个点生成 time_from_startFollowJointTrajectory 直接执行即可)
trajectory_processing::IterativeParabolicTimeParameterization time_param;
if (!time_param.computeTimeStamps(original_traj, velocity_scaling, acceleration_scaling))
{
// 1) 使用 S 型曲线参数化MoveIt 路径 + S-curve time scaling
bool s_curve_ok = false;
if (!plan.trajectory_.joint_trajectory.points.empty()) {
trajectory_msgs::msg::JointTrajectory s_curve_traj;
s_curve_ok = sCurveTimeParameterize_(
arm_id,
plan.trajectory_.joint_trajectory,
s_curve_traj,
velocity_scaling,
acceleration_scaling);
if (s_curve_ok) {
plan.trajectory_.joint_trajectory = s_curve_traj;
}
}
if (!s_curve_ok) {
if (node_) {
RCLCPP_ERROR(node_->get_logger(), "computeTimeStamps failed for arm_id %d", static_cast<int>(arm_id));
RCLCPP_ERROR(node_->get_logger(),
"S-curve time parameterization failed for arm_id %d",
static_cast<int>(arm_id));
}
return false;
}
const size_t waypoint_count = original_traj.getWayPointCount();
if (waypoint_count == 0)
{
if (node_) {
RCLCPP_ERROR(node_->get_logger(), "Empty trajectory after time parameterization for arm_id %d", static_cast<int>(arm_id));
}
return false;
}
// 3) 写回 plan.trajectory_带时间戳的原始轨迹不做固定 dt 重采样)
original_traj.getRobotTrajectoryMsg(plan.trajectory_);
// 4) 存入内部 TrajectoryState使用 msg 内的 time_from_start并保留 vel/acc
// 2) 存入内部 TrajectoryState使用 msg 内的 time_from_start并保留 vel/acc
const auto& jt = plan.trajectory_.joint_trajectory;
if (jt.points.empty())
{
@@ -340,6 +332,192 @@ bool ArmControl::timeParameterizeAndStore_(
return true;
}
bool ArmControl::sCurveTimeParameterize_(
uint8_t arm_id,
const trajectory_msgs::msg::JointTrajectory& in_traj,
trajectory_msgs::msg::JointTrajectory& out_traj,
double velocity_scaling,
double acceleration_scaling) const
{
(void)arm_id;
if (in_traj.joint_names.empty() || in_traj.points.empty()) {
return false;
}
const size_t dof = in_traj.joint_names.size();
std::vector<std::vector<double>> waypoints;
waypoints.reserve(in_traj.points.size());
for (const auto& p : in_traj.points) {
if (p.positions.size() < dof) {
return false;
}
waypoints.push_back(p.positions);
}
if (waypoints.size() == 1) {
out_traj = in_traj;
out_traj.points.clear();
trajectory_msgs::msg::JointTrajectoryPoint point;
point.positions = waypoints.front();
point.velocities.assign(dof, 0.0);
point.accelerations.assign(dof, 0.0);
point.time_from_start = rclcpp::Duration::from_seconds(0.0);
out_traj.points.push_back(std::move(point));
return true;
}
// 1) 计算路径累计长度与各关节的 |dq/ds| 上界
const double kEps = 1e-9;
std::vector<double> s_cum(waypoints.size(), 0.0);
std::vector<double> max_abs_dqds(dof, 0.0);
double total_length = 0.0;
for (size_t i = 1; i < waypoints.size(); ++i) {
double seg_sq = 0.0;
for (size_t j = 0; j < dof; ++j) {
const double dq = waypoints[i][j] - waypoints[i - 1][j];
seg_sq += dq * dq;
}
const double seg_len = std::sqrt(seg_sq);
total_length += seg_len;
s_cum[i] = total_length;
if (seg_len > kEps) {
for (size_t j = 0; j < dof; ++j) {
const double dq = waypoints[i][j] - waypoints[i - 1][j];
max_abs_dqds[j] = std::max(max_abs_dqds[j], std::abs(dq) / seg_len);
}
}
}
if (total_length <= kEps) {
out_traj = in_traj;
out_traj.points.clear();
trajectory_msgs::msg::JointTrajectoryPoint point;
point.positions = waypoints.back();
point.velocities.assign(dof, 0.0);
point.accelerations.assign(dof, 0.0);
point.time_from_start = rclcpp::Duration::from_seconds(0.0);
out_traj.points.push_back(std::move(point));
return true;
}
// 2) 读取关节限速/限加速度,映射到路径参数 s 的标量限制
std::vector<double> v_limits(dof, 0.0);
std::vector<double> a_limits(dof, 0.0);
for (size_t j = 0; j < dof; ++j) {
auto it = joints_info_map_.find(in_traj.joint_names[j]);
if (it == joints_info_map_.end()) {
return false;
}
const double vmax = std::abs(it->second.max_velocity) * velocity_scaling;
const double amax = std::abs(it->second.max_acceleration) * acceleration_scaling;
if (vmax <= 0.0 || amax <= 0.0) {
return false;
}
v_limits[j] = vmax;
a_limits[j] = amax;
}
double s_max_vel = std::numeric_limits<double>::infinity();
double s_max_acc = std::numeric_limits<double>::infinity();
for (size_t j = 0; j < dof; ++j) {
if (max_abs_dqds[j] <= kEps) {
continue;
}
s_max_vel = std::min(s_max_vel, v_limits[j] / max_abs_dqds[j]);
s_max_acc = std::min(s_max_acc, a_limits[j] / max_abs_dqds[j]);
}
if (!std::isfinite(s_max_vel) || !std::isfinite(s_max_acc) ||
s_max_vel <= 0.0 || s_max_acc <= 0.0) {
return false;
}
// 3) 使用单轴 S 型曲线对路径参数 s 进行时间标定
S_CurveTrajectory s_curve(
std::vector<double>{s_max_vel},
std::vector<double>{s_max_acc},
std::vector<double>{s_max_acc * 10.0});
s_curve.init(std::vector<double>{0.0}, std::vector<double>{total_length});
const double dt = std::max(0.001, static_cast<double>(CYCLE_TIME) / 1000.0);
out_traj.header = in_traj.header;
out_traj.joint_names = in_traj.joint_names;
out_traj.points.clear();
size_t seg_idx = 0;
double t = 0.0;
bool done = false;
const double min_time = total_length / std::max(1e-6, s_max_vel);
const size_t max_iter =
static_cast<size_t>(std::ceil((min_time * 5.0) / dt)) + 1000;
for (size_t iter = 0; iter < max_iter; ++iter) {
const std::vector<double> s_vec = s_curve.update(t);
double s = s_vec.empty() ? 0.0 : s_vec[0];
if (s > total_length) s = total_length;
const double s_dot = s_curve.getVelocity()[0];
const double s_ddot = s_curve.getAcceleration()[0];
while (seg_idx + 1 < s_cum.size() && s > s_cum[seg_idx + 1]) {
++seg_idx;
}
if (seg_idx + 1 >= waypoints.size()) {
seg_idx = waypoints.size() - 2;
}
const double seg_len = s_cum[seg_idx + 1] - s_cum[seg_idx];
double alpha = 0.0;
if (seg_len > kEps) {
alpha = (s - s_cum[seg_idx]) / seg_len;
alpha = std::clamp(alpha, 0.0, 1.0);
}
trajectory_msgs::msg::JointTrajectoryPoint point;
point.positions.resize(dof, 0.0);
point.velocities.resize(dof, 0.0);
point.accelerations.resize(dof, 0.0);
for (size_t j = 0; j < dof; ++j) {
const double q0 = waypoints[seg_idx][j];
const double q1 = waypoints[seg_idx + 1][j];
const double dq = q1 - q0;
point.positions[j] = q0 + alpha * dq;
if (seg_len > kEps) {
const double dqds = dq / seg_len;
point.velocities[j] = s_dot * dqds;
point.accelerations[j] = s_ddot * dqds;
}
}
point.time_from_start = rclcpp::Duration::from_seconds(t);
out_traj.points.push_back(std::move(point));
if (s_curve.isFinished(t) || (total_length - s) <= 1e-6) {
done = true;
break;
}
t += dt;
}
if (!done || out_traj.points.empty()) {
return false;
}
// 确保末点速度/加速度为 0
auto& last = out_traj.points.back();
last.positions = waypoints.back();
last.velocities.assign(dof, 0.0);
last.accelerations.assign(dof, 0.0);
return true;
}
// 辅助函数:更新 joints_info_map_ 从单个轨迹点(区分左右臂)
void ArmControl::UpdateJointsInfo(uint8_t arm_id, const TrajectoryPoint& point)
{

View File

@@ -14,7 +14,10 @@ RobotControlNode::RobotControlNode() : Node("robot_control_node")
is_robot_enabled_ = false;
// 创建发布器和客户端
gpioPub_ = this->create_publisher<control_msgs::msg::DynamicInterfaceGroupValues>("/gpio_command_controller/commands", 10);
left_gpio_pub_ = this->create_publisher<control_msgs::msg::DynamicInterfaceGroupValues>(
"/left_arm_gpio_controller/commands", 10);
right_gpio_pub_ = this->create_publisher<control_msgs::msg::DynamicInterfaceGroupValues>(
"/right_arm_gpio_controller/commands", 10);
switch_client_ = create_client<controller_manager_msgs::srv::SwitchController>("/controller_manager/switch_controller");
imuMsgSub_ = this->create_subscription<ImuMsg>("/openzen/imu_msg", 10,std::bind(&RobotControlNode::ImuMsgCallback, this, std::placeholders::_1));
jointStatesSub_ = this->create_subscription<sensor_msgs::msg::JointState>("/joint_states", 10,std::bind(&RobotControlNode::JointStatesCallback, this, std::placeholders::_1));
@@ -32,12 +35,23 @@ RobotControlNode::RobotControlNode() : Node("robot_control_node")
robotControlManager_);
action_manager_->initialize();
const bool ret = activateController("trajectory_controller");
if (ret) {
RCLCPP_INFO(this->get_logger(), "Activated controller: trajectory_controller");
const bool ret1 = activateController("left_arm_controller");
if (ret1) {
RCLCPP_INFO(this->get_logger(), "Activated left_arm_controller: trajectory_controller");
} else {
RCLCPP_ERROR(this->get_logger(), "Failed to activate controller: trajectory_controller");
RCLCPP_ERROR(this->get_logger(), "Failed to activate left_arm_controller: trajectory_controller");
}
const bool ret2 = activateController("right_arm_controller");
if (ret2) {
RCLCPP_INFO(this->get_logger(), "Activated right_arm_controller: trajectory_controller");
} else {
RCLCPP_ERROR(this->get_logger(), "Failed to activate right_arm_controller: trajectory_controller");
}
motor_enable(0, 1);
// 初始化MoveIt需要在ROS节点创建后调用
@@ -137,52 +151,87 @@ bool RobotControlNode::activateController(const std::string& controller_name)
void RobotControlNode::motor_fault(int id, double fault)
{
if (!gpioPub_) return;
control_msgs::msg::DynamicInterfaceGroupValues msg;
const MotionParameters& motion_params = robotControlManager_.GetMotionParameters();
// id == 0: apply to all joints
// id != 0: apply only to that joint index (1-based), others set to 0
for (size_t i = 0; i < motion_params.all_joint_names_.size(); ++i) {
const std::string& group = motion_params.all_joint_names_[i];
msg.interface_groups.push_back(group);
control_msgs::msg::InterfaceValue v;
v.interface_names = {"fault"};
if (id == 0) {
v.values = {fault};
} else {
v.values = {(static_cast<int>(i) + 1 == id) ? fault : 0.0};
}
msg.interface_values.push_back(v);
std::vector<std::string> left;
std::vector<std::string> right;
if (!split_arm_joint_names_(motion_params, left, right)) {
return;
}
gpioPub_->publish(msg);
const size_t arm_dof = motion_params.arm_dof_;
const int target_index = id - 1;
(void)target_index;
publish_gpio_command_(left_gpio_pub_, left, "fault", id, fault, 0);
publish_gpio_command_(right_gpio_pub_, right, "fault", id, fault, static_cast<int>(arm_dof));
}
void RobotControlNode::motor_enable(int id, double enable)
{
if (!gpioPub_) return;
const MotionParameters& motion_params = robotControlManager_.GetMotionParameters();
std::vector<std::string> left;
std::vector<std::string> right;
if (!split_arm_joint_names_(motion_params, left, right)) {
return;
}
const size_t arm_dof = motion_params.arm_dof_;
const int target_index = id - 1;
(void)target_index;
publish_gpio_command_(left_gpio_pub_, left, "enable", id, enable, 0);
publish_gpio_command_(right_gpio_pub_, right, "enable", id, enable, static_cast<int>(arm_dof));
}
bool RobotControlNode::split_arm_joint_names_(
const MotionParameters& motion_params,
std::vector<std::string>& left,
std::vector<std::string>& right) const
{
left.clear();
right.clear();
if (motion_params.dual_arm_joint_names_.empty() || motion_params.arm_dof_ == 0) {
return false;
}
const size_t arm_dof = motion_params.arm_dof_;
if (motion_params.dual_arm_joint_names_.size() < 2 * arm_dof) {
return false;
}
left.assign(
motion_params.dual_arm_joint_names_.begin(),
motion_params.dual_arm_joint_names_.begin() + arm_dof);
right.assign(
motion_params.dual_arm_joint_names_.begin() + arm_dof,
motion_params.dual_arm_joint_names_.begin() + 2 * arm_dof);
return true;
}
void RobotControlNode::publish_gpio_command_(
const rclcpp::Publisher<control_msgs::msg::DynamicInterfaceGroupValues>::SharedPtr& pub,
const std::vector<std::string>& joints,
const std::string& interface_name,
int id,
double value,
int offset) const
{
if (!pub) return;
control_msgs::msg::DynamicInterfaceGroupValues msg;
const MotionParameters& motion_params = robotControlManager_.GetMotionParameters();
for (size_t i = 0; i < motion_params.all_joint_names_.size(); ++i) {
const std::string& group = motion_params.all_joint_names_[i];
msg.interface_groups.push_back(group);
for (size_t i = 0; i < joints.size(); ++i) {
msg.interface_groups.push_back(joints[i]);
control_msgs::msg::InterfaceValue v;
v.interface_names = {"enable"};
v.interface_names = {interface_name};
if (id == 0) {
v.values = {enable};
v.values = {value};
} else {
v.values = {(static_cast<int>(i) + 1 == id) ? enable : 0.0};
const int global_idx = static_cast<int>(offset + i);
v.values = {(global_idx == (id - 1)) ? value : 0.0};
}
msg.interface_values.push_back(v);
}
gpioPub_->publish(msg);
pub->publish(msg);
}
void RobotControlNode::motor_reset_fault_all()

View File

@@ -1,129 +1,51 @@
#!/usr/bin/env python3
"""
Plot joint trajectory from CSV file saved by follow_jt_executor.cpp
The CSV file contains time and position information for each joint.
"""
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
import csv
import sys
import os
import glob
def plot_trajectory_from_csv(file_path):
"""
Read trajectory CSV file and plot time vs position for each joint.
Args:
file_path: Path to the CSV file
"""
# Check if file exists
if not os.path.exists(file_path):
print(f"Error: File does not exist - {file_path}")
return
# Read CSV file using pandas
try:
df = pd.read_csv(file_path)
except Exception as e:
print(f"Error reading CSV file: {e}")
return
# Check if required columns exist
if 'time' not in df.columns:
print("Error: CSV file must contain 'time' column")
return
# Get time column
time = df['time'].values
# Find all position columns (columns ending with '_position')
position_columns = [col for col in df.columns if col.endswith('_position')]
if not position_columns:
print("Warning: No position columns found in CSV file")
return
# Create figure with subplots
num_joints = len(position_columns)
# If many joints, use a grid layout
if num_joints <= 4:
rows, cols = num_joints, 1
fig, axes = plt.subplots(rows, cols, figsize=(10, 4*num_joints))
if num_joints == 1:
axes = [axes]
elif num_joints <= 8:
rows, cols = 2, 4
fig, axes = plt.subplots(rows, cols, figsize=(16, 8))
axes = axes.flatten()
else:
rows = (num_joints + 3) // 4
cols = 4
fig, axes = plt.subplots(rows, cols, figsize=(16, 4*rows))
axes = axes.flatten()
# Plot each joint
for idx, col in enumerate(position_columns):
joint_name = col.replace('_position', '')
position = df[col].values
ax = axes[idx] if idx < len(axes) else None
if ax is None:
import matplotlib.pyplot as plt
def load_csv(path):
with open(path, "r", newline="") as f:
reader = csv.reader(f)
rows = list(reader)
if not rows or len(rows) < 2:
raise ValueError("CSV is empty or missing data rows")
header = rows[0]
if len(header) < 2 or header[0] != "time":
raise ValueError("Invalid header, expected: time,<joint>_position...")
time = []
series = [[] for _ in header[1:]]
for row in rows[1:]:
if not row:
continue
ax.plot(time, position, linewidth=2, label=joint_name)
ax.set_xlabel('Time (seconds)', fontsize=10)
ax.set_ylabel('Position (rad)', fontsize=10)
ax.set_title(f'{joint_name}', fontsize=12, fontweight='bold')
ax.grid(True, linestyle='--', alpha=0.7)
ax.legend(loc='best')
# Hide unused subplots
for idx in range(len(position_columns), len(axes)):
axes[idx].set_visible(False)
plt.suptitle('Joint Trajectory - Time vs Position', fontsize=14, fontweight='bold', y=0.995)
plt.tight_layout()
plt.show()
# Also create a combined plot with all joints on one figure
plt.figure(figsize=(12, 8))
for col in position_columns:
joint_name = col.replace('_position', '')
position = df[col].values
plt.plot(time, position, linewidth=2, label=joint_name, marker='o', markersize=2)
plt.xlabel('Time (seconds)', fontsize=12)
plt.ylabel('Position (rad)', fontsize=12)
plt.title('All Joints Trajectory - Combined View', fontsize=14, fontweight='bold')
plt.grid(True, linestyle='--', alpha=0.7)
plt.legend(bbox_to_anchor=(1.05, 1), loc='upper left', fontsize=10)
plt.tight_layout()
plt.show()
time.append(float(row[0]))
for i, val in enumerate(row[1:]):
series[i].append(float(val))
return time, header[1:], series
def main():
"""Main function"""
if len(sys.argv) > 1:
# Use file path from command line argument
file_path = sys.argv[1]
else:
# Try to find the most recent trajectory file in /tmp
pattern = "/tmp/trajectory_20260117_121313.csv"
files = glob.glob(pattern)
if not files:
print(f"Error: No trajectory CSV files found in /tmp")
print(f"Usage: {sys.argv[0]} <csv_file_path>")
print(f"Or place a trajectory_*.csv file in /tmp/")
return
# Get the most recent file
file_path = max(files, key=os.path.getmtime)
print(f"Using most recent file: {file_path}")
plot_trajectory_from_csv(file_path)
if len(sys.argv) < 2:
print("Usage: plot_trajectory_csv.py /tmp/trajectory_*.csv")
sys.exit(1)
path = sys.argv[1]
time, labels, series = load_csv(path)
for label, data in zip(labels, series):
plt.plot(time, data, label=label)
plt.xlabel("time (s)")
plt.ylabel("position (rad)")
plt.title(path)
plt.grid(True)
plt.legend()
plt.show()
if __name__ == "__main__":
main()