--- tags: ROS --- # ROS 問題 1. ```ros.spin()``` 的作用? 第一點需要釐清的是 ```ros.spin()``` 究竟屬於哪種語言? 如果沒猜錯的話你們應該是用 python,也就是 ```rospy.spin()```。相對於 C++ 中 ```ros::spin()``` 這兩者在 ROS 1 中的表現是<font color="#f00">**不一致的**</font>。 - For C++: :::info C++ 的 callback 會被放到一個叫 GlobalCallbackQueue 的地方,會在 spin 的時候執行 ```getGlobalCallbackQueue()獲取和相關方法去執行裡面的函式```,請參考[==本連結==](http://wiki.ros.org/roscpp/Overview/Callbacks%20and%20Spinning#:~:text=1-,%23include%20%3Cros/callback_queue.h%3E,-2%20ros%3A%3A) ::: ```cpp // 迴圈的寫法 // method 1 -> non-blocking and sync ros::Rate r(10); while (ros::ok()) { // ... do some work, publish some messages, etc. ... ros::spinOnce(); r.sleep(); } // method 2 -> blocking and sync // ... do some work, publish some messages, etc. ... ros::spin(); // put this at the end of your function. This line blocking this thread ``` 總結就是 c++ 中預設你要執行 callback,一定要調用 spin() 或 spinOnce(),除非你要用一些神奇魔法(**就是創建 subscriber 時指定 callback 存取到自己創建的 queue 中** - For Python: [python 和 c++ 的差異 for spin in ROS1](https://ithelp.ithome.com.tw/articles/10238734#:~:text=%E8%B7%9Fpython%E7%89%88%E7%9A%84%E5%B7%AE%E7%95%B0%E6%87%89%E8%A9%B2%E5%B0%B1%E6%98%AF%EF%BC%8Cpython%E7%89%88%E5%8F%AA%E8%A6%81%E5%AE%9A%E7%BE%A9%E5%A5%BDsubscriber%E4%BB%A5%E5%BE%8C%EF%BC%8C%E4%B8%80%E6%94%B6%E5%88%B0topic%E5%B0%B1%E6%9C%83%E9%A6%AC%E4%B8%8A%E5%9F%B7%E8%A1%8Ccallback%EF%BC%8C%E8%80%8Cc%2B%2B%E7%89%88%E7%9A%84%E5%89%87%E8%A6%81%E4%B8%80%E7%9B%B4%E5%88%B0%E5%91%BC%E5%8F%ABros%3A%3Aspin()%E4%BB%A5%E5%BE%8C%E6%89%8D%E6%9C%83%E5%9F%B7%E8%A1%8Ccallback%EF%BC%8C%E5%85%A9%E6%94%AFAPI%E7%9A%84%E5%85%B1%E9%80%9A%E9%BB%9E%E6%98%AF%E6%9C%83%E8%AE%93%E7%A8%8B%E5%BC%8F%E5%81%9C%E5%9C%A8%E9%82%A3%E8%A1%8C%E4%B8%8D%E7%B9%BC%E7%BA%8C%E5%BE%80%E4%B8%8B%E5%AF%A6%E4%BD%9C%E6%88%96%E7%B5%82%E6%AD%A2%E3%80%82) 官方的解釋是 :::success The final addition, rospy.spin() simply keeps your node from exiting until the node has been shutdown. Unlike roscpp, rospy.spin() does not affect the subscriber callback functions, as those have their own threads. ::: 也就是 rospy 的 spin 只有 block 的功能而已,所以如果你本身就有 ```while(rospy.ok())``` 迴圈應該會沒差,因為 ```spin()``` 就是這個東東的簡單寫法罷了。 2. 最低調整到 1 Hz ... ? 我們可以將問題延伸至如果 callback 很耗時怎麼辦? 先討論 callback 應該長怎樣 :::info callback 太耗時怎麼辦? 這個在很多時候都會遇到,像是處理光達的資訊,我們會想要在 callback 的時候將收到的訊息轉換成對機器人有用的數據後回傳 (甚至在 callback 中呼叫其他龐大的 function 更新新指令等等),但 callback 的本意並不是拿來做耗時的處理的,應該做的是: <font color="#f00">將獲得的資訊「轉移到」可自由存取的變數中</font>,這樣我們才能在零碎、想要的空閒時間進行數據、命令的更新,另外一個是你寫了一個非常耗時的 callback (e.g. 500 ms),如果你的控制迴圈也寫在同一個 script 那一定炸歪,因為控制需要一定的更新率。 ::: 所以 callback 應該長的類似是: ```python global_var = 0 # or a queue def callback(data): # main context variable global_var = data # or global_var.push(data) return if __name__ = "__main__": # ... # in main loop, where you land the complicated algorithm process(global_var) # ... ``` [roscpp 的 spinner 模式,可以好好看看](https://answers.ros.org/question/53055/ros-callbacks-threads-and-spinning/) 接著討論你問的問題,process 還是很耗時,還是會卡住 main loop 阿? **<font color="#f00">沒錯</font>**,~~所以要精簡 R~~ 開玩笑的,但有一個觀念是如果處理要花 2 秒,就真的要花 2 秒,不過我們可以決定這兩秒會不會影響到其他應該執行的程序,可以簡要成下面兩種方式。 - 透過 async (coroutine, 協程) [參考 - ros 請求網頁使用 async 的方法](https://answers.ros.org/question/362598/asyncawait-in-subscriber-callback/) - 透過 multiple threading (線程),這個可以直接搜尋 python multiple threading 應該會有很多範例 threading 中你要做的是: <font color="#f00">創建一個迴圈在新的thread (不是 main thread) 中,當數值改變時就執行 process,並將新的數據賦值到全域變數中</font>(當然有另一種方法是收到新訊息就新增一個 process 的 threading,跑完之後就結束,但這種方法會消耗資源,詳情要去看 thread 的 context switching 原理) 但異步 (async) 則是用單個 thread 和 一些特殊的寫法來控制某個程式碼段現在是等待 (await) 還是執行(或說恢復, resume),上網查 async 應該各大語言都會有,這種寫法適用於 callback 是因為 io 造成的耗時 (e.g. 你請求一個網站來獲得機器人的位置,網站要 2 秒後才會回覆你,這樣 callback 的執行時間可能是 2.0x秒,適用 async) ## 小結 這兩者的差別是 multiple threading 是==透過開啟新的 thread (新增資源,代價是 thread 之間會來回切換,context switching,也是需要消耗資源的[硬體限制] )== 的方式來抵銷原本會衝突的計算資源,將耗時長的部分分離出來,適用 function 是實際上進行大量運算的情況。 async 更適合解決因為其他設備的反應慢造的 blocking 現象,例如網路上的傳輸、資料庫的寫入等等 (這也是為什麼會將資料庫的讀寫分離,因為讀比寫久...),將原本沒計算而是等待的資源用來做其他事情,當等待的事件回傳結束事件(或錯誤事件後)再 resume 處理回傳值。 (這部分可以參考 javascript 在網頁上的處理方式,因為瀏覽器特性,網頁程式大量使用 async 的方式改進請求伺服器而產生延遲的資源浪費) 3. subscriber 有暫存區嗎? - 連結 [cpp example](https://blog.csdn.net/A_L_A_N/article/details/115064896?app_version=5.7.2&code=app_1562916241&csdn_share_tail=%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22article%22%2C%22rId%22%3A%22115064896%22%2C%22source%22%3A%22unlogin%22%7D&uLinkId=usr1mkqgl919blen&utm_source=app) [知乎上的解釋](https://zhuanlan.zhihu.com/p/423316470) - 小結 簡單來說,有的,[ROS 文件](https://docs.ros.org/en/electric/api/rospy/html/rospy.topics.Subscriber-class.html)有講到當你創建 subscriber 時,有一個 arg 叫做 queue_size 就是決定你可以緩存幾個訊息,不過如果設置很大就要考慮的資料的實時性和正確性!