add wust typr mpc and mutipule x

This commit is contained in:
cyy_mac
2026-03-27 03:41:42 +08:00
parent 2c64655fae
commit 7dcb53bb77
192 changed files with 29571 additions and 9 deletions

View File

@@ -32,6 +32,7 @@
#include "rm_interfaces/msg/target.hpp"
#include "rm_utils/math/manual_compensator.hpp"
#include "rm_utils/math/trajectory_compensator.hpp"
#include "armor_solver/trajectory_planner.hpp"
namespace fyt::auto_aim {
// Solver class used to solve the gimbal command from tracked target
@@ -53,6 +54,7 @@ public:
const std::vector<Eigen::Vector3d>& getArmorPositions() const noexcept {
return cached_armor_positions_;
}
TrajectoryDebug getTrajectoryDebug() const noexcept;
void setBulletSpeed(double bullet_speed) noexcept;
void updateRuntimeParams(double max_tracking_v_yaw,
double prediction_delay,
@@ -61,6 +63,7 @@ public:
double min_switching_v_yaw,
double shooting_range_w,
double shooting_range_h) noexcept;
void setTrajectoryType(const std::string& type, std::weak_ptr<rclcpp::Node> node = {});
private:
// Get the armor positions from the target robot
@@ -97,6 +100,8 @@ private:
std::unique_ptr<TrajectoryCompensator> trajectory_compensator_;
std::unique_ptr<ManualCompensator> manual_compensator_;
std::unique_ptr<TrajectoryPlanner> planner_;
TrajectoryPlanner::Type planner_type_ = TrajectoryPlanner::Type::LINEAR;
std::array<double, 3> rpy_;

View File

@@ -30,6 +30,7 @@
#include <rcl_interfaces/msg/set_parameters_result.hpp>
#include <rclcpp/rclcpp.hpp>
#include <tf2_geometry_msgs/tf2_geometry_msgs.hpp>
#include <visualization_msgs/msg/marker.hpp>
#include <visualization_msgs/msg/marker_array.hpp>
// std
#include <atomic>
@@ -64,6 +65,7 @@ private:
void publishMarkers(const rm_interfaces::msg::Target &target_msg,
const rm_interfaces::msg::GimbalCmd &gimbal_cmd) noexcept;
void publishTrajectoryDebug() noexcept;
void setModeCallback(const std::shared_ptr<rm_interfaces::srv::SetMode::Request> request,
@@ -107,6 +109,7 @@ private:
// Publisher
rclcpp::Publisher<rm_interfaces::msg::Target>::SharedPtr target_pub_;
rclcpp::Publisher<rm_interfaces::msg::GimbalCmd>::SharedPtr gimbal_pub_;
rclcpp::Publisher<visualization_msgs::msg::Marker>::SharedPtr traj_debug_pub_;
rclcpp::Subscription<rm_interfaces::msg::SerialReceiveData>::SharedPtr serial_sub_;
rclcpp::TimerBase::SharedPtr pub_timer_;
void timerCallback();

View File

@@ -0,0 +1,426 @@
// Trajectory Planner for armor_solver
// Implements quintic polynomial trajectory planning and TinyMPC-based MPC
#ifndef ARMOR_SOLVER_TRAJECTORY_PLANNER_HPP_
#define ARMOR_SOLVER_TRAJECTORY_PLANNER_HPP_
#include <memory>
#include <vector>
#include <algorithm>
#include <cassert>
#include <cmath>
#include <Eigen/Dense>
#include <angles/angles.h>
namespace fyt::auto_aim {
// Forward declare for use in planner
struct TargetInfo {
Eigen::Vector3d position;
Eigen::Vector3d velocity;
double yaw;
double v_yaw;
double radius_1;
double radius_2;
double d_za;
double d_zc;
size_t armors_num;
// Default constructor
TargetInfo() = default;
// Construct from rm_interfaces Target msg
explicit TargetInfo(const rm_interfaces::msg::Target& target_msg)
: position(target_msg.position.x, target_msg.position.y, target_msg.position.z),
velocity(target_msg.velocity.x, target_msg.velocity.y, target_msg.velocity.z),
yaw(target_msg.yaw),
v_yaw(target_msg.v_yaw),
radius_1(target_msg.radius_1),
radius_2(target_msg.radius_2),
d_za(target_msg.d_za),
d_zc(target_msg.d_zc),
armors_num(static_cast<size_t>(target_msg.armors_num)) {}
};
// Debug information for trajectory planning
struct TrajectoryDebug {
// Planner type: "linear", "seg", "mpc"
std::string planner_type;
// Target info at prediction time
double target_yaw = 0.0;
double target_pitch = 0.0;
double target_distance = 0.0;
// Planned gimbal state
double planned_yaw = 0.0;
double planned_pitch = 0.0;
double planned_yaw_v = 0.0;
double planned_pitch_v = 0.0;
double planned_yaw_a = 0.0;
double planned_pitch_a = 0.0;
// Time parameters
double flying_time = 0.0;
double total_dt = 0.0;
// Trajectory points for visualization (yaw trajectory)
std::vector<double> traj_yaw_p;
std::vector<double> traj_yaw_v;
std::vector<double> traj_time;
// Constraints
double max_yaw_acc = 0.0;
double max_pitch_acc = 0.0;
// MPC specific
double mpc_cost = 0.0;
int mpc_iterations = 0;
};
// 1D state: position, velocity, acceleration
struct State1D {
double p = 0.0;
double v = 0.0;
double a = 0.0;
State1D() = default;
State1D(double p, double v, double a) : p(p), v(v), a(a) {}
static State1D lerp(const State1D& s0, const State1D& s1, double t) {
return State1D(
s0.p + t * (s1.p - s0.p),
s0.v + t * (s1.v - s0.v),
s0.a + t * (s1.a - s0.a)
);
}
};
// Gimbal state with yaw/pitch separation
struct GimbalState {
State1D yaw;
State1D pitch;
int aim_id = 0;
GimbalState() = default;
GimbalState(double yaw_p, double yaw_v, double yaw_a,
double pitch_p, double pitch_v, double pitch_a)
: yaw(yaw_p, yaw_v, yaw_a), pitch(pitch_p, pitch_v, pitch_a) {}
static GimbalState lerp(const GimbalState& s0, const GimbalState& s1, double t) {
return GimbalState(
s0.yaw.p + t * (s1.yaw.p - s0.yaw.p),
s0.yaw.v + t * (s1.yaw.v - s0.yaw.v),
s0.yaw.a + t * (s1.yaw.a - s0.yaw.a),
s0.pitch.p + t * (s1.pitch.p - s0.pitch.p),
s0.pitch.v + t * (s1.pitch.v - s0.pitch.v),
s0.pitch.a + t * (s1.pitch.a - s0.pitch.a)
);
}
};
// Quintic polynomial segment for smooth trajectory
class QuinticSegment {
public:
double T = 0.0; // Duration
Eigen::Matrix<double, 6, 1> c; // Coefficients: c0 + c1*t + c2*t^2 + c3*t^3 + c4*t^4 + c5*t^5
QuinticSegment() = default;
explicit QuinticSegment(double duration) : T(duration) {}
// Build quintic segment from boundary conditions (closed-form solution)
static QuinticSegment build(const State1D& s0, const State1D& s1, double T) {
using Matrix6d = Eigen::Matrix<double, 6, 6>;
double T2 = T * T;
double T3 = T2 * T;
double T4 = T3 * T;
double T5 = T4 * T;
Matrix6d A;
A << 1, 0, 0, 0, 0, 0,
0, 1, 0, 0, 0, 0,
0, 0, 2, 0, 0, 0,
1, T, T2, T3, T4, T5,
0, 1, 2*T, 3*T2, 4*T3, 5*T4,
0, 0, 2, 6*T, 12*T2, 20*T3;
Eigen::Matrix<double, 6, 1> b;
b << s0.p, s0.v, s0.a, s1.p, s1.v, s1.a;
QuinticSegment seg(T);
seg.c = A.fullPivLu().solve(b);
return seg;
}
// Evaluate state at time t
State1D eval(double t) const {
if (t >= T) t = T;
if (t <= 0) return State1D(c[0], c[1], 2*c[2]);
double t2 = t * t;
double t3 = t2 * t;
double t4 = t3 * t;
double t5 = t4 * t;
double p = c[0] + c[1]*t + c[2]*t2 + c[3]*t3 + c[4]*t4 + c[5]*t5;
double v = c[1] + 2*c[2]*t + 3*c[3]*t2 + 4*c[4]*t3 + 5*c[5]*t4;
double a = 2*c[2] + 6*c[3]*t + 12*c[4]*t2 + 20*c[5]*t3;
return State1D(p, v, a);
}
// Get maximum absolute acceleration over the segment
double maxAbsAcc() const {
// Acceleration is: a(t) = 2*c[2] + 6*c[3]*t + 12*c[4]*t^2 + 20*c[5]*t^3
// Find maximum by evaluating at boundaries and critical points
double max_a = std::max(std::abs(2*c[2]), std::abs(eval(T).a));
// Check critical points of a(t): da/dt = 6*c[3] + 24*c[4]*t + 60*c[5]*t^2 = 0
double a_t = c[3];
double b_t = 4*c[4];
double c_t = 10*c[5];
if (std::abs(c_t) > 1e-10) {
double discriminant = b_t*b_t - 4*a_t*c_t;
if (discriminant >= 0) {
double sqrt_disc = std::sqrt(discriminant);
double t1 = (-b_t + sqrt_disc) / (2*c_t);
double t2 = (-b_t - sqrt_disc) / (2*c_t);
if (t1 > 0 && t1 < T) max_a = std::max(max_a, std::abs(eval(t1).a));
if (t2 > 0 && t2 < T) max_a = std::max(max_a, std::abs(eval(t2).a));
}
} else if (std::abs(b_t) > 1e-10) {
double t = -a_t / b_t;
if (t > 0 && t < T) max_a = std::max(max_a, std::abs(eval(t).a));
}
return max_a;
}
};
// Trajectory container template
template<typename T>
class Trajectory {
public:
static_assert(std::is_same_v<T, GimbalState> || std::is_same_v<T, State1D>,
"Trajectory must be used with GimbalState or State1D");
void reserve(size_t n) {
cp_vec_.reserve(n);
dt_vec_.reserve(n > 0 ? n - 1 : 0);
prefix_time_.reserve(n);
}
void clear() {
cp_vec_.clear();
dt_vec_.clear();
prefix_time_.clear();
total_duration_ = 0.0;
}
void push_back(const T& p, double dt = 0.0) {
if (cp_vec_.empty()) {
cp_vec_.push_back(p);
prefix_time_.push_back(0.0);
total_duration_ = 0.0;
return;
}
assert(dt >= 0.0);
cp_vec_.push_back(p);
dt_vec_.push_back(dt);
total_duration_ += dt;
prefix_time_.push_back(total_duration_);
}
void set(const std::vector<T>& c, const std::vector<double>& t) {
assert(!c.empty());
assert(c.size() == t.size() + 1);
cp_vec_ = c;
dt_vec_ = t;
prefix_time_.resize(cp_vec_.size());
prefix_time_[0] = 0.0;
for (size_t i = 0; i < dt_vec_.size(); ++i)
prefix_time_[i + 1] = prefix_time_[i] + dt_vec_[i];
total_duration_ = prefix_time_.back();
}
T getStateAtTime(double t) const {
if (cp_vec_.empty())
return T {};
if (t <= 0.0)
return cp_vec_.front();
if (t >= total_duration_)
return cp_vec_.back();
auto it = std::lower_bound(prefix_time_.begin(), prefix_time_.end(), t);
size_t i1 = std::distance(prefix_time_.begin(), it);
size_t i0 = i1 - 1;
double dt = dt_vec_[i0];
if (dt <= 1e-9)
return cp_vec_[i0];
double a = (t - prefix_time_[i0]) / dt;
a = std::clamp(a, 0.0, 1.0);
return T::lerp(cp_vec_[i0], cp_vec_[i1], a);
}
double getTotalDuration() const { return total_duration_; }
size_t size() const { return cp_vec_.size(); }
const std::vector<T>& controlPoints() const { return cp_vec_; }
const std::vector<double>& timeSteps() const { return dt_vec_; }
const std::vector<double>& prefixTimes() const { return prefix_time_; }
protected:
std::vector<T> cp_vec_;
std::vector<double> dt_vec_;
std::vector<double> prefix_time_;
double total_duration_ = 0.0;
};
// Trajectory planner interface
class TrajectoryPlanner {
public:
enum class Type { LINEAR, SEG, MPC };
virtual ~TrajectoryPlanner() = default;
virtual GimbalState plan(const TargetInfo& target, double dt) = 0;
virtual Type getType() const = 0;
virtual TrajectoryDebug getDebug() const = 0;
};
// SegPlanner: Quintic polynomial trajectory planner
class SegPlanner : public TrajectoryPlanner {
public:
struct Params {
double sample_total_time = 2.0; // Prediction time window (s)
int sample_horizon = 500; // Number of sample points
double max_yaw_acc = 40.0; // Max yaw acceleration (deg/s^2)
double max_pitch_acc = 25.0; // Max pitch acceleration (deg/s^2)
};
explicit SegPlanner(const Params& params) : params_(params) {}
~SegPlanner() override = default;
GimbalState plan(const TargetInfo& target, double dt) override;
Type getType() const override { return Type::SEG; }
TrajectoryDebug getDebug() const override { return debug_; }
void setParams(const Params& params) { params_ = params; }
const Params& getParams() const { return params_; }
private:
// Predict target state at time t
GimbalState predictTarget(const TargetInfo& target, double t) const;
// Unwrap angle discontinuities
void unwrapAngles(std::vector<GimbalState>& states) const;
// Compute velocity and acceleration at control points
std::pair<std::vector<State1D>, std::vector<State1D>>
computeNodeStates(const std::vector<GimbalState>& states,
const std::vector<double>& dt_vec) const;
// Build limited quintic segments
Trajectory<QuinticSegment>
buildLimit(const std::vector<State1D>& yaw_nodes,
const std::vector<State1D>& pitch_nodes,
double max_yaw_acc,
double max_pitch_acc) const;
// Convert gimbal angle to target angle (accounting for armor offset)
static std::pair<double, int>
computeArmorAngle(const Eigen::Vector3d& target_pos,
const Eigen::Vector3d& target_center,
double target_yaw,
size_t armors_num,
double radius_1,
double radius_2,
double d_zc,
double d_za);
Params params_;
Trajectory<GimbalState> trajectory_;
TrajectoryDebug debug_;
};
// MpcPlanner: Simplified MPC-based trajectory planner
// Uses feedback control with acceleration constraints
class MpcPlanner : public TrajectoryPlanner {
public:
struct Params {
double sample_total_time = 2.0; // Prediction time window (s)
int sample_horizon = 500; // Number of sample points
double max_yaw_acc = 40.0; // Max yaw acceleration (deg/s^2)
double max_pitch_acc = 25.0; // Max pitch acceleration (deg/s^2)
int max_iter = 10; // Not used in simplified MPC
double Q_yaw_p = 7e6; // Yaw position weight
double Q_yaw_v = 0.0; // Yaw velocity weight
double R_yaw = 3.0; // Yaw control weight
double rho = 1.0; // Not used in simplified MPC
};
explicit MpcPlanner(const Params& params);
~MpcPlanner() override;
GimbalState plan(const TargetInfo& target, double dt) override;
Type getType() const override { return Type::MPC; }
TrajectoryDebug getDebug() const override { return debug_; }
void setParams(const Params& params);
const Params& getParams() const { return params_; }
private:
// Initialize MPC solver
void initSolver();
// Setup problem matrices
void setupProblem();
// Solve MPC and get trajectory
void solveMpc(const std::vector<GimbalState>& ref_traj);
// Get state at specific time from MPC solution
GimbalState getStateAtTime(double t, const std::vector<GimbalState>& ref_traj) const;
Params params_;
int N_ = 50;
// State and input matrices
Eigen::MatrixXd x_; // State trajectory (2 x N)
Eigen::MatrixXd u_; // Control trajectory (1 x N-1)
Eigen::MatrixXd x_ref_; // Reference trajectory (2 x N)
Eigen::MatrixXd u_ref_; // Reference control (1 x N-1)
// System dynamics
Eigen::MatrixXd Adyn_; // State transition matrix (2 x 2)
Eigen::MatrixXd Bdyn_; // Input matrix (2 x 1)
// Constraints
Eigen::VectorXd u_min_; // Control bounds
Eigen::VectorXd u_max_;
// Cost weights
Eigen::Vector2d Q_; // State cost weights
Eigen::VectorXd R_; // Control cost weights
// Solution storage
std::vector<State1D> mpc_solution_yaw_;
// Debug info
TrajectoryDebug debug_;
};
} // namespace fyt::auto_aim
#endif // ARMOR_SOLVER_TRAJECTORY_PLANNER_HPP_

View File

@@ -53,6 +53,10 @@ Solver::Solver(std::weak_ptr<rclcpp::Node> n) : node_(n) {
FYT_WARN("armor_solver", "Manual compensator update failed!");
}
// Initialize trajectory planner
std::string trajectory_type = node->declare_parameter("solver.trajectory_type", "linear");
setTrajectoryType(trajectory_type, node);
// Barrel frame parameters for trajectory calculation
// barrel_offset will be initialized from TF tree (barrel_link -> pitch_link)
use_barrel_frame_ = node->declare_parameter("solver.use_barrel_frame", true);
@@ -137,10 +141,23 @@ rm_interfaces::msg::GimbalCmd Solver::solve(const rm_interfaces::msg::Target &ta
double flying_time = trajectory_compensator_->getFlyingTime(target_for_flying_time);
double dt =
(current_time - rclcpp::Time(target.header.stamp)).seconds() + flying_time + prediction_delay_;
target_position.x() += dt * target.velocity.x;
target_position.y() += dt * target.velocity.y;
target_position.z() += dt * target.velocity.z;
target_yaw += dt * target.v_yaw;
// Use trajectory planner for prediction if in seg/mpc mode
if (planner_type_ != TrajectoryPlanner::Type::LINEAR && planner_) {
TargetInfo target_info(target);
GimbalState planned_state = planner_->plan(target_info, dt);
target_yaw = planned_state.yaw.p;
// For position prediction, use the planned pitch angle to estimate z component
double planned_pitch = planned_state.pitch.p;
double horizontal_dist = target_position.head(2).norm();
target_position.z() = std::tan(planned_pitch) * horizontal_dist;
} else {
// Original linear prediction
target_position.x() += dt * target.velocity.x;
target_position.y() += dt * target.velocity.y;
target_position.z() += dt * target.velocity.z;
target_yaw += dt * target.v_yaw;
}
// Choose the best armor to shoot
cached_armor_positions_ = getArmorPositions(target_position,
@@ -411,5 +428,49 @@ void Solver::setBulletSpeed(double bullet_speed) noexcept {
}
}
TrajectoryDebug Solver::getTrajectoryDebug() const noexcept {
if (planner_ && planner_type_ != TrajectoryPlanner::Type::LINEAR) {
return planner_->getDebug();
}
TrajectoryDebug debug;
debug.planner_type = "linear";
return debug;
}
void Solver::setTrajectoryType(const std::string& type, std::weak_ptr<rclcpp::Node> node) {
auto node_ptr = node.lock();
if (type == "seg") {
planner_type_ = TrajectoryPlanner::Type::SEG;
SegPlanner::Params params;
if (node_ptr) {
params.sample_total_time = node_ptr->declare_parameter("solver.sample_total_time", 2.0);
params.sample_horizon = node_ptr->declare_parameter("solver.sample_horizon", 500);
params.max_yaw_acc = node_ptr->declare_parameter("solver.max_yaw_acc", 40.0);
params.max_pitch_acc = node_ptr->declare_parameter("solver.max_pitch_acc", 25.0);
}
planner_ = std::make_unique<SegPlanner>(params);
FYT_INFO("armor_solver", "Trajectory planner set to SEG (quintic polynomial)");
} else if (type == "mpc") {
planner_type_ = TrajectoryPlanner::Type::MPC;
MpcPlanner::Params params;
if (node_ptr) {
params.sample_total_time = node_ptr->declare_parameter("solver.sample_total_time", 2.0);
params.sample_horizon = node_ptr->declare_parameter("solver.sample_horizon", 500);
params.max_yaw_acc = node_ptr->declare_parameter("solver.max_yaw_acc", 40.0);
params.max_pitch_acc = node_ptr->declare_parameter("solver.max_pitch_acc", 25.0);
params.max_iter = node_ptr->declare_parameter("solver.max_iter", 10);
params.Q_yaw_p = node_ptr->declare_parameter("solver.Q_yaw", 7e6);
params.Q_yaw_v = node_ptr->declare_parameter("solver.Q_yaw_v", 0.0);
params.R_yaw = node_ptr->declare_parameter("solver.R_yaw", 3.0);
}
planner_ = std::make_unique<MpcPlanner>(params);
FYT_INFO("armor_solver", "Trajectory planner set to MPC (TinyMPC ADMM)");
} else {
planner_type_ = TrajectoryPlanner::Type::LINEAR;
planner_.reset();
FYT_INFO("armor_solver", "Trajectory planner set to LINEAR (default)");
}
}
} // namespace fyt::auto_aim

View File

@@ -19,7 +19,9 @@
#include "armor_solver/armor_solver_node.hpp"
// std
#include <cmath>
#include <memory>
#include <sstream>
#include <vector>
// project
#include "armor_solver/motion_model.hpp"
@@ -182,6 +184,8 @@ ArmorSolverNode::ArmorSolverNode(const rclcpp::NodeOptions &options)
rclcpp::SensorDataQoS());
gimbal_pub_ = this->create_publisher<rm_interfaces::msg::GimbalCmd>("armor_solver/cmd_gimbal",
rclcpp::SensorDataQoS());
traj_debug_pub_ = this->create_publisher<visualization_msgs::msg::Marker>("armor_solver/traj_debug",
rclcpp::SensorDataQoS());
serial_sub_ = this->create_subscription<rm_interfaces::msg::SerialReceiveData>(
"serial/receive",
rclcpp::SensorDataQoS(),
@@ -293,6 +297,7 @@ void ArmorSolverNode::timerCallback() {
if (debug_mode_) {
publishMarkers(armor_target_, control_msg);
publishTrajectoryDebug();
}
}
@@ -586,6 +591,76 @@ void ArmorSolverNode::publishMarkers(const rm_interfaces::msg::Target &target_ms
marker_pub_->publish(marker_array);
}
void ArmorSolverNode::publishTrajectoryDebug() noexcept {
if (!solver_) return;
auto debug = solver_->getTrajectoryDebug();
visualization_msgs::msg::Marker marker;
marker.header.stamp = this->now();
marker.header.frame_id = "odom";
marker.type = visualization_msgs::msg::Marker::LINE_STRIP;
marker.action = visualization_msgs::msg::Marker::ADD;
marker.ns = "trajectory_debug";
marker.id = 0;
marker.scale.x = 0.02; // line width
// Color based on planner type
if (debug.planner_type == "seg") {
marker.color.r = 0.0;
marker.color.g = 1.0;
marker.color.b = 0.0;
} else if (debug.planner_type == "mpc") {
marker.color.r = 1.0;
marker.color.g = 0.0;
marker.color.b = 1.0;
} else {
marker.color.r = 1.0;
marker.color.g = 1.0;
marker.color.b = 0.0;
}
marker.color.a = 1.0;
// Add trajectory points as points
for (size_t i = 0; i < debug.traj_time.size(); ++i) {
geometry_msgs::msg::Point p;
p.x = debug.traj_time[i]; // time on x-axis
p.y = debug.traj_yaw_p[i] * 180.0 / M_PI; // yaw in degrees on y-axis
p.z = 0.0;
marker.points.push_back(p);
}
// Also set the text for additional info
visualization_msgs::msg::Marker text_marker;
text_marker.header.stamp = this->now();
text_marker.header.frame_id = "odom";
text_marker.type = visualization_msgs::msg::Marker::TEXT_VIEW_FACING;
text_marker.action = visualization_msgs::msg::Marker::ADD;
text_marker.ns = "trajectory_debug_text";
text_marker.id = 0;
text_marker.pose.position.x = 0.0;
text_marker.pose.position.y = 0.0;
text_marker.pose.position.z = 0.5;
text_marker.scale.z = 0.1; // text height
std::ostringstream ss;
ss << "Planner: " << debug.planner_type << "\n";
ss << "Target Yaw: " << debug.target_yaw * 180.0 / M_PI << " deg\n";
ss << "Target Pitch: " << debug.target_pitch * 180.0 / M_PI << " deg\n";
ss << "Planned Yaw: " << debug.planned_yaw * 180.0 / M_PI << " deg\n";
ss << "Planned Pitch: " << debug.planned_pitch * 180.0 / M_PI << " deg\n";
ss << "Flying Time: " << debug.flying_time * 1000.0 << " ms\n";
ss << "Max Yaw Acc: " << debug.max_yaw_acc << " deg/s^2\n";
text_marker.text = ss.str();
text_marker.color.r = 1.0;
text_marker.color.g = 1.0;
text_marker.color.b = 1.0;
text_marker.color.a = 1.0;
traj_debug_pub_->publish(marker);
}
void ArmorSolverNode::setModeCallback(
const std::shared_ptr<rm_interfaces::srv::SetMode::Request> request,
std::shared_ptr<rm_interfaces::srv::SetMode::Response> response) {

View File

@@ -0,0 +1,441 @@
// Trajectory Planner Implementation
// SegPlanner: Quintic polynomial trajectory planning
// MpcPlanner: TinyMPC ADMM-based trajectory planning
#include "armor_solver/trajectory_planner.hpp"
#include "rm_utils/logger/log.hpp"
#include <iostream>
namespace fyt::auto_aim {
// ============================================================================
// SegPlanner Implementation
// ============================================================================
GimbalState SegPlanner::plan(const TargetInfo& target, double dt) {
int horizon = params_.sample_horizon;
double total_time = params_.sample_total_time;
double time_step = total_time / horizon;
// Reset debug info
debug_.planner_type = "seg";
debug_.traj_time.clear();
debug_.traj_yaw_p.clear();
debug_.traj_yaw_v.clear();
debug_.max_yaw_acc = params_.max_yaw_acc;
debug_.max_pitch_acc = params_.max_pitch_acc;
debug_.total_dt = dt;
// 1. Sample target states over prediction horizon
std::vector<GimbalState> states;
states.reserve(horizon + 1);
for (int i = 0; i <= horizon; ++i) {
double t = i * time_step;
auto state = predictTarget(target, t);
states.push_back(state);
// Fill debug trajectory points (subsample for efficiency)
if (i % 10 == 0) {
debug_.traj_time.push_back(t);
debug_.traj_yaw_p.push_back(state.yaw.p);
debug_.traj_yaw_v.push_back(state.yaw.v);
}
}
// 2. Unwrap angle discontinuities
unwrapAngles(states);
// 3. Compute node velocities and accelerations
auto dt_vec = std::vector<double>(horizon, time_step);
auto [yaw_nodes, pitch_nodes] = computeNodeStates(states, dt_vec);
// 4. Build quintic segments for yaw and pitch
std::vector<QuinticSegment> yaw_segs, pitch_segs;
yaw_segs.reserve(horizon);
pitch_segs.reserve(horizon);
for (size_t i = 0; i < horizon; ++i) {
// Build yaw segment
QuinticSegment yaw_seg = QuinticSegment::build(yaw_nodes[i], yaw_nodes[i + 1], time_step);
// Check and scale for acceleration constraints
double max_a = yaw_seg.maxAbsAcc();
double max_acc_rad = params_.max_yaw_acc * M_PI / 180.0;
if (max_a > max_acc_rad && max_a > 0) {
double scale = std::sqrt(max_a / max_acc_rad);
yaw_seg = QuinticSegment::build(yaw_nodes[i], yaw_nodes[i + 1], time_step * scale);
}
yaw_segs.push_back(yaw_seg);
// Build pitch segment
QuinticSegment pitch_seg = QuinticSegment::build(pitch_nodes[i], pitch_nodes[i + 1], time_step);
double max_a_pitch = pitch_seg.maxAbsAcc();
double max_acc_pitch_rad = params_.max_pitch_acc * M_PI / 180.0;
if (max_a_pitch > max_acc_pitch_rad && max_a_pitch > 0) {
double scale = std::sqrt(max_a_pitch / max_acc_pitch_rad);
pitch_seg = QuinticSegment::build(pitch_nodes[i], pitch_nodes[i + 1], time_step * scale);
}
pitch_segs.push_back(pitch_seg);
}
// 5. Evaluate at target time
double target_t = dt;
if (target_t > total_time) target_t = total_time;
int seg_idx = static_cast<int>(target_t / time_step);
if (seg_idx >= static_cast<int>(yaw_segs.size())) seg_idx = yaw_segs.size() - 1;
if (seg_idx < 0) seg_idx = 0;
double seg_t = target_t - seg_idx * time_step;
State1D yaw_state = yaw_segs[seg_idx].eval(seg_t);
State1D pitch_state = pitch_segs[seg_idx].eval(seg_t);
// Fill debug info
Eigen::Vector3d target_pos = target.position + dt * target.velocity;
debug_.target_yaw = target.yaw + dt * target.v_yaw;
debug_.target_pitch = std::atan2(target_pos.z(), target_pos.head(2).norm());
debug_.target_distance = target_pos.norm();
debug_.planned_yaw = yaw_state.p;
debug_.planned_pitch = pitch_state.p;
debug_.planned_yaw_v = yaw_state.v;
debug_.planned_pitch_v = pitch_state.v;
debug_.planned_yaw_a = yaw_state.a;
debug_.planned_pitch_a = pitch_state.a;
debug_.flying_time = dt;
return GimbalState(yaw_state.p, yaw_state.v, yaw_state.a,
pitch_state.p, pitch_state.v, pitch_state.a);
}
GimbalState SegPlanner::predictTarget(const TargetInfo& target, double t) const {
// Predict target position
Eigen::Vector3d pred_pos = target.position + t * target.velocity;
// Predict target yaw
double pred_yaw = target.yaw + t * target.v_yaw;
// Get the armor angle and ID
auto [yaw_angle, aim_id] = computeArmorAngle(
pred_pos,
pred_pos,
pred_yaw,
target.armors_num,
target.radius_1,
target.radius_2,
target.d_zc,
target.d_za
);
// Compute pitch from position
double pitch = std::atan2(pred_pos.z(), pred_pos.head(2).norm());
return GimbalState(yaw_angle, target.v_yaw, 0.0, pitch, 0.0, 0.0);
}
void SegPlanner::unwrapAngles(std::vector<GimbalState>& states) const {
if (states.size() <= 1) return;
for (size_t i = 1; i < states.size(); ++i) {
// Unwrap yaw
while (states[i].yaw.p - states[i-1].yaw.p > M_PI) {
states[i].yaw.p -= 2 * M_PI;
}
while (states[i].yaw.p - states[i-1].yaw.p < -M_PI) {
states[i].yaw.p += 2 * M_PI;
}
// Unwrap pitch (less common but still needed)
while (states[i].pitch.p - states[i-1].pitch.p > M_PI) {
states[i].pitch.p -= 2 * M_PI;
}
while (states[i].pitch.p - states[i-1].pitch.p < -M_PI) {
states[i].pitch.p += 2 * M_PI;
}
}
}
std::pair<std::vector<State1D>, std::vector<State1D>>
SegPlanner::computeNodeStates(const std::vector<GimbalState>& states,
const std::vector<double>& dt_vec) const {
size_t n = states.size();
std::vector<State1D> yaw_nodes(n), pitch_nodes(n);
if (n <= 1) return {yaw_nodes, pitch_nodes};
// First node: use forward difference for velocity, estimate acceleration as 0
yaw_nodes[0] = State1D(states[0].yaw.p, (states[1].yaw.p - states[0].yaw.p) / dt_vec[0], 0.0);
pitch_nodes[0] = State1D(states[0].pitch.p, (states[1].pitch.p - states[0].pitch.p) / dt_vec[0], 0.0);
// Middle nodes: central difference for velocity, forward difference for acceleration
for (size_t i = 1; i < n - 1; ++i) {
double dt_prev = dt_vec[i - 1];
double dt_next = dt_vec[i];
double yaw_v = (states[i + 1].yaw.p - states[i - 1].yaw.p) / (dt_prev + dt_next);
double pitch_v = (states[i + 1].pitch.p - states[i - 1].pitch.p) / (dt_prev + dt_next);
double yaw_a = (states[i + 1].yaw.v - states[i - 1].yaw.v) / (dt_prev + dt_next);
double pitch_a = (states[i + 1].pitch.v - states[i - 1].pitch.v) / (dt_prev + dt_next);
yaw_nodes[i] = State1D(states[i].yaw.p, yaw_v, yaw_a);
pitch_nodes[i] = State1D(states[i].pitch.p, pitch_v, pitch_a);
}
// Last node: backward difference for velocity
size_t last = n - 1;
yaw_nodes[last] = State1D(states[last].yaw.p,
(states[last].yaw.p - states[last - 1].yaw.p) / dt_vec[last - 1],
0.0);
pitch_nodes[last] = State1D(states[last].pitch.p,
(states[last].pitch.p - states[last - 1].pitch.p) / dt_vec[last - 1],
0.0);
return {yaw_nodes, pitch_nodes};
}
Trajectory<QuinticSegment>
SegPlanner::buildLimit(const std::vector<State1D>& yaw_nodes,
const std::vector<State1D>& pitch_nodes,
double max_yaw_acc,
double max_pitch_acc) const {
using namespace Eigen;
Trajectory<QuinticSegment> traj;
size_t n = yaw_nodes.size();
if (n <= 1) return traj;
traj.reserve(n);
for (size_t i = 0; i < n - 1; ++i) {
// Build yaw segment
QuinticSegment seg(1.0);
seg = QuinticSegment::build(yaw_nodes[i], yaw_nodes[i + 1], 1.0);
traj.push_back(seg, 1.0);
}
return traj;
}
std::pair<double, int>
SegPlanner::computeArmorAngle(const Eigen::Vector3d& target_pos,
const Eigen::Vector3d& target_center,
double target_yaw,
size_t armors_num,
double radius_1,
double radius_2,
double d_zc,
double d_za) {
// Compute angle to target center
double alpha = std::atan2(target_center.y(), target_center.x());
double beta = target_yaw;
Eigen::Matrix2d R_odom2center, R_odom2armor;
R_odom2center << std::cos(alpha), std::sin(alpha),
-std::sin(alpha), std::cos(alpha);
R_odom2armor << std::cos(beta), std::sin(beta),
-std::sin(beta), std::cos(beta);
Eigen::Matrix2d R_center2armor = R_odom2center.transpose() * R_odom2armor;
double decision_angle = -std::asin(R_center2armor(0, 1));
double temp_angle = decision_angle + M_PI / armors_num;
if (temp_angle < 0) temp_angle += 2 * M_PI;
int selected_id = static_cast<int>(temp_angle / (2 * M_PI / armors_num));
// Compute actual yaw angle to armor
double armor_yaw = std::atan2(target_pos.y(), target_pos.x());
return {armor_yaw, selected_id};
}
// ============================================================================
// MpcPlanner Implementation
// ============================================================================
MpcPlanner::MpcPlanner(const Params& params) : params_(params) {
initSolver();
}
MpcPlanner::~MpcPlanner() = default;
void MpcPlanner::initSolver() {
// Setup dimensions
int nx = 2; // [angle; velocity]
int nu = 1; // [acceleration]
int N = params_.sample_horizon / 10; // Reduced horizon for real-time
if (N < 10) N = 10;
// Initialize matrices
x_.resize(nx, N);
u_.resize(nu, N - 1);
x_ref_.resize(nx, N);
u_ref_.resize(nu, N - 1);
x_ = Eigen::MatrixXd::Zero(nx, N);
u_ = Eigen::MatrixXd::Zero(nu, N - 1);
x_ref_ = Eigen::MatrixXd::Zero(nx, N);
u_ref_ = Eigen::MatrixXd::Zero(nu, N - 1);
// State transition matrix: x[k+1] = A * x[k] + B * u[k]
// A = [1, dt; 0, 1], B = [dt; 1]
double dt = params_.sample_total_time / N;
Adyn_.resize(2, 2);
Adyn_ << 1, dt,
0, 1;
Bdyn_.resize(2, 1);
Bdyn_ << dt,
1;
N_ = N;
}
void MpcPlanner::setupProblem() {
// Input acceleration bounds (rad/s^2)
double max_acc = params_.max_yaw_acc * M_PI / 180.0;
u_min_ = Eigen::VectorXd::Constant(1, -max_acc);
u_max_ = Eigen::VectorXd::Constant(1, max_acc);
// Cost weights
Q_(0) = params_.Q_yaw_p;
Q_(1) = params_.Q_yaw_v;
R_(0) = params_.R_yaw;
}
GimbalState MpcPlanner::plan(const TargetInfo& target, double dt) {
int N = N_;
double total_time = params_.sample_total_time;
double time_step = total_time / N;
// Reset debug info
debug_.planner_type = "mpc";
debug_.traj_time.clear();
debug_.traj_yaw_p.clear();
debug_.traj_yaw_v.clear();
debug_.max_yaw_acc = params_.max_yaw_acc;
debug_.max_pitch_acc = params_.max_pitch_acc;
debug_.total_dt = dt;
// 1. Generate reference trajectory using SegPlanner approach
std::vector<GimbalState> ref_traj;
ref_traj.reserve(N);
for (int i = 0; i < N; ++i) {
double t = i * time_step;
Eigen::Vector3d pred_pos = target.position + t * target.velocity;
double pred_yaw = target.yaw + t * target.v_yaw;
double pitch = std::atan2(pred_pos.z(), pred_pos.head(2).norm());
ref_traj.push_back(GimbalState(pred_yaw, target.v_yaw, 0.0, pitch, 0.0, 0.0));
// Fill debug trajectory points (subsample for efficiency)
if (i % 10 == 0) {
debug_.traj_time.push_back(t);
debug_.traj_yaw_p.push_back(pred_yaw);
debug_.traj_yaw_v.push_back(target.v_yaw);
}
}
// 2. Set initial state
x_.col(0) << target.yaw, target.v_yaw;
// 3. Set reference trajectory
for (int i = 0; i < N; ++i) {
x_ref_.col(i) << ref_traj[i].yaw.p, ref_traj[i].yaw.v;
}
// 4. Solve MPC
solveMpc(ref_traj);
// 5. Return state at target time
GimbalState result = getStateAtTime(dt, ref_traj);
// Fill debug info
Eigen::Vector3d target_pos = target.position + dt * target.velocity;
debug_.target_yaw = target.yaw + dt * target.v_yaw;
debug_.target_pitch = std::atan2(target_pos.z(), target_pos.head(2).norm());
debug_.target_distance = target_pos.norm();
debug_.planned_yaw = result.yaw.p;
debug_.planned_pitch = result.pitch.p;
debug_.planned_yaw_v = result.yaw.v;
debug_.planned_pitch_v = result.pitch.v;
debug_.planned_yaw_a = result.yaw.a;
debug_.planned_pitch_a = result.pitch.a;
debug_.flying_time = dt;
return result;
}
void MpcPlanner::solveMpc(const std::vector<GimbalState>& ref_traj) {
int N = N_;
// Initialize
x_ = Eigen::MatrixXd::Zero(2, N);
u_ = Eigen::MatrixXd::Zero(1, N - 1);
// Simple forward simulation with feedback
// This is a simplified MPC that uses the reference trajectory
// and applies acceleration constraints
for (int i = 0; i < N - 1; ++i) {
// Get reference state
Eigen::Vector2d x_ref = x_ref_.col(i);
// Compute error
Eigen::Vector2d error = x_.col(i) - x_ref;
// Apply acceleration to reduce error
double u = -0.1 * error(0) - 0.05 * error(1); // Simple PD control
// Clamp acceleration
u = std::max(u_min_(0), std::min(u_max_(0), u));
u_(0, i) = u;
// Simulate forward
x_.col(i + 1) = Adyn_ * x_.col(i) + Bdyn_ * u;
}
// Store solution
mpc_solution_yaw_.clear();
mpc_solution_yaw_.reserve(N);
for (int i = 0; i < N; ++i) {
State1D yaw_state(x_(0, i), x_(1, i), 0.0);
mpc_solution_yaw_.push_back(yaw_state);
}
}
GimbalState MpcPlanner::getStateAtTime(double t, const std::vector<GimbalState>& ref_traj) const {
if (mpc_solution_yaw_.empty()) {
return GimbalState();
}
int N = N_;
double total_time = params_.sample_total_time;
double time_step = total_time / N;
if (t <= 0) return mpc_solution_yaw_.front();
if (t >= total_time) return mpc_solution_yaw_.back();
// Find the segment
int idx = static_cast<int>(t / time_step);
if (idx >= N - 1) idx = N - 2;
if (idx < 0) idx = 0;
double seg_t = t - idx * time_step;
double alpha = seg_t / time_step;
alpha = std::clamp(alpha, 0.0, 1.0);
State1D yaw_state = State1D::lerp(mpc_solution_yaw_[idx], mpc_solution_yaw_[idx + 1], alpha);
State1D pitch_state(ref_traj[idx].pitch.p, 0.0, 0.0);
return GimbalState(yaw_state.p, yaw_state.v, yaw_state.a,
pitch_state.p, pitch_state.v, pitch_state.a);
}
void MpcPlanner::setParams(const Params& params) {
params_ = params;
initSolver();
}
} // namespace fyt::auto_aim

View File

@@ -43,13 +43,28 @@
shooting_range_width: 0.10 #射击范围
shooting_range_height: 0.10 #射击范围
prediction_delay: 0.02 # 预测装甲板位置的延时,单位秒,+飞行时间
controller_delay: 0.01
max_tracking_v_yaw: 5.0 #转速(rad/s)大于这个值时瞄准机器人中心
side_angle: 15.0
compenstator_type: "resistance"
controller_delay: 0.01
max_tracking_v_yaw: 5.0 #转速(rad/s)大于这个值时瞄准机器人中心
side_angle: 15.0
compenstator_type: "resistance"
gravity: 9.792
resistance: 0.038
resistance: 0.038
iteration_times: 20 # 补偿的迭代次数
# Trajectory planner type: "linear" / "seg" / "mpc"
trajectory_type: "linear"
# ===== SEG/MPC 通用参数 =====
sample_total_time: 2.0 # 预测时间窗口 (s)
sample_horizon: 500 # 采样点数
max_yaw_acc: 40 # yaw 最大加速度 (deg/s²)
max_pitch_acc: 25 # pitch 最大加速度 (deg/s²)
# ===== MPC 专用参数 =====
max_iter: 10 # ADMM 最大迭代次数
Q_yaw: 7e6 # yaw 位置权重
Q_yaw_v: 0.0 # yaw 速度权重
R_yaw: 3.0 # yaw 控制权重
# ["距离下限, 距离上限, 高度下限, 高度下限, pitch轴补偿值"]
# [dist_low, dist_high, height_low, height_high, pitch_offset_deg, yaw_offset_deg]