optimal
This commit is contained in:
@@ -105,6 +105,54 @@ void describeBinding(std::ostringstream& out, const char* name, const ProfileBin
|
||||
<< " m yaw=" << binding.default_yaw_tolerance << " rad\n";
|
||||
}
|
||||
|
||||
/// Dịch patience gen-1 sang gen-2. Gen-1: mốc + patience luôn ở quá khứ khi patience <= 0, tức là
|
||||
/// "fail -> recovery NGAY". Gen-2: <= 0 nghĩa là TẮT đồng hồ — ngược nghĩa hoàn toàn. Giữ hành vi
|
||||
/// cũ bằng cách dịch thành đúng một chu kỳ điều khiển (gen-1 cũng chỉ phản ứng theo cycle).
|
||||
double legacyPatience(double value, double control_period_s, const char* key)
|
||||
{
|
||||
if (value > 0.0)
|
||||
{
|
||||
return value;
|
||||
}
|
||||
robot::log_warning(
|
||||
"[move_base2] legacy %s = %.3f: gen-1 hiểu là 'fail -> recovery ngay', gen-2 hiểu là 'tắt "
|
||||
"đồng hồ'. Dịch thành một chu kỳ điều khiển (%.4f s) để giữ hành vi cũ.",
|
||||
key, value, control_period_s);
|
||||
return control_period_s;
|
||||
}
|
||||
|
||||
/// Đọc binding của một profile theo schema gen-1: tên local planner ở khoá `<profile>_planner_name`
|
||||
/// tại root, global planner ở section con mang TÊN planner đó (thiếu thì dùng global mặc định).
|
||||
void readLegacyBinding(robot::NodeHandle& nh, const std::string& name_key,
|
||||
const std::string& default_global, double xy_tolerance,
|
||||
double yaw_tolerance, ProfileBinding& binding)
|
||||
{
|
||||
binding.default_xy_tolerance = xy_tolerance;
|
||||
binding.default_yaw_tolerance = yaw_tolerance;
|
||||
binding.global_planner_name = default_global;
|
||||
|
||||
// Default để RỖNG chứ không lấy default gen-1 ("mkt_algorithm/..."): các plugin đó không tồn tại
|
||||
// trong workspace, và profile không khai coi như không dùng — validate sẽ chặn nếu cả bốn rỗng.
|
||||
std::string local_name;
|
||||
nh.param(name_key, local_name, std::string(""));
|
||||
if (local_name.empty())
|
||||
{
|
||||
robot::log_warning("[move_base2] legacy: thiếu '%s' — profile này bị tắt", name_key.c_str());
|
||||
return;
|
||||
}
|
||||
binding.local_planner_name = local_name;
|
||||
|
||||
robot::NodeHandle planner_nh(nh, local_name);
|
||||
if (planner_nh.hasParam("base_global_planner"))
|
||||
{
|
||||
planner_nh.param("base_global_planner", binding.global_planner_name,
|
||||
binding.global_planner_name);
|
||||
}
|
||||
robot::log_info("[move_base2] legacy: %s='%s' -> local='%s' global='%s'", name_key.c_str(),
|
||||
local_name.c_str(), binding.local_planner_name.c_str(),
|
||||
binding.global_planner_name.c_str());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void MoveBase2Config::fromNodeHandle(robot::NodeHandle& nh)
|
||||
@@ -243,6 +291,140 @@ std::string MoveBase2Config::describe() const
|
||||
return out.str();
|
||||
}
|
||||
|
||||
|
||||
// ================================================================================================
|
||||
// Đọc theo schema gen-1
|
||||
// ================================================================================================
|
||||
|
||||
void MoveBase2Config::fromLegacyNodeHandle(robot::NodeHandle& nh)
|
||||
{
|
||||
// Default của gen-1 khác gen-2 ở hai chỗ; chế độ legacy giữ default gen-1 để không đổi hành vi
|
||||
// của một hệ đang chạy chỉ vì đổi runtime.
|
||||
robot_base_frame = "base_footprint";
|
||||
position.default_xy_tolerance = 0.2; // [m]
|
||||
position.default_yaw_tolerance = 0.2; // [rad]
|
||||
|
||||
readDouble(nh, "controller_frequency", controller_frequency);
|
||||
readDouble(nh, "planner_frequency", planner_frequency);
|
||||
readDouble(nh, "planner_patience", state_machine.planner_patience);
|
||||
readDouble(nh, "controller_patience", state_machine.controller_patience);
|
||||
readDouble(nh, "oscillation_timeout", state_machine.oscillation_timeout);
|
||||
readDouble(nh, "oscillation_distance", state_machine.oscillation_distance);
|
||||
readInt(nh, "max_planning_retries", state_machine.max_planning_retries);
|
||||
readBool(nh, "recovery_behavior_enabled", state_machine.recovery_enabled);
|
||||
|
||||
readString(nh, "global_frame", global_frame);
|
||||
readString(nh, "robot_base_frame", robot_base_frame);
|
||||
|
||||
// Sai số ở root là default chung cho cả bốn profile.
|
||||
double xy = position.default_xy_tolerance;
|
||||
double yaw = position.default_yaw_tolerance;
|
||||
readDouble(nh, "xy_goal_tolerance", xy);
|
||||
readDouble(nh, "yaw_goal_tolerance", yaw);
|
||||
|
||||
std::string root_global_planner;
|
||||
readString(nh, "base_global_planner", root_global_planner);
|
||||
|
||||
if (nh.hasParam("base_local_planner"))
|
||||
{
|
||||
std::string adapter;
|
||||
nh.param("base_local_planner", adapter, adapter);
|
||||
// `LocalPlannerAdapter` là cầu nhúng planner gen-2 vào move_base gen-1. move_base2 gọi thẳng
|
||||
// interface gen-2 qua ControllerPort nên không cần cầu đó — bỏ qua CÓ LOG, để không ai tưởng
|
||||
// khoá này vẫn đang có hiệu lực.
|
||||
robot::log_warning("[move_base2] schema gen-1: bỏ qua base_local_planner='%s' — move_base2 gọi "
|
||||
"thẳng local planner, không qua adapter.", adapter.c_str());
|
||||
}
|
||||
|
||||
struct LegacyProfile
|
||||
{
|
||||
const char* key;
|
||||
ProfileBinding* binding;
|
||||
};
|
||||
const LegacyProfile profiles[] = {
|
||||
{ "position_planner_name", &position },
|
||||
{ "docking_planner_name", &docking },
|
||||
{ "go_straight_planner_name", &go_straight },
|
||||
{ "rotate_planner_name", &rotate },
|
||||
};
|
||||
|
||||
for (const LegacyProfile& profile : profiles)
|
||||
{
|
||||
profile.binding->default_xy_tolerance = xy;
|
||||
profile.binding->default_yaw_tolerance = yaw;
|
||||
|
||||
if (!nh.hasParam(profile.key))
|
||||
{
|
||||
robot::log_warning("[move_base2] schema gen-1: thiếu '%s', profile này sẽ từ chối mọi yêu cầu",
|
||||
profile.key);
|
||||
continue;
|
||||
}
|
||||
|
||||
std::string local_planner;
|
||||
nh.param(profile.key, local_planner, local_planner);
|
||||
profile.binding->local_planner_name = local_planner;
|
||||
|
||||
// Global planner riêng của profile nằm trong namespace mang tên chính planner đó; thiếu thì
|
||||
// rơi về khoá ở root. Đúng cách bản cũ tra (`NodeHandle(private_nh_, position_planner_name_)`).
|
||||
profile.binding->global_planner_name = root_global_planner;
|
||||
if (!local_planner.empty())
|
||||
{
|
||||
robot::NodeHandle planner_nh(nh, local_planner);
|
||||
if (planner_nh.hasParam("base_global_planner"))
|
||||
{
|
||||
std::string profile_global;
|
||||
planner_nh.param("base_global_planner", profile_global, profile_global);
|
||||
profile.binding->global_planner_name = profile_global;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Dịch tường minh khác biệt NGỮ NGHĨA của patience. Gen-1: 0 nghĩa là "fail -> recovery NGAY"
|
||||
// (mốc + 0 luôn ở quá khứ). Gen-2: 0 nghĩa là "tắt đồng hồ" — ngược hẳn. Dịch thành đúng MỘT chu
|
||||
// kỳ điều khiển: gen-1 cũng chỉ phản ứng được ở độ phân giải cycle nên hành vi giữ nguyên.
|
||||
const double one_cycle = controller_frequency > 0.0 ? 1.0 / controller_frequency : 0.05; // [s]
|
||||
if (state_machine.planner_patience <= 0.0)
|
||||
{
|
||||
robot::log_warning("[move_base2] schema gen-1: planner_patience <= 0 được dịch thành %.3f s "
|
||||
"(một chu kỳ điều khiển), không phải 'tắt'.", one_cycle);
|
||||
state_machine.planner_patience = one_cycle;
|
||||
}
|
||||
if (state_machine.controller_patience <= 0.0)
|
||||
{
|
||||
robot::log_warning("[move_base2] schema gen-1: controller_patience <= 0 được dịch thành %.3f s "
|
||||
"(một chu kỳ điều khiển), không phải 'tắt'.", one_cycle);
|
||||
state_machine.controller_patience = one_cycle;
|
||||
}
|
||||
}
|
||||
|
||||
MoveBase2Config MoveBase2Config::load(robot::NodeHandle& root_nh)
|
||||
{
|
||||
MoveBase2Config config;
|
||||
|
||||
// Nhận diện schema bằng một khoá bắt buộc phải có ở cả hai. KHÔNG trộn từng khoá giữa hai schema:
|
||||
// hai nguồn cùng có hiệu lực cho một tham số là đúng kiểu lỗi "sửa config mãi không ăn" mà hai cây
|
||||
// config trùng tên của workspace này đã gây ra một lần.
|
||||
robot::NodeHandle modern_nh(root_nh, "move_base2");
|
||||
if (modern_nh.hasParam("controller_frequency"))
|
||||
{
|
||||
robot::log_info("[move_base2] dùng schema mới (namespace 'move_base2').");
|
||||
config.fromNodeHandle(modern_nh);
|
||||
return config;
|
||||
}
|
||||
|
||||
if (root_nh.hasParam("controller_frequency") || root_nh.hasParam("base_global_planner"))
|
||||
{
|
||||
robot::log_warning("[move_base2] không thấy namespace 'move_base2'; đọc theo schema gen-1 của "
|
||||
"move_base_common_params.yaml.");
|
||||
config.fromLegacyNodeHandle(root_nh);
|
||||
return config;
|
||||
}
|
||||
|
||||
robot::log_error("[move_base2] không tìm thấy cấu hình nào — chạy với toàn bộ giá trị mặc định. "
|
||||
"Kiểm PNKX_NAV_CORE_CONFIG_DIR và sự tồn tại của file config.");
|
||||
return config;
|
||||
}
|
||||
|
||||
ControlLoopConfig MoveBase2Config::toControlLoopConfig() const
|
||||
{
|
||||
ControlLoopConfig config;
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
#include <cmath>
|
||||
#include <sstream>
|
||||
|
||||
#include <robot/console.h>
|
||||
|
||||
namespace move_base2
|
||||
{
|
||||
namespace
|
||||
@@ -400,6 +402,13 @@ bool ControlLoop::step()
|
||||
action_feedback_ = ActionFeedback::kIdle;
|
||||
|
||||
// --- 3. Thi hành output ---------------------------------------------------------------------
|
||||
// Nhãn của yêu cầu SẮP bị thay. Preempt bật cả `report_outcome` lẫn `accept_request` trong cùng
|
||||
// một cycle, mà phần báo kết quả nằm sau phần nhận yêu cầu — không giữ lại id cũ ở đây thì kết
|
||||
// quả của chặng cũ sẽ được báo dưới id của chặng MỚI, và mission layer mất dấu cả hai.
|
||||
const std::uint64_t outgoing_mission_id =
|
||||
has_active_request_ ? active_request_.mission_sequence_id : 0;
|
||||
const bool had_active_request = has_active_request_;
|
||||
|
||||
if (output.accept_request)
|
||||
{
|
||||
active_request_ = pending_request_;
|
||||
@@ -408,6 +417,21 @@ bool ControlLoop::step()
|
||||
latest_plan_.clear();
|
||||
has_outcome_ = false;
|
||||
|
||||
// Log một lần tại sườn nhận goal — không nằm trên đường lặp của control loop.
|
||||
if (active_request_.has_goal)
|
||||
{
|
||||
robot::log_info("[move_base2] Nhận goal (mission %llu): x=%.3f y=%.3f frame=%s.\n",
|
||||
static_cast<unsigned long long>(active_request_.mission_sequence_id),
|
||||
active_request_.goal.pose.position.x, active_request_.goal.pose.position.y,
|
||||
active_request_.goal.header.frame_id.c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
robot::log_info("[move_base2] Nhận yêu cầu chỉ-action (mission %llu), %zu action.\n",
|
||||
static_cast<unsigned long long>(active_request_.mission_sequence_id),
|
||||
active_request_.actions.size());
|
||||
}
|
||||
|
||||
// Nhãn mới: mọi lượt lập plan đang bay thuộc về goal cũ và phải bị vứt khi về.
|
||||
++plan_tag_;
|
||||
deps_.planner->cancelPlan();
|
||||
@@ -546,19 +570,49 @@ bool ControlLoop::step()
|
||||
has_outcome_ = true;
|
||||
++outcome_report_count_;
|
||||
|
||||
if (deps_.mission != nullptr && has_active_request_ &&
|
||||
active_request_.mission_sequence_id != 0)
|
||||
// Log kết cục ra terminal — bản cũ báo "Goal reached!" tại đây, host và người vận hành đều
|
||||
// dựa vào dòng này để biết chặng kết thúc ra sao. Chỉ chạy đúng một lần mỗi chặng.
|
||||
switch (output.outcome)
|
||||
{
|
||||
deps_.mission->reportOutcome(active_request_.mission_sequence_id, output.outcome);
|
||||
case NavigationOutcome::kSucceeded:
|
||||
robot::log_success("[move_base2] Goal reached (mission %llu).\n",
|
||||
static_cast<unsigned long long>(outgoing_mission_id));
|
||||
break;
|
||||
case NavigationOutcome::kPreempted:
|
||||
robot::log_info("[move_base2] Goal bị thay bởi goal mới (mission %llu: PREEMPTED).\n",
|
||||
static_cast<unsigned long long>(outgoing_mission_id));
|
||||
break;
|
||||
case NavigationOutcome::kCancelled:
|
||||
robot::log_info("[move_base2] Goal bị huỷ (mission %llu: CANCELLED).\n",
|
||||
static_cast<unsigned long long>(outgoing_mission_id));
|
||||
break;
|
||||
case NavigationOutcome::kFailed:
|
||||
robot::log_error("[move_base2] Navigation thất bại (mission %llu: ABORTED): %s\n",
|
||||
static_cast<unsigned long long>(outgoing_mission_id),
|
||||
last_reason_ != nullptr ? last_reason_ : "");
|
||||
break;
|
||||
}
|
||||
|
||||
has_active_request_ = false;
|
||||
if (deps_.mission != nullptr && had_active_request && outgoing_mission_id != 0)
|
||||
{
|
||||
deps_.mission->reportOutcome(outgoing_mission_id, output.outcome);
|
||||
}
|
||||
|
||||
// Preempt vừa báo kết quả chặng cũ VỪA nhận chặng mới trong cùng cycle. Chỉ xoá yêu cầu đang
|
||||
// chạy khi thực sự không có chặng mới nào thay thế.
|
||||
if (!output.accept_request)
|
||||
{
|
||||
has_active_request_ = false;
|
||||
latest_plan_.clear();
|
||||
planner_running_ = false;
|
||||
}
|
||||
cancel_requested_ = false;
|
||||
latest_plan_.clear();
|
||||
planner_running_ = false;
|
||||
}
|
||||
|
||||
return !output.report_outcome;
|
||||
// false = "yêu cầu hiện tại vừa kết thúc, không còn gì để chạy". Preempt CŨNG báo kết quả (cho
|
||||
// chặng cũ) nhưng ngay lập tức nhận chặng mới — lúc đó runtime vẫn đang chạy, nên không được trả
|
||||
// false. Điều kiện đúng là "vào state terminal", không phải "có báo kết quả".
|
||||
return !isTerminal(output.state);
|
||||
}
|
||||
|
||||
const char* ControlLoop::lastOutcome() const
|
||||
|
||||
148
src/io/costmap_exporter.cpp
Normal file
148
src/io/costmap_exporter.cpp
Normal file
@@ -0,0 +1,148 @@
|
||||
/*********************************************************************
|
||||
*
|
||||
* Software License Agreement (BSD License)
|
||||
*
|
||||
* move_base2 — cài đặt CostmapExporter.
|
||||
*
|
||||
* Author: DuongTD
|
||||
*********************************************************************/
|
||||
#include <move_base2/io/costmap_exporter.h>
|
||||
|
||||
#include <array>
|
||||
|
||||
#include <boost/thread/locks.hpp>
|
||||
|
||||
#include <robot/robot.h>
|
||||
#include <robot_costmap_2d/cost_values.h>
|
||||
#include <robot_costmap_2d/costmap_2d_robot.h>
|
||||
#include <robot_costmap_2d/layered_costmap.h>
|
||||
|
||||
namespace move_base2
|
||||
{
|
||||
namespace
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief Bảng quy đổi 0–255 của costmap sang −1..100 của OccupancyGrid.
|
||||
*
|
||||
* Dựng một lần, bất biến. Bản cũ dùng `new char[256]` gán vào biến static rồi không bao giờ giải
|
||||
* phóng; ở đây là một mảng có tuổi thọ tĩnh, không cấp phát động.
|
||||
*/
|
||||
const std::array<signed char, 256>& costTranslationTable()
|
||||
{
|
||||
static const std::array<signed char, 256> table = [] {
|
||||
std::array<signed char, 256> t{};
|
||||
|
||||
// Dải thường: 10..252 của costmap co về 1..98 của OccupancyGrid.
|
||||
for (int i = static_cast<int>(robot_costmap_2d::FREE_SPACE) + 1;
|
||||
i < static_cast<int>(robot_costmap_2d::INSCRIBED_INFLATED_OBSTACLE); ++i)
|
||||
{
|
||||
t[static_cast<std::size_t>(i)] = static_cast<signed char>(1 + (97 * (i - 1)) / 251);
|
||||
}
|
||||
|
||||
// Các giá trị đặc biệt, đặt SAU dải thường để không bị nó ghi đè.
|
||||
t[static_cast<std::size_t>(robot_costmap_2d::PREFERRED_SPACE)] = 120;
|
||||
for (int i = static_cast<int>(robot_costmap_2d::PREFERRED_SPACE) + 1;
|
||||
i <= static_cast<int>(robot_costmap_2d::FREE_SPACE); ++i)
|
||||
{
|
||||
t[static_cast<std::size_t>(i)] = 0; // không có vật cản
|
||||
}
|
||||
t[static_cast<std::size_t>(robot_costmap_2d::CRITICAL_SPACE)] = 150;
|
||||
t[static_cast<std::size_t>(robot_costmap_2d::INSCRIBED_INFLATED_OBSTACLE)] = 99;
|
||||
t[static_cast<std::size_t>(robot_costmap_2d::LETHAL_OBSTACLE)] = 100;
|
||||
t[static_cast<std::size_t>(robot_costmap_2d::NO_INFORMATION)] = -1; // chưa biết
|
||||
|
||||
return t;
|
||||
}();
|
||||
return table;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
CostmapExporter::CostmapExporter() = default;
|
||||
|
||||
void CostmapExporter::attach(robot_costmap_2d::Costmap2DROBOT* costmap,
|
||||
const std::string& global_frame)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
costmap_ = costmap;
|
||||
if (!global_frame.empty())
|
||||
{
|
||||
global_frame_ = global_frame;
|
||||
}
|
||||
}
|
||||
|
||||
void CostmapExporter::prepareGridLocked()
|
||||
{
|
||||
robot_costmap_2d::Costmap2D* costmap = costmap_->getCostmap();
|
||||
const double resolution = costmap->getResolution(); // [m/cell]
|
||||
|
||||
grid_.header.frame_id = global_frame_;
|
||||
grid_.header.stamp = robot::Time::now();
|
||||
grid_.info.resolution = resolution;
|
||||
grid_.info.width = costmap->getSizeInCellsX();
|
||||
grid_.info.height = costmap->getSizeInCellsY();
|
||||
|
||||
// Gốc của OccupancyGrid là GÓC ô (0,0), còn mapToWorld trả TÂM ô — lệch nửa ô. Bỏ qua phép trừ
|
||||
// này thì cả bản đồ trên rviz dịch đi 2.5 cm ở độ phân giải 0.05 m, đủ để mọi thứ trông "gần
|
||||
// đúng" mà không ai chỉ ra được sai ở đâu.
|
||||
double wx = 0.0;
|
||||
double wy = 0.0;
|
||||
costmap->mapToWorld(0, 0, wx, wy);
|
||||
grid_.info.origin.position.x = wx - resolution / 2.0; // [m]
|
||||
grid_.info.origin.position.y = wy - resolution / 2.0; // [m]
|
||||
grid_.info.origin.position.z = 0.0;
|
||||
grid_.info.origin.orientation.w = 1.0;
|
||||
|
||||
saved_origin_x_ = costmap->getOriginX();
|
||||
saved_origin_y_ = costmap->getOriginY();
|
||||
|
||||
grid_.data.resize(static_cast<std::size_t>(grid_.info.width) * grid_.info.height);
|
||||
|
||||
const unsigned char* data = costmap->getCharMap();
|
||||
const std::array<signed char, 256>& table = costTranslationTable();
|
||||
for (std::size_t i = 0; i < grid_.data.size(); ++i)
|
||||
{
|
||||
grid_.data[i] = table[data[i]];
|
||||
}
|
||||
}
|
||||
|
||||
void CostmapExporter::fill(robot_nav_msgs::OccupancyGrid& grid,
|
||||
robot_map_msgs::OccupancyGridUpdate& /*update*/, bool& is_updated)
|
||||
{
|
||||
is_updated = false;
|
||||
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
if (costmap_ == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
robot_costmap_2d::Costmap2D* costmap = costmap_->getCostmap();
|
||||
if (costmap == nullptr || costmap->getSizeInCellsX() == 0 || costmap->getSizeInCellsY() == 0)
|
||||
{
|
||||
// Costmap chưa nhận được static map. Trả về mà không ghi gì: publish một lưới rỗng sẽ xoá trắng
|
||||
// thứ đang hiển thị trên rviz.
|
||||
return;
|
||||
}
|
||||
|
||||
// Khoá costmap trong lúc đọc: thread cập nhật của costmap đang ghi vào chính mảng này.
|
||||
boost::unique_lock<robot_costmap_2d::Costmap2D::mutex_t> costmap_lock(*(costmap->getMutex()));
|
||||
|
||||
// LUÔN dựng lại lưới đầy đủ.
|
||||
//
|
||||
// Đường gửi phần-thay-đổi chỉ đúng khi có ĐÚNG MỘT consumer, mà host thì gọi getGlobalData() từ
|
||||
// HAI ros::Timer: costmap (1 Hz) và plan (10 Hz). Timer plan tiêu thụ hầu hết các lần gọi rồi vứt
|
||||
// phần costmap đi, nên tới lượt timer costmap thì vùng thay đổi đã bị reset — và lưới cache chỉ
|
||||
// được dựng lại khi HÌNH HỌC đổi. Local costmap là rolling window nên gốc toạ độ dịch liên tục và
|
||||
// vẫn tươi; global costmap hình học cố định nên đóng băng ở ảnh chụp đầu tiên vĩnh viễn.
|
||||
//
|
||||
// Bản cũ có cờ `always_send_full_costmap` cho đúng tình huống này. Ở đây chọn luôn gửi đầy đủ:
|
||||
// 400x400 ô là 160 KB, ở 1-5 Hz là không đáng kể so với một lớp lỗi im lặng.
|
||||
prepareGridLocked();
|
||||
grid = grid_;
|
||||
is_updated = false;
|
||||
|
||||
}
|
||||
|
||||
} // namespace move_base2
|
||||
@@ -33,3 +33,16 @@ robot::move_base_core::BaseNavigation::Ptr createMoveBase2()
|
||||
} // namespace move_base2
|
||||
|
||||
BOOST_DLL_ALIAS(move_base2::createMoveBase2, MoveBase2)
|
||||
|
||||
/**
|
||||
* Alias thứ hai, cùng trỏ vào một factory.
|
||||
*
|
||||
* `amr_control` import alias **"MoveBase"** — tên đó hard-code trong host
|
||||
* (`amr_control.cpp:244-246`), không đọc từ config. Không export nó thì đổi
|
||||
* `MoveBase: library_path: libmove_base2` sẽ hỏng lúc chạy với lỗi không tìm thấy symbol, và
|
||||
* `boost::dll` không kiểm kiểu qua ranh giới `.so` nên thông báo lỗi cũng không nói được vì sao.
|
||||
*
|
||||
* Có cả hai alias nghĩa là chuyển đổi giữa hai runtime chỉ tốn đúng một dòng YAML, không phải sửa
|
||||
* host. `MoveBase2` giữ lại cho host nào muốn nạp tường minh bản mới.
|
||||
*/
|
||||
BOOST_DLL_ALIAS(move_base2::createMoveBase2, MoveBase)
|
||||
|
||||
202
src/navigation_runtime.cpp
Normal file
202
src/navigation_runtime.cpp
Normal file
@@ -0,0 +1,202 @@
|
||||
/*********************************************************************
|
||||
*
|
||||
* Software License Agreement (BSD License)
|
||||
*
|
||||
* move_base2 — cài đặt NavigationRuntime.
|
||||
*
|
||||
* Author: DuongTD
|
||||
*********************************************************************/
|
||||
#include <move_base2/navigation_runtime.h>
|
||||
|
||||
#include <exception>
|
||||
|
||||
#include <robot/node_handle.h>
|
||||
#include <robot/robot.h>
|
||||
#include <tf3/buffer_core.h>
|
||||
|
||||
namespace move_base2
|
||||
{
|
||||
|
||||
NavigationRuntime::NavigationRuntime() = default;
|
||||
|
||||
NavigationRuntime::~NavigationRuntime()
|
||||
{
|
||||
// Dừng thread cập nhật costmap trước khi bất kỳ thành viên nào bị huỷ: các runner giữ con trỏ tới
|
||||
// costmap, và costmap giữ thread đang chạy qua chuỗi layer.
|
||||
stop();
|
||||
}
|
||||
|
||||
bool NavigationRuntime::buildCostmaps(const std::shared_ptr<tf3::BufferCore>& tf,
|
||||
std::string& error)
|
||||
{
|
||||
if (built_ || costmapsReady())
|
||||
{
|
||||
error = "NavigationRuntime::buildCostmaps() gọi lần thứ hai";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!tf)
|
||||
{
|
||||
error = "NavigationRuntime cần TF buffer khác null";
|
||||
return false;
|
||||
}
|
||||
|
||||
tf_ = tf;
|
||||
|
||||
// --- 1. Config ---------------------------------------------------------------------------------
|
||||
robot::NodeHandle root_nh("~");
|
||||
config_ = MoveBase2Config::load(root_nh);
|
||||
|
||||
// --- 2. Costmap --------------------------------------------------------------------------------
|
||||
//
|
||||
// Dựng nhưng CHƯA start: thread cập nhật chạy trong lúc planner chưa nạp xong là cửa sổ để mọi
|
||||
// thứ chạm vào nhau ở trạng thái nửa vời. start() nằm ở hàm riêng, gọi sau khi lắp xong.
|
||||
try
|
||||
{
|
||||
global_costmap_.reset(new robot_costmap_2d::Costmap2DROBOT("global_costmap", *tf_));
|
||||
local_costmap_.reset(new robot_costmap_2d::Costmap2DROBOT("local_costmap", *tf_));
|
||||
}
|
||||
catch (const std::exception& ex)
|
||||
{
|
||||
error = std::string("không dựng được costmap: ") + ex.what();
|
||||
global_costmap_.reset();
|
||||
local_costmap_.reset();
|
||||
return false;
|
||||
}
|
||||
|
||||
global_costmap_->pause();
|
||||
local_costmap_->pause();
|
||||
|
||||
// Hai nguồn pose, khác frame — xem doc của thành viên. Bản cũ cũng vậy: `makePlan` lấy start từ
|
||||
// `planner_costmap_robot_` (map), còn `LocalPlannerAdapter` lấy pose từ costmap local (odom).
|
||||
global_pose_.setCostmap(global_costmap_.get());
|
||||
local_pose_.setCostmap(local_costmap_.get());
|
||||
|
||||
// Gắn ngay: host có thể hỏi dữ liệu hiển thị bất cứ lúc nào sau initialize(), kể cả trước khi
|
||||
// costmap có nội dung. Exporter tự trả về "chưa có gì" thay vì lưới rỗng.
|
||||
global_exporter_.attach(global_costmap_.get(), config_.global_frame);
|
||||
local_exporter_.attach(local_costmap_.get(), local_costmap_->getGlobalFrameID());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NavigationRuntime::buildRunners(std::string& error)
|
||||
{
|
||||
if (built_)
|
||||
{
|
||||
error = "NavigationRuntime::buildRunners() gọi lần thứ hai";
|
||||
return false;
|
||||
}
|
||||
if (!costmapsReady())
|
||||
{
|
||||
error = "buildRunners() gọi trước buildCostmaps()";
|
||||
return false;
|
||||
}
|
||||
|
||||
robot::NodeHandle root_nh("~");
|
||||
|
||||
// --- 3. Planner và controller ------------------------------------------------------------------
|
||||
if (!planner_.configure(root_nh, global_costmap_.get(), config_.position.global_planner_name,
|
||||
error))
|
||||
{
|
||||
global_costmap_.reset();
|
||||
local_costmap_.reset();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!controller_.configure(root_nh, tf_, local_costmap_.get(), &local_pose_,
|
||||
config_.position.local_planner_name, error))
|
||||
{
|
||||
global_costmap_.reset();
|
||||
local_costmap_.reset();
|
||||
return false;
|
||||
}
|
||||
|
||||
// --- 4. Recovery -------------------------------------------------------------------------------
|
||||
RecoveryRunner::Deps recovery_deps;
|
||||
recovery_deps.clock = &clock_;
|
||||
recovery_deps.pose = &local_pose_;
|
||||
recovery_deps.local_costmap = local_costmap_.get();
|
||||
recovery_deps.global_costmap = global_costmap_.get();
|
||||
recovery_.setDeps(recovery_deps);
|
||||
recovery_.setNamespace(config_.recovery_namespace);
|
||||
|
||||
if (!recovery_.configure(root_nh))
|
||||
{
|
||||
// Không dừng lại: một behavior hỏng không nên xoá sạch các đường phục hồi còn lại.
|
||||
// `behaviorCount()` bên dưới phản ánh số nạp được THẬT, và `validate()` sẽ chặn nếu con số đó
|
||||
// bằng 0 trong khi recovery vẫn đang bật.
|
||||
robot::log_warning("[move_base2] NavigationRuntime: có behavior recovery nạp hỏng; chạy tiếp "
|
||||
"với %zu behavior còn lại.\n", recovery_.behaviorCount());
|
||||
}
|
||||
|
||||
// Ràng buộc thứ tự khởi tạo — xem doc của lớp. Con số này KHÔNG đến từ YAML.
|
||||
config_.state_machine.recovery_behavior_count = recovery_.behaviorCount();
|
||||
|
||||
// --- 5. Action ---------------------------------------------------------------------------------
|
||||
action_.setClock(&clock_);
|
||||
action_.setNamespace(config_.action_namespace);
|
||||
|
||||
if (!action_.configure(root_nh))
|
||||
{
|
||||
robot::log_warning("[move_base2] NavigationRuntime: có action handler nạp hỏng; chạy tiếp với "
|
||||
"%zu handler còn lại.\n", action_.handlerCount());
|
||||
}
|
||||
|
||||
// --- 6. Kiểm cấu hình sau cùng -----------------------------------------------------------------
|
||||
if (!config_.validate(error))
|
||||
{
|
||||
global_costmap_.reset();
|
||||
local_costmap_.reset();
|
||||
return false;
|
||||
}
|
||||
|
||||
robot::log_info("[move_base2] NavigationRuntime dựng xong:\n%s", config_.describe().c_str());
|
||||
|
||||
built_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void NavigationRuntime::start()
|
||||
{
|
||||
if (!built_)
|
||||
{
|
||||
return;
|
||||
}
|
||||
global_costmap_->start();
|
||||
local_costmap_->start();
|
||||
mission_.start();
|
||||
}
|
||||
|
||||
void NavigationRuntime::stop()
|
||||
{
|
||||
mission_.stop();
|
||||
if (local_costmap_)
|
||||
{
|
||||
local_costmap_->stop();
|
||||
}
|
||||
if (global_costmap_)
|
||||
{
|
||||
global_costmap_->stop();
|
||||
}
|
||||
}
|
||||
|
||||
ControlLoopDeps NavigationRuntime::deps()
|
||||
{
|
||||
ControlLoopDeps deps;
|
||||
if (!built_)
|
||||
{
|
||||
return deps;
|
||||
}
|
||||
|
||||
deps.clock = &clock_;
|
||||
deps.pose = &global_pose_; // planner start + chống quẩn + pose báo về host: frame `map`
|
||||
deps.planner = &planner_;
|
||||
deps.controller = &controller_;
|
||||
deps.recovery = &recovery_;
|
||||
deps.mission = &mission_;
|
||||
deps.action = &action_;
|
||||
return deps;
|
||||
}
|
||||
|
||||
} // namespace move_base2
|
||||
@@ -39,7 +39,64 @@ NavigationServer::NavigationServer()
|
||||
nav_feedback_->is_ready = false;
|
||||
}
|
||||
|
||||
NavigationServer::~NavigationServer() = default;
|
||||
NavigationServer::~NavigationServer()
|
||||
{
|
||||
// Dừng control thread TRƯỚC khi bất kỳ thành viên nào bị huỷ: nó đang chạm loop_, runtime_ và
|
||||
// sensors_ ở mỗi cycle. Phần join thread còn lại (planner, map update) nằm ở destructor của
|
||||
// runtime_ và các thành viên, chạy ngay sau đây theo thứ tự khai báo.
|
||||
shutdown();
|
||||
}
|
||||
|
||||
void NavigationServer::shutdown()
|
||||
{
|
||||
stopControlThread();
|
||||
if (runtime_ != nullptr)
|
||||
{
|
||||
runtime_->stop();
|
||||
}
|
||||
}
|
||||
|
||||
bool NavigationServer::startControlThread(double frequency)
|
||||
{
|
||||
if (!loop_.initialized())
|
||||
{
|
||||
robot::log_error("[move_base2] startControlThread() trước khi control loop được cấu hình.\n");
|
||||
return false;
|
||||
}
|
||||
if (control_thread_running_.load())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!(frequency > 0.0))
|
||||
{
|
||||
robot::log_error("[move_base2] controller_frequency phải > 0 [Hz], nhận %.3f.\n", frequency);
|
||||
return false;
|
||||
}
|
||||
|
||||
control_thread_running_.store(true);
|
||||
control_thread_ = std::thread([this, frequency]() {
|
||||
robot::Rate rate(frequency);
|
||||
while (control_thread_running_.load())
|
||||
{
|
||||
// Bỏ qua giá trị trả về: false chỉ nghĩa là yêu cầu hiện tại vừa kết thúc, không phải lý do
|
||||
// dừng vòng lặp — thread phải sống để nhận goal kế tiếp.
|
||||
spinOnce();
|
||||
rate.sleep();
|
||||
}
|
||||
});
|
||||
|
||||
robot::log_info("[move_base2] control thread chạy ở %.2f Hz.\n", frequency);
|
||||
return true;
|
||||
}
|
||||
|
||||
void NavigationServer::stopControlThread()
|
||||
{
|
||||
control_thread_running_.store(false);
|
||||
if (control_thread_.joinable())
|
||||
{
|
||||
control_thread_.join();
|
||||
}
|
||||
}
|
||||
|
||||
// ================================================================================================
|
||||
// Cấu hình lõi
|
||||
@@ -57,6 +114,7 @@ bool NavigationServer::configureLoop(const ControlLoopConfig& config, const Cont
|
||||
|
||||
robot_base_frame_ = config.robot_base_frame;
|
||||
|
||||
|
||||
nav_feedback_->is_ready = true;
|
||||
nav_feedback_->feed_back_str = "sẵn sàng";
|
||||
refreshFeedback();
|
||||
@@ -102,9 +160,18 @@ bool NavigationServer::spinOnce()
|
||||
// không ở cuối cycle để trần vận tốc có hiệu lực ngay trong chính cycle này — chậm một cycle
|
||||
// nghĩa là một chu kỳ nữa chạy quá tốc độ mà tầng an toàn vừa yêu cầu hạ.
|
||||
pushHostInputsToController();
|
||||
drainLifecycleRequests();
|
||||
|
||||
// Mission layer cất chặng mới trong bridge; đây là chỗ duy nhất nó được đẩy vào lõi, trên đúng
|
||||
// control thread.
|
||||
if (runtime_ != nullptr)
|
||||
{
|
||||
runtime_->mission().pumpPendingRequest();
|
||||
}
|
||||
|
||||
const bool running = loop_.step();
|
||||
publishCommand();
|
||||
cachePlans();
|
||||
refreshFeedback();
|
||||
return running;
|
||||
}
|
||||
@@ -116,14 +183,39 @@ void NavigationServer::publishCommand()
|
||||
// — robot giữ nguyên tốc độ hiện tại vô hạn và VelocityArbiter bị vô hiệu hoàn toàn.
|
||||
//
|
||||
// Nguồn duy nhất đúng là lệnh vừa qua bộ trọng tài. Dấu thời gian lấy theo cycle của control loop
|
||||
// chứ không phải giờ hệ thống lúc gọi: host loại lệnh quá hạn, nên control loop treo phải làm dấu
|
||||
// thời gian đứng yên để host thấy được và ngừng phát.
|
||||
// chứ không phải giờ hệ thống lúc gọi: host loại lệnh quá hạn (cửa 0.05 s, amr_publiser.cpp:361),
|
||||
// nên control loop treo phải làm dấu thời gian đứng yên để host thấy được và ngừng phát.
|
||||
//
|
||||
// Và khi KHÔNG có yêu cầu nào, dấu thời gian cũng phải đứng yên — đóng dấu mọi cycle là host
|
||||
// phát 0 lên /cmd_vel ở 20 Hz vĩnh viễn, đè chết teleop/joystick (rqt_robot_steering 10 Hz thua
|
||||
// 2:1 — robot chỉ nhích rồi đứng im). Bản cũ chỉ đóng dấu trong executeCycle và ở
|
||||
// publishZeroVelocity(), tức chỉ khi đang điều khiển; đây là cùng ngữ nghĩa. Giữ thêm một cửa
|
||||
// ân hạn sau khi yêu cầu kết thúc để lệnh dừng cuối cùng chắc chắn lọt qua cửa 0.05 s của host
|
||||
// — kết thúc mà đóng băng ngay thì robot giữ nguyên vận tốc chót vì lệnh 0 không bao giờ được
|
||||
// publish.
|
||||
constexpr double kStopGraceSeconds = 0.5; // [s] ~10 cửa sổ 0.05s của host
|
||||
|
||||
const robot_geometry_msgs::Twist& command = loop_.lastCommand();
|
||||
const robot::Time cycle_time = loop_.lastCycleTime();
|
||||
|
||||
if (loop_.hasActiveRequest())
|
||||
{
|
||||
last_command_active_time_ = cycle_time;
|
||||
}
|
||||
|
||||
const bool commanding =
|
||||
loop_.hasActiveRequest() ||
|
||||
(!last_command_active_time_.isZero() &&
|
||||
(cycle_time - last_command_active_time_).toSec() < kStopGraceSeconds);
|
||||
|
||||
std::lock_guard<std::mutex> lock(data_mutex_);
|
||||
twist_.velocity = robot_nav_2d_utils::twist3Dto2D(command);
|
||||
twist_.header.stamp = loop_.lastCycleTime();
|
||||
twist_.header.frame_id = robot_base_frame_;
|
||||
if (commanding)
|
||||
{
|
||||
twist_.header.stamp = cycle_time;
|
||||
}
|
||||
// Không commanding: giữ nguyên stamp cũ — host thấy lệnh quá hạn và nhường /cmd_vel cho nguồn khác.
|
||||
}
|
||||
|
||||
robot::move_base_core::State NavigationServer::toHostState(NavigationState state)
|
||||
@@ -187,14 +279,99 @@ void NavigationServer::initialize(robot::TFListenerPtr tf)
|
||||
{
|
||||
tf_ = tf;
|
||||
|
||||
// Phần dựng costmap, planner runner, controller runner và recovery runner từ tf này thuộc bước
|
||||
// nối dây runtime. Cho tới lúc đó, các cổng phải được bơm vào qua configureLoop() — cố ý KHÔNG
|
||||
// tự dựng cổng giả ở đây, vì một runtime chạy được với cổng giả là thứ nguy hiểm nhất có thể có.
|
||||
if (!loop_.initialized())
|
||||
if (loop_.initialized())
|
||||
{
|
||||
nav_feedback_->is_ready = false;
|
||||
nav_feedback_->feed_back_str = "đã nhận tf, chờ configureLoop() nạp các cổng runtime";
|
||||
// Cổng đã được bơm vào từ ngoài (test, hoặc host tự lắp). Không dựng đè lên.
|
||||
return;
|
||||
}
|
||||
|
||||
runtime_.reset(new NavigationRuntime());
|
||||
|
||||
std::string error;
|
||||
|
||||
// --- Pha 1: config + costmap. CHƯA nạp plugin nào. -------------------------------------------
|
||||
if (!runtime_->buildCostmaps(tf_, error))
|
||||
{
|
||||
runtime_.reset();
|
||||
nav_feedback_->is_ready = false;
|
||||
nav_feedback_->feed_back_str = "không dựng được costmap: " + error;
|
||||
robot::log_error("[move_base2] initialize() thất bại: %s\n", error.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
if (!configureSensors(runtime_->config().sensors, error))
|
||||
{
|
||||
runtime_.reset();
|
||||
nav_feedback_->is_ready = false;
|
||||
nav_feedback_->feed_back_str = "cấu hình cảm biến lỗi: " + error;
|
||||
return;
|
||||
}
|
||||
|
||||
// --- Gắn costmap và PHÁT LẠI static map, TRƯỚC khi nạp planner -------------------------------
|
||||
//
|
||||
// Thứ tự này bắt buộc. `SBPLLatticePlanner::initialize` chặn cho tới khi costmap có kích thước
|
||||
// khác 0 (`sbpl_lattice_planner.cpp:132`), mà kích thước đó chỉ được đặt khi static map tới
|
||||
// `StaticLayer`. Nạp planner trước khi map vào được costmap là khoá chết: planner chờ map, map
|
||||
// chờ planner xong. SBPL `exit(1)` sau 2 giây và giết cả tiến trình.
|
||||
//
|
||||
// Bản cũ đi đúng thứ tự này: dựng costmap (`move_base.cpp:260`) -> đẩy map (`:287`) -> nạp
|
||||
// planner (`:306`).
|
||||
// Frame của hai lưới lấy TỪ chính costmap, không đoán: global thường là `map`, local là `odom`,
|
||||
// nhưng cả hai đều do config quyết định.
|
||||
global_frame_ = runtime_->globalCostmap()->getGlobalFrameID();
|
||||
local_frame_ = runtime_->localCostmap()->getGlobalFrameID();
|
||||
|
||||
attachCostmaps(runtime_->globalCostmap()->getLayeredCostmap(),
|
||||
runtime_->localCostmap()->getLayeredCostmap());
|
||||
|
||||
// --- Pha 2: nạp planner, controller, recovery, action ----------------------------------------
|
||||
if (!runtime_->buildRunners(error))
|
||||
{
|
||||
runtime_.reset();
|
||||
nav_feedback_->is_ready = false;
|
||||
nav_feedback_->feed_back_str = "không nạp được runtime: " + error;
|
||||
robot::log_error("[move_base2] initialize() thất bại: %s\n", error.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
if (!configureLoop(runtime_->config().toControlLoopConfig(), runtime_->deps(), error))
|
||||
{
|
||||
runtime_.reset();
|
||||
robot::log_error("[move_base2] initialize() không cấu hình được control loop: %s\n",
|
||||
error.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
// Mission layer đẩy chặng xuống qua callback này. Callback chạy trên CONTROL THREAD — bridge cất
|
||||
// chặng lại và chỉ nhả ra ở pumpPendingRequest(), nên submit() không bị gọi từ thread mission.
|
||||
runtime_->mission().setRequestCallback([this](const NavigationRequest& request) {
|
||||
std::string reason;
|
||||
if (!loop_.submit(request, reason))
|
||||
{
|
||||
last_reject_reason_ = reason;
|
||||
robot::log_error("[move_base2] từ chối chặng mission %llu: %s\n",
|
||||
static_cast<unsigned long long>(request.mission_sequence_id),
|
||||
reason.c_str());
|
||||
}
|
||||
});
|
||||
runtime_->mission().setCancelCallback([this]() { cancel(); });
|
||||
|
||||
// start() sau cùng: cho thread cập nhật costmap chạy khi mọi thứ khác đã lắp xong.
|
||||
runtime_->start();
|
||||
|
||||
// Rồi mới cho control loop chạy. Đây là thứ biến một tập thành phần đã lắp thành một runtime
|
||||
// đang chạy — thiếu nó thì goal chỉ nằm trong chỗ chờ.
|
||||
if (!startControlThread(runtime_->config().controller_frequency))
|
||||
{
|
||||
runtime_.reset();
|
||||
nav_feedback_->is_ready = false;
|
||||
nav_feedback_->feed_back_str = "không khởi động được control thread";
|
||||
return;
|
||||
}
|
||||
|
||||
nav_feedback_->is_ready = true;
|
||||
nav_feedback_->feed_back_str = "sẵn sàng";
|
||||
refreshFeedback();
|
||||
}
|
||||
|
||||
// ================================================================================================
|
||||
@@ -497,19 +674,60 @@ bool NavigationServer::rotateTo(const robot_geometry_msgs::PoseStamped& goal,
|
||||
// Điều khiển vòng đời
|
||||
// ================================================================================================
|
||||
|
||||
// Ba hàm dưới đây được host gọi từ thread OPC-UA và VDA5050 (amr_control.cpp:159, 184), KHÔNG phải
|
||||
// từ control thread. `ControlLoop` tự khai là không thread-safe, và `requestPause()` còn ghi hai cờ
|
||||
// không nguyên tử — xen kẽ với `requestResume()` có thể để lại cả hai cùng false. Vì vậy ở đây chỉ
|
||||
// ghi nhận yêu cầu dưới `data_mutex_`; `spinOnce()` chuyển tiếp xuống lõi trên control thread.
|
||||
|
||||
void NavigationServer::pause()
|
||||
{
|
||||
loop_.requestPause();
|
||||
std::lock_guard<std::mutex> lock(data_mutex_);
|
||||
pause_requested_ = true;
|
||||
resume_requested_ = false; // Hai cờ đối nghịch, đặt cùng nhau dưới một lock.
|
||||
}
|
||||
|
||||
void NavigationServer::resume()
|
||||
{
|
||||
loop_.requestResume();
|
||||
std::lock_guard<std::mutex> lock(data_mutex_);
|
||||
resume_requested_ = true;
|
||||
pause_requested_ = false;
|
||||
}
|
||||
|
||||
void NavigationServer::cancel()
|
||||
{
|
||||
loop_.requestCancel();
|
||||
std::lock_guard<std::mutex> lock(data_mutex_);
|
||||
cancel_requested_ = true;
|
||||
}
|
||||
|
||||
void NavigationServer::drainLifecycleRequests()
|
||||
{
|
||||
bool pause = false;
|
||||
bool resume = false;
|
||||
bool cancel = false;
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(data_mutex_);
|
||||
pause = pause_requested_;
|
||||
resume = resume_requested_;
|
||||
cancel = cancel_requested_;
|
||||
pause_requested_ = false;
|
||||
resume_requested_ = false;
|
||||
cancel_requested_ = false;
|
||||
}
|
||||
|
||||
// Huỷ trước: nó thắng mọi thứ khác. Tạm dừng rồi huỷ và huỷ rồi tạm dừng phải cho cùng kết quả.
|
||||
if (cancel)
|
||||
{
|
||||
loop_.requestCancel();
|
||||
}
|
||||
if (pause)
|
||||
{
|
||||
loop_.requestPause();
|
||||
}
|
||||
if (resume)
|
||||
{
|
||||
loop_.requestResume();
|
||||
}
|
||||
}
|
||||
|
||||
bool NavigationServer::setTwistLinear(const robot_geometry_msgs::Vector3& linear)
|
||||
@@ -640,14 +858,95 @@ robot::move_base_core::NavFeedback* NavigationServer::getFeedback()
|
||||
return nav_feedback_.get();
|
||||
}
|
||||
|
||||
// `getGlobalData`/`getLocalData` KHÔNG phải getter thuần: bản cũ làm việc thật bên trong chúng
|
||||
// (điền plan, chuyển costmap sang OccupancyGrid, cập nhật footprint) và host gọi từ bốn ros::Timer
|
||||
// riêng để publish lên rviz — đây là toàn bộ đường xuất visualization.
|
||||
//
|
||||
// Đã truy hết consumer: **chỉ rviz**. OPC-UA và VDA5050 không đụng tới, không có gì trong control
|
||||
// loop hay mission path đọc chúng. Vì vậy phần plan và footprint được điền ở đây (rẻ, không cần
|
||||
// chuyển đổi gì), còn phần lưới costmap -> OccupancyGrid vẫn để trống — xem @warning ở header.
|
||||
|
||||
void NavigationServer::cachePlans()
|
||||
{
|
||||
// Chạy trên CONTROL THREAD. Đây là chỗ duy nhất chạm vào controller để lấy quỹ đạo cục bộ —
|
||||
// `ControllerPort` không thread-safe và plugin (Priest) lại càng không. Gọi nó từ ros::Timer của
|
||||
// host là chạm plugin song song với lúc control thread đang tính lệnh trên chính plugin đó.
|
||||
// CHỈ hỏi khi controller thật sự đang bám plan. Hỏi mỗi cycle kể cả lúc rảnh nghĩa là gọi vào
|
||||
// plugin 30 lần/giây để nhận về "chưa có gì" — PriestLocalPlanner log cảnh báo mỗi lần, và đó
|
||||
// đúng là kiểu spam trong control loop mà quy ước của repo cấm. Không ở kControlling thì cũng
|
||||
// không có quỹ đạo cục bộ nào để hiển thị.
|
||||
robot_nav_2d_msgs::Path2D local;
|
||||
ControllerPort* controller = loop_.controllerPort();
|
||||
if (controller != nullptr && loop_.state() == NavigationState::kControlling)
|
||||
{
|
||||
controller->getLocalPlan(local);
|
||||
}
|
||||
|
||||
robot_nav_2d_msgs::Path2D global = robot_nav_2d_utils::posesToPath2D(loop_.latestPlan());
|
||||
|
||||
// Đóng dấu bằng đồng hồ control loop: host bỏ qua plan cũ hơn 0.5 s và plan có dấu bằng 0
|
||||
// (amr_publiser.cpp:481-495). Loop treo thì dấu đứng yên và host ngừng publish — đúng ý.
|
||||
const robot::Time stamp = loop_.lastCycleTime();
|
||||
global.header.stamp = stamp;
|
||||
local.header.stamp = stamp;
|
||||
if (global.header.frame_id.empty())
|
||||
{
|
||||
global.header.frame_id = global_frame_;
|
||||
}
|
||||
if (local.header.frame_id.empty())
|
||||
{
|
||||
local.header.frame_id = local_frame_;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(data_mutex_);
|
||||
cached_global_plan_ = std::move(global);
|
||||
cached_local_plan_ = std::move(local);
|
||||
}
|
||||
|
||||
robot::move_base_core::PlannerDataOutput NavigationServer::buildPlannerData(CostmapExporter* exporter,
|
||||
bool local_plan)
|
||||
{
|
||||
// Dựng vào một BẢN CỤC BỘ rồi trả về theo giá trị. Ghi thẳng vào `global_data_`/`local_data_` là
|
||||
// hỏng thật: host gọi getGlobalData() và getLocalData() từ BỐN ros::Timer khác nhau, và hai timer
|
||||
// dùng chung một thành viên sẽ ghi đè nhau giữa chừng — đã gây `std::bad_alloc` rồi
|
||||
// `malloc(): invalid size` và giết cả tiến trình.
|
||||
robot::move_base_core::PlannerDataOutput data;
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(data_mutex_);
|
||||
data.plan = local_plan ? cached_local_plan_ : cached_global_plan_;
|
||||
|
||||
data.footprint.header.frame_id = robot_base_frame_;
|
||||
data.footprint.polygon.points.reserve(footprint_.size());
|
||||
for (const auto& point : footprint_)
|
||||
{
|
||||
robot_geometry_msgs::Point32 p32;
|
||||
p32.x = static_cast<float>(point.x);
|
||||
p32.y = static_cast<float>(point.y);
|
||||
p32.z = static_cast<float>(point.z);
|
||||
data.footprint.polygon.points.push_back(p32);
|
||||
}
|
||||
}
|
||||
|
||||
if (exporter != nullptr)
|
||||
{
|
||||
// `CostmapExporter` tự khoá bên trong, nên gọi từ nhiều thread là an toàn.
|
||||
exporter->fill(data.costmap, data.costmap_update, data.is_costmap_updated);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
robot::move_base_core::PlannerDataOutput NavigationServer::getGlobalData()
|
||||
{
|
||||
return global_data_;
|
||||
return buildPlannerData(runtime_ != nullptr ? &runtime_->globalExporter() : nullptr,
|
||||
/*local_plan=*/false);
|
||||
}
|
||||
|
||||
robot::move_base_core::PlannerDataOutput NavigationServer::getLocalData()
|
||||
{
|
||||
return local_data_;
|
||||
return buildPlannerData(runtime_ != nullptr ? &runtime_->localExporter() : nullptr,
|
||||
/*local_plan=*/true);
|
||||
}
|
||||
|
||||
} // namespace move_base2
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
|
||||
#include <robot/plugin_loader_helper.h>
|
||||
#include <robot/robot.h>
|
||||
#include <robot_costmap_2d/costmap_2d_robot.h>
|
||||
#include <robot_nav_2d_utils/conversions.h>
|
||||
|
||||
namespace move_base2
|
||||
{
|
||||
@@ -37,8 +39,9 @@ bool isFiniteTwist(const robot_geometry_msgs::Twist& twist)
|
||||
ControllerRunner::ControllerRunner() = default;
|
||||
ControllerRunner::~ControllerRunner() = default;
|
||||
|
||||
bool ControllerRunner::configure(const robot::NodeHandle& nh, tf3::BufferCore* tf,
|
||||
robot_costmap_2d::Costmap2DROBOT* costmap,
|
||||
bool ControllerRunner::configure(const robot::NodeHandle& nh,
|
||||
const std::shared_ptr<tf3::BufferCore>& tf,
|
||||
robot_costmap_2d::Costmap2DROBOT* costmap, const PosePort* pose,
|
||||
const std::string& initial_controller, std::string& error)
|
||||
{
|
||||
if (configured_)
|
||||
@@ -53,9 +56,18 @@ bool ControllerRunner::configure(const robot::NodeHandle& nh, tf3::BufferCore* t
|
||||
return false;
|
||||
}
|
||||
|
||||
if (pose == nullptr)
|
||||
{
|
||||
// Gen-2 nhận pose làm THAM SỐ của computeVelocityCommands/isGoalReached; không có nguồn pose
|
||||
// thì không gọi được hàm nào trong hai hàm đó.
|
||||
error = "ControllerRunner cần PosePort khác null";
|
||||
return false;
|
||||
}
|
||||
|
||||
nh_ = nh;
|
||||
tf_ = tf;
|
||||
costmap_ = costmap;
|
||||
pose_ = pose;
|
||||
configured_ = true;
|
||||
|
||||
if (!initial_controller.empty() && !swapPlanner(initial_controller))
|
||||
@@ -68,7 +80,7 @@ bool ControllerRunner::configure(const robot::NodeHandle& nh, tf3::BufferCore* t
|
||||
return true;
|
||||
}
|
||||
|
||||
robot_nav_core::BaseLocalPlanner* ControllerRunner::acquire(const std::string& name)
|
||||
robot_nav_core2::LocalPlanner* ControllerRunner::acquire(const std::string& name)
|
||||
{
|
||||
const auto cached = controllers_.find(name);
|
||||
if (cached != controllers_.end())
|
||||
@@ -91,7 +103,7 @@ robot_nav_core::BaseLocalPlanner* ControllerRunner::acquire(const std::string& n
|
||||
|
||||
try
|
||||
{
|
||||
loaded.factory = boost::dll::import_alias<robot_nav_core::BaseLocalPlanner::Ptr()>(
|
||||
loaded.factory = boost::dll::import_alias<robot_nav_core2::LocalPlanner::Ptr()>(
|
||||
library_path, name, boost::dll::load_mode::append_decorations);
|
||||
}
|
||||
catch (const boost::system::system_error& ex)
|
||||
@@ -126,9 +138,9 @@ robot_nav_core::BaseLocalPlanner* ControllerRunner::acquire(const std::string& n
|
||||
|
||||
try
|
||||
{
|
||||
// Khác BaseGlobalPlanner: initialize ở đây trả void, nên không có cách nào biết plugin tự thấy
|
||||
// mình hỏng. Chỉ chặn được exception.
|
||||
loaded.instance->initialize(name, tf_, costmap_);
|
||||
// Chữ ký gen-2: (parent NodeHandle, tên, TF, costmap). Khác hẳn gen-1 — và vì Boost.DLL không
|
||||
// kiểm kiểu, gọi nhầm chữ ký sẽ không lỗi biên dịch mà hỏng vtable lúc chạy.
|
||||
loaded.instance->initialize(nh_, name, tf_, costmap_);
|
||||
}
|
||||
catch (const std::exception& ex)
|
||||
{
|
||||
@@ -141,7 +153,7 @@ robot_nav_core::BaseLocalPlanner* ControllerRunner::acquire(const std::string& n
|
||||
return inserted.first->second.instance.get();
|
||||
}
|
||||
|
||||
void ControllerRunner::applyPendingLimits(robot_nav_core::BaseLocalPlanner* controller)
|
||||
void ControllerRunner::applyPendingLimits(robot_nav_core2::LocalPlanner* controller)
|
||||
{
|
||||
if (controller == nullptr)
|
||||
{
|
||||
@@ -191,7 +203,7 @@ bool ControllerRunner::swapPlanner(const std::string& planner_name)
|
||||
return true; // Đã đúng controller; không log để khỏi spam ở cửa vào mỗi yêu cầu.
|
||||
}
|
||||
|
||||
robot_nav_core::BaseLocalPlanner* controller = acquire(planner_name);
|
||||
robot_nav_core2::LocalPlanner* controller = acquire(planner_name);
|
||||
if (controller == nullptr)
|
||||
{
|
||||
// Giữ nguyên controller đang chạy: bên gọi từ chối yêu cầu dựa vào giá trị trả về.
|
||||
@@ -200,6 +212,7 @@ bool ControllerRunner::swapPlanner(const std::string& planner_name)
|
||||
|
||||
active_ = controller;
|
||||
active_name_ = planner_name;
|
||||
has_active_goal_ = false; // Instance mới chưa biết goal nào.
|
||||
applyPendingLimits(active_);
|
||||
|
||||
robot::log_info("[move_base2] ControllerRunner: local planner đang dùng là '%s'.\n",
|
||||
@@ -240,7 +253,23 @@ bool ControllerRunner::setPlan(const std::vector<robot_geometry_msgs::PoseStampe
|
||||
|
||||
try
|
||||
{
|
||||
return active_->setPlan(plan);
|
||||
// Gen-2 tách goal khỏi plan: `setGoalPose` phải gọi TRƯỚC `setPlan`, đúng như
|
||||
// `LocalPlannerAdapter` làm. Goal là pose CUỐI của plan.
|
||||
const robot_nav_2d_msgs::Path2D path = robot_nav_2d_utils::posesToPath2D(plan);
|
||||
if (path.poses.empty())
|
||||
{
|
||||
robot::log_error_throttle(kHotPathLogThrottle,
|
||||
"[move_base2] ControllerRunner: plan chuyển sang Path2D bị rỗng.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
// `Path2D::poses` đã là vector<Pose2DStamped>, nên pose cuối dùng thẳng làm goal.
|
||||
const robot_nav_2d_msgs::Pose2DStamped goal_pose = path.poses.back();
|
||||
|
||||
active_->setGoalPose(goal_pose);
|
||||
active_->setPlan(path);
|
||||
has_active_goal_ = true;
|
||||
return true;
|
||||
}
|
||||
catch (const std::exception& ex)
|
||||
{
|
||||
@@ -251,6 +280,21 @@ bool ControllerRunner::setPlan(const std::vector<robot_geometry_msgs::PoseStampe
|
||||
}
|
||||
}
|
||||
|
||||
bool ControllerRunner::currentPose(robot_nav_2d_msgs::Pose2DStamped& pose) const
|
||||
{
|
||||
if (pose_ == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
robot_geometry_msgs::PoseStamped stamped;
|
||||
if (!pose_->getRobotPose(stamped))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
pose = robot_nav_2d_utils::poseStampedToPose2D(stamped);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ControllerRunner::computeVelocityCommands(robot_geometry_msgs::Twist& cmd)
|
||||
{
|
||||
cmd = robot_geometry_msgs::Twist();
|
||||
@@ -263,23 +307,35 @@ bool ControllerRunner::computeVelocityCommands(robot_geometry_msgs::Twist& cmd)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!has_active_goal_)
|
||||
{
|
||||
// Chưa có plan nào được nạp. Không phải lỗi — chỉ là chưa tới lúc tính lệnh.
|
||||
return false;
|
||||
}
|
||||
|
||||
robot_nav_2d_msgs::Pose2DStamped pose;
|
||||
if (!currentPose(pose))
|
||||
{
|
||||
robot::log_error_throttle(kHotPathLogThrottle,
|
||||
"[move_base2] ControllerRunner: mất pose, không tính lệnh.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
robot_geometry_msgs::Twist result;
|
||||
bool ok = false;
|
||||
|
||||
try
|
||||
{
|
||||
ok = active_->computeVelocityCommands(measured_velocity_, result);
|
||||
// Gen-2 trả THẲNG lệnh (không có cờ thành công/thất bại) và ném exception khi không tính được —
|
||||
// ngược với gen-1. Vì vậy nhánh "không có lệnh hợp lệ" ở đây là nhánh catch.
|
||||
const robot_nav_2d_msgs::Twist2DStamped cmd_2d =
|
||||
active_->computeVelocityCommands(pose, robot_nav_2d_utils::twist3Dto2D(measured_velocity_));
|
||||
result = robot_nav_2d_utils::twist2Dto3D(cmd_2d.velocity);
|
||||
}
|
||||
catch (const std::exception& ex)
|
||||
{
|
||||
robot::log_error_throttle(kHotPathLogThrottle,
|
||||
"[move_base2] ControllerRunner: '%s' ném exception khi tính lệnh: "
|
||||
"%s\n", active_name_.c_str(), ex.what());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ok)
|
||||
{
|
||||
"[move_base2] ControllerRunner: '%s' không sinh được lệnh: %s\n",
|
||||
active_name_.c_str(), ex.what());
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -304,9 +360,26 @@ bool ControllerRunner::isGoalReached()
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!has_active_goal_)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
robot_nav_2d_msgs::Pose2DStamped pose;
|
||||
if (!currentPose(pose))
|
||||
{
|
||||
return false; // Mất pose: "chưa tới đích" là phía an toàn.
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return active_->isGoalReached();
|
||||
const bool reached =
|
||||
active_->isGoalReached(pose, robot_nav_2d_utils::twist3Dto2D(measured_velocity_));
|
||||
if (reached)
|
||||
{
|
||||
has_active_goal_ = false;
|
||||
}
|
||||
return reached;
|
||||
}
|
||||
catch (const std::exception& ex)
|
||||
{
|
||||
@@ -319,6 +392,30 @@ bool ControllerRunner::isGoalReached()
|
||||
}
|
||||
}
|
||||
|
||||
void ControllerRunner::getLocalPlan(robot_nav_2d_msgs::Path2D& plan)
|
||||
{
|
||||
plan = robot_nav_2d_msgs::Path2D();
|
||||
|
||||
if (!configured_ || active_ == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
active_->getPlan(plan);
|
||||
}
|
||||
catch (const std::exception& ex)
|
||||
{
|
||||
// Không phải mọi planner đều hỗ trợ; gen-2 cho phép ném. Đây chỉ là dữ liệu hiển thị nên nuốt
|
||||
// exception là đúng — nhưng vẫn log để không ai tưởng rviz đang hiện quỹ đạo thật.
|
||||
robot::log_error_throttle(kHotPathLogThrottle,
|
||||
"[move_base2] ControllerRunner: '%s' không trả được quỹ đạo cục bộ: "
|
||||
"%s\n", active_name_.c_str(), ex.what());
|
||||
plan = robot_nav_2d_msgs::Path2D();
|
||||
}
|
||||
}
|
||||
|
||||
void ControllerRunner::setMeasuredVelocity(const robot_geometry_msgs::Twist& velocity)
|
||||
{
|
||||
if (!isFiniteTwist(velocity))
|
||||
|
||||
@@ -158,6 +158,68 @@ void StateMachine::finish(NavigationState terminal, NavigationOutcome outcome,
|
||||
enter(terminal, now, reason, out);
|
||||
}
|
||||
|
||||
void StateMachine::acceptPendingRequest(const StateMachineInput& in, StateMachineOutput& out)
|
||||
{
|
||||
out.accept_request = true;
|
||||
recovery_index_ = 0;
|
||||
request_has_goal_ = in.pending_request_has_goal;
|
||||
action_count_ = in.pending_request_action_count;
|
||||
action_index_ = 0;
|
||||
|
||||
if (!request_has_goal_)
|
||||
{
|
||||
// D8: yêu cầu chỉ-có-action — không có gì để lập plan, vào thẳng thực thi action.
|
||||
if (action_count_ == 0)
|
||||
{
|
||||
// Không goal lẫn action là vi phạm contract; mission layer đã validate nhưng lõi vẫn phải tự
|
||||
// vệ: kết thúc tường minh thay vì treo ở một state không có đường ra.
|
||||
finish(NavigationState::kAborted, NavigationOutcome::kFailed, in.now,
|
||||
"yêu cầu không có goal lẫn action", out);
|
||||
return;
|
||||
}
|
||||
out.start_action = true;
|
||||
out.action_index = 0;
|
||||
action_started_at_ = in.now;
|
||||
enter(NavigationState::kExecutingActions, in.now, "yêu cầu chỉ có action", out);
|
||||
return;
|
||||
}
|
||||
|
||||
out.start_planner = true;
|
||||
beginPlanningCycle(in.now);
|
||||
last_valid_control_ = in.now;
|
||||
last_oscillation_reset_ = in.now;
|
||||
out.reset_oscillation_origin = true;
|
||||
enter(NavigationState::kPlanning, in.now, "nhận yêu cầu mới", out);
|
||||
}
|
||||
|
||||
bool StateMachine::preemptIfRequested(const StateMachineInput& in, StateMachineOutput& out)
|
||||
{
|
||||
if (!in.has_pending_request)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Dọn sạch yêu cầu cũ TRƯỚC khi nhận yêu cầu mới: planner đang chạy phải bị huỷ (kết quả của nó
|
||||
// thuộc goal cũ), recovery và action đang chạy phải được bảo dừng an toàn.
|
||||
out.stop_planner = true;
|
||||
if (state_ == NavigationState::kRecovering)
|
||||
{
|
||||
out.cancel_recovery = true;
|
||||
}
|
||||
if (state_ == NavigationState::kExecutingActions)
|
||||
{
|
||||
out.cancel_action = true;
|
||||
}
|
||||
|
||||
// Báo kết quả cho chặng cũ ĐÚNG MỘT LẦN, ngay tại đây. Không báo thì mission layer chờ vĩnh viễn
|
||||
// một chặng đã bị thay.
|
||||
out.report_outcome = true;
|
||||
out.outcome = NavigationOutcome::kPreempted;
|
||||
|
||||
acceptPendingRequest(in, out);
|
||||
return true;
|
||||
}
|
||||
|
||||
StateMachineOutput StateMachine::update(const StateMachineInput& in)
|
||||
{
|
||||
StateMachineOutput out;
|
||||
@@ -193,36 +255,7 @@ StateMachineOutput StateMachine::update(const StateMachineInput& in)
|
||||
{
|
||||
if (in.has_pending_request)
|
||||
{
|
||||
out.accept_request = true;
|
||||
recovery_index_ = 0;
|
||||
request_has_goal_ = in.pending_request_has_goal;
|
||||
action_count_ = in.pending_request_action_count;
|
||||
action_index_ = 0;
|
||||
|
||||
if (!request_has_goal_)
|
||||
{
|
||||
// D8: yêu cầu chỉ-có-action — không có gì để lập plan, vào thẳng thực thi action.
|
||||
if (action_count_ == 0)
|
||||
{
|
||||
// Không goal lẫn action là vi phạm contract; mission layer đã validate nhưng lõi vẫn
|
||||
// phải tự vệ: kết thúc tường minh thay vì treo ở một state không có đường ra.
|
||||
finish(NavigationState::kAborted, NavigationOutcome::kFailed, in.now,
|
||||
"yêu cầu không có goal lẫn action", out);
|
||||
break;
|
||||
}
|
||||
out.start_action = true;
|
||||
out.action_index = 0;
|
||||
action_started_at_ = in.now;
|
||||
enter(NavigationState::kExecutingActions, in.now, "yêu cầu chỉ có action", out);
|
||||
break;
|
||||
}
|
||||
|
||||
out.start_planner = true;
|
||||
beginPlanningCycle(in.now);
|
||||
last_valid_control_ = in.now;
|
||||
last_oscillation_reset_ = in.now;
|
||||
out.reset_oscillation_origin = true;
|
||||
enter(NavigationState::kPlanning, in.now, "nhận yêu cầu mới", out);
|
||||
acceptPendingRequest(in, out);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -244,6 +277,12 @@ StateMachineOutput StateMachine::update(const StateMachineInput& in)
|
||||
break;
|
||||
}
|
||||
|
||||
// Goal mới chen ngang: thay ngay, không xếp hàng.
|
||||
if (preemptIfRequested(in, out))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (in.planner == PlannerFeedback::kPlanReady)
|
||||
{
|
||||
out.apply_plan = true;
|
||||
@@ -299,6 +338,12 @@ StateMachineOutput StateMachine::update(const StateMachineInput& in)
|
||||
break;
|
||||
}
|
||||
|
||||
// Goal mới chen ngang: thay ngay, không xếp hàng.
|
||||
if (preemptIfRequested(in, out))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// Plan mới tới giữa lúc đang bám plan cũ: nhận ngay, vẫn ở kControlling.
|
||||
if (in.planner == PlannerFeedback::kPlanReady)
|
||||
{
|
||||
@@ -379,6 +424,13 @@ StateMachineOutput StateMachine::update(const StateMachineInput& in)
|
||||
// --------------------------------------------------------------------------------------
|
||||
case NavigationState::kRecovering:
|
||||
{
|
||||
// Goal mới chen ngang: thay ngay, không xếp hàng. Đặt TRƯỚC mọi xử lý khác của state này —
|
||||
// yêu cầu cũ đã bị thay thì mọi quyết định dựa trên nó đều vô nghĩa.
|
||||
if (preemptIfRequested(in, out))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (in.cancel_requested)
|
||||
{
|
||||
out.cancel_recovery = true;
|
||||
@@ -417,6 +469,13 @@ StateMachineOutput StateMachine::update(const StateMachineInput& in)
|
||||
// --------------------------------------------------------------------------------------
|
||||
case NavigationState::kExecutingActions:
|
||||
{
|
||||
// Goal mới chen ngang: thay ngay, không xếp hàng. Đặt TRƯỚC mọi xử lý khác của state này —
|
||||
// yêu cầu cũ đã bị thay thì mọi quyết định dựa trên nó đều vô nghĩa.
|
||||
if (preemptIfRequested(in, out))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (in.cancel_requested)
|
||||
{
|
||||
out.cancel_action = true;
|
||||
@@ -478,6 +537,13 @@ StateMachineOutput StateMachine::update(const StateMachineInput& in)
|
||||
// --------------------------------------------------------------------------------------
|
||||
case NavigationState::kPaused:
|
||||
{
|
||||
// Goal mới chen ngang: thay ngay, không xếp hàng. Đặt TRƯỚC mọi xử lý khác của state này —
|
||||
// yêu cầu cũ đã bị thay thì mọi quyết định dựa trên nó đều vô nghĩa.
|
||||
if (preemptIfRequested(in, out))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (in.cancel_requested)
|
||||
{
|
||||
enter(NavigationState::kCancelling, in.now, "huỷ khi đang tạm dừng", out);
|
||||
|
||||
Reference in New Issue
Block a user