optimal & fix file cmake
This commit is contained in:
@@ -2,12 +2,13 @@
|
||||
*
|
||||
* Software License Agreement (BSD License)
|
||||
*
|
||||
* recovery_core — per-cycle rotate recovery plugin (goal-driven).
|
||||
* recovery_core — quay tại chỗ, quét cung trước khi quay và đo bằng pose thật.
|
||||
*
|
||||
* Author: DuongTD
|
||||
*********************************************************************/
|
||||
|
||||
#include <recovery_core/recovery_behavior.h>
|
||||
#include <recovery_core/recovery_math.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
@@ -20,18 +21,30 @@ namespace recovery_plugins
|
||||
{
|
||||
namespace
|
||||
{
|
||||
constexpr double kDefaultTargetAngle = 1.57079632679; // pi/2 rad.
|
||||
constexpr double kDefaultAngularSpeed = 0.4; // rad/s.
|
||||
constexpr double kDefaultControlPeriod = 0.1; // s per update tick.
|
||||
constexpr double kTwoPi = 2.0 * M_PI;
|
||||
constexpr double kDefaultAngularSpeed = 0.4; // [rad/s] độ lớn
|
||||
constexpr double kDefaultAccLimTheta = 0.8; // [rad/s^2]
|
||||
constexpr double kDefaultSimGranularity = 0.1; // [rad] bước quét cung
|
||||
constexpr double kMaxAngularSpeed = 2.0; // [rad/s] trần vệ sinh cho param sai
|
||||
constexpr double kGoalTolerance = 1e-3; // [rad]
|
||||
} // namespace
|
||||
|
||||
/**
|
||||
* @class RotateRecovery
|
||||
* @brief Quay tại chỗ tới GÓC ĐÍCH do caller yêu cầu ở start(goal).
|
||||
* @brief Quay tại chỗ, mặc định đủ một vòng, để costmap quan sát lại xung quanh.
|
||||
*
|
||||
* goal.angle (rad, có dấu) là góc quay lượt này; 0 nghĩa là dùng default configured. Tốc độ
|
||||
* góc mặc định đọc từ param, có thể override qua goal.params["angular_speed"]. Mỗi update() trả
|
||||
* Twist.angular.z kèm progress/remaining tới khi đủ góc -> kSucceeded (zero command).
|
||||
* Quay đủ 2π là công dụng chính của rotate trong một bộ recovery: nó cho obstacle/voxel layer nhìn
|
||||
* thấy toàn bộ vùng quanh robot rồi mới lập plan lại. Đặt `full_rotation: false` hoặc truyền
|
||||
* `goal.angle` để quay một góc cụ thể.
|
||||
*
|
||||
* Hai lớp bảo vệ so với bản trước (bản trước không dùng `ctx()` một lần nào):
|
||||
* 1. **Quét toàn bộ cung sẽ quay** tại `onStart()` theo bước `sim_granularity`; chạm vật cản ở bất
|
||||
* kỳ góc nào là từ chối khởi động, chứ không quay tới nơi mới phát hiện.
|
||||
* 2. **Tiến độ đo bằng pose thật**, cộng dồn từng chênh lệch yaw đã chuẩn hoá — nên quay > π vẫn
|
||||
* đếm đúng, và loop chạy chậm không làm robot quay quá góc.
|
||||
*
|
||||
* @note Yêu cầu robot xoay tại chỗ được (differential/omni). Config workspace hiện tại thoả:
|
||||
* `min_turn_radius: 0.0`, `use_rotate_to_heading: true`.
|
||||
*/
|
||||
class RotateRecovery final : public recovery_core::RecoveryBehavior
|
||||
{
|
||||
@@ -43,95 +56,221 @@ public:
|
||||
return std::make_shared<RotateRecovery>();
|
||||
}
|
||||
|
||||
recovery_core::RecoveryOutputType outputKind() const override
|
||||
{
|
||||
return recovery_core::RecoveryOutputType::kVelocity;
|
||||
}
|
||||
|
||||
protected:
|
||||
void onConfigure() override
|
||||
bool onConfigure(robot::NodeHandle& nh) override
|
||||
{
|
||||
robot::NodeHandle private_nh("~/" + name_);
|
||||
private_nh.param("target_angle", default_target_angle_, kDefaultTargetAngle);
|
||||
private_nh.param("angular_speed", default_angular_speed_, kDefaultAngularSpeed);
|
||||
private_nh.param("control_period", control_period_, kDefaultControlPeriod);
|
||||
nh.param("full_rotation", full_rotation_, true);
|
||||
nh.param("target_angle", default_target_angle_, kTwoPi);
|
||||
nh.param("angular_speed", default_angular_speed_, kDefaultAngularSpeed);
|
||||
nh.param("acc_lim_theta", acc_lim_theta_, kDefaultAccLimTheta);
|
||||
nh.param("sim_granularity", sim_granularity_, kDefaultSimGranularity);
|
||||
|
||||
if (!std::isfinite(default_target_angle_) || std::abs(default_target_angle_) <= 0.0)
|
||||
default_target_angle_ = clampAngle(default_target_angle_, kTwoPi);
|
||||
default_angular_speed_ = clampSpeed(default_angular_speed_, kDefaultAngularSpeed);
|
||||
|
||||
if (!std::isfinite(acc_lim_theta_) || acc_lim_theta_ < 0.0)
|
||||
{
|
||||
robot::log_warning("[recovery_core] Invalid target_angle for '%s'; using pi/2.",
|
||||
name_.c_str());
|
||||
default_target_angle_ = kDefaultTargetAngle;
|
||||
robot::log_warning("[recovery_core] '%s': acc_lim_theta=%.3f rad/s^2 is invalid; using %.3f.",
|
||||
name().c_str(), acc_lim_theta_, kDefaultAccLimTheta);
|
||||
acc_lim_theta_ = kDefaultAccLimTheta;
|
||||
}
|
||||
if (!std::isfinite(default_angular_speed_) || default_angular_speed_ <= 0.0)
|
||||
|
||||
if (!std::isfinite(sim_granularity_) || sim_granularity_ <= 0.0)
|
||||
{
|
||||
robot::log_warning("[recovery_core] Invalid angular_speed for '%s'; using 0.4 rad/s.",
|
||||
name_.c_str());
|
||||
default_angular_speed_ = kDefaultAngularSpeed;
|
||||
}
|
||||
if (!std::isfinite(control_period_) || control_period_ <= 0.0)
|
||||
{
|
||||
robot::log_warning("[recovery_core] Invalid control_period for '%s'; using 0.1 s.",
|
||||
name_.c_str());
|
||||
control_period_ = kDefaultControlPeriod;
|
||||
robot::log_warning("[recovery_core] '%s': sim_granularity=%.3f rad is invalid; using %.3f "
|
||||
"rad.", name().c_str(), sim_granularity_, kDefaultSimGranularity);
|
||||
sim_granularity_ = kDefaultSimGranularity;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
recovery_core::RecoveryResult onStart(const recovery_core::RecoveryGoal& goal) override
|
||||
bool onStart(const recovery_core::RecoveryGoal& goal) override
|
||||
{
|
||||
// Góc đích: goal.angle nếu hợp lệ, ngược lại default configured.
|
||||
target_angle_ = (std::isfinite(goal.angle) && std::abs(goal.angle) > 0.0)
|
||||
? goal.angle
|
||||
: default_target_angle_;
|
||||
|
||||
angular_speed_ = std::abs(goal.param("angular_speed", default_angular_speed_));
|
||||
if (!std::isfinite(angular_speed_) || angular_speed_ <= 0.0)
|
||||
robot_geometry_msgs::PoseStamped pose;
|
||||
if (!ctx().pose->getRobotPose(pose))
|
||||
{
|
||||
angular_speed_ = default_angular_speed_;
|
||||
robot::log_warning("[recovery_core] '%s': could not get a pose at start-up (TF stale?).",
|
||||
name().c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
rotated_angle_ = 0.0;
|
||||
return recovery_core::RecoveryResult::Velocity(robot_geometry_msgs::Twist(),
|
||||
recovery_core::RecoveryStatus::kRunning)
|
||||
.withProgress(0.0, std::abs(target_angle_))
|
||||
.withMessage("rotate start");
|
||||
start_pose_ = pose;
|
||||
start_yaw_ = recovery_core::yawOf(pose);
|
||||
last_yaw_ = start_yaw_;
|
||||
rotated_ = 0.0;
|
||||
current_speed_ = 0.0;
|
||||
|
||||
// goal.angle có giá trị (kể cả 0.0) thì tôn trọng đúng giá trị đó. Bản trước coi 0 là "chưa
|
||||
// đặt" nên một góc tính ra ~0 bị âm thầm thay bằng pi/2.
|
||||
const double requested = goal.angle.value_or(full_rotation_ ? kTwoPi : default_target_angle_);
|
||||
target_angle_ = clampAngle(requested, default_target_angle_);
|
||||
angular_speed_ = clampSpeed(std::abs(goal.param("angular_speed", default_angular_speed_)),
|
||||
default_angular_speed_);
|
||||
|
||||
if (std::abs(target_angle_) <= kGoalTolerance)
|
||||
{
|
||||
// Caller nói rõ "đừng quay". Đó là một yêu cầu hợp lệ và hoàn tất ngay.
|
||||
zero_rotation_ = true;
|
||||
return true;
|
||||
}
|
||||
zero_rotation_ = false;
|
||||
|
||||
if (!arcIsClear())
|
||||
{
|
||||
robot::log_warning("[recovery_core] '%s': the %.3f rad arc is blocked, refusing to rotate.",
|
||||
name().c_str(), target_angle_);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
recovery_core::RecoveryResult onUpdate() override
|
||||
recovery_core::RecoveryResult onUpdate(const robot::Time& /*now*/, double dt) override
|
||||
{
|
||||
const double target = std::abs(target_angle_);
|
||||
|
||||
if (rotated_angle_ >= target)
|
||||
if (zero_rotation_)
|
||||
{
|
||||
return succeeded(target);
|
||||
return stopResult(recovery_core::RecoveryStatus::kSucceeded)
|
||||
.withProgress(1.0, 0.0)
|
||||
.withMessage("rotate 0 rad — nothing to rotate");
|
||||
}
|
||||
|
||||
robot_geometry_msgs::PoseStamped pose;
|
||||
if (!ctx().pose->getRobotPose(pose))
|
||||
{
|
||||
current_speed_ = 0.0;
|
||||
return stopResult(recovery_core::RecoveryStatus::kFailed)
|
||||
.withMessage("robot pose lost (TF stale?) — stopping the rotation");
|
||||
}
|
||||
|
||||
const double yaw = recovery_core::yawOf(pose);
|
||||
|
||||
// Cộng dồn từng bước đã chuẩn hoá: cách duy nhất đếm đúng khi tổng góc quay vượt pi.
|
||||
rotated_ += std::abs(recovery_core::normalizeAngle(yaw - last_yaw_));
|
||||
last_yaw_ = yaw;
|
||||
|
||||
const double remaining = target - rotated_;
|
||||
if (remaining <= kGoalTolerance)
|
||||
{
|
||||
current_speed_ = 0.0;
|
||||
return stopResult(recovery_core::RecoveryStatus::kSucceeded)
|
||||
.withProgress(1.0, 0.0)
|
||||
.withMessage("rotate complete");
|
||||
}
|
||||
|
||||
double speed = recovery_core::rampToward(angular_speed_, current_speed_, acc_lim_theta_, dt);
|
||||
if (dt > 0.0)
|
||||
{
|
||||
speed = std::min(speed, remaining / dt);
|
||||
}
|
||||
speed = std::max(speed, 0.0);
|
||||
|
||||
// Cung đã quét ở onStart(), nhưng costmap đổi giữa chừng thì phải phát hiện: kiểm góc dự đoán
|
||||
// ở cuối chu kỳ tới trước khi phát lệnh.
|
||||
const double direction = target_angle_ >= 0.0 ? 1.0 : -1.0;
|
||||
const double next_yaw = yaw + direction * std::max(speed * dt, sim_granularity_);
|
||||
if (ctx().collision->footprintCost(pose.pose.position.x, pose.pose.position.y, next_yaw) < 0.0)
|
||||
{
|
||||
current_speed_ = 0.0;
|
||||
return stopResult(recovery_core::RecoveryStatus::kFailed)
|
||||
.withMessage("the rotation arc became blocked midway — stopping the rotation");
|
||||
}
|
||||
|
||||
current_speed_ = speed;
|
||||
|
||||
robot_geometry_msgs::Twist command;
|
||||
command.angular.z = std::copysign(angular_speed_, target_angle_);
|
||||
rotated_angle_ =
|
||||
std::min(target, rotated_angle_ + std::abs(command.angular.z) * control_period_);
|
||||
|
||||
if (rotated_angle_ >= target)
|
||||
{
|
||||
return succeeded(target);
|
||||
}
|
||||
command.angular.z = std::copysign(speed, target_angle_); // [rad/s], + = ngược chiều kim đồng hồ
|
||||
|
||||
return recovery_core::RecoveryResult::Velocity(command,
|
||||
recovery_core::RecoveryStatus::kRunning)
|
||||
.withProgress(rotated_angle_ / target, target - rotated_angle_)
|
||||
.withProgress(rotated_ / target, remaining)
|
||||
.withMessage("rotating");
|
||||
}
|
||||
|
||||
private:
|
||||
recovery_core::RecoveryResult succeeded(double target)
|
||||
recovery_core::RecoveryResult onCancel() override
|
||||
{
|
||||
return recovery_core::RecoveryResult::Velocity(robot_geometry_msgs::Twist(),
|
||||
recovery_core::RecoveryStatus::kSucceeded)
|
||||
.withProgress(1.0, 0.0)
|
||||
.withMessage("rotate complete");
|
||||
current_speed_ = 0.0;
|
||||
return stopResult(recovery_core::RecoveryStatus::kCancelled).withMessage("rotate cancelled");
|
||||
}
|
||||
|
||||
double default_target_angle_ = kDefaultTargetAngle;
|
||||
double default_angular_speed_ = kDefaultAngularSpeed;
|
||||
double control_period_ = kDefaultControlPeriod;
|
||||
private:
|
||||
/// @return true nếu toàn bộ cung sẽ quay đều đặt được footprint.
|
||||
bool arcIsClear() const
|
||||
{
|
||||
const double target = std::abs(target_angle_);
|
||||
const double direction = target_angle_ >= 0.0 ? 1.0 : -1.0;
|
||||
const double x = start_pose_.pose.position.x;
|
||||
const double y = start_pose_.pose.position.y;
|
||||
|
||||
double target_angle_ = kDefaultTargetAngle;
|
||||
double angular_speed_ = kDefaultAngularSpeed;
|
||||
double rotated_angle_ = 0.0;
|
||||
for (double swept = 0.0; swept < target; swept += sim_granularity_)
|
||||
{
|
||||
if (ctx().collision->footprintCost(x, y, start_yaw_ + direction * swept) < 0.0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Kiểm luôn góc cuối: vòng lặp trên dừng trước target nếu target không chia hết cho bước quét.
|
||||
return ctx().collision->footprintCost(x, y, start_yaw_ + direction * target) >= 0.0;
|
||||
}
|
||||
|
||||
double clampAngle(double value, double fallback) const
|
||||
{
|
||||
if (!std::isfinite(value))
|
||||
{
|
||||
robot::log_warning("[recovery_core] '%s': target_angle is not finite; using %.3f rad.",
|
||||
name().c_str(), fallback);
|
||||
return fallback;
|
||||
}
|
||||
if (std::abs(value) > kTwoPi)
|
||||
{
|
||||
robot::log_warning("[recovery_core] '%s': target_angle=%.3f rad exceeds +/-2pi; clamped.",
|
||||
name().c_str(), value);
|
||||
return std::copysign(kTwoPi, value);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
double clampSpeed(double value, double fallback) const
|
||||
{
|
||||
if (!std::isfinite(value) || value <= 0.0)
|
||||
{
|
||||
robot::log_warning("[recovery_core] '%s': angular_speed=%.3f rad/s is invalid; using %.3f "
|
||||
"rad/s.", name().c_str(), value, fallback);
|
||||
return fallback;
|
||||
}
|
||||
if (value > kMaxAngularSpeed)
|
||||
{
|
||||
robot::log_warning("[recovery_core] '%s': angular_speed=%.3f rad/s > limit %.3f; clamped.",
|
||||
name().c_str(), value, kMaxAngularSpeed);
|
||||
return kMaxAngularSpeed;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
// Config
|
||||
bool full_rotation_ = true;
|
||||
double default_target_angle_ = kTwoPi; ///< [rad]
|
||||
double default_angular_speed_ = kDefaultAngularSpeed; ///< [rad/s]
|
||||
double acc_lim_theta_ = kDefaultAccLimTheta; ///< [rad/s^2]
|
||||
double sim_granularity_ = kDefaultSimGranularity; ///< [rad]
|
||||
|
||||
// Trạng thái lượt hiện tại
|
||||
robot_geometry_msgs::PoseStamped start_pose_;
|
||||
double start_yaw_ = 0.0; ///< [rad]
|
||||
double last_yaw_ = 0.0; ///< [rad]
|
||||
double rotated_ = 0.0; ///< [rad] cộng dồn, luôn >= 0
|
||||
double target_angle_ = kTwoPi; ///< [rad] có dấu
|
||||
double angular_speed_ = kDefaultAngularSpeed; ///< [rad/s] độ lớn
|
||||
double current_speed_ = 0.0; ///< [rad/s] đang phát, để ramp
|
||||
bool zero_rotation_ = false; ///< caller yêu cầu góc 0
|
||||
};
|
||||
|
||||
} // namespace recovery_plugins
|
||||
|
||||
Reference in New Issue
Block a user