參數系統(Parameter)
- ros2
- parameter
- yaml
- rclpy
問題定義
寫死在程式碼裡的數值(PID 增益、最大速度、topic 名稱)在開發階段很方便,但正式部署時,你會需要在不重新編譯、甚至不重啟節點的情況下調整這些數值——這就是 ROS2 參數系統要解決的問題:讓設定值可以在外部被查詢、修改,並讓節點有機會對「值被改變」這件事做出反應(例如驗證新值是否合理)。
核心概念說明
參數的宣告與型別
ROS2 的參數必須先「宣告」才能使用,宣告時會固定型別(int、double、string、bool,以及對應的陣列型別)。宣告時可以指定描述子(descriptor),加上範圍限制、唯讀等中繼資料:
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:
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
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()
編譯執行:
colcon build --symlink-install --packages-select my_package
source install/setup.bash
ros2 run my_package tunable_node
預期輸出(啟動後)
[INFO] [tunable_node]: [unnamed_robot] 目前 max_speed = 0.5
[INFO] [tunable_node]: [unnamed_robot] 目前 max_speed = 0.5
動態修改參數
打開另一個終端機:
ros2 param get /tunable_node max_speed
Double value is: 0.5
ros2 param set /tunable_node max_speed 1.2
Set parameter successful
回到節點的終端機,會看到回呼被觸發,接著讀到新的值:
[INFO] [tunable_node]: max_speed 即將變更為 1.2
[INFO] [tunable_node]: [unnamed_robot] 目前 max_speed = 1.2
嘗試設定超出範圍的值:
ros2 param set /tunable_node max_speed 5.0
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
tunable_node:
ros__parameters:
max_speed: 1.0
robot_name: "warehouse_bot_01"
啟動時透過 --params-file 載入(第 3.4 節的 launch 檔案會示範怎麼把這個步驟自動化):
ros2 run my_package tunable_node --ros-args --params-file src/my_package/config/tunable_node.yaml
預期輸出
[INFO] [tunable_node]: [warehouse_bot_01] 目前 max_speed = 1.0
確認目前節點實際生效的所有參數(不只是 yaml 檔案裡寫的):
ros2 param dump /tunable_node
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 list 或 ros2 param dump 確認實際生效的參數值,不要只相信 yaml 檔案內容跟預期一致:
ros2 param list /tunable_node
max_speed
robot_name
use_sim_time
錯誤二:add_on_set_parameters_callback 裡邏輯寫錯,導致所有參數都無法修改
Setting parameter failed
原因:回呼函式如果漏寫 return SetParametersResult(successful=True)(例如忘記處理「不需要特別驗證」的參數分支,導致函式最後沒有明確回傳值,Python 預設回傳 None),rclpy 會把這種非預期回傳值視為驗證失敗,所有透過這個回呼的參數變更都會被拒絕。
排除方式:確保回呼函式在所有分支都有明確的 return SetParametersResult(...),養成先寫「預設允許」再疊加特定拒絕條件的順序,而不是反過來。
小結
參數系統讓節點的行為可以在不重新編譯的情況下被調整,型別與範圍限制在宣告階段就能擋掉大部分無效輸入,add_on_set_parameters_callback 則讓你能對「值即將改變」這件事做進一步驗證或即時反應。yaml 檔案是批次管理參數的標準做法,但務必記得它是「盡力寫入」,載入後要主動確認實際生效的值。到這裡,第 2 章介紹的五種通訊機制(節點、主題、服務、動作、參數)就是 ROS2 系統的完整地基——下一章我們會進入日常開發會大量用到的除錯與工具鏈。
延伸閱讀
常見問題
- 參數可以在節點執行期間改型別嗎(例如從 int 改成 string)?
- 預設不行。ROS2 參數宣告時會固定型別,執行期間嘗試設定不同型別的值會被拒絕(回傳失敗),除非你在宣告時明確使用 PARAMETER_DYNAMIC_TYPING(不建議,容易讓程式邏輯難以推理)。
- yaml 參數檔裡打錯參數名稱,會怎麼樣?
- 節點不會報錯,會直接忽略檔案裡「節點沒有宣告過」的參數項目。這也是為什麼參數載入後,建議用 ros2 param list 或 ros2 param dump 確認實際生效的參數,而不是只相信 yaml 檔案內容。
- add_on_set_parameters_callback 可以攔截節點啟動時的初始參數嗎?
- 不行,這個 callback 只會在節點啟動「之後」,透過 ros2 param set 或其他節點呼叫參數服務動態修改時才會被觸發。啟動時透過 declare_parameter 給定的初始值,或透過 yaml 檔案載入的初始值,不會經過這個 callback。