movej for single arm and dual arm

This commit is contained in:
Your Name
2026-02-26 18:11:25 +08:00
parent 64ee485100
commit 3c1f82d1ae
11 changed files with 495 additions and 156 deletions

View File

@@ -4,11 +4,11 @@ moveit_controller_manager: moveit_simple_controller_manager/MoveItSimpleControll
moveit_simple_controller_manager:
controller_names:
- arm_left_controller
- arm_right_controller
- left_arm_controller
- right_arm_controller
# - dual_arm_controller
arm_left_controller:
left_arm_controller:
type: FollowJointTrajectory
joints:
- left_arm_joint_1
@@ -19,7 +19,7 @@ moveit_simple_controller_manager:
- left_arm_joint_6
action_ns: follow_joint_trajectory
default: true
arm_right_controller:
right_arm_controller:
type: FollowJointTrajectory
joints:
- right_arm_joint_1

View File

@@ -3,11 +3,11 @@ controller_manager:
ros__parameters:
update_rate: 100 # Hz
arm_left_controller:
left_arm_controller:
type: joint_trajectory_controller/JointTrajectoryController
arm_right_controller:
right_arm_controller:
type: joint_trajectory_controller/JointTrajectoryController
@@ -18,7 +18,7 @@ controller_manager:
joint_state_broadcaster:
type: joint_state_broadcaster/JointStateBroadcaster
arm_left_controller:
left_arm_controller:
ros__parameters:
joints:
- left_arm_joint_1
@@ -32,7 +32,14 @@ arm_left_controller:
state_interfaces:
- position
- velocity
arm_right_controller:
state_publish_rate: 50.0
action_monitor_rate: 20.0
allow_partial_joints_goal: false
stop_trajectory_duration: 0.25
constraints:
goal_time: 0.5
stopped_velocity_tolerance: 0.01
right_arm_controller:
ros__parameters:
joints:
- right_arm_joint_1
@@ -46,6 +53,13 @@ arm_right_controller:
state_interfaces:
- position
- velocity
state_publish_rate: 50.0
action_monitor_rate: 20.0
allow_partial_joints_goal: false
stop_trajectory_duration: 0.25
constraints:
goal_time: 0.5
stopped_velocity_tolerance: 0.01
# dual_arm_controller:
# ros__parameters:
# joints:

View File

@@ -117,7 +117,7 @@ private:
rclcpp_action::Client<FollowJTA>::SharedPtr right_fjt_client_;
bool save_trajectory_enabled_{true};
bool trajectory_execution_enabled_{false};
bool trajectory_execution_enabled_{true};
};
} // namespace robot_control

View File

