/********************************************************************* * * Software License Agreement (BSD License) * * move_base2 — cài đặt ControllerRunner. * * Author: DuongTD *********************************************************************/ #include #include #include #include #include #include #include #include #include #include #include namespace move_base2 { namespace { /// [s] Giãn cách log cho cảnh báo phát sinh trong đường nóng — nhịp control loop, không được spam. constexpr double kHotPathLogThrottle = 5.0; bool isFiniteTwist(const robot_geometry_msgs::Twist& twist) { return std::isfinite(twist.linear.x) && std::isfinite(twist.linear.y) && std::isfinite(twist.angular.z); } } // namespace ControllerRunner::ControllerRunner() = default; ControllerRunner::~ControllerRunner() = default; void ControllerRunner::attachStats(RuntimeStats* stats) { stats_ = stats; if (stats_ == nullptr) { return; } section_compute_ = stats_->section("controller.compute"); section_local_plan_ = stats_->section("controller.getLocalPlan"); } bool ControllerRunner::configure(const robot::NodeHandle& nh, const std::shared_ptr& tf, robot_costmap_2d::Costmap2DROBOT* costmap, const PosePort* pose, const std::string& initial_controller, std::string& error) { if (configured_) { error = "ControllerRunner::configure() called twice"; return false; } if (costmap == nullptr) { error = "ControllerRunner needs a non-null local costmap"; 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 needs a non-null PosePort"; return false; } nh_ = nh; tf_ = tf; costmap_ = costmap; pose_ = pose; configured_ = true; if (!initial_controller.empty() && !swapPlanner(initial_controller)) { error = "could not load the initial local planner '" + initial_controller + "'"; configured_ = false; return false; } return true; } robot_nav_core2::LocalPlanner* ControllerRunner::acquire(const std::string& name) { const auto cached = controllers_.find(name); if (cached != controllers_.end()) { if (!marker_dirty_) { return cached->second.instance.get(); } // Marker vừa đổi: instance này có thể đã đọc `maker_name` trong initialize() và không bao giờ // đọc lại. Dựng lại từ factory sẵn có (không dlopen lại); instance mới thay chỗ instance cũ // CHỈ khi initialize() thành công — thất bại thì giữ nguyên cache và trả lỗi để bên gọi từ // chối yêu cầu, không để lại trạng thái nửa vời. try { robot_nav_core2::LocalPlanner::Ptr fresh = cached->second.factory(); if (!fresh) { robot::log_error("[move_base2] ControllerRunner: factory of '%s' returned nullptr while " "rebuilding for the new marker.\n", name.c_str()); return nullptr; } fresh->initialize(nh_, name, tf_, costmap_); cached->second.instance = std::move(fresh); } catch (const std::exception& ex) { robot::log_error("[move_base2] ControllerRunner: rebuilding '%s' for the new marker failed: " "%s\n", name.c_str(), ex.what()); return nullptr; } marker_dirty_ = false; return cached->second.instance.get(); } robot::PluginLoaderHelper loader(nh_); const std::string library_path = loader.findLibraryPath(name); if (library_path.empty()) { robot::log_error("[move_base2] ControllerRunner: no library found for '%s' — check the key " "'%s/library_path' in the YAML and that the .so file exists in devel/lib.\n", name.c_str(), name.c_str()); return nullptr; } Loaded loaded; try { loaded.factory = boost::dll::import_alias( library_path, name, boost::dll::load_mode::append_decorations); } catch (const boost::system::system_error& ex) { robot::log_error("[move_base2] ControllerRunner: could not load symbol '%s' from '%s': %s\n", name.c_str(), library_path.c_str(), ex.what()); return nullptr; } catch (const std::exception& ex) { robot::log_error("[move_base2] ControllerRunner: error while loading '%s': %s\n", name.c_str(), ex.what()); return nullptr; } try { loaded.instance = loaded.factory(); } catch (const std::exception& ex) { robot::log_error("[move_base2] ControllerRunner: factory of '%s' threw an exception: %s\n", name.c_str(), ex.what()); return nullptr; } if (!loaded.instance) { robot::log_error("[move_base2] ControllerRunner: factory of '%s' returned nullptr.\n", name.c_str()); return nullptr; } try { // 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) { robot::log_error("[move_base2] ControllerRunner: initialize() of '%s' threw an exception: %s\n", name.c_str(), ex.what()); return nullptr; } const auto inserted = controllers_.emplace(name, std::move(loaded)); // Instance mới vừa initialize() với `maker_name` hiện hành — marker không còn "chưa được đọc". marker_dirty_ = false; return inserted.first->second.instance.get(); } bool ControllerRunner::setDockingMarker(const std::string& marker) { if (!configured_) { robot::log_error("[move_base2] ControllerRunner: setDockingMarker() before configure().\n"); return false; } // Validate với danh sách `maker_sources` (chuỗi cách nhau bằng space, maker_sources.yaml) — // đúng phép kiểm bản cũ làm ở cửa dockTo (move_base.cpp:1161-1173). Marker lạ phải bị chặn ở // đây: để lọt xuống thì getMaker() của docking planner âm thầm không match source nào và robot // đứng im không lý do. std::string sources; nh_.param("maker_sources", sources, std::string("")); std::stringstream ss(sources); std::string source; bool known = false; while (ss >> source) { if (source == marker) { known = true; break; } } if (!known) { robot::log_error("[move_base2] ControllerRunner: marker '%s' is not listed in maker_sources " "('%s').\n", marker.c_str(), sources.c_str()); return false; } std::string current; nh_.param("maker_name", current, std::string("")); if (current != marker) { nh_.setParam("maker_name", marker); marker_dirty_ = true; } return true; } void ControllerRunner::applyPendingLimits(robot_nav_core2::LocalPlanner* controller) { if (controller == nullptr) { return; } // Trần vận tốc thuộc về YÊU CẦU, không thuộc về instance planner. Instance mới nạp không biết gì // về các trần host đã đặt trước đó — không áp lại là robot lặng lẽ chạy nhanh hơn mức tầng an // toàn cho phép, và không có dấu hiệu nào cả. try { if (has_limit_linear_forward_) { controller->setTwistLinear(limit_linear_forward_); } if (has_limit_linear_backward_) { controller->setTwistLinear(limit_linear_backward_); } if (has_limit_angular_) { controller->setTwistAngular(limit_angular_); } } catch (const std::exception& ex) { robot::log_error("[move_base2] ControllerRunner: error while re-applying the velocity limits: " "%s\n", ex.what()); } } bool ControllerRunner::swapPlanner(const std::string& planner_name) { if (!configured_) { robot::log_error("[move_base2] ControllerRunner: swapPlanner() before configure().\n"); return false; } if (planner_name.empty()) { robot::log_error("[move_base2] ControllerRunner: empty controller name.\n"); return false; } if (planner_name == active_name_ && active_ != nullptr && !marker_dirty_) { // Đã đúng controller; không log để khỏi spam ở cửa vào mỗi yêu cầu. Riêng khi marker vừa đổi // thì KHÔNG được đi tắt: dock lại cùng planner với marker khác phải rơi xuống acquire() để // instance được dựng lại và initialize() đọc `maker_name` mới. return true; } 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ề. return false; } active_ = controller; active_name_ = planner_name; has_active_goal_ = false; // Instance mới chưa biết goal nào. active_plan_.clear(); applyPendingLimits(active_); robot::log_info("[move_base2] ControllerRunner: active local planner is '%s'.\n", planner_name.c_str()); return true; } bool ControllerRunner::setPlan(const std::vector& plan) { if (!configured_ || active_ == nullptr) { robot::log_error_throttle(kHotPathLogThrottle, "[move_base2] ControllerRunner: setPlan() with no controller " "loaded.\n"); return false; } if (plan.empty()) { // Plan rỗng lọt xuống sẽ thành front()/back() trên vector rỗng bên trong planner. robot::log_error_throttle(kHotPathLogThrottle, "[move_base2] ControllerRunner: rejecting an empty plan.\n"); return false; } try { // 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 converted to Path2D came out " "empty.\n"); return false; } // `Path2D::poses` đã là vector, 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; active_plan_ = plan; return true; } catch (const std::exception& ex) { robot::log_error_throttle(kHotPathLogThrottle, "[move_base2] ControllerRunner: '%s' threw an exception in setPlan: " "%s\n", active_name_.c_str(), ex.what()); return false; } } bool ControllerRunner::refreshActivePlanner() { if (!configured_ || active_ == nullptr) { // Chưa có controller active thì không có cache nào cần refresh. Hành vi này giúp footprint có // thể được host đặt trước goal đầu tiên mà không biến thành lỗi khởi tạo. return true; } const auto loaded = controllers_.find(active_name_); if (loaded == controllers_.end()) { robot::log_error("[move_base2] ControllerRunner: active local planner '%s' is absent from " "the plugin cache.\n", active_name_.c_str()); return false; } const bool had_active_goal = has_active_goal_; const std::vector saved_plan = active_plan_; if (had_active_goal && saved_plan.empty()) { // Không thay instance cũ nếu không thể khôi phục goal đang chạy. Giữ controller hiện tại vẫn // an toàn hơn việc âm thầm biến navigation thành controller không có plan. robot::log_error("[move_base2] ControllerRunner: active planner '%s' has a goal but no " "cached plan to restore after a footprint change.\n", active_name_.c_str()); return false; } robot_nav_core2::LocalPlanner::Ptr fresh; try { fresh = loaded->second.factory(); if (!fresh) { robot::log_error("[move_base2] ControllerRunner: factory of '%s' returned nullptr while " "refreshing its footprint cache.\n", active_name_.c_str()); return false; } fresh->initialize(nh_, active_name_, tf_, costmap_); } catch (const std::exception& ex) { // Chỉ thay cache SAU initialize thành công, nên lỗi này không làm mất controller cũ. robot::log_error("[move_base2] ControllerRunner: refreshing '%s' after a footprint change " "failed: %s\n", active_name_.c_str(), ex.what()); return false; } loaded->second.instance = std::move(fresh); active_ = loaded->second.instance.get(); has_active_goal_ = false; active_plan_.clear(); applyPendingLimits(active_); if (had_active_goal && !setPlan(saved_plan)) { robot::log_error("[move_base2] ControllerRunner: could not restore the active plan after " "refreshing '%s' for a footprint change.\n", active_name_.c_str()); return false; } robot::log_info("[move_base2] ControllerRunner: refreshed '%s' after the local costmap " "footprint changed.\n", active_name_.c_str()); return true; } 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(); if (!configured_ || active_ == nullptr) { robot::log_error_throttle(kHotPathLogThrottle, "[move_base2] ControllerRunner: computeVelocityCommands() with no " "controller loaded.\n"); 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: pose lost, not computing a " "command.\n"); return false; } robot_geometry_msgs::Twist result; try { // Đo đúng lời gọi vào plugin, không đo cả hàm: phần còn lại (tra pose, kiểm NaN) là chi phí của // move_base2, còn đây mới là chi phí của local planner đang cấu hình. ScopedSection timer(stats_, section_compute_); // 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' produced no command: %s\n", active_name_.c_str(), ex.what()); return false; } if (!isFiniteTwist(result)) { // VelocityArbiter cũng chặn NaN/Inf, nhưng chặn ngay tại nguồn cho biết ĐÚNG plugin nào đang // trả dữ liệu hỏng — arbiter chỉ thấy một con số vô nghĩa không rõ từ đâu. robot::log_error_throttle(kHotPathLogThrottle, "[move_base2] ControllerRunner: '%s' returned a command containing " "NaN/Inf.\n", active_name_.c_str()); return false; } cmd = result; return true; } bool ControllerRunner::isGoalReached() { if (!configured_ || active_ == nullptr) { 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 { const bool reached = active_->isGoalReached(pose, robot_nav_2d_utils::twist3Dto2D(measured_velocity_)); if (reached) { has_active_goal_ = false; active_plan_.clear(); } return reached; } catch (const std::exception& ex) { // Trả false: "chưa tới đích" là phía an toàn — báo nhầm đã tới sẽ kết thúc chặng đường sớm và // robot dừng ở chỗ không phải đích. robot::log_error_throttle(kHotPathLogThrottle, "[move_base2] ControllerRunner: '%s' threw an exception in " "isGoalReached: %s\n", active_name_.c_str(), ex.what()); return false; } } void ControllerRunner::getLocalPlan(robot_nav_2d_msgs::Path2D& plan) { plan = robot_nav_2d_msgs::Path2D(); if (!configured_ || active_ == nullptr) { return; } try { ScopedSection timer(stats_, section_local_plan_); 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' could not return a local " "trajectory: %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)) { // Giữ giá trị cũ thay vì đưa NaN vào hàm tính lệnh — nhiều local planner dùng nó làm mốc giới // hạn gia tốc, và NaN ở đó lan ra toàn bộ cost function. robot::log_error_throttle(kHotPathLogThrottle, "[move_base2] ControllerRunner: dropping a measured velocity " "containing NaN/Inf.\n"); return; } measured_velocity_ = velocity; } bool ControllerRunner::setTwistLinear(const robot_geometry_msgs::Vector3& linear) { if (!std::isfinite(linear.x) || !std::isfinite(linear.y) || !std::isfinite(linear.z)) { robot::log_error("[move_base2] ControllerRunner: linear velocity limit contains NaN/Inf, " "ignored.\n"); return false; } // Dấu chọn chiều — xem doc của ControllerPort::setTwistLinear. Nhớ cả hai chiều riêng để // swapPlanner còn áp lại được lên instance mới. if (linear.x < 0.0) { limit_linear_backward_ = linear; has_limit_linear_backward_ = true; } else { limit_linear_forward_ = linear; has_limit_linear_forward_ = true; } if (active_ == nullptr) { // Host đặt trần trước khi controller được nạp là chuyện bình thường: thứ tự khởi tạo không do // move_base2 quyết. Đã nhớ lại, sẽ áp khi có controller. return true; } try { return active_->setTwistLinear(linear); } catch (const std::exception& ex) { robot::log_error("[move_base2] ControllerRunner: '%s' threw an exception in setTwistLinear: " "%s\n", active_name_.c_str(), ex.what()); return false; } } bool ControllerRunner::setTwistAngular(const robot_geometry_msgs::Vector3& angular) { if (!std::isfinite(angular.x) || !std::isfinite(angular.y) || !std::isfinite(angular.z)) { robot::log_error("[move_base2] ControllerRunner: angular velocity limit contains NaN/Inf, " "ignored.\n"); return false; } limit_angular_ = angular; has_limit_angular_ = true; if (active_ == nullptr) { return true; } try { return active_->setTwistAngular(angular); } catch (const std::exception& ex) { robot::log_error("[move_base2] ControllerRunner: '%s' threw an exception in setTwistAngular: " "%s\n", active_name_.c_str(), ex.what()); return false; } } std::string ControllerRunner::activeController() const { return active_ != nullptr ? active_name_ : std::string(); } } // namespace move_base2