در فصل قبل، ARCHO یاد گرفت چطور نقشه انبار را بسازد. حالا سؤال بعدی این است: با داشتن آن نقشه، ARCHO چطور خودش تصمیم بگیرد از کجا برود، از موانع فرار کند، و به مقصد برسد؟ این دقیقاً کاری است که Navigation2 (Nav2) انجام میدهد.
در فصلهای قبل دیدیم ros2_control چطور فرمانهای خام به موتور میدهد. Nav2 یک لایه بالاتر از آن نشسته: بهجای اینکه بگوید «موتور چپ با ۱.۲ رادیان بچرخ»، میگوید «باید بروی قفسه ۲۴». این فناوریای است که پشت تقریباً هر AMR، ربات انباری، ربات بیمارستانی و Delivery Robot امروزی قرار دارد.
قبل از اینکه وارد جزئیات شویم، باید چهار سؤال را از هم جدا کنیم — هرکدام به یک بخش متفاوت از Nav2 مربوط است:
| سؤال | پاسخدهنده |
|---|---|
| من الان کجا هستم؟ | Localization (AMCL) |
| دنیا چه شکلی است؟ | Map (Map Server) |
| از کجا رفتن بهتر و امنتر است؟ | Costmap |
| چطور از اینجا به مقصد برسم؟ | Global Planner + Local Controller |
روز اول ARCHO با SLAM Toolbox نقشه ساخت. از روز دوم به بعد، دیگر نیازی به ساخت دوباره نقشه نیست:
Adaptive Monte Carlo Localization — با استفاده از داده LiDAR زنده و مقایسه آن با نقشه ذخیرهشده، به این سؤال پاسخ میدهد: «ربات دقیقاً کجای این نقشه ایستاده؟» بر خلاف SLAM Toolbox، AMCL نقشهای نمیسازد — فقط موقعیت را روی نقشهای که از قبل داری پیدا میکند.
نصب کامل Nav2:
sudo apt install \
ros-jazzy-navigation2 \
ros-jazzy-nav2-bringup
Navigation2 برای یک ربات سفارشی مثل ARCHO به چهار چیز درستشده نیاز دارد: Map، TF، Odometry و LaserScan معتبر، بههمراه Footprint دقیق. Nav2 هیچکدام از خطاهای لایههای پایینتر (فصلهای ۵، ۸ و ۹) را جادویی حل نمیکند — روی همان پایهای کار میکند که تو ساختهای.
Nav2 باید بداند ARCHO دقیقاً چه شکل و اندازهای دارد تا بتواند برخورد را پیشبینی کند.
# مدل مستطیلی — نمای دقیقتر
footprint: "[[0.25, 0.18], [0.25, -0.18], [-0.25, -0.18], [-0.25, 0.18]]"
# یا مدل دایرهای ساده
robot_radius: 0.30
| خطا | پیامد |
|---|---|
| Footprint کوچکتر از ربات واقعی | برخورد واقعی با قفسه یا دیوار |
| Footprint بزرگتر از ربات واقعی | ARCHO از درها و راهروهای باریک رد نمیشود |
نقشه معمولی فقط میگوید کجا دیوار است. Costmap یک لایه هوشمندتر روی آن نقشه است که میگوید کجا رفتن «گران» یا خطرناک است — حتی جاهایی که هنوز دیواری وجود ندارد اما به آن نزدیک است.
Nav2 دو Costmap جدا دارد، هرکدام برای یک هدف متفاوت:
| Global Costmap | Local Costmap | |
|---|---|---|
| Frame مرجع | map | odom |
| پوشش | کل نقشه | یک پنجره کوچک اطراف ربات (مثلاً ۴×۴ متر) |
| نرخ بهروزرسانی | کند (۱-۲ هرتز) | سریع (۵-۱۰ هرتز) |
| کاربرد | برنامهریزی مسیر کلی | واکنش آنی به مانع نزدیک |
Local Controller به یک حرکت نرم و پیوسته نیاز دارد. فریم map ممکن است هر بار AMCL
تصحیح انجام دهد کمی جهش کند؛ اما فریم odom همیشه پیوسته و بدون پرش است (یادت هست، فصل
۵). برای کنترل لحظهبهلحظه، پیوستگی مهمتر از دقت مطلق است.
# global_costmap.yaml
global_costmap:
global_costmap:
ros__parameters:
global_frame: map
robot_base_frame: base_link
update_frequency: 2.0
publish_frequency: 1.0
resolution: 0.05
track_unknown_space: true
plugins:
- static_layer
- obstacle_layer
- inflation_layer
# local_costmap.yaml
local_costmap:
local_costmap:
ros__parameters:
global_frame: odom
robot_base_frame: base_link
rolling_window: true
width: 4.0
height: 4.0
resolution: 0.05
update_frequency: 10.0
publish_frequency: 5.0
# Obstacle Layer — مانع را از LiDAR اضافه میکند
obstacle_layer:
plugin: nav2_costmap_2d::ObstacleLayer
observation_sources: scan
scan:
topic: /scan
data_type: LaserScan
clearing: true
marking: true
max_obstacle_height: 2.0
obstacle_max_range: 3.5
raytrace_max_range: 4.0
# Inflation Layer — حاشیه امنیتی دور موانع میکشد
inflation_layer:
plugin: nav2_costmap_2d::InflationLayer
inflation_radius: 0.45
cost_scaling_factor: 4.0
| مفهوم | معنی |
|---|---|
marking | وقتی LiDAR مانعی میبیند، آن را روی Costmap ثبت کن |
clearing | اگر فضایی که قبلاً اشغال بود دوباره آزاد دیده شد، پاکش کن |
inflation_radius | شعاع حاشیه امنی دور هر مانع |
اگر inflation_radius خیلی کم باشد، ARCHO به دیوار و قفسه میچسبد و ریسک برخورد بالا
میرود. اگر خیلی زیاد باشد، راهروهای باریک انبار بهکل «بسته» دیده میشوند و Planner مسیری برایشان
پیدا نمیکند — حتی اگر عملاً عبور از آنها ممکن باشد.
یک مثال از زندگی واقعی: وقتی به فرودگاه میروی، دو نوع تصمیم کاملاً متفاوت میگیری.
| برنامه کلی سفر | تصمیم لحظهای رانندگی | |
|---|---|---|
| مثال | خانه → بزرگراه → خروجی ۵ → فرودگاه | کمی فرمان به چپ، ترمز، دور زدن یک دوچرخه |
| در Nav2 | Global Planner | Local Controller |
وظیفهاش پیدا کردن بهترین مسیر کلی از نقطه شروع تا مقصد است — بدون توجه به اینکه چند درجه فرمان باید چرخید. ورودیاش Map + Costmap + Goal است، خروجیاش فقط یک Path.
| پلاگین Planner | ویژگی |
|---|---|
| NavFn | ساده و سریع، مبتنی بر جستجوی گراف پایه |
| Smac Planner | پشتیبانی از A* و Hybrid A*، برای رباتهای غیردایرهای مناسبتر |
| Theta* | مسیرهای صافتر با کمترین تعداد پیچ |
Planner فقط مسیر کلی را میدهد؛ نمیگوید موتور چپ چند رادیان بر ثانیه بچرخد. این کار Local Controller است — که هر چند میلیثانیه یکبار (مثلاً ۲۰-۵۰ بار در ثانیه) تصمیم میگیرد.
| Controller | ایده اصلی |
|---|---|
| Pure Pursuit | بهجای کل مسیر، فقط به یک نقطه چند متر جلوتر نگاه میکند — دقیقاً مثل رانندگی که به سپر جلو نگاه نمیکنی |
| DWB (Dynamic Window Approach) | دهها ترکیب سرعت خطی و زاویهای را امتحان میکند و بهترین را انتخاب میکند |
| MPPI (Model Predictive Path Integral) | آینده را شبیهسازی میکند: «اگر این فرمان را بدهم، ۲ ثانیه بعد کجا خواهم بود؟» و از میان صدها احتمال بهترین را انتخاب میکند |
اگر Planner هر ۲۰ میلیثانیه دوباره مسیر ۲۰۰ متری را محاسبه کند، پردازنده ربات فلج میشود. پس Planner فقط وقتی اجرا میشود که لازم باشد: مقصد جدید، مسیر مسدود، یا مانع بزرگ. اما Controller باید دائماً فعال باشد چون ربات هر لحظه در حال حرکت است — یک کارگر که از مقابل ARCHO رد میشود نیازی به محاسبه دوباره مسیر کلی ندارد، فقط یک اصلاح کوچک لحظهای لازم است. اما اگر یک لیفتراک کل راهرو را ببندد، Controller دیگر نمیتواند کاری کند و از Planner کمک میخواهد.
سیستم مدیریت انبار به ARCHO میگوید «برو قفسه ۲۴». Planner مسیر کلی را طراحی میکند و ARCHO حرکت میکند. یک کارگر از مقابلش رد میشود — Local Controller سرعت را کم میکند و مسیر را کمی اصلاح میکند، بدون اینکه Planner خبردار شود. چند لحظه بعد یک لیفتراک کل راهرو را میبندد — اینبار اصلاح جزئی کافی نیست، پس Controller دوباره از Planner یک مسیر کاملاً جدید از راهروی دیگر میخواهد.
# planner_server.yaml
planner_server:
ros__parameters:
planner_plugins:
- GridBased
GridBased:
plugin: nav2_navfn_planner::NavfnPlanner
tolerance: 0.5
use_astar: false
allow_unknown: true
# controller_server.yaml
controller_server:
ros__parameters:
controller_frequency: 20.0
controller_plugins:
- FollowPath
FollowPath:
plugin: nav2_regulated_pure_pursuit_controller::RegulatedPurePursuitController
desired_linear_vel: 0.4
lookahead_dist: 0.6
min_lookahead_dist: 0.3
max_lookahead_dist: 0.9
use_velocity_scaled_lookahead_dist: true
use_collision_detection: true
ترتیب راهاندازی برای اولینبار اهمیت دارد:
| ترتیب | چه چیزی روشن میشود |
|---|---|
| ۱ | Gazebo (فصل ۷) |
| ۲ | Robot + Controllers (فصل ۸) |
| ۳ | Map Server |
| ۴ | AMCL |
| ۵ | Nav2 (Planner + Controller + Behavior Tree) |
| ۶ | RViz |
در RViz:
برای بررسی کامل سیستم:
ros2 topic list
ros2 node list
ros2 lifecycle nodes
ros2 action list
ros2 control list_controllers
Navigation2 زمانی خوب کار میکند که ربات از نظر TF، Odometry، Footprint و Sensor Data از قبل درست مهندسی شده باشد؛ Nav2 نمیتواند خطاهای لایههای پایینتر را جادویی حل کند.
ARCHO حالا میتواند در انباری که قبلاً نقشهاش را ساخته، خودش موقعیتش را پیدا کند، مسیر کلی را برنامهریزی کند، و لحظهبهلحظه با موانع متحرک کنار بیاید. اما آنچه هنوز ندیدیم این است که این تصمیمها — کِی مسیر دوباره محاسبه شود، کِی صبر کند، کِی بازیابی انجام دهد — چطور واقعاً هماهنگ میشوند. این موضوع فصل بعد است.
پروژه ARCHO اکنون بسته archo_navigation با Map Server، AMCL، Costmapهای دوگانه، Planner Server و Controller Server کامل پیکربندی شده و ARCHO برای اولین بار بهطور کاملاً خودکار در انبار مسیر پیدا میکند.
در فصل دوازدهم وارد قلب تصمیمگیری Nav2 میشویم: Behavior Tree و Lifecycle Nodes — جایی که میبینی Nav2 اصلاً یک برنامه خطی نیست، بلکه مثل مغز یک ربات واقعی فکر میکند.