@@ -195,6 +195,9 @@ namespace robot_control
uint8_t current_arm_id_; ///< 当前控制的臂ID0=左臂1=右臂2=双臂)
// 是否启用 S 型曲线时间参数化(默认关闭,便于对比测试)
bool enable_s_curve_time_parameterization_ {true};
/**
* @brief 根据arm_id获取对应的MoveGroup接口
* @param arm_id 手臂ID0=左臂1=右臂2=双臂)

View File

@@ -18,7 +18,7 @@ def generate_launch_description():
action_name_arg = DeclareLaunchArgument(
'action_name',
default_value='/arm_right_controller/follow_joint_trajectory',
default_value='/right_arm_controller/follow_joint_trajectory',
description='Action server name for FollowJointTrajectory'
)

View File

@@ -35,13 +35,13 @@ class DualArmActionClient(Node):
self.get_logger().info('Action server已就绪')
return True
def send_goal(self, arm_motion_params_list, velocity_scaling=0.5):
def send_goal(self, arm_motion_params_list, velocity_scaling=50):
"""
发送运动目标
Args:
arm_motion_params_list: ArmMotionParams消息列表
velocity_scaling: 速度缩放因子 [0,1]
velocity_scaling: 速度缩放因子 [1,100]
"""
# reset per-goal futures
self._goal_handle = None
@@ -122,7 +122,7 @@ class DualArmActionClient(Node):
def send_goal_and_wait(
self,
arm_motion_params_list,
velocity_scaling=0.5,
velocity_scaling=50,
result_timeout_sec=30.0,
):
"""
@@ -158,7 +158,7 @@ class DualArmActionClient(Node):
return bool(result.success), str(result.message)
def create_arm_motion_param(arm_id, motion_type, target_joints=None, target_pose=None, blend_radius=0.0):
def create_arm_motion_param(arm_id, motion_type, target_joints=None, target_pose=None, blend_radius=0):
"""
创建ArmMotionParams消息
@@ -223,7 +223,7 @@ def example_left_and_right_arm_joint_motion():
)
ok, msg = client.send_goal_and_wait(
arm_motion_params_list=[left_param_up],
velocity_scaling=0.5,
velocity_scaling= 50,
result_timeout_sec=30.0,
)
client.get_logger().info(f'左臂 move-to 结果: ok={ok}, msg={msg}')
@@ -238,7 +238,7 @@ def example_left_and_right_arm_joint_motion():
)
ok, msg = client.send_goal_and_wait(
arm_motion_params_list=[left_param_home],
velocity_scaling=0.5,
velocity_scaling=50,
result_timeout_sec=30.0,
)
client.get_logger().info(f'左臂回零结果: ok={ok}, msg={msg}')
@@ -260,7 +260,7 @@ def example_left_and_right_arm_joint_motion():
)
ok, msg = client.send_goal_and_wait(
arm_motion_params_list=[right_param_up],
velocity_scaling=0.5,
velocity_scaling=50,
result_timeout_sec=30.0,
)
client.get_logger().info(f'右臂 move-to 结果: ok={ok}, msg={msg}')
@@ -274,7 +274,7 @@ def example_left_and_right_arm_joint_motion():
)
ok, msg = client.send_goal_and_wait(
arm_motion_params_list=[right_param_home],
velocity_scaling=0.5,
velocity_scaling=50,
result_timeout_sec=30.0,
)
client.get_logger().info(f'右臂回零结果: ok={ok}, msg={msg}')
@@ -301,7 +301,7 @@ def example_left_and_right_arm_joint_motion():
ok, msg = client.send_goal_and_wait(
arm_motion_params_list=[left_param_dual, right_param_dual],
velocity_scaling=0.5,
velocity_scaling=50,
result_timeout_sec=30.0,
)
client.get_logger().info(f'双臂同时运动结果: ok={ok}, msg={msg}')

View File

@@ -63,6 +63,6 @@ if __name__ == "__main__":
print("用法: python plot_joint_trajectory_multi.py <数据文件路径>")
sys.exit(1)
file_path = sys.argv[1]
file_path = "/tmp/trajectory_dual_right_20260226_151304.csv"
plot_joint_trajectories(file_path)

View File

@@ -5,6 +5,7 @@
#include <chrono>
#include <ctime>
#include <algorithm>
#include <cmath>
#include <fstream>
#include <iomanip>
#include <sstream>
@@ -13,6 +14,129 @@
#include "core/robot_control_manager.hpp"
#include "core/common_enum.hpp"
namespace
{
inline double duration_to_sec(const builtin_interfaces::msg::Duration& d)
{
return static_cast<double>(d.sec) + static_cast<double>(d.nanosec) * 1e-9;
}
inline void sec_to_duration(double sec, builtin_interfaces::msg::Duration* out)
{
if (!out) return;
if (sec < 0.0) sec = 0.0;
const int64_t s = static_cast<int64_t>(std::floor(sec));
const double rem = sec - static_cast<double>(s);
const uint32_t ns = static_cast<uint32_t>(std::round(rem * 1e9));
out->sec = static_cast<int32_t>(s);
out->nanosec = ns;
}
inline bool goal_velocity_to_scale(double goal_value, double* out_scale, std::string* error_msg)
{
if (!out_scale) {
if (error_msg) *error_msg = "Null out_scale";
return false;
}
if (!std::isfinite(goal_value)) {
if (error_msg) *error_msg = "velocity_scaling is NaN/Inf";
return false;
}
// Backward compatibility:
// - Some clients may send [0,1] scaling.
// - Some clients may send [1,100] percent.
// The action definition in repo says int32 percent, but generated type is currently double.
if (goal_value > 1.0) {
if (goal_value < 1.0 || goal_value > 100.0) {
if (error_msg) *error_msg = "velocity_scaling percent out of range [1,100]: " + std::to_string(goal_value);
return false;
}
*out_scale = std::clamp(goal_value / 100.0, 0.01, 1.0);
return true;
}
if (goal_value <= 0.0 || goal_value > 1.0) {
if (error_msg) *error_msg = "velocity_scaling scale out of range (0,1]: " + std::to_string(goal_value);
return false;
}
*out_scale = std::clamp(goal_value, 0.01, 1.0);
return true;
}
inline void ensure_nonzero_start_time(trajectory_msgs::msg::JointTrajectory* traj, double min_start_sec)
{
if (!traj || traj->points.empty()) return;
if (min_start_sec <= 0.0) min_start_sec = 0.01;
// If first point is at t=0 and we have more than one point, drop it (common cause of startup jerk).
const double t0 = duration_to_sec(traj->points.front().time_from_start);
if (t0 <= 1e-9 && traj->points.size() > 1) {
traj->points.erase(traj->points.begin());
}
if (traj->points.empty()) return;
const double t_first = duration_to_sec(traj->points.front().time_from_start);
if (t_first >= min_start_sec) return;
const double shift = (min_start_sec - t_first);
for (auto& p : traj->points) {
const double t = duration_to_sec(p.time_from_start) + shift;
sec_to_duration(t, &p.time_from_start);
}
}
inline bool get_current_positions(
const robot_control::RobotControlManager* mgr,
const std::vector<std::string>& joint_names,
std::vector<double>* out_positions,
std::string* error_msg)
{
if (!mgr || !out_positions) {
if (error_msg) *error_msg = "Null manager or out_positions";
return false;
}
out_positions->clear();
out_positions->reserve(joint_names.size());
const auto& mp = mgr->GetMotionParameters();
const auto& jp = mgr->GetJointPositions();
for (const auto& jn : joint_names) {
const auto it = mp.joint_name_to_index_.find(jn);
if (it == mp.joint_name_to_index_.end()) {
if (error_msg) *error_msg = "Joint name not mapped: " + jn;
return false;
}
const size_t idx = it->second;
if (idx >= jp.size()) {
if (error_msg) *error_msg = "Joint index out of range for " + jn;
return false;
}
out_positions->push_back(jp[idx]);
}
return true;
}
inline trajectory_msgs::msg::JointTrajectory make_hold_trajectory(
const std::vector<std::string>& joint_names,
const std::vector<double>& positions,
double hold_time_sec)
{
trajectory_msgs::msg::JointTrajectory traj;
traj.joint_names = joint_names;
trajectory_msgs::msg::JointTrajectoryPoint p;
p.positions = positions;
p.velocities.assign(joint_names.size(), 0.0);
p.accelerations.assign(joint_names.size(), 0.0);
sec_to_duration(std::max(0.05, hold_time_sec), &p.time_from_start);
traj.points.push_back(std::move(p));
return traj;
}
} // namespace
namespace robot_control
{
DualArmAction::DualArmAction(const ActionContext& ctx)
@@ -30,28 +154,6 @@ 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>(
@@ -72,9 +174,24 @@ rclcpp_action::GoalResponse DualArmAction::handle_goal_(
if (!ctx_.robot_control_manager) return rclcpp_action::GoalResponse::REJECT;
RCLCPP_INFO(ctx_.node->get_logger(),
"Received DualArm goal: velocity_scaling=%.2f, arm_motion_params_count=%zu",
"Received DualArm goal: velocity_scaling=%d, arm_motion_params_count=%zu",
goal->velocity_scaling, goal->arm_motion_params.size());
// 关节状态未稳定前进行规划/执行,容易出现起点跳变(看起来像启停抖动)
if (!ctx_.robot_control_manager->RobotInitFinished()) {
RCLCPP_ERROR(ctx_.node->get_logger(),
"DualArm goal rejected: robot init not finished (joint_states not stabilized yet)");
return rclcpp_action::GoalResponse::REJECT;
}
double scale = 0.0;
std::string scale_err;
if (!goal_velocity_to_scale(goal->velocity_scaling, &scale, &scale_err)) {
RCLCPP_ERROR(ctx_.node->get_logger(), "DualArm goal rejected: %s", scale_err.c_str());
return rclcpp_action::GoalResponse::REJECT;
}
RCLCPP_INFO(ctx_.node->get_logger(), "DualArm derived velocity_scale=%.3f", scale);
// 统一检查 MoveIt 规划器占用:同一时刻只能进行一组规划
if (planner_busy_.load()) {
RCLCPP_ERROR(ctx_.node->get_logger(), "DualArm goal rejected: MoveIt planner is busy");
@@ -398,7 +515,7 @@ void DualArmAction::save_trajectory_to_file_(
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 << "_"
ss << "trajectory_" << tag << "_"
<< std::put_time(std::localtime(&time_t), "%Y%m%d_%H%M%S") << ".csv";
const std::string filename = ss.str();
@@ -462,10 +579,19 @@ void DualArmAction::execute_single_arm_(
// 使用单臂规划接口
ArmMotionParams arm_params = arm_param;
double velocity_scaling = 0.0;
std::string scale_err;
if (!goal_velocity_to_scale(goal->velocity_scaling, &velocity_scaling, &scale_err)) {
planner_busy_.store(false);
result->success = false;
result->message = std::string("Invalid velocity_scaling: ") + scale_err;
goal_handle->abort(result);
return;
}
if (!ctx_.robot_control_manager->PlanArmMotion(
arm_params,
goal->velocity_scaling,
velocity_scaling,
acceleration_scaling)) {
planner_busy_.store(false);
result->success = false;
@@ -483,6 +609,8 @@ void DualArmAction::execute_single_arm_(
goal_handle->abort(result);
return;
}
// Normalize for startup smoothness (avoid t=0 jump)
ensure_nonzero_start_time(&traj_single, 0.05);
save_trajectory_to_file_(traj_single, (arm_id == 0) ? "left_arm" : "right_arm");
if (!trajectory_execution_enabled_) {
result->success = false;
@@ -534,7 +662,19 @@ void DualArmAction::execute_single_arm_(
[&res_future]() {
return res_future.wait_for(std::chrono::milliseconds(1)) == std::future_status::ready;
},
[&fjt_client, &goal_handle_arm, &arm_id, this]() {
[&fjt_client, &goal_handle_arm, &arm_id, joint_names = traj_single.joint_names, this]() {
// Soft stop: send a short "hold current position" trajectory to reduce jerk on cancel.
if (ctx_.robot_control_manager) {
std::vector<double> cur;
std::string err;
if (get_current_positions(ctx_.robot_control_manager, joint_names, &cur, &err)) {
FollowJTA::Goal stop_goal;
stop_goal.trajectory = make_hold_trajectory(joint_names, cur, 0.2);
(void)fjt_client->async_send_goal(stop_goal);
} else if (ctx_.node) {
RCLCPP_WARN(ctx_.node->get_logger(), "Soft stop skipped (single arm): %s", err.c_str());
}
}
(void)fjt_client->async_cancel_goal(goal_handle_arm);
if (arm_id == 0) executing_left_.store(false);
else executing_right_.store(false);
@@ -615,9 +755,19 @@ void DualArmAction::execute_dual_arm_(
return;
}
double velocity_scaling = 0.0;
std::string scale_err;
if (!goal_velocity_to_scale(goal->velocity_scaling, &velocity_scaling, &scale_err)) {
planner_busy_.store(false);
result->success = false;
result->message = std::string("Invalid velocity_scaling: ") + scale_err;
goal_handle->abort(result);
return;
}
if (!ctx_.robot_control_manager->PlanDualArmJointMotion(
need_left, left_target, need_right, right_target,
goal->velocity_scaling, acceleration_scaling))
velocity_scaling, acceleration_scaling))
{
planner_busy_.store(false);
result->success = false;
@@ -642,6 +792,8 @@ void DualArmAction::execute_dual_arm_(
trajectory_msgs::msg::JointTrajectory traj_left;
trajectory_msgs::msg::JointTrajectory traj_right;
split_dual_arm_trajectory_(motion_params, ARM_DOF, traj, &traj_left, &traj_right);
ensure_nonzero_start_time(&traj_left, 0.05);
ensure_nonzero_start_time(&traj_right, 0.05);
save_trajectory_to_file_(traj_left, "dual_left");
save_trajectory_to_file_(traj_right, "dual_right");
if (!trajectory_execution_enabled_) {
@@ -712,7 +864,27 @@ void DualArmAction::execute_dual_arm_(
(right_res_future.wait_for(std::chrono::milliseconds(1)) == std::future_status::ready);
return left_done && right_done;
},
[&left_handle, &right_handle, this]() {
[&left_handle, &right_handle, left_joint_names = traj_left.joint_names, right_joint_names = traj_right.joint_names, this]() {
// Soft stop for both arms before cancel.
if (ctx_.robot_control_manager) {
std::vector<double> cur_left;
std::vector<double> cur_right;
std::string err;
if (get_current_positions(ctx_.robot_control_manager, left_joint_names, &cur_left, &err)) {
FollowJTA::Goal stop_goal;
stop_goal.trajectory = make_hold_trajectory(left_joint_names, cur_left, 0.2);
(void)left_fjt_client_->async_send_goal(stop_goal);
} else if (ctx_.node) {
RCLCPP_WARN(ctx_.node->get_logger(), "Soft stop skipped (left arm): %s", err.c_str());
}
if (get_current_positions(ctx_.robot_control_manager, right_joint_names, &cur_right, &err)) {
FollowJTA::Goal stop_goal;
stop_goal.trajectory = make_hold_trajectory(right_joint_names, cur_right, 0.2);
(void)right_fjt_client_->async_send_goal(stop_goal);
} else if (ctx_.node) {
RCLCPP_WARN(ctx_.node->get_logger(), "Soft stop skipped (right arm): %s", err.c_str());
}
}
(void)left_fjt_client_->async_cancel_goal(left_handle);
(void)right_fjt_client_->async_cancel_goal(right_handle);
executing_.store(false);

View File

@@ -16,6 +16,75 @@
namespace robot_control
{
namespace
{
inline double duration_to_sec(const builtin_interfaces::msg::Duration& d)
{
return static_cast<double>(d.sec) + static_cast<double>(d.nanosec) * 1e-9;
}
inline void sec_to_duration(double sec, builtin_interfaces::msg::Duration* out)
{
if (!out) return;
if (!std::isfinite(sec) || sec < 0.0) sec = 0.0;
const int64_t s = static_cast<int64_t>(std::floor(sec));
const double rem = sec - static_cast<double>(s);
const uint32_t ns = static_cast<uint32_t>(std::round(rem * 1e9));
out->sec = static_cast<int32_t>(s);
out->nanosec = ns;
}
inline void normalize_and_fix_time_from_start(trajectory_msgs::msg::JointTrajectory* traj, double default_dt)
{
if (!traj || traj->points.empty()) return;
if (!std::isfinite(default_dt) || default_dt <= 0.0) default_dt = 0.01;
// Shift so that the first point starts at t=0 (GetInterpolatedPoint assumes this).
const double t0 = duration_to_sec(traj->points.front().time_from_start);
bool need_rebuild = (!std::isfinite(t0) || t0 < 0.0);
std::vector<double> t;
t.reserve(traj->points.size());
for (const auto& p : traj->points) {
const double ti = duration_to_sec(p.time_from_start);
t.push_back(ti);
if (!std::isfinite(ti) || ti < 0.0) {
need_rebuild = true;
}
}
if (!need_rebuild) {
for (auto& ti : t) {
ti -= t0;
}
// Ensure strictly increasing and non-zero duration.
for (size_t i = 1; i < t.size(); ++i) {
if (!(t[i] > t[i - 1] + 1e-9)) {
need_rebuild = true;
break;
}
}
if (t.size() >= 2 && t.back() <= default_dt * 0.5) {
need_rebuild = true;
}
}
if (need_rebuild) {
t.clear();
t.reserve(traj->points.size());
double acc = 0.0;
for (size_t i = 0; i < traj->points.size(); ++i) {
t.push_back(acc);
acc += default_dt;
}
}
for (size_t i = 0; i < traj->points.size(); ++i) {
sec_to_duration(t[i], &traj->points[i].time_from_start);
}
}
} // namespace
ArmControl::ArmControl(
const std::vector<std::string>& joint_names,
const std::map<std::string, JointInfo>& joints_info_map,
@@ -258,27 +327,34 @@ bool ArmControl::timeParameterizeAndStore_(
double velocity_scaling,
double 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;
// 1) 时间参数化可选S 型曲线
if (enable_s_curve_time_parameterization_) {
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(),
"S-curve time parameterization failed for arm_id %d",
static_cast<int>(arm_id));
if (!s_curve_ok) {
if (node_) {
RCLCPP_ERROR(node_->get_logger(),
"S-curve time parameterization failed for arm_id %d",
static_cast<int>(arm_id));
}
return false;
}
return false;
} else {
// 不进行 S 曲线:直接使用 MoveIt 规划结果自带的 time_from_start
// 但要保证:从 0 开始、严格递增、总时长 > 0否则插补会瞬间结束导致跳变
const double default_dt = std::max(0.001, static_cast<double>(CYCLE_TIME) / 1000.0);
normalize_and_fix_time_from_start(&plan.trajectory_.joint_trajectory, default_dt);
}
// 2) 存入内部 TrajectoryState使用 msg 内的 time_from_start并保留 vel/acc
@@ -361,8 +437,10 @@ bool ArmControl::sCurveTimeParameterize_(
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);
// Leave vel/acc empty so downstream controller does not attempt
// higher-order interpolation with potentially inconsistent derivatives.
point.velocities.clear();
point.accelerations.clear();
point.time_from_start = rclcpp::Duration::from_seconds(0.0);
out_traj.points.push_back(std::move(point));
return true;
@@ -397,8 +475,8 @@ bool ArmControl::sCurveTimeParameterize_(
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.velocities.clear();
point.accelerations.clear();
point.time_from_start = rclcpp::Duration::from_seconds(0.0);
out_traj.points.push_back(std::move(point));
return true;
@@ -449,10 +527,15 @@ bool ArmControl::sCurveTimeParameterize_(
out_traj.joint_names = in_traj.joint_names;
out_traj.points.clear();
size_t seg_idx = 0;
// First pass: sample the scalar path progress s(t).
std::vector<double> t_samples;
std::vector<double> s_samples;
double t = 0.0;
bool done = false;
const double min_time = total_length / std::max(1e-6, s_max_vel);
t_samples.reserve(static_cast<size_t>(std::ceil(min_time / dt)) + 5);
s_samples.reserve(t_samples.capacity());
const size_t max_iter =
static_cast<size_t>(std::ceil((min_time * 5.0) / dt)) + 1000;
@@ -461,8 +544,34 @@ bool ArmControl::sCurveTimeParameterize_(
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];
t_samples.push_back(t);
s_samples.push_back(s);
if (s_curve.isFinished(t) || (total_length - s) <= 1e-6) {
done = true;
break;
}
t += dt;
}
if (!done || t_samples.empty() || s_samples.empty()) {
return false;
}
// If the scalar profile under-travels (s_end < total_length) we scale s(t)
// so that the last sample exactly reaches total_length, avoiding the last-cycle jump.
const double s_end = s_samples.back();
if (!(s_end > kEps)) {
return false;
}
const double scale = total_length / s_end;
// Second pass: convert scaled s(t) to joint positions along piecewise-linear path.
size_t seg_idx = 0;
out_traj.points.reserve(t_samples.size());
for (size_t i = 0; i < t_samples.size(); ++i) {
double s = s_samples[i] * scale;
if (s > total_length) s = total_length;
while (seg_idx + 1 < s_cum.size() && s > s_cum[seg_idx + 1]) {
++seg_idx;
@@ -480,41 +589,25 @@ bool ArmControl::sCurveTimeParameterize_(
trajectory_msgs::msg::JointTrajectoryPoint point;
point.positions.resize(dof, 0.0);
point.velocities.resize(dof, 0.0);
point.accelerations.resize(dof, 0.0);
point.velocities.clear();
point.accelerations.clear();
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);
point.time_from_start = rclcpp::Duration::from_seconds(t_samples[i]);
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;
// Ensure the last point is exactly the last waypoint (numerical guard).
if (!out_traj.points.empty()) {
out_traj.points.back().positions = waypoints.back();
}
// 确保末点速度/加速度为 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;
}

View File

@@ -1,5 +1,6 @@
#include <thread>
#include "rclcpp/rclcpp.hpp"
#include "rclcpp/executors/single_threaded_executor.hpp"
#include "core/robot_control_node.hpp"
#include "actions/action_manager.hpp"
#include "core/motion_parameters.hpp"
@@ -51,8 +52,6 @@ RobotControlNode::RobotControlNode() : Node("robot_control_node")
RCLCPP_ERROR(this->get_logger(), "Failed to activate right_arm_controller: trajectory_controller");
}
motor_enable(0, 1);
// 初始化MoveIt需要在ROS节点创建后调用
if (robotControlManager_.InitializeArmMoveIt(this)) {
@@ -145,7 +144,30 @@ bool RobotControlNode::activateController(const std::string& controller_name)
request->timeout.nanosec = 0;
auto future = switch_client_->async_send_request(request);
rclcpp::executors::SingleThreadedExecutor exec;
exec.add_node(this->get_node_base_interface());
const auto ret = exec.spin_until_future_complete(future, std::chrono::seconds(5));
if (ret != rclcpp::FutureReturnCode::SUCCESS) {
RCLCPP_ERROR(this->get_logger(),
"activateController: switch_controller timeout/failure for '%s'",
controller_name.c_str());
return false;
}
const auto resp = future.get();
if (!resp) {
RCLCPP_ERROR(this->get_logger(),
"activateController: null response for '%s'",
controller_name.c_str());
return false;
}
if (!resp->ok) {
RCLCPP_ERROR(this->get_logger(),
"activateController: switch_controller returned ok=false for '%s'",
controller_name.c_str());
return false;
}
return true;
}
@@ -241,8 +263,3 @@ void RobotControlNode::motor_reset_fault_all()
motor_fault(0, 0);
}
void RobotControlNode::motor_enable_all(double enable)
{
motor_enable(0, enable);
usleep(20000);
}

View File

@@ -247,8 +247,6 @@ std::vector<double> S_CurveTrajectory::update(double time)
double dir = (target_pos_[i] - start_pos_[i]) > 0 ? 1.0 : -1.0;
double j = max_jerk_[i];
double a_max = max_acceleration_[i];
double v_max_axis = v_max_[i];
double pos_offset = 0.0;
double vel = 0.0;
double acc = 0.0;
@@ -256,61 +254,103 @@ std::vector<double> S_CurveTrajectory::update(double time)
// 使用统一时间,但各轴的阶段时间可能不同
double T1 = T1_[i], T2 = T2_[i], T3 = T3_[i], T4 = T4_[i];
double T5 = T5_[i], T6 = T6_[i];
double T7 = T7_[i];
if (t <= T1) {
// 阶段1加加速jerk = +j
double dt = t;
acc = dir * j * dt;
vel = 0.5 * dir * j * dt * dt;
pos_offset = dir * j * dt * dt * dt / 6.0;
} else if (t <= T1 + T2) {
// 阶段2匀加速jerk = 0, acc = max_a
double dt = t - T1;
acc = dir * a_max;
vel = 0.5 * dir * j * T1 * T1 + dir * a_max * dt;
pos_offset = dir * j * T1 * T1 * T1 / 6.0 + 0.5 * dir * j * T1 * T1 * dt + 0.5 * dir * a_max * dt * dt;
} else if (t <= T1 + T2 + T3) {
// 阶段3减加速jerk = -j
double dt = t - T1 - T2;
acc = dir * a_max - dir * j * dt;
double v_T2 = 0.5 * dir * j * T1 * T1 + dir * a_max * T2;
vel = v_T2 + dir * a_max * dt - 0.5 * dir * j * dt * dt;
double s_T2 = dir * j * T1 * T1 * T1 / 6.0 + 0.5 * dir * j * T1 * T1 * T2 + 0.5 * dir * a_max * T2 * T2;
pos_offset = s_T2 + v_T2 * dt + 0.5 * dir * a_max * dt * dt - dir * j * dt * dt * dt / 6.0;
} else if (t <= T1 + T2 + T3 + T4) {
// 阶段4匀速jerk = 0, acc = 0
double dt = t - T1 - T2 - T3;
acc = 0.0;
vel = dir * v_max_axis;
double s_T3 = dir * j * T1 * T1 * T1 / 6.0 + 0.5 * dir * j * T1 * T1 * (T2 + T3) + 0.5 * dir * a_max * (T2 * T2 + 2 * T2 * T3) - dir * j * T3 * T3 * T3 / 6.0;
pos_offset = s_T3 + dir * v_max_axis * dt;
} else if (t <= T1 + T2 + T3 + T4 + T5) {
// 阶段5加减速jerk = -j加速度从0到-max_a
double dt = t - T1 - T2 - T3 - T4;
acc = -dir * j * dt;
vel = dir * v_max_axis - 0.5 * dir * j * dt * dt;
double s_T4 = dir * j * T1 * T1 * T1 / 6.0 + 0.5 * dir * j * T1 * T1 * (T2 + T3) + 0.5 * dir * a_max * (T2 * T2 + 2 * T2 * T3) - dir * j * T3 * T3 * T3 / 6.0 + dir * v_max_axis * T4;
pos_offset = s_T4 + dir * v_max_axis * dt - dir * j * dt * dt * dt / 6.0;
} else if (t <= T1 + T2 + T3 + T4 + T5 + T6) {
// 阶段6匀减速jerk = 0, acc = -max_a
double dt = t - T1 - T2 - T3 - T4 - T5;
acc = -dir * a_max;
double v_T5 = dir * v_max_axis - 0.5 * dir * j * T5 * T5;
vel = v_T5 - dir * a_max * dt;
double s_T5 = dir * j * T1 * T1 * T1 / 6.0 + 0.5 * dir * j * T1 * T1 * (T2 + T3) + 0.5 * dir * a_max * (T2 * T2 + 2 * T2 * T3) - dir * j * T3 * T3 * T3 / 6.0 + dir * v_max_axis * T4 - dir * j * T5 * T5 * T5 / 6.0;
pos_offset = s_T5 + v_T5 * dt - 0.5 * dir * a_max * dt * dt;
// Use progress variables in positive direction, then apply sign.
const double j_abs = std::abs(j);
const double a_abs = std::abs(a_max);
const double sign = (dir >= 0.0) ? 1.0 : -1.0;
auto clamp01 = [](double x) { return std::clamp(x, 0.0, 1.0); };
(void)clamp01;
const double t1 = T1;
const double t2 = T2;
const double t3 = T3;
const double t4 = T4;
const double t5 = T5;
const double t6 = T6;
const double t7 = T7;
// Boundary values (positive direction)
const double s1 = j_abs * t1 * t1 * t1 / 6.0;
const double v1 = 0.5 * j_abs * t1 * t1;
const double s2 = s1 + v1 * t2 + 0.5 * a_abs * t2 * t2;
const double v2 = v1 + a_abs * t2;
const double s3 = s2 + v2 * t3 + 0.5 * a_abs * t3 * t3 - j_abs * t3 * t3 * t3 / 6.0;
const double v3 = v2 + a_abs * t3 - 0.5 * j_abs * t3 * t3;
const double s4 = s3 + v3 * t4;
const double s5 = s4 + v3 * t5 - j_abs * t5 * t5 * t5 / 6.0;
const double v5 = v3 - 0.5 * j_abs * t5 * t5;
const double s6 = s5 + v5 * t6 - 0.5 * a_abs * t6 * t6;
const double v6 = v5 - a_abs * t6;
double s_prog = 0.0; // [0, dist]
double v_prog = 0.0;
double a_prog = 0.0;
if (t <= t1) {
// Phase 1: jerk +j
const double dt = t;
a_prog = j_abs * dt;
v_prog = 0.5 * j_abs * dt * dt;
s_prog = j_abs * dt * dt * dt / 6.0;
} else if (t <= t1 + t2) {
// Phase 2: const acc +a
const double dt = t - t1;
a_prog = a_abs;
v_prog = v1 + a_abs * dt;
s_prog = s1 + v1 * dt + 0.5 * a_abs * dt * dt;
} else if (t <= t1 + t2 + t3) {
// Phase 3: jerk -j
const double dt = t - t1 - t2;
a_prog = a_abs - j_abs * dt;
v_prog = v2 + a_abs * dt - 0.5 * j_abs * dt * dt;
s_prog = s2 + v2 * dt + 0.5 * a_abs * dt * dt - j_abs * dt * dt * dt / 6.0;
} else if (t <= t1 + t2 + t3 + t4) {
// Phase 4: const vel
const double dt = t - t1 - t2 - t3;
a_prog = 0.0;
v_prog = v3;
s_prog = s3 + v3 * dt;
} else if (t <= t1 + t2 + t3 + t4 + t5) {
// Phase 5: jerk -j (acc 0 -> -a)
const double dt = t - t1 - t2 - t3 - t4;
a_prog = -j_abs * dt;
v_prog = v3 - 0.5 * j_abs * dt * dt;
s_prog = s4 + v3 * dt - j_abs * dt * dt * dt / 6.0;
} else if (t <= t1 + t2 + t3 + t4 + t5 + t6) {
// Phase 6: const acc -a
const double dt = t - t1 - t2 - t3 - t4 - t5;
a_prog = -a_abs;
v_prog = v5 - a_abs * dt;
s_prog = s5 + v5 * dt - 0.5 * a_abs * dt * dt;
} else {
// 阶段7减减速jerk = +j加速度从-max_a到0
double dt = t - T1 - T2 - T3 - T4 - T5 - T6;
acc = -dir * a_max + dir * j * dt;
double v_T6 = dir * v_max_axis - 0.5 * dir * j * T5 * T5 - dir * a_max * T6;
vel = v_T6 - dir * a_max * dt + 0.5 * dir * j * dt * dt;
// 简化:直接使用目标位置
pos_offset = dist;
acc = 0.0;
vel = 0.0;
// Phase 7: jerk +j (acc -a -> 0)
double dt = t - t1 - t2 - t3 - t4 - t5 - t6;
if (dt < 0.0) dt = 0.0;
if (t7 > 0.0) dt = std::min(dt, t7);
a_prog = -a_abs + j_abs * dt;
v_prog = v6 - a_abs * dt + 0.5 * j_abs * dt * dt;
s_prog = s6 + v6 * dt - 0.5 * a_abs * dt * dt + j_abs * dt * dt * dt / 6.0;
}
if (!std::isfinite(s_prog) || !std::isfinite(v_prog) || !std::isfinite(a_prog)) {
s_prog = 0.0;
v_prog = 0.0;
a_prog = 0.0;
}
s_prog = std::clamp(s_prog, 0.0, dist);
pos_offset = sign * s_prog;
vel = sign * v_prog;
acc = sign * a_prog;
current_pos_[i] = start_pos_[i] + pos_offset;
current_velocity_[i] = vel;
current_acceleration_[i] = acc;