參數系統(Parameter)

2026-07-18
  • ros2
  • parameter
  • yaml
  • rclpy

問題定義

寫死在程式碼裡的數值(PID 增益、最大速度、topic 名稱)在開發階段很方便,但正式部署時,你會需要在不重新編譯、甚至不重啟節點的情況下調整這些數值——這就是 ROS2 參數系統要解決的問題:讓設定值可以在外部被查詢、修改,並讓節點有機會對「值被改變」這件事做出反應(例如驗證新值是否合理)。

核心概念說明

參數的宣告與型別

ROS2 的參數必須先「宣告」才能使用,宣告時會固定型別(intdoublestringbool,以及對應的陣列型別)。宣告時可以指定描述子(descriptor),加上範圍限制、唯讀等中繼資料:

python
from rcl_interfaces.msg import ParameterDescriptor, FloatingPointRange

descriptor = ParameterDescriptor(
    description="移動速度上限(m/s)",
    floating_point_range=[FloatingPointRange(from_value=0.0, to_value=2.0, step=0.0)],
)
self.declare_parameter("max_speed", 0.5, descriptor)

這個範圍限制不只是文件用途——ros2 param set 嘗試設定超出範圍的值時,會被系統拒絕。

用回呼攔截參數變更

如果你想在參數被外部修改時,做額外的驗證或立即套用到執行中的邏輯(例如速度上限被調低時,立刻降低目前的目標速度),要註冊 add_on_set_parameters_callback

python
from rcl_interfaces.msg import SetParametersResult

def on_parameter_change(self, params):
    for param in params:
        if param.name == "max_speed" and param.value > 2.0:
            return SetParametersResult(
                successful=False, reason="max_speed 不能超過 2.0"
            )
    return SetParametersResult(successful=True)

這個回呼在「參數即將被更新」時觸發,回傳 successful=False 可以拒絕這次變更(連帶回範圍限制一起用,等於是雙重保險:範圍限制擋掉明顯超界的值,回呼可以做更複雜的跨參數邏輯驗證,例如「min_speed 不能大於 max_speed」這種需要同時看兩個參數的檢查)。

實作範例:可動態調整的節點

檔案位置:~/ros2_ws/src/my_package/my_package/tunable_node.py

python
import rclpy
from rclpy.node import Node
from rcl_interfaces.msg import (
    ParameterDescriptor,
    FloatingPointRange,
    SetParametersResult,
)


class TunableNode(Node):
    def __init__(self):
        super().__init__("tunable_node")

        speed_descriptor = ParameterDescriptor(
            description="移動速度上限(m/s)",
            floating_point_range=[
                FloatingPointRange(from_value=0.0, to_value=2.0, step=0.0)
            ],
        )
        self.declare_parameter("max_speed", 0.5, speed_descriptor)
        self.declare_parameter("robot_name", "unnamed_robot")

        self.add_on_set_parameters_callback(self.on_parameter_change)
        self.timer = self.create_timer(2.0, self.on_timer)

    def on_parameter_change(self, params):
        for param in params:
            if param.name == "max_speed":
                self.get_logger().info(f"max_speed 即將變更為 {param.value}")
        return SetParametersResult(successful=True)

    def on_timer(self):
        max_speed = self.get_parameter("max_speed").value
        robot_name = self.get_parameter("robot_name").value
        self.get_logger().info(
            f"[{robot_name}] 目前 max_speed = {max_speed}"
        )


def main():
    rclpy.init()
    node = TunableNode()
    try:
        rclpy.spin(node)
    finally:
        node.destroy_node()
        rclpy.shutdown()


if __name__ == "__main__":
    main()

編譯執行:

bash
colcon build --symlink-install --packages-select my_package
source install/setup.bash
ros2 run my_package tunable_node

預期輸出(啟動後)

text
[INFO] [tunable_node]: [unnamed_robot] 目前 max_speed = 0.5
[INFO] [tunable_node]: [unnamed_robot] 目前 max_speed = 0.5

動態修改參數

打開另一個終端機:

bash
ros2 param get /tunable_node max_speed
text
Double value is: 0.5
bash
ros2 param set /tunable_node max_speed 1.2
text
Set parameter successful

回到節點的終端機,會看到回呼被觸發,接著讀到新的值:

text
[INFO] [tunable_node]: max_speed 即將變更為 1.2
[INFO] [tunable_node]: [unnamed_robot] 目前 max_speed = 1.2

嘗試設定超出範圍的值:

bash
ros2 param set /tunable_node max_speed 5.0
text
Setting parameter failed: [rcl_interfaces/msg/SetParametersResult]: max_speed 超出允許範圍 [0.0, 2.0]

用 .yaml 檔案批次載入初始參數

實務上很少一個個手動 ros2 param set,通常會把一組參數寫在 .yaml 檔案裡,啟動節點時一次載入。

檔案位置:~/ros2_ws/src/my_package/config/tunable_node.yaml

yaml
tunable_node:
  ros__parameters:
    max_speed: 1.0
    robot_name: "warehouse_bot_01"

啟動時透過 --params-file 載入(第 3.4 節的 launch 檔案會示範怎麼把這個步驟自動化):

bash
ros2 run my_package tunable_node --ros-args --params-file src/my_package/config/tunable_node.yaml

預期輸出

text
[INFO] [tunable_node]: [warehouse_bot_01] 目前 max_speed = 1.0

確認目前節點實際生效的所有參數(不只是 yaml 檔案裡寫的):

bash
ros2 param dump /tunable_node
yaml
tunable_node:
  ros__parameters:
    max_speed: 1.0
    robot_name: warehouse_bot_01
    use_sim_time: false

常見錯誤與除錯技巧

錯誤一:yaml 檔案裡的參數名稱打錯,節點卻沒有任何警告

現象:在 yaml 檔案裡把 max_speed 誤植為 max_spedd,節點啟動後行為完全跟沒載入 yaml 一樣(用的是程式碼裡宣告的預設值),但終端機沒有任何錯誤或警告。

原因:ROS2 的參數載入機制是「盡力寫入」——yaml 裡宣告但節點沒有 declare_parameter 過的參數項目,會被靜默忽略,不會報錯中斷。

排除方式:載入後務必用 ros2 param listros2 param dump 確認實際生效的參數值,不要只相信 yaml 檔案內容跟預期一致:

bash
ros2 param list /tunable_node
text
max_speed
robot_name
use_sim_time

錯誤二:add_on_set_parameters_callback 裡邏輯寫錯,導致所有參數都無法修改

text
Setting parameter failed

原因:回呼函式如果漏寫 return SetParametersResult(successful=True)(例如忘記處理「不需要特別驗證」的參數分支,導致函式最後沒有明確回傳值,Python 預設回傳 None),rclpy 會把這種非預期回傳值視為驗證失敗,所有透過這個回呼的參數變更都會被拒絕。

排除方式:確保回呼函式在所有分支都有明確的 return SetParametersResult(...),養成先寫「預設允許」再疊加特定拒絕條件的順序,而不是反過來。

小結

參數系統讓節點的行為可以在不重新編譯的情況下被調整,型別與範圍限制在宣告階段就能擋掉大部分無效輸入,add_on_set_parameters_callback 則讓你能對「值即將改變」這件事做進一步驗證或即時反應。yaml 檔案是批次管理參數的標準做法,但務必記得它是「盡力寫入」,載入後要主動確認實際生效的值。到這裡,第 2 章介紹的五種通訊機制(節點、主題、服務、動作、參數)就是 ROS2 系統的完整地基——下一章我們會進入日常開發會大量用到的除錯與工具鏈。

延伸閱讀

常見問題

faq_01.log
參數可以在節點執行期間改型別嗎(例如從 int 改成 string)?
預設不行。ROS2 參數宣告時會固定型別,執行期間嘗試設定不同型別的值會被拒絕(回傳失敗),除非你在宣告時明確使用 PARAMETER_DYNAMIC_TYPING(不建議,容易讓程式邏輯難以推理)。
faq_02.log
yaml 參數檔裡打錯參數名稱,會怎麼樣?
節點不會報錯,會直接忽略檔案裡「節點沒有宣告過」的參數項目。這也是為什麼參數載入後,建議用 ros2 param list 或 ros2 param dump 確認實際生效的參數,而不是只相信 yaml 檔案內容。
faq_03.log
add_on_set_parameters_callback 可以攔截節點啟動時的初始參數嗎?
不行,這個 callback 只會在節點啟動「之後」,透過 ros2 param set 或其他節點呼叫參數服務動態修改時才會被觸發。啟動時透過 declare_parameter 給定的初始值,或透過 yaml 檔案載入的初始值,不會經過這個 callback。