launch 檔案設計
- ros2
- launch
- rclpy
- automation
問題定義
到目前為止,每次要測試一組系統(例如 talker + listener + add_server),都要開好幾個終端機分別下指令。真實專案的節點數量只會更多,還常常需要搭配不同的參數檔案、不同的 remapping。逐一手動啟動不但麻煩,還很容易漏掉某個節點或用錯參數檔案。launch 檔案就是解決這個問題的標準做法:把「啟動一整套系統」這件事寫成一份可重複執行、版本控制的設定檔。
核心概念說明
LaunchDescription:一份啟動系統的完整描述
一個 launch 檔案的核心是回傳一個 LaunchDescription 物件,裡面包含一系列要執行的「動作」(action)——最常見的動作是 Node,代表啟動一個節點。你在前面章節手動輸入的 ros2 run my_package talker,在 launch 檔案裡對應的就是一個 Node action。
為什麼要用 DeclareLaunchArgument,而不是把值寫死
如果每個環境(開發機、測試機、實際機器人)需要不同的參數檔路徑,把路徑寫死在 launch 檔案裡就會需要維護多份幾乎一樣的檔案。DeclareLaunchArgument 讓你把這些「會變動的值」變成呼叫 launch 檔案時可以傳入的參數,搭配 LaunchConfiguration 在檔案內部引用,讓同一份 launch 檔案能適應不同場景。
實作範例:一次啟動 talker、listener,並注入參數檔
1. 建立 launch 檔案
ROS2 慣例會把 launch 檔案放在套件底下的 launch/ 資料夾:
mkdir -p ~/ros2_ws/src/my_package/launch
檔案位置:~/ros2_ws/src/my_package/launch/demo.launch.py
from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument
from launch.substitutions import LaunchConfiguration
from launch_ros.actions import Node
def generate_launch_description():
use_verbose_arg = DeclareLaunchArgument(
"verbose",
default_value="false",
description="是否啟用更詳細的 log 輸出",
)
talker_node = Node(
package="my_package",
executable="talker",
name="talker",
output="screen",
)
listener_node = Node(
package="my_package",
executable="listener",
name="listener",
output="screen",
remappings=[("chatter", "chat_topic")],
)
tunable_node = Node(
package="my_package",
executable="tunable_node",
name="tunable_node",
output="screen",
parameters=[
{"max_speed": 1.0},
{"robot_name": "warehouse_bot_01"},
],
arguments=["--ros-args", "--log-level", LaunchConfiguration("verbose")],
)
return LaunchDescription([
use_verbose_arg,
talker_node,
listener_node,
tunable_node,
])
這個範例示範了三個實用細節:
remappings=[("chatter", "chat_topic")]:把listener訂閱的主題名稱從chatter改成chat_topic,不需要修改程式碼就能讓同一支節點在不同場景下連到不同主題名稱。注意這裡故意示範一個常見的設定錯誤——這樣寫會導致listener收不到talker發布的訊息,因為talker還是發布到chatter,兩者對不上。這個坑會在下面「常見錯誤」小節詳細說明。parameters=[{...}, {...}]:直接在 launch 檔案裡以字典形式注入初始參數,效果等同第 2.5 節用--params-file載入 yaml,但這裡改成內嵌在 launch 檔案裡管理。LaunchConfiguration("verbose"):把DeclareLaunchArgument宣告的參數值,傳遞進arguments清單裡,讓呼叫這份 launch 檔案的人可以在指令列覆蓋。
2. 修改 setup.py,確保 launch 檔案會被安裝
在 setup.py 的 data_files 加上:
import os
from glob import glob
data_files=[
...
(os.path.join("share", package_name, "launch"), glob("launch/*.launch.py")),
],
3. 編譯並執行
colcon build --symlink-install --packages-select my_package
source install/setup.bash
ros2 launch my_package demo.launch.py
預期輸出
[INFO] [launch]: All log files can be found below /home/user/.ros/log/2026-07-18-15-02-11-123456-host-12345
[INFO] [launch]: Default logging verbosity is set to INFO
[INFO] [talker-1]: process started with pid [12346]
[INFO] [listener-2]: process started with pid [12347]
[INFO] [tunable_node-3]: process started with pid [12348]
[talker-1] [INFO] [talker]: publish: hello ros2, count=0
[tunable_node-3] [INFO] [tunable_node]: [warehouse_bot_01] 目前 max_speed = 1.0
注意 listener-2 完全沒有輸出——這正是上面刻意留下的 remapping 錯誤造成的(下面會說明排除方式)。
搭配 DeclareLaunchArgument 傳入自訂值:
ros2 launch my_package demo.launch.py verbose:=true
常見錯誤與除錯技巧
錯誤一:remapping 設定不對稱,導致節點之間連不上
現象:如上面範例,listener 被 remap 到訂閱 chat_topic,但 talker 沒有同步 remap,還是發布到 chatter,兩個節點各自「正常執行」,但完全沒有互動。
排除方式:remapping 必須兩端對稱,或者只 remap 其中一端但確保主題名稱最終一致。正確的寫法是同時 remap 兩個節點,或乾脆都不 remap:
talker_node = Node(
package="my_package",
executable="talker",
name="talker",
output="screen",
remappings=[("chatter", "chat_topic")],
)
listener_node = Node(
package="my_package",
executable="listener",
name="listener",
output="screen",
remappings=[("chatter", "chat_topic")],
)
用 ros2 topic list 檢查目前實際存在的主題名稱,確認兩端的訂閱/發布是不是真的指向同一個名稱,是排查這類問題最直接的方式。
錯誤二:ros2 launch 找不到 launch 檔案
Package 'my_package' found at '/home/user/ros2_ws/install/my_package' but libexec directory does not exist
或
No launch file supplied
原因:忘記在 setup.py 的 data_files 註冊 launch/ 資料夾,導致 colcon build 沒有把 launch 檔案安裝到 install/ 底下對應的位置。
排除方式:確認 setup.py 有正確的 data_files 設定(如本文範例),重新編譯後確認檔案確實被安裝:
colcon build --packages-select my_package
find install/my_package/share/my_package/launch/
install/my_package/share/my_package/launch/
install/my_package/share/my_package/launch/demo.launch.py
小結
launch 檔案把「啟動一整套系統」從一連串手動指令,變成一份可版本控制、可重複執行的設定檔,DeclareLaunchArgument 讓同一份檔案能適應不同場景而不用複製修改。到這裡,第 3 章介紹的四個開發工具(rqt、CLI 速查、rosbag2、launch)已經足以支撐日常的多節點系統開發與除錯——下一章我們會進入機器人建模與座標系統,把 URDF、TF2 這些之前提過但還沒深入的主題補齊。
延伸閱讀
常見問題
- 一定要用 Python 寫 launch 檔案嗎?
- ROS2 也支援 XML 與 YAML 格式的 launch 檔案,語法更精簡,但表達能力(條件判斷、動態組合)不如 Python 版本靈活。實務上簡單場景三種格式都能用,但只要牽涉到需要依條件組合節點、讀取環境變數做邏輯判斷,幾乎都會選 Python。
- launch 檔案裡可以同時啟動不同套件的節點嗎?
- 可以,Node action 的 package 參數本來就是指定套件名稱,一個 launch 檔案裡的多個 Node 動作可以分別來自不同套件,這也是 launch 檔案存在的核心價值——把分散在不同套件的節點組合成一個完整系統。
- launch 檔案裡的參數設定,優先權會不會跟指令列傳入的參數衝突?
- 會有明確的優先順序:直接在指令列用 --ros-args -p 傳入的參數,通常會覆蓋 launch 檔案裡用 parameters= 設定的值。實務上建議只在需要臨時覆蓋做測試時用指令列,正式的參數配置應該維護在 launch 檔案或它引用的 yaml 檔案裡,避免團隊成員各自用不同指令啟動導致參數不一致。