National Central University
Uedu Main Site
Explore Uedu
Student Console
Register as Member/Login
Research Informed Consent Center
Survey Center
Educational Big Data Challenge
Claim or create a course
Try an AI Scenario Task
Support & Messages
Uptime Data

UeduGPTs

--

Jupyters

3

Local AI

--

Uedu Code

--

中央大學 AQI 80 24°C PM2.5 21
AI Reply Desktop Notifications

Show a desktop notification when the AI TA finishes replying

Chat Message Notifications

Notify me when classmates post messages in the forum

Sound notification

Play an alert sound whenever there is a new notification

Unit 05

service 與參數

問一句答一句的呼叫,不改程式就能調整的設定
Interaction 30 minute(s)

Learning Objectives

  • 判斷一個功能該用 topic 還是 service
  • 寫 service 伺服器與用戶端,使用 wait_for_service、call_async、spin_until_future_complete
  • 說明為什麼不能在回呼裡用 client.call 等回應
  • 用參數調整節點的設定,並在 add_on_set_parameters_callback 裡拒絕不合理的值

有些事情不是持續的資料流

光達每秒掃好幾次,topic 很合適。但「把地圖存檔」「重設里程計」「請告訴我這個點能不能到」這些事情,是有人問一次、對方答一次,而且問的人想知道答案。用 topic 做的話,要自己配對「哪個回答對應哪個問題」,也不知道對方到底有沒有收到。

service 就是問一句答一句:用戶端(client)送請求(request),伺服器(server)算完回回應(response)。

topic service
形式 一直發,誰想聽就聽 問一次答一次
對象 多對多,發布者不知道誰在收 一個請求對應一個回應
適合 感測器、速度指令、狀態 查詢、切換設定、觸發很快做完的動作
不適合 需要知道對方是否完成 高頻率資料;要跑很久的工作

要跑很久的工作不要用 service(例如「走到教室門口」),因為用戶端看不到進度也不能取消,那是下一課 action 的工作。

互動練習:官方的加法 service

add_server.py 提供 add_two_ints 這個 service;add_client.py 送兩個數字過去,等答案。下面的輸出是模擬環境的輸出。

模擬裡還安排了一件事:第 1 秒時,有人在另一個終端機下了這個指令,你在日誌會看到伺服器收到它。

ros2 service call /add_two_ints example_interfaces/srv/AddTwoInts "{a: 7, b: 8}"

一步一步看非同步呼叫

call_async 送出請求後馬上回來,手上拿到一個 future(一張還沒兌現的取貨單)。要讓執行器有機會處理,答案才會進來。這裡用 spin_until_future_complete:spin 到這個 future 完成為止。為了方便看,伺服器和用戶端放在同一個節點。

在回呼裡等回應會卡住

rclpy 還有一個同步的 client.call(request),看起來比較簡單:呼叫、等、拿到答案。官方原始碼的警告是:不要在回呼裡呼叫它,可能死結或逾時。

原因和第 3 課的「回呼排隊」一樣。回應要由執行器處理,但執行器正在跑你的回呼,而你的回呼在等回應:

你的回呼:等回應回來才結束
執行器:  等回呼結束,才能處理回應

兩邊互相等,永遠不會結束。機器人的控制迴圈就這樣停了,也沒有任何錯誤訊息。解法是用 call_async 加上 done callback,把「收到回應之後要做的事」寫成另一個回呼,這樣目前的回呼可以先結束:

future = self.cli.call_async(req)
future.add_done_callback(self.on_response)

真實環境裡這種卡住不會有任何提示;模擬環境為了教學,偵測到就會停下來說明。

互動練習:解開卡住的回呼

caller.py 每 0.5 秒問一次加法。照註解改掉卡住的那一行,on_response 已經幫你寫好了。

參數:不改程式就能調整的設定

速度上限、感測器週期、PID 的係數,這些數字會因機器人與場地不同而變。寫死在程式裡的話,每調一次就要改程式、重新啟動。ROS 2 的參數(parameter)是節點自己帶的設定值:

self.declare_parameter('max_speed', 0.3)              # 宣告,並給預設值
speed = self.get_parameter('max_speed').value         # 讀

參數一定要先宣告才能用(否則丟出例外),型別由預設值決定,例如 0.3 就是浮點數。可以在啟動時給值,也可以在執行中從外面改:

ros2 run my_robot driver --ros-args -p max_speed:=0.2
ros2 param set /driver max_speed 0.15

外面改得到,也就可能改錯。 有人打成 5.5,或是另一個節點傳來算錯的值,機器人就可能在人群裡全速前進。所以要加一道檢查,add_on_set_parameters_callback 註冊的函數會在參數改變之前被呼叫,回傳不成功就拒絕,參數維持原值。

有兩個細節要注意:

  • get_parameter 要在每次用到時讀(或在 post-set callback 裡更新),只在 __init__ 讀一次就不會跟著改。
  • 這只是軟體上的第一道關。真實機器人的硬體與驅動層也應該有上限與緊急停止,不能只靠一個 Python 判斷。

互動練習:拒絕不合理的速度

模擬裡安排了有人在另一個終端機改參數:第 3 秒改成合理的 0.15,第 6 秒手滑打成 5.5。

ros2 param set /driver max_speed 0.15
ros2 param set /driver max_speed 5.5

這台模擬機器人的硬體上限是 0.5 m/s,所以實際速度不會真的到 5.5,但程式已經下了這個指令。「數值曲線」畫的是程式送出的速度指令。

確認一下

1.

下面哪一個功能最適合用 service?

2.

future = self.cli.call_async(req) 的下一行寫 print(future.result().sum),中間沒有 spin。會發生什麼?

3.

一個計時器回呼裡寫 response = self.cli.call(req),伺服器在另一支程式、也正常運作。這個節點只用預設的單執行緒 spin。會怎樣?

4.

on-set callback 回傳 successful=False 之後,參數的值是?

5.

節點只在 init 寫 self.max_speed = self.get_parameter('max_speed').value,之後 tick 都用 self.max_speed。有人下 ros2 param set 改值會怎樣?

第 1 次作答

重點整理

  • topic 是持續的資料流,service 是一問一答。要跑很久的工作留給 action。
  • 用戶端先 wait_for_service,再 call_async;答案要靠 spin 或 done callback 才拿得到。
  • 不要在回呼裡用 client.call:執行器等回呼,回呼等回應,兩邊都不會結束。
  • 參數先宣告才能用,可以在啟動時給值,也可以在執行中修改;型別固定。
  • 影響安全的參數要在 add_on_set_parameters_callback 裡檢查範圍,並且不能是唯一的防線。

下一課處理要跑很久、中途要回報進度、還可能被取消的工作:action。

這一課完成了!
Next lesson:action
你目前沒有登入,學習進度只存在這個瀏覽器。 Login 後,進度會跟著帳號,換電腦也看得到。