戰(zhàn):rostopic工具與msg消息類型詳解)
1. 前言從一次小車控制失靈說(shuō)起做機(jī)器人開發(fā)繞不開ROS而ROS里最基礎(chǔ)也最常用的通信機(jī)制就是話題Topic。我見(jiàn)過(guò)不少初學(xué)者剛裝好ROS就急著跑launch文件對(duì)著終端里嘩嘩滾動(dòng)的日志一臉茫然根本不知道底層發(fā)生了什么。直到有一次我在調(diào)試一個(gè)兩輪差速小車時(shí)方向盤沒(méi)反應(yīng)電機(jī)紋絲不動(dòng)查了半天才發(fā)現(xiàn)是話題沒(méi)對(duì)上——發(fā)布端在發(fā)/cmd_vel訂閱端卻盯著/cmd_vel_mux/input/teleop等數(shù)據(jù)。那一刻我意識(shí)到不懂rostopic工具和msg消息結(jié)構(gòu)做ROS開發(fā)就像閉著眼開車。這篇內(nèi)容面向所有準(zhǔn)備入門或正在入門ROS的開發(fā)者也適合那些已經(jīng)能跑通demo但總感覺(jué)“原理不清楚”的朋友。我會(huì)完整拆解ROS話題通信的核心概念、rostopic工具的實(shí)際用法、msg消息類型的定義要點(diǎn)以及我用真實(shí)項(xiàng)目驗(yàn)證過(guò)的常見(jiàn)問(wèn)題排查思路。主題就是ROS里的topic話題、rostopic工具、msg消息三個(gè)關(guān)鍵詞爭(zhēng)取用最直白的話把這條通信鏈路講透。給新手先打個(gè)底R(shí)OS節(jié)點(diǎn)之間的數(shù)據(jù)交換核心方式之一就是話題通信。一個(gè)節(jié)點(diǎn)發(fā)布消息另一個(gè)節(jié)點(diǎn)訂閱消息雙方通過(guò)話題名和消息類型建立連接話題本身不存數(shù)據(jù)它只負(fù)責(zé)轉(zhuǎn)交。rostopic就是那個(gè)能讓你“看見(jiàn)”這條鏈路的命令行工具。下面我按自己的理解從原理到實(shí)操?gòu)目垂ぞ叩綄憁sg一步步展開。2. 話題通信機(jī)制為什么ROS不直接用函數(shù)調(diào)用2.1 分布式架構(gòu)下的通信需求ROS節(jié)點(diǎn)本身就是獨(dú)立進(jìn)程一個(gè)節(jié)點(diǎn)是一個(gè)可執(zhí)行文件運(yùn)行時(shí)各自獨(dú)立互不干擾。既然進(jìn)程獨(dú)立就不能像寫單體程序那樣直接函數(shù)調(diào)用你需要一種進(jìn)程間通信的手段。ROS提供了兩種主流方式話題和服務(wù)。服務(wù)是請(qǐng)求-應(yīng)答模式一問(wèn)一答適合同步調(diào)用話題則是發(fā)布-訂閱模式發(fā)布者持續(xù)往外發(fā)訂閱者持續(xù)往里收兩者完全解耦適合傳感器數(shù)據(jù)、控制指令這類連續(xù)數(shù)據(jù)流。剛開始我不理解為什么ROS要搞兩套通信方式后來(lái)對(duì)比過(guò)真實(shí)場(chǎng)景才明白拿激光雷達(dá)來(lái)說(shuō)它每秒鐘產(chǎn)生幾十幀掃描數(shù)據(jù)如果每個(gè)需要使用這些數(shù)據(jù)的模塊都用服務(wù)去請(qǐng)求那雷達(dá)節(jié)點(diǎn)會(huì)被請(qǐng)求阻塞數(shù)據(jù)根本來(lái)不及向外發(fā)。用話題就輕松多了發(fā)布者只管發(fā)訂閱者按自己的頻率去收誰(shuí)也不會(huì)拖累誰(shuí)。2.2 話題消息的發(fā)布-訂閱模型話題通信最典型的場(chǎng)景就是傳感器數(shù)據(jù)發(fā)布和算法節(jié)點(diǎn)訂閱。發(fā)布端通過(guò)advertise聲明“我要發(fā)某類消息到某個(gè)話題”訂閱端通過(guò)subscribe聲明“我要從某個(gè)話題收某類消息”。兩邊對(duì)接的關(guān)鍵是話題名一致、消息類型一致。如果只有話題名相同但類型不同ROS會(huì)自動(dòng)提示錯(cuò)誤不會(huì)幫你做任何類型轉(zhuǎn)換。維護(hù)一個(gè)話題通信網(wǎng)絡(luò)的核心角色叫Master節(jié)點(diǎn)早期版本或ROS 2的Discovery機(jī)制后期版本。在ROS 1里每個(gè)節(jié)點(diǎn)啟動(dòng)后先去Master注冊(cè)自己的話題、服務(wù)等信息發(fā)布者和訂閱者通過(guò)Master牽線搭橋完成匹配。匹配成功后數(shù)據(jù)流就不經(jīng)過(guò)Master直接在兩者之間點(diǎn)對(duì)點(diǎn)傳輸。這也是為什么你在終端看到rosrun啟動(dòng)的節(jié)點(diǎn)會(huì)自動(dòng)輸出一個(gè)URI這個(gè)URI就是它用來(lái)和Master打交道用的。2.3 話題、消息與服務(wù)的關(guān)系梳理很多教程把這些概念混在一起講搞得很亂。我建議直接記結(jié)論話題是數(shù)據(jù)流動(dòng)的通道消息是通道里流動(dòng)的數(shù)據(jù)格式服務(wù)則是另一套完全不同的同步交互機(jī)制。一個(gè)話題由話題名和消息類型唯一確定。消息類型可以理解成一種結(jié)構(gòu)體里面定義了字段的名字和類型。舉個(gè)例子我發(fā)一個(gè)速度指令到/cmd_vel消息類型是geometry_msgs/Twist里面包含linear線速度和angular角速度兩個(gè)三維向量分量。訂閱端只要拿到這個(gè)類型的數(shù)據(jù)就能解析出手把對(duì)應(yīng)字段控制電機(jī)轉(zhuǎn)速。背出這些定義之后你就能很快看懂別人寫的代碼——話題名決定了你監(jiān)聽(tīng)什么消息類型決定了你收到后能取出什么字段。注意ROS話題通信是異步的發(fā)布方不關(guān)心訂閱方是否存在。你單獨(dú)運(yùn)行一個(gè)發(fā)布程序哪怕完全沒(méi)有任何訂閱者消息照樣會(huì)往話題里發(fā)只是沒(méi)有接收方而已。這在調(diào)試時(shí)常常讓人困惑“為什么我的話題沒(méi)數(shù)據(jù)輸出”結(jié)果檢查發(fā)現(xiàn)話題確實(shí)在發(fā)只是沒(méi)人訂閱。3. rostopic工具實(shí)用指南終端里摸清話題的底細(xì)3.1 常用命令速查與場(chǎng)景對(duì)照rostopic工具是排查話題問(wèn)題最直接的手段。我平時(shí)調(diào)試新節(jié)點(diǎn)第一步永遠(yuǎn)是查看當(dāng)前系統(tǒng)里到底有哪些話題在活動(dòng)。先跑rostopic list看看全局再跑rostopic info 話題名看看某個(gè)話題的具體信息如果想知道數(shù)據(jù)內(nèi)容就用rostopic echo這個(gè)命令會(huì)持續(xù)打印消息內(nèi)容。我用一個(gè)表格整理常用rostopic子命令和對(duì)應(yīng)場(chǎng)景方便你快速索引命令功能典型使用場(chǎng)景rostopic list列出當(dāng)前所有活躍話題查看節(jié)點(diǎn)啟動(dòng)后到底有沒(méi)有創(chuàng)建預(yù)期話題rostopic info查看話題詳情類型、發(fā)布者和訂閱者數(shù)量確認(rèn)兩端是否已建立連接排查消息類型是否匹配rostopic echo打印話題里的實(shí)際消息內(nèi)容查看數(shù)據(jù)數(shù)值變化是否正常rostopic hz統(tǒng)計(jì)話題發(fā)布頻率驗(yàn)證發(fā)布頻率是否符合設(shè)計(jì)預(yù)期rostopic bw統(tǒng)計(jì)話題帶寬占用排查數(shù)據(jù)量是否異常偏大rostopic type輸出話題的消息類型快速獲取消息類型全名rostopic pub手動(dòng)向話題發(fā)布一條消息調(diào)試接收端程序免去等待真實(shí)發(fā)布者啟動(dòng)3.2 親手實(shí)戰(zhàn)用rostopic觀察機(jī)器人速度話題我們用一個(gè)真實(shí)存在的場(chǎng)景來(lái)演示。我這邊有個(gè)TurtleBot相關(guān)的基礎(chǔ)模擬環(huán)境打開終端啟動(dòng)模擬器后我執(zhí)行$ rostopic list /rosout /rosout_agg /turtle1/cmd_vel /turtle1/pose /turtle1/teleop_relative /turtle1/velocity_relative/turtle1/cmd_vel顯然是速度指令話題/turtle1/pose是位姿話題。此時(shí)我輸入rostopic info /turtle1/cmd_vel輸出會(huì)告訴我消息類型是geometry_msgs/Twist發(fā)布者有一個(gè)訂閱者數(shù)量取決于當(dāng)前有哪些節(jié)點(diǎn)在訂閱。然后我用rostopic echo /turtle1/pose就能看到類似這樣的連續(xù)輸出x: 5.544444561 y: 5.544444561 theta: 0.0000 linear_velocity: 2.0000 angular_velocity: 0.0000這個(gè)數(shù)據(jù)說(shuō)明了當(dāng)前的坐標(biāo)、朝向和速度信息。如果你在調(diào)試時(shí)數(shù)據(jù)紋絲不動(dòng)說(shuō)明沒(méi)有新的發(fā)布數(shù)據(jù)到達(dá)要么發(fā)布端卡住了要么話題名沒(méi)對(duì)上。3.3 實(shí)操心得rostopic echo最常見(jiàn)的坑用rostopic echo時(shí)要小心消息量。有些話題頻率很高比如IMU數(shù)據(jù)或點(diǎn)云數(shù)據(jù)一旦echo起來(lái)屏幕刷新速度極快甚至影響系統(tǒng)性能。我的做法是先用rostopic hz看發(fā)布頻率如果太高就只echo一次或者直接看rostopic hz的統(tǒng)計(jì)結(jié)果不要糾結(jié)具體數(shù)據(jù)值。實(shí)踐中我踩過(guò)另一個(gè)坑有時(shí)候rostopic list能看到話題但rostopic echo卻沒(méi)有任何輸出。這種情況通常是因?yàn)樵掝}有發(fā)布者但發(fā)布者還沒(méi)開始頻率發(fā)送或者消息頻率太低、間隔太久。你先確認(rèn)rostopic hz的輸出如果連它都顯示no new messages那就是真的沒(méi)有數(shù)據(jù)重點(diǎn)找發(fā)布端的bug如果hz正常但echo不輸出多半是終端緩沖區(qū)的問(wèn)題重新跑一個(gè)干凈的終端就正常了。提示rostopic pub配合-r參數(shù)可以指定發(fā)布頻率用于測(cè)試非常方便。比如我要模擬一個(gè)速度發(fā)布器可以執(zhí)行rostopic pub -r 10 /cmd_vel geometry_msgs/Twist {linear: {x: 1.0}, angular: {z: 0.5}}這句話表示以10Hz的頻率向/cmd_vel發(fā)布線速度1.0、角速度0.5的Twist消息。注意消息中間不能有多余空格否則bash會(huì)解析錯(cuò)誤。4. msg消息類型結(jié)構(gòu)從標(biāo)準(zhǔn)類型到自定義消息4.1 ROS標(biāo)準(zhǔn)消息類型一覽與選擇邏輯消息類型是ROS通信的“詞匯表”。標(biāo)準(zhǔn)消息類型由不同的功能包組織比如geometry_msgs負(fù)責(zé)幾何相關(guān)sensor_msgs負(fù)責(zé)傳感器數(shù)據(jù)std_msgs負(fù)責(zé)基礎(chǔ)數(shù)據(jù)類型。我在決定用哪種消息類型時(shí)會(huì)先問(wèn)自己一個(gè)問(wèn)題我要傳的數(shù)據(jù)本質(zhì)是什么是速度就用Twist是位姿就用Pose是圖像就用Image是文本就用String。初學(xué)者很容易一股腦用std_msgs/String傳一切其實(shí)不推薦。比如你要傳機(jī)器人位置用String拼字符串是能運(yùn)行但接收端要自己拆分字符串不僅麻煩而且容易出錯(cuò)。如果你用geometry_msgs/Pose里面直接就有x、y、z、朝向四元數(shù)誰(shuí)都看得懂還自動(dòng)配套了坐標(biāo)系消息頭。這就是選擇標(biāo)準(zhǔn)類型的意義語(yǔ)義清晰和現(xiàn)有工具鏈無(wú)縫對(duì)接。4.2 自定義msg文件編寫方法與package.xml配置當(dāng)標(biāo)準(zhǔn)消息類型不夠用的時(shí)候就需要自定義msg文件。我的經(jīng)驗(yàn)是三步走在功能包目錄下創(chuàng)建msg文件夾新建一個(gè).msg文件然后修改CMakeLists.txt和package.xml。假設(shè)我要定義一個(gè)自定義消息PersonInfo.msg內(nèi)容包含姓名、年齡和分?jǐn)?shù)string name uint8 age float32 scoremsg文件語(yǔ)法很直觀每行格式是“類型 字段名”。類型支持標(biāo)準(zhǔn)ROS基礎(chǔ)類型例如int32、float64、string也支持其他自定義消息類型但需要在CMakeLists里聲明依賴。接下來(lái)修改CMakeLists.txt找到原來(lái)注釋掉的add_message_files塊取消注釋并填入你的msg文件名add_message_files( FILES PersonInfo.msg )找到generate_messages塊確認(rèn)填入依賴包名generate_messages( DEPENDENCIES std_msgs )package.xml里要加這么兩行聲明消息生成依賴build_dependmessage_generation/build_depend exec_dependmessage_runtime/exec_depend然后回到工作空間根目錄執(zhí)行catkin_make或catkin build生成的消息頭文件會(huì)出現(xiàn)在devel/include文件夾里對(duì)應(yīng)的編程語(yǔ)言綁定代碼也會(huì)自動(dòng)生成。4.3 常見(jiàn)錯(cuò)誤解決catkin_make找不到自定義消息新手編譯自定義msg時(shí)遇到最多的問(wèn)題就是在其他包里include這個(gè)自定義消息頭文件結(jié)果編譯時(shí)報(bào)錯(cuò)找不到頭文件。這個(gè)問(wèn)題的根源是包間依賴沒(méi)有聲明好編譯器不知道去哪找這個(gè)頭文件。解決思路是在你要使用自定義消息的包的CMakeLists.txt里find_package中加上那個(gè)自定義包的名字同時(shí)package.xml里增加depend標(biāo)簽。比如說(shuō)我在包/demo_pkg里定義了一個(gè)PersonInfo.msg同時(shí)導(dǎo)出了一個(gè)庫(kù)demo_msgs另一個(gè)包要用它則那個(gè)包必須find_package(catkin REQUIRED COMPONENTS roscpp std_msgs demo_msgs )在代碼里include時(shí)要注意路徑C里應(yīng)該是這樣的#include demo_msgs/PersonInfo.h一旦編譯錯(cuò)誤提示找不到這個(gè)頭文件九成是上述依賴沒(méi)配置好或者catkin_make后忘了重新source環(huán)境變量文件。4.4 消息嵌套與復(fù)雜消息結(jié)構(gòu)經(jīng)驗(yàn)談自定義消息不一定只是基礎(chǔ)類型的堆疊ROS允許嵌套其他消息類型。比如我定義一個(gè)結(jié)構(gòu)Header header geometry_msgs/Pose pose float64 confidenceHeader是一個(gè)標(biāo)準(zhǔn)消息頭包含時(shí)間戳和坐標(biāo)系ID。加上之后接收端能拿到數(shù)據(jù)產(chǎn)生的時(shí)間與坐標(biāo)系信息這在多傳感器融合中非常關(guān)鍵。特別是在做SLAM或?qū)Ш綍r(shí)位姿消息基本必須帶Header不然拿到數(shù)據(jù)都不知道是什么時(shí)刻、在哪個(gè)坐標(biāo)系下測(cè)的。嵌套消息的編譯配置和普通自定義msg沒(méi)有本質(zhì)區(qū)別只是generate_messages(DEPENDENCIES ...)里的依賴要加上geometry_msgs。寫msg文件時(shí)每一行依然遵循“類型空格字段名”當(dāng)類型里包含斜杠時(shí)說(shuō)明是跨包的消息類型不能省略包名前綴。5. COM口速率參數(shù)換算Windows串口通信中的經(jīng)典困惑這個(gè)部分看起來(lái)和ROS無(wú)關(guān)但它是我在實(shí)際項(xiàng)目中頻繁遇到的問(wèn)題當(dāng)你在ROS中通過(guò)串口控制下位機(jī)單片機(jī)或電機(jī)驅(qū)動(dòng)板時(shí)串口的波特率設(shè)置必須和底層固件完全一致否則數(shù)據(jù)包解碼必然出錯(cuò)。5.1 波特率本質(zhì)每秒傳輸?shù)姆?hào)數(shù)波特率表示每秒傳輸?shù)男盘?hào)碼元數(shù)單位是baud。但很多時(shí)候我們直接把它等同于比特率這在二進(jìn)制調(diào)制下確實(shí)一致。比如我設(shè)置波特率115200意味著每秒通過(guò)串口傳輸115200個(gè)二進(jìn)制位。換算成字節(jié)大致是115200除以10起始位數(shù)據(jù)位8停止位1有校驗(yàn)位則為11約11520字節(jié)每秒。這個(gè)帶寬對(duì)普通傳感器數(shù)據(jù)足夠了。5.2 串口參數(shù)換算公式與實(shí)操示例拿一個(gè)我常用的9軸IMU模塊為例它輸出的數(shù)據(jù)幀結(jié)構(gòu)為幀頭2字節(jié)數(shù)據(jù)26字節(jié)校驗(yàn)位2字節(jié)總共30字節(jié)。我希望IMU以50Hz頻率向ROS發(fā)送數(shù)據(jù)那么妙算最低需要多少波特率呢50Hz乘以30字節(jié)等于1500字節(jié)每秒。按常規(guī)串口10位編碼方式比特率為1500乘10等于15000bps即至少需要15000波特率才能不丟數(shù)據(jù)。實(shí)際上為了穩(wěn)定我會(huì)選用更高的檔位比如38400或57600。這個(gè)換算過(guò)程公式是波特率 數(shù)據(jù)字節(jié)每秒 × 每個(gè)字節(jié)的位數(shù)每個(gè)字節(jié)的位數(shù)通常是101個(gè)起始位 8個(gè)數(shù)據(jù)位 1個(gè)停止位如果有校驗(yàn)位則加1。實(shí)際選型時(shí)留30%到50%的余量確保系統(tǒng)在峰值數(shù)據(jù)量時(shí)不會(huì)因緩沖區(qū)溢出而丟幀。5.3 串口調(diào)試經(jīng)驗(yàn)ros::serial與自定義驅(qū)動(dòng)節(jié)點(diǎn)在ROS中操作串口的方案有現(xiàn)成的serial功能包。我一般這樣初始化serial::Serial ser; ser.setPort(/dev/ttyUSB0); ser.setBaudrate(115200); serial::Timeout to serial::Timeout::simpleTimeout(100); ser.setTimeout(to); ser.open();open成功后通過(guò)ser.read()讀取字節(jié)流再按自定義協(xié)議解析出IMU加速度、角速度字段最后封裝成sensor_msgs::Imu消息發(fā)布到話題上。這一步的處理流程是先讀取原始字節(jié)解析出一個(gè)完整的幀再把幀里物理量換算成標(biāo)準(zhǔn)單位最后填充消息字段、打上時(shí)間戳并publish。遇到串口數(shù)據(jù)亂碼時(shí)不要急著看代碼邏輯先檢查幾個(gè)固定問(wèn)題波特率是否一致、串口設(shè)備號(hào)是否選對(duì)、數(shù)據(jù)幀長(zhǎng)度是否和代碼假設(shè)一致。我有一次花了兩個(gè)小時(shí)排查最后發(fā)現(xiàn)只是同時(shí)插了兩個(gè)USB轉(zhuǎn)串口適配器程序讀取的是錯(cuò)誤的/dev/ttyUSB1而不是/dev/ttyUSB0。提示在Linux下查看串口設(shè)備可以執(zhí)行l(wèi)s -l /dev/ttyUSB*或dmesg | grep ttyUSB確認(rèn)當(dāng)前插入的適配器被分配到了哪個(gè)設(shè)備節(jié)點(diǎn)。6. 典型調(diào)試場(chǎng)景復(fù)盤從話題數(shù)據(jù)流到節(jié)點(diǎn)運(yùn)行6.1 場(chǎng)景一明明發(fā)了消息訂閱端卻收不到這個(gè)場(chǎng)景我遇到過(guò)很多次。寫了一個(gè)發(fā)布/cmd_vel的遙控節(jié)點(diǎn)啟動(dòng)后控制小車但小車沒(méi)反應(yīng)。使用rostopic echo /cmd_vel查看控制指令確實(shí)在發(fā)那問(wèn)題就出在訂閱端。使用rostopic info /cmd_vel查看訂閱者數(shù)量結(jié)果是Subscriber: None說(shuō)明小車控制節(jié)點(diǎn)壓根沒(méi)有訂閱這個(gè)話題。再看小車控制節(jié)點(diǎn)的launch文件發(fā)現(xiàn)它訂閱的是/mobile_base/cmd_vel名稱都不一樣自然不會(huì)通。ROS的話題名匹配是嚴(yán)格的字符串精確匹配不存在模糊匹配機(jī)制所以“名稱一致”是最基礎(chǔ)的前提。這種問(wèn)題用rostopic工具一分鐘就能定位到很多新手卻喜歡反復(fù)改代碼重新編譯效率很低。6.2 場(chǎng)景二頻率異常波動(dòng)與發(fā)布節(jié)奏影響還有一種典型情況rostopic hz /scan顯示的激光雷達(dá)數(shù)據(jù)頻率忽高忽低從9Hz到11Hz來(lái)回跳。打開發(fā)布端日志發(fā)現(xiàn)是主機(jī)CPU占用過(guò)高導(dǎo)致發(fā)布線程被系統(tǒng)調(diào)度延誤。此時(shí)你需要優(yōu)化線程優(yōu)先級(jí)或降低其他節(jié)點(diǎn)的負(fù)荷。ROS話題通信的實(shí)時(shí)性受系統(tǒng)調(diào)度影響明顯實(shí)時(shí)性要求高的場(chǎng)景需要額外配置實(shí)時(shí)內(nèi)核或使用實(shí)時(shí)線程機(jī)制。從發(fā)布節(jié)奏角度說(shuō)如果你的發(fā)布者代碼里有一個(gè)耗時(shí)的循環(huán)比如發(fā)送數(shù)據(jù)前先做大量圖像處理后處理那么實(shí)際發(fā)布頻率會(huì)被拉的遠(yuǎn)低于設(shè)計(jì)值。建議單獨(dú)開辟一個(gè)發(fā)送線程專門處理數(shù)據(jù)發(fā)送不參與耗時(shí)計(jì)算。6.3 場(chǎng)景三消息類型匹配錯(cuò)誤引發(fā)的暗坑當(dāng)兩個(gè)節(jié)點(diǎn)使用同一話題名但消息類型不一致時(shí)ROS終端通常會(huì)打印錯(cuò)誤消息提示類型沖突。但有一種隱蔽的出錯(cuò)方式數(shù)字類型被錯(cuò)誤選擇導(dǎo)致數(shù)值范圍不夠。例如我用int8存儲(chǔ)一個(gè)角度值0到180超過(guò)了int8能表達(dá)的范圍-128到127消息被截?cái)嗷蛞绯鼋邮斩四玫絹y碼。遇到這類問(wèn)題先用rostopic type 話題名確認(rèn)類型再對(duì)比代碼里賦值時(shí)變量的真實(shí)類型。工具不會(huì)騙你它顯示什么類型就是實(shí)際傳輸用的類型。寫代碼時(shí)盡量用float32或float64除非你明確知道數(shù)據(jù)范圍很小且無(wú)需小數(shù)。7. 常見(jiàn)問(wèn)題速查表一表解決大部分話題調(diào)試?yán)_我把這些年經(jīng)常碰到的話題、消息、rostopic工具相關(guān)問(wèn)題整理成了速查表你可以直接把它貼在工位旁邊排查問(wèn)題時(shí)對(duì)照著看現(xiàn)象可能原因排查方法rostopic list看不到預(yù)期話題發(fā)布節(jié)點(diǎn)沒(méi)啟動(dòng)或者崩潰檢查節(jié)點(diǎn)進(jìn)程是否存活源碼中advertise是否真正被執(zhí)行話題存在但無(wú)數(shù)據(jù)發(fā)布者發(fā)送頻率太低或發(fā)送邏輯處于條件分支中使用rostopic hz確認(rèn)實(shí)際頻率在代碼中臨時(shí)加日志輸出消息類型不匹配發(fā)布端和訂閱端使用了不同msg類型rostopic info 話題名查看兩端信息比對(duì)完整類型全名echo數(shù)據(jù)頻繁刷新卡屏發(fā)布頻率高且消息量大改用rostopic hz或rostopic bw統(tǒng)計(jì)信息避免實(shí)時(shí)echo大流量話題自定義msg編譯失敗缺少依賴聲明或msg文件夾路徑不對(duì)檢查msg文件位置、CMakeLists配置、package.xml聲明串口亂碼波特率不匹配、數(shù)據(jù)位/停止位設(shè)置不對(duì)確認(rèn)下位機(jī)參數(shù)先用串口助手發(fā)固定字節(jié)做回環(huán)測(cè)試訂閱回調(diào)不執(zhí)行話題沒(méi)有觸發(fā)消息或回調(diào)函數(shù)本身有阻塞操作確認(rèn)消息有發(fā)布回調(diào)里輸出日志避免回調(diào)內(nèi)做耗時(shí)大計(jì)算發(fā)布頻率遠(yuǎn)低于預(yù)期發(fā)布循環(huán)里有耗時(shí)操作將發(fā)布邏輯從重計(jì)算中分離單獨(dú)線程發(fā)送注意以上排查手段都基于ROS 1的roscore環(huán)境。如果你用的是ROS 2很多命令格式有變化比如rostopic list變成了ros2 topic listrostopic echo變成了ros2 topic echo但思路完全一致——先看話題列表再看內(nèi)容再查頻率。8. 一步到位從一個(gè)msg文件到完整的發(fā)布訂閱Demo最后我還是想帶你動(dòng)手寫一個(gè)完整的示例把之前講的東西串起來(lái)。這個(gè)例子我常用來(lái)帶新人入門十分鐘能跑通然后你就理解了話題通信全流程。先建一個(gè)功能包c(diǎn)atkin_create_pkg demo_ros roscpp std_msgs geometry_msgs自定義消息放到demo_ros/msg/PersonInfo.msgstring name uint8 age float64 score然后修改CMakeLists.txt加入add_message_files(FILES PersonInfo.msg) generate_messages(DEPENDENCIES std_msgs)新建src/publisher.cpp#include ros/ros.h #include demo_ros/PersonInfo.h int main(int argc, char** argv) { ros::init(argc, argv, person_info_publisher); ros::NodeHandle nh; ros::Publisher pub nh.advertisedemo_ros::PersonInfo(/person_info, 10); ros::Rate rate(1.0); int count 0; while (ros::ok()) { demo_ros::PersonInfo msg; msg.name developer; msg.age 25 count; msg.score 88.5; pub.publish(msg); count; rate.sleep(); } return 0; }新建src/subscriber.cpp#include ros/ros.h #include demo_ros/PersonInfo.h void personInfoCallback(const demo_ros::PersonInfo::ConstPtr msg) { ROS_INFO(Received name: [%s], age: [%d], score: [%.2f], msg-name.c_str(), msg-age, msg-score); } int main(int argc, char** argv) { ros::init(argc, argv, person_info_subscriber); ros::NodeHandle nh; ros::Subscriber sub nh.subscribe(/person_info, 10, personInfoCallback); ros::spin(); return 0; }修改CMakeLists.txt可執(zhí)行文件部分add_executable(person_info_publisher src/publisher.cpp) target_link_libraries(person_info_publisher ${catkin_LIBRARIES}) add_dependencies(person_info_publisher demo_ros_generate_messages_cpp) add_executable(person_info_subscriber src/subscriber.cpp) target_link_libraries(person_info_subscriber ${catkin_LIBRARIES}) add_dependencies(person_info_subscriber demo_ros_generate_messages_cpp)然后編譯、source環(huán)境變量先運(yùn)行roscore再運(yùn)行發(fā)布和訂閱兩個(gè)節(jié)點(diǎn)。用rostopic echo /person_info你就能看到消息內(nèi)容用rostopic info /person_info能看到類型和訂閱者數(shù)量。此時(shí)再回頭看整個(gè)通信鏈路會(huì)發(fā)現(xiàn)思路清晰很多。提示add_dependencies這一行是新手最容易漏掉的。沒(méi)有它自定義消息的頭文件可能還沒(méi)生成就被編譯器拿過(guò)去include導(dǎo)致編譯失敗。專門記一下這個(gè)坑。我做ROS項(xiàng)目這些年最大的體會(huì)是話題通信看著簡(jiǎn)單真正熟練掌握需要你對(duì)工具使用足夠熟練。rostopic工具不是擺設(shè)在調(diào)試時(shí)能節(jié)省大量時(shí)間msg類型選對(duì)了代碼邏輯清晰團(tuán)隊(duì)協(xié)作也順暢。希望這篇內(nèi)容把你帶進(jìn)ROS的大門讓你在遇到問(wèn)題時(shí)不再無(wú)從下手而是打開終端用幾個(gè)命令就能鎖定方向。