From 34cabd2083edd58f3593284f70020cf01a9926b9 Mon Sep 17 00:00:00 2001 From: HiepLM Date: Thu, 26 Feb 2026 14:55:46 +0700 Subject: [PATCH] update --- config/costmap_common_params.yaml | 110 ----------- .../NavigationExample.csproj | 3 + examples/NavigationExample/Program.cs | 183 ++++++++++++++++-- examples/NavigationExample/libnav_c_api.so | Bin 147840 -> 147840 bytes src/APIs/c_api/CMakeLists.txt | 3 +- src/APIs/c_api/include/nav_c_api.h | 6 +- src/APIs/c_api/src/nav_c_api.cpp | 10 +- .../src/diff/diff_go_straight.cpp | 2 - .../src/pnkx_go_straight_local_planner.cpp | 10 + src/Libraries/costmap_2d | 2 +- .../robot_nav_2d_utils/CMakeLists.txt | 5 + .../move_base/include/move_base/move_base.h | 11 ++ .../Packages/move_base/src/move_base.cpp | 25 ++- 13 files changed, 226 insertions(+), 144 deletions(-) diff --git a/config/costmap_common_params.yaml b/config/costmap_common_params.yaml index a847d64..22cba75 100755 --- a/config/costmap_common_params.yaml +++ b/config/costmap_common_params.yaml @@ -61,113 +61,3 @@ obstacles: max_obstacle_height: 0.25 -global_costmap: - robot_base_frame: base_footprint - transform_tolerance: 1.0 - obstacle_range: 3.0 - #mark_threshold: 1 - publish_voxel_map: true - footprint_padding: 0.0 - - navigation_map: - map_topic: /map - map_pkg: managerments - map_file: maze - - virtual_walls_map: - map_topic: /virtual_walls/map - namespace: /virtual_walls - map_pkg: managerments - map_file: maze - use_maximum: true - lethal_cost_threshold: 100 - - obstacles: - observation_sources: f_scan_marking f_scan_clearing b_scan_marking b_scan_clearing - f_scan_marking: - topic: /f_scan - data_type: LaserScan - clearing: false - marking: true - inf_is_valid: false - min_obstacle_height: 0.0 - max_obstacle_height: 0.25 - f_scan_clearing: - topic: /f_scan - data_type: LaserScan - clearing: true - marking: false - inf_is_valid: false - min_obstacle_height: 0.0 - max_obstacle_height: 0.25 - b_scan_marking: - topic: /b_scan - data_type: LaserScan - clearing: false - marking: true - inf_is_valid: false - min_obstacle_height: 0.0 - max_obstacle_height: 0.25 - b_scan_clearing: - topic: /b_scan - data_type: LaserScan - clearing: true - marking: false - inf_is_valid: false - min_obstacle_height: 0.0 - max_obstacle_height: 0.25 -local_costmap: - robot_base_frame: base_footprint - transform_tolerance: 1.0 - obstacle_range: 3.0 - #mark_threshold: 1 - publish_voxel_map: true - footprint_padding: 0.0 - - navigation_map: - map_topic: /map - map_pkg: managerments - map_file: maze - - virtual_walls_map: - map_topic: /virtual_walls/map - namespace: /virtual_walls - map_pkg: managerments - map_file: maze - use_maximum: true - lethal_cost_threshold: 100 - - obstacles: - observation_sources: f_scan_marking f_scan_clearing b_scan_marking b_scan_clearing - f_scan_marking: - topic: /f_scan - data_type: LaserScan - clearing: false - marking: true - inf_is_valid: false - min_obstacle_height: 0.0 - max_obstacle_height: 0.25 - f_scan_clearing: - topic: /f_scan - data_type: LaserScan - clearing: true - marking: false - inf_is_valid: false - min_obstacle_height: 0.0 - max_obstacle_height: 0.25 - b_scan_marking: - topic: /b_scan - data_type: LaserScan - clearing: false - marking: true - inf_is_valid: false - min_obstacle_height: 0.0 - max_obstacle_height: 0.25 - b_scan_clearing: - topic: /b_scan - data_type: LaserScan - clearing: true - marking: false - inf_is_valid: false - min_obstacle_height: 0.0 - max_obstacle_height: 0.25 \ No newline at end of file diff --git a/examples/NavigationExample/NavigationExample.csproj b/examples/NavigationExample/NavigationExample.csproj index 0d40596..48dbb2c 100644 --- a/examples/NavigationExample/NavigationExample.csproj +++ b/examples/NavigationExample/NavigationExample.csproj @@ -5,6 +5,9 @@ linux-x64 true + + + Always diff --git a/examples/NavigationExample/Program.cs b/examples/NavigationExample/Program.cs index 48015e8..e078b89 100644 --- a/examples/NavigationExample/Program.cs +++ b/examples/NavigationExample/Program.cs @@ -1,9 +1,24 @@ using System; +using System.Globalization; +using System.IO; using System.Runtime.InteropServices; using System.Runtime.CompilerServices; +using System.Text.RegularExpressions; +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.PixelFormats; namespace NavigationExample { + /// Thông tin map đọc từ file YAML (maze.yaml). + public struct MazeMapConfig + { + public string ImagePath; // đường dẫn đầy đủ tới file ảnh + public float Resolution; + public double OriginX, OriginY, OriginZ; + public int Negate; // 0 hoặc 1 + public double OccupiedThresh; + public double FreeThresh; + } /// /// C# P/Invoke wrapper for Navigation C API /// @@ -308,7 +323,7 @@ namespace NavigationExample [DllImport(DllName, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] [return: MarshalAs(UnmanagedType.I1)] - public static extern bool navigation_move_straight_to(NavigationHandle handle, PoseStamped goal, double xy_goal_tolerance); + public static extern bool navigation_move_straight_to(NavigationHandle handle, double distance); [DllImport(DllName, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] [return: MarshalAs(UnmanagedType.I1)] @@ -370,6 +385,105 @@ namespace NavigationExample // ============================================================================ class Program { + /// + /// Đọc file maze.yaml (image, resolution, origin, negate, occupied_thresh, free_thresh). + /// ImagePath được resolve tuyệt đối theo thư mục chứa file yaml. + /// + static bool TryLoadMazeYaml(string yamlPath, out MazeMapConfig config) + { + config = default; + if (string.IsNullOrEmpty(yamlPath) || !File.Exists(yamlPath)) + return false; + string dir = Path.GetDirectoryName(Path.GetFullPath(yamlPath)) ?? ""; + try + { + string[] lines = File.ReadAllLines(yamlPath); + foreach (string line in lines) + { + string trimmed = line.Trim(); + if (trimmed.Length == 0 || trimmed.StartsWith("#")) continue; + int colon = trimmed.IndexOf(':'); + if (colon <= 0) continue; + string key = trimmed.Substring(0, colon).Trim(); + string value = trimmed.Substring(colon + 1).Trim(); + if (key == "image") + config.ImagePath = Path.Combine(dir, value); + else if (key == "resolution" && float.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out float res)) + config.Resolution = res; + else if (key == "origin") + { + var m = Regex.Match(value, @"\[\s*([-\d.]+\s*),\s*([-\d.]+\s*),\s*([-\d.]+)\s*\]"); + if (m.Success) + { + config.OriginX = double.Parse(m.Groups[1].Value, CultureInfo.InvariantCulture); + config.OriginY = double.Parse(m.Groups[2].Value, CultureInfo.InvariantCulture); + config.OriginZ = double.Parse(m.Groups[3].Value, CultureInfo.InvariantCulture); + } + } + else if (key == "negate" && int.TryParse(value, out int neg)) + config.Negate = neg; + else if (key == "occupied_thresh" && double.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out double ot)) + config.OccupiedThresh = ot; + else if (key == "free_thresh" && double.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out double ft)) + config.FreeThresh = ft; + } + return !string.IsNullOrEmpty(config.ImagePath); + } + catch + { + return false; + } + } + + /// + /// Đọc file ảnh PNG và chuyển pixel sang byte[] occupancy (0=free, 100=occupied, 255=unknown). + /// Dùng negate, occupied_thresh, free_thresh từ maze.yaml nếu cung cấp. + /// + static byte[] LoadMazePixelsAsOccupancy(string imagePath, out int width, out int height, + int negate = 0, double occupiedThresh = 0.65, double freeThresh = 0.196) + { + width = 0; + height = 0; + if (string.IsNullOrEmpty(imagePath) || !File.Exists(imagePath)) + { + LogError($"File not found: {imagePath}"); + return null; + } + try + { + using var image = Image.Load(imagePath); + int w = image.Width; + int h = image.Height; + width = w; + height = h; + byte[] data = new byte[w * h]; + image.ProcessPixelRows(accessor => + { + for (int y = 0; y < accessor.Height; y++) + { + Span row = accessor.GetRowSpan(y); + for (int x = 0; x < row.Length; x++) + { + ref readonly Rgba32 p = ref row[x]; + double gray = (p.R + p.G + p.B) / 255.0 / 3.0; + if (negate != 0) gray = 1.0 - gray; + byte occ; + if (gray <= freeThresh) occ = 0; + else if (gray >= occupiedThresh) occ = 100; + else occ = 255; // unknown + data[y * w + x] = occ; + } + } + }); + return data; + } + catch (Exception ex) + { + LogError($"Load image failed: {ex.Message}"); + return null; + } + } + // Helper method để hiển thị file và line number tự động static void LogError(string message, [CallerFilePath] string filePath = "", @@ -459,8 +573,7 @@ namespace NavigationExample Console.WriteLine($"State: {stateStr}, Feedback: {feedbackStr}"); NavigationAPI.navigation_free_feedback(ref feedback); } - - System.Threading.Thread.Sleep(1000); + IntPtr fFrameId = Marshal.StringToHGlobalAnsi("fscan"); NavigationAPI.Header fscanHeader = NavigationAPI.header_create(Marshal.PtrToStringAnsi(fFrameId)); NavigationAPI.LaserScan fscanHandle; @@ -480,7 +593,6 @@ namespace NavigationExample fscanHandle.intensities_count = new UIntPtr(5); NavigationAPI.navigation_add_laser_scan(navHandle, "/fscan", fscanHandle); - IntPtr bFrameId = Marshal.StringToHGlobalAnsi("bscan"); NavigationAPI.Header bscanHeader = NavigationAPI.header_create(Marshal.PtrToStringAnsi(bFrameId)); NavigationAPI.LaserScan bscanHandle; @@ -500,8 +612,6 @@ namespace NavigationExample bscanHandle.intensities_count = new UIntPtr(5); NavigationAPI.navigation_add_laser_scan(navHandle, "/bscan", bscanHandle); - System.Threading.Thread.Sleep(1000); - IntPtr oFrameId = Marshal.StringToHGlobalAnsi("odom"); NavigationAPI.Header odometryHeader = NavigationAPI.header_create(Marshal.PtrToStringAnsi(oFrameId)); NavigationAPI.Odometry odometryHandle = new NavigationAPI.Odometry(); @@ -539,18 +649,53 @@ namespace NavigationExample odometryHandle.twist.covariance_count = new UIntPtr((uint)twist_covariance.Length); NavigationAPI.navigation_add_odometry(navHandle, "odometry", odometryHandle); - System.Threading.Thread.Sleep(1000); - // Add static map + // Add static map: đọc maze.yaml rồi load ảnh và cập nhật mapMetaData + string mapYamlPath = args.Length > 0 ? args[0] : Path.Combine( + "/home/robotics/AGV/Diff_Wheel_Prj/t800_v2_ws/src/Managerments/maps/maze", "maze.yaml"); + int mapWidth, mapHeight; + byte[] data; + MazeMapConfig mapConfig; + if (TryLoadMazeYaml(mapYamlPath, out mapConfig)) + { + data = LoadMazePixelsAsOccupancy(mapConfig.ImagePath, out mapWidth, out mapHeight, + mapConfig.Negate, mapConfig.OccupiedThresh, mapConfig.FreeThresh); + if (data == null) + { + mapWidth = 3; + mapHeight = 10; + data = new byte[30]; + for (int i = 0; i < data.Length; i++) data[i] = 100; + Console.WriteLine("YAML loaded but image failed; using default map 3x10"); + } + else + { + Console.WriteLine("Loaded map from {0}: {1}x{2}, resolution={3}, origin=({4},{5},{6})", + mapConfig.ImagePath, mapWidth, mapHeight, mapConfig.Resolution, + mapConfig.OriginX, mapConfig.OriginY, mapConfig.OriginZ); + } + } + else + { + mapWidth = 3; + mapHeight = 10; + data = new byte[30]; + for (int i = 0; i < data.Length; i++) data[i] = 100; + mapConfig = default; + mapConfig.Resolution = 0.05f; + mapConfig.OriginX = mapConfig.OriginY = mapConfig.OriginZ = 0.0; + Console.WriteLine("maze.yaml not found, using default map 3x10"); + } + NavigationAPI.Time mapLoadTime = NavigationAPI.time_create(); NavigationAPI.MapMetaData mapMetaData = new NavigationAPI.MapMetaData(); mapMetaData.map_load_time = mapLoadTime; - mapMetaData.resolution = 0.05f; - mapMetaData.width = 3; - mapMetaData.height = 10; + mapMetaData.resolution = mapConfig.Resolution; + mapMetaData.width = (uint)mapWidth; + mapMetaData.height = (uint)mapHeight; mapMetaData.origin = new NavigationAPI.Pose(); - mapMetaData.origin.position.x = 0.0; - mapMetaData.origin.position.y = 0.0; - mapMetaData.origin.position.z = 0.0; + mapMetaData.origin.position.x = mapConfig.OriginX; + mapMetaData.origin.position.y = mapConfig.OriginY; + mapMetaData.origin.position.z = mapConfig.OriginZ; mapMetaData.origin.orientation.x = 0.0; mapMetaData.origin.orientation.y = 0.0; mapMetaData.origin.orientation.z = 0.0; @@ -559,10 +704,6 @@ namespace NavigationExample IntPtr mapFrameId = Marshal.StringToHGlobalAnsi("map"); occupancyGrid.header = NavigationAPI.header_create(Marshal.PtrToStringAnsi(mapFrameId)); occupancyGrid.info = mapMetaData; - byte[] data = new byte[30]; - for (int i = 0; i < data.Length; i++) { - data[i] = 100; - } occupancyGrid.data = Marshal.AllocHGlobal(sizeof(byte) * data.Length); Marshal.Copy(data, 0, occupancyGrid.data, data.Length); occupancyGrid.data_count = new UIntPtr((uint)data.Length); @@ -574,7 +715,7 @@ namespace NavigationExample NavigationAPI.navigation_add_static_map(navHandle, "/map", occupancyGrid); - System.Threading.Thread.Sleep(1000); + System.Threading.Thread.Sleep(500); NavigationAPI.Twist2DStamped twist = new NavigationAPI.Twist2DStamped(); if (NavigationAPI.navigation_get_twist(navHandle, ref twist)) @@ -584,8 +725,12 @@ namespace NavigationExample NavigationAPI.MarshalString(twist.header.frame_id), twist.velocity.x, twist.velocity.y, twist.velocity.theta); } // Cleanup + NavigationAPI.navigation_move_straight_to(navHandle, 1.0); + NavigationAPI.navigation_destroy(navHandle); NavigationAPI.tf_listener_destroy(tfHandle); + Console.WriteLine("Press any key to exit..."); + Console.ReadKey(); } } } diff --git a/examples/NavigationExample/libnav_c_api.so b/examples/NavigationExample/libnav_c_api.so index c9c303771948257ffd5734cae2f64049754722e3..654d9a60ba382a4aa179ea9e7a10f3677c4728e5 100755 GIT binary patch delta 31941 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 diff --git a/src/APIs/c_api/CMakeLists.txt b/src/APIs/c_api/CMakeLists.txt index 165b552..efeb0da 100644 --- a/src/APIs/c_api/CMakeLists.txt +++ b/src/APIs/c_api/CMakeLists.txt @@ -13,9 +13,10 @@ endif() # Find Boost (filesystem needed for plugin path / boost::dll usage) find_package(Boost REQUIRED COMPONENTS filesystem system) -# Dependencies +# Dependencies (robot_nav_2d_utils_conversions provides poseStampedToPose2D used by move_base_core/navigation.h) set(PACKAGES_DIR move_base_core + robot_nav_2d_utils tf3 robot_time robot_cpp diff --git a/src/APIs/c_api/include/nav_c_api.h b/src/APIs/c_api/include/nav_c_api.h index 0b4c51b..6811104 100644 --- a/src/APIs/c_api/include/nav_c_api.h +++ b/src/APIs/c_api/include/nav_c_api.h @@ -218,12 +218,10 @@ extern "C" /** * @brief Move straight toward the target position * @param handle Navigation handle - * @param goal Target pose - * @param xy_goal_tolerance Acceptable positional error (meters) + * @param distance Distance to move (meters) * @return true if command issued successfully */ - bool navigation_move_straight_to(NavigationHandle handle, const PoseStamped goal, - double xy_goal_tolerance); + bool navigation_move_straight_to(NavigationHandle handle, const double distance); /** * @brief Rotate in place to align with target orientation diff --git a/src/APIs/c_api/src/nav_c_api.cpp b/src/APIs/c_api/src/nav_c_api.cpp index 9d69a99..0bd1393 100644 --- a/src/APIs/c_api/src/nav_c_api.cpp +++ b/src/APIs/c_api/src/nav_c_api.cpp @@ -354,8 +354,7 @@ extern "C" bool navigation_dock_to(NavigationHandle handle, const char *marker, // return false; // } -extern "C" bool navigation_move_straight_to(NavigationHandle handle, const PoseStamped goal, - double xy_goal_tolerance) +extern "C" bool navigation_move_straight_to(NavigationHandle handle, const double distance) { if (!handle.ptr) { @@ -368,8 +367,11 @@ extern "C" bool navigation_move_straight_to(NavigationHandle handle, const PoseS if (!nav_ptr || !*nav_ptr) return false; auto *nav = nav_ptr->get(); - robot_geometry_msgs::PoseStamped cpp_goal = convert2CppPoseStamped(goal); - return nav->moveStraightTo(cpp_goal, xy_goal_tolerance); + robot_geometry_msgs::PoseStamped pose; + if (!nav->getRobotPose(pose)) + return false; + robot_geometry_msgs::PoseStamped goal = robot::move_base_core::offset_goal(pose, distance); + return nav->moveStraightTo(goal); } catch (...) { diff --git a/src/Algorithms/Libraries/mkt_algorithm/src/diff/diff_go_straight.cpp b/src/Algorithms/Libraries/mkt_algorithm/src/diff/diff_go_straight.cpp index 23ce2c8..c02e6a0 100644 --- a/src/Algorithms/Libraries/mkt_algorithm/src/diff/diff_go_straight.cpp +++ b/src/Algorithms/Libraries/mkt_algorithm/src/diff/diff_go_straight.cpp @@ -9,8 +9,6 @@ void mkt_algorithm::diff::GoStraight::initialize( { nh_ = nh; nh_priv_ = robot::NodeHandle(nh, name); - robot::log_info_at(__FILE__, __LINE__, "nh_ : %s", nh_.getNamespace().c_str()); - robot::log_info_at(__FILE__, __LINE__, "nh_priv_ : %s", nh_priv_.getNamespace().c_str()); name_ = name; tf_ = tf; traj_ = traj; diff --git a/src/Algorithms/Packages/local_planners/pnkx_local_planner/src/pnkx_go_straight_local_planner.cpp b/src/Algorithms/Packages/local_planners/pnkx_local_planner/src/pnkx_go_straight_local_planner.cpp index c07c1c2..401e9b6 100644 --- a/src/Algorithms/Packages/local_planners/pnkx_local_planner/src/pnkx_go_straight_local_planner.cpp +++ b/src/Algorithms/Packages/local_planners/pnkx_local_planner/src/pnkx_go_straight_local_planner.cpp @@ -35,6 +35,16 @@ void pnkx_local_planner::PNKXGoStraightLocalPlanner::initialize(robot::NodeHandl if (!initialized_) { robot::log_info_at(__FILE__, __LINE__, "Using parent name \"%s\"", name.c_str()); + if(costmap_robot == nullptr) + { + robot::log_error_at(__FILE__, __LINE__, "Costmap2DROBOT is nullptr"); + throw robot_nav_core2::LocalPlannerException("Costmap2DROBOT is nullptr"); + } + if(tf == nullptr) + { + robot::log_error_at(__FILE__, __LINE__, "TFListener is nullptr"); + throw robot_nav_core2::LocalPlannerException("TFListener is nullptr"); + } tf_ = tf; costmap_robot_ = costmap_robot; costmap_ = costmap_robot_->getCostmap(); diff --git a/src/Libraries/costmap_2d b/src/Libraries/costmap_2d index eb52edc..3d621de 160000 --- a/src/Libraries/costmap_2d +++ b/src/Libraries/costmap_2d @@ -1 +1 @@ -Subproject commit eb52edc6e81537f408c4e343887cd4cbb0ed13a2 +Subproject commit 3d621de80918df34c8e5ec67e95843bc9225e825 diff --git a/src/Libraries/robot_nav_2d_utils/CMakeLists.txt b/src/Libraries/robot_nav_2d_utils/CMakeLists.txt index b6eb228..b188e59 100755 --- a/src/Libraries/robot_nav_2d_utils/CMakeLists.txt +++ b/src/Libraries/robot_nav_2d_utils/CMakeLists.txt @@ -132,6 +132,11 @@ else() ) target_link_libraries(${PROJECT_NAME} INTERFACE + ${PROJECT_NAME}_conversions + ${PROJECT_NAME}_path_ops + ${PROJECT_NAME}_polygons + ${PROJECT_NAME}_bounds + ${PROJECT_NAME}_tf_help ${PACKAGES_DIR} ${Boost_LIBRARIES} ${TF3_LIBRARY} diff --git a/src/Navigations/Packages/move_base/include/move_base/move_base.h b/src/Navigations/Packages/move_base/include/move_base/move_base.h index 8ce9909..66a860f 100644 --- a/src/Navigations/Packages/move_base/include/move_base/move_base.h +++ b/src/Navigations/Packages/move_base/include/move_base/move_base.h @@ -35,6 +35,17 @@ namespace move_base { + template + void move_parameter(robot::NodeHandle& old_h, robot::NodeHandle& new_h, std::string name,T& value) + { + if (!old_h.hasParam(name)) + return; + + old_h.getParam(name, value); + new_h.setParam(name, value); + } + + // typedefs to help us out with the action server so that we don't hace to type so much typedef move_base::SimpleActionServer MoveBaseActionServer; diff --git a/src/Navigations/Packages/move_base/src/move_base.cpp b/src/Navigations/Packages/move_base/src/move_base.cpp index 7ce4f58..7c31ce2 100644 --- a/src/Navigations/Packages/move_base/src/move_base.cpp +++ b/src/Navigations/Packages/move_base/src/move_base.cpp @@ -32,7 +32,6 @@ #include #include - move_base::MoveBase::MoveBase() : initialized_(false), planner_costmap_robot_(NULL), controller_costmap_robot_(NULL), @@ -54,6 +53,7 @@ move_base::MoveBase::MoveBase(robot::TFListenerPtr tf) move_base::MoveBase::~MoveBase() { + robot::log_warning("Destroying MoveBase instance..."); if (as_ != NULL) { as_->stop(); @@ -124,8 +124,16 @@ void move_base::MoveBase::initialize(robot::TFListenerPtr tf) printf("[%s:%d]\n ========== Begin: initialize() ==========\n", __FILE__, __LINE__); if (!initialized_) { + if(tf == nullptr) + { + robot::log_error("[%s:%d]\n ERROR: tf is nullptr", __FILE__, __LINE__); + throw std::runtime_error("Failed to create the tf"); + } + else + { + robot::log_info("[%s:%d]\n INFO: tf is not nullptr", __FILE__, __LINE__); + } tf_ = tf; - as_ = new MoveBaseActionServer("move_base", std::bind(&MoveBase::executeCb, this, std::placeholders::_1), true); setupActionServerCallbacks(); @@ -250,6 +258,11 @@ void move_base::MoveBase::initialize(robot::TFListenerPtr tf) try { controller_costmap_robot_ = new robot_costmap_2d::Costmap2DROBOT("local_costmap", *tf_); + if(controller_costmap_robot_ == nullptr) + { + robot::log_error("[%s:%d]\n ERROR: controller_costmap_robot_ is nullptr", __FILE__, __LINE__); + throw std::runtime_error("Failed to create the controller_costmap_robot_"); + } controller_costmap_robot_->pause(); robot_costmap_2d::LayeredCostmap *layered_costmap_ = controller_costmap_robot_->getLayeredCostmap(); } @@ -1128,6 +1141,12 @@ bool move_base::MoveBase::moveStraightTo(const robot_geometry_msgs::PoseStamped } robot::log_info("[MoveBase::moveStraightTo] Processing goal through action server..."); + if(controller_costmap_robot_ == nullptr) + { + robot::log_error("[MoveBase::moveStraightTo] controller_costmap_robot_ is null!"); + return false; + } + as_->processGoal(action_goal); robot::log_info("[MoveBase::moveStraightTo] Goal processed successfully by action server"); } @@ -1771,7 +1790,7 @@ void move_base::MoveBase::resetState() // if we shutdown our costmaps when we're deactivated... we'll do that now if (shutdown_costmaps_) { - std::cout << "Stopping costmaps" << std::endl; + robot::log_info("Stopping costmaps"); planner_costmap_robot_->stop(); controller_costmap_robot_->stop(); }