器人科研實(shí)訓(xùn)平臺(tái)定制方案:ROS分層架構(gòu)與Docker部署實(shí)踐)
1. 從遙操到實(shí)訓(xùn)這套方案到底在解決誰(shuí)的痛點(diǎn)第一次接觸時(shí)空行者VR遙操機(jī)器人這個(gè)項(xiàng)目名的時(shí)候我腦子里冒出來(lái)的第一個(gè)念頭是又是一個(gè)把VR當(dāng)噱頭的演示項(xiàng)目。但真正把需求拆開(kāi)看——適配科研實(shí)訓(xùn)場(chǎng)景——才發(fā)現(xiàn)這里面的坑遠(yuǎn)比想象中深??蒲袑?shí)訓(xùn)和工業(yè)遙操作完全是兩碼事工業(yè)場(chǎng)景追求的是穩(wěn)定、低延遲、單一任務(wù)閉環(huán)而科研實(shí)訓(xùn)要的是可復(fù)現(xiàn)、可拆解、可二次開(kāi)發(fā)學(xué)生要能在上面改代碼、換算法、加傳感器還得保證不同基礎(chǔ)的人都能跑起來(lái)。這套定制方案的核心說(shuō)白了就是把一套原本面向演示的VR遙操作系統(tǒng)改造成一個(gè)教學(xué)級(jí)實(shí)驗(yàn)平臺(tái)。它要同時(shí)滿足三類人的需求剛?cè)腴T(mén)的學(xué)生能一鍵跑通看到機(jī)器人跟著手柄動(dòng)、做課題的研究生能替換IK解算、接入自己的控制算法、以及帶課的教師能快速部署到多臺(tái)機(jī)器、統(tǒng)一環(huán)境。這三類人的訴求經(jīng)常是打架的——入門(mén)者要簡(jiǎn)單研究者要開(kāi)放教師要可管理。方案的價(jià)值就在于用一套分層架構(gòu)把這三種需求隔離開(kāi)。關(guān)鍵詞里出現(xiàn)的VR、ROS、SDK、C、Python其實(shí)已經(jīng)勾勒出了整個(gè)技術(shù)棧的輪廓。VR負(fù)責(zé)交互層ROS負(fù)責(zé)通信與調(diào)度SDK負(fù)責(zé)硬件抽象C扛實(shí)時(shí)性要求高的部分Python負(fù)責(zé)快速迭代和算法驗(yàn)證。這個(gè)組合不是隨便選的后面我會(huì)詳細(xì)拆解為什么這么分工。適合讀這篇內(nèi)容的人應(yīng)該是正在做機(jī)器人實(shí)訓(xùn)平臺(tái)、VR遙操作研究或者需要把實(shí)驗(yàn)室設(shè)備改造成教學(xué)工具的同學(xué)和工程師。如果你只是想看看VR怎么控制機(jī)器人那這篇可能有點(diǎn)重但如果你要真正落地一套能上課、能做課題的系統(tǒng)這里面的取舍經(jīng)驗(yàn)應(yīng)該能幫你少走幾個(gè)月彎路。2. 為什么科研實(shí)訓(xùn)場(chǎng)景不能用工業(yè)遙操作那套直接搬2.1 工業(yè)遙操作和實(shí)訓(xùn)平臺(tái)的目標(biāo)函數(shù)根本不同工業(yè)遙操作系統(tǒng)的設(shè)計(jì)目標(biāo)非常明確在特定任務(wù)下把延遲壓到最低、把可靠性拉到最高。它通常針對(duì)固定型號(hào)的機(jī)械臂、固定的作業(yè)流程做深度優(yōu)化操作者經(jīng)過(guò)長(zhǎng)期訓(xùn)練人機(jī)之間的映射關(guān)系是固化的。你去看那些成熟的工業(yè)遙操作方案手柄推多少、機(jī)械臂走多少比例是寫(xiě)死的甚至連關(guān)節(jié)限位都做了硬約束操作者幾乎沒(méi)有自由發(fā)揮的空間??蒲袑?shí)訓(xùn)恰恰相反。學(xué)生需要看到如果我改了參數(shù)會(huì)發(fā)生什么需要能故意讓機(jī)械臂走到奇異位形去觀察現(xiàn)象需要把VR手柄的輸入映射到不同的坐標(biāo)系去對(duì)比效果。如果直接搬工業(yè)方案第一件事就是把所有可調(diào)參數(shù)鎖死那這套系統(tǒng)在教學(xué)上就廢了一半。我在實(shí)際改造中遇到過(guò)最典型的問(wèn)題原系統(tǒng)的IK解算被封裝在一個(gè)閉源SDK里學(xué)生想換成自己寫(xiě)的雅可比偽逆解根本找不到入口。這就是工業(yè)思維和教學(xué)思維的直接沖突。2.2 實(shí)訓(xùn)場(chǎng)景對(duì)可觀測(cè)性的要求遠(yuǎn)高于性能工業(yè)系統(tǒng)追求黑盒式的穩(wěn)定操作者不需要知道內(nèi)部發(fā)生了什么。但實(shí)訓(xùn)平臺(tái)必須把中間狀態(tài)暴露出來(lái)。舉個(gè)具體例子VR手柄的位姿數(shù)據(jù)從設(shè)備傳到機(jī)械臂末端中間要經(jīng)過(guò)坐標(biāo)變換、濾波、IK解算、關(guān)節(jié)限位裁剪、軌跡插補(bǔ)好幾個(gè)環(huán)節(jié)。工業(yè)方案里這些環(huán)節(jié)是串起來(lái)的黑盒而實(shí)訓(xùn)平臺(tái)需要把每一環(huán)的輸入輸出都做成可訂閱的ROS話題讓學(xué)生能用rostopic echo直接看到數(shù)據(jù)流。這就帶來(lái)一個(gè)架構(gòu)上的硬性要求整個(gè)數(shù)據(jù)鏈路必須基于ROS的話題/服務(wù)機(jī)制重新組織而不是把SDK內(nèi)部的私有通信藏著掖著。我在方案里做的第一件事就是把原系統(tǒng)里所有跨模塊的數(shù)據(jù)交互全部改成ROS話題哪怕這會(huì)帶來(lái)一點(diǎn)點(diǎn)額外的序列化開(kāi)銷。實(shí)測(cè)下來(lái)在局域網(wǎng)內(nèi)這個(gè)話題通信的延遲在2-5ms量級(jí)對(duì)于教學(xué)場(chǎng)景完全夠用但換來(lái)的是整個(gè)系統(tǒng)的完全可觀測(cè)。2.3 多用戶、多設(shè)備的部署復(fù)雜度被嚴(yán)重低估工業(yè)現(xiàn)場(chǎng)通常是一套系統(tǒng)配一臺(tái)設(shè)備部署一次用幾年。實(shí)訓(xùn)場(chǎng)景是幾十個(gè)學(xué)生、幾十臺(tái)機(jī)器每周可能都要重裝環(huán)境。這里面的坑包括不同電腦的顯卡驅(qū)動(dòng)版本不一致導(dǎo)致VR SDK初始化失敗、ROS版本和Ubuntu版本不匹配、Python環(huán)境里numpy版本沖突導(dǎo)致IK解算報(bào)錯(cuò)。我見(jiàn)過(guò)最離譜的一次一個(gè)實(shí)驗(yàn)室20臺(tái)機(jī)器里有7種不同的環(huán)境組合助教光配環(huán)境就花了兩天。所以這套定制方案里環(huán)境標(biāo)準(zhǔn)化是重中之重。我的做法是把整個(gè)軟件棧打包成Docker鏡像ROS、Python依賴、SDK運(yùn)行時(shí)全部固化進(jìn)去學(xué)生機(jī)器上只需要裝好顯卡驅(qū)動(dòng)和Docker一條命令拉起容器就能用。這個(gè)決策后面會(huì)詳細(xì)講因?yàn)樗苯佑绊懥薙DK的集成方式。3. 分層架構(gòu)拆解VR交互層、ROS調(diào)度層、硬件抽象層怎么切3.1 交互層VR手柄數(shù)據(jù)如何變成機(jī)器人能懂的指令VR交互層是整個(gè)系統(tǒng)的入口也是最容易被低估的部分。很多人以為VR遙操作就是讀手柄位姿發(fā)給機(jī)器人實(shí)際上從手柄到機(jī)器人指令之間有一堆必須處理的細(xì)節(jié)。首先是坐標(biāo)系問(wèn)題。VR設(shè)備通常有自己的世界坐標(biāo)系手柄位姿是相對(duì)于這個(gè)坐標(biāo)系的。而機(jī)械臂工作在ROS的base_link坐標(biāo)系下。這兩者之間的變換不是簡(jiǎn)單的平移旋轉(zhuǎn)還涉及到操作者站位、VR房間標(biāo)定、手柄握持姿態(tài)等因素。我的做法是在交互層里做一個(gè)標(biāo)定態(tài)啟動(dòng)時(shí)讓操作者把手柄放在一個(gè)已知位置系統(tǒng)記錄下這個(gè)位姿作為參考原點(diǎn)后續(xù)所有手柄運(yùn)動(dòng)都相對(duì)于這個(gè)原點(diǎn)做增量映射。這樣操作者不需要站在固定位置換個(gè)人用重新標(biāo)定一次就行。其次是數(shù)據(jù)頻率和濾波。主流VR設(shè)備的位姿輸出頻率在60-90Hz而機(jī)械臂的控制周期通常是100-1000Hz。直接把手柄數(shù)據(jù)透?jìng)鹘o機(jī)械臂會(huì)導(dǎo)致運(yùn)動(dòng)抖動(dòng)因?yàn)槭直旧碛性肼暋N以诮换蛹恿艘患?jí)低通濾波截止頻率設(shè)在10Hz左右實(shí)測(cè)能有效抑制手部微小抖動(dòng)同時(shí)不引入明顯延遲。濾波后的數(shù)據(jù)再通過(guò)ROS話題發(fā)布出去話題名統(tǒng)一用/vr/hand_pose消息類型用geometry_msgs/PoseStamped這樣任何節(jié)點(diǎn)都能訂閱。還有一個(gè)容易被忽略的點(diǎn)手柄的按鍵和扳機(jī)事件。教學(xué)場(chǎng)景里經(jīng)常需要用按鍵來(lái)切換控制模式比如位置控制/速度控制切換、觸發(fā)抓取、或者急停。這些事件不能和位姿數(shù)據(jù)混在一個(gè)話題里我單獨(dú)開(kāi)了/vr/button_event話題用自定義消息類型把按鍵ID和事件類型按下/松開(kāi)打包發(fā)出去。這樣上層控制節(jié)點(diǎn)可以靈活綁定按鍵功能學(xué)生也能自己改。3.2 調(diào)度層ROS話題設(shè)計(jì)決定了系統(tǒng)的可擴(kuò)展性ROS調(diào)度層是整個(gè)系統(tǒng)的骨架它的設(shè)計(jì)質(zhì)量直接決定了這套平臺(tái)能不能被學(xué)生玩起來(lái)。我在設(shè)計(jì)話題結(jié)構(gòu)時(shí)遵循了一個(gè)原則每個(gè)可獨(dú)立替換的算法模塊都必須有清晰的輸入輸出話題邊界。具體來(lái)說(shuō)從VR手柄到機(jī)械臂關(guān)節(jié)指令中間至少經(jīng)過(guò)這幾個(gè)模塊手柄位姿接收、坐標(biāo)變換、IK解算、關(guān)節(jié)限位處理、軌跡插補(bǔ)、關(guān)節(jié)指令下發(fā)。每個(gè)模塊都是一個(gè)獨(dú)立的ROS節(jié)點(diǎn)節(jié)點(diǎn)之間通過(guò)話題連接。這樣做的好處是學(xué)生想替換IK解算只需要寫(xiě)一個(gè)新節(jié)點(diǎn)訂閱/vr/target_pose、發(fā)布/robot/joint_command把原來(lái)的IK節(jié)點(diǎn)停掉就行其他部分完全不用動(dòng)。話題的命名也做了規(guī)范。所有VR相關(guān)的用/vr/前綴機(jī)器人狀態(tài)相關(guān)的用/robot/前綴算法中間結(jié)果用/debug/前綴。這樣學(xué)生用rostopic list一看就知道每個(gè)話題屬于哪一層。消息類型盡量用ROS標(biāo)準(zhǔn)類型只有確實(shí)需要自定義的才自己定義減少學(xué)習(xí)成本。這里有個(gè)實(shí)操經(jīng)驗(yàn)ROS1的話題通信在數(shù)據(jù)量大時(shí)會(huì)有明顯延遲特別是圖像和點(diǎn)云。VR遙操作里如果要把VR頭顯的畫(huà)面回傳給操作者千萬(wàn)別用ROS話題傳圖像延遲能到幾百毫秒體驗(yàn)極差。我的做法是VR畫(huà)面直接在VR設(shè)備本地渲染ROS只傳控制指令和狀態(tài)反饋這樣控制回路的延遲能控制在10ms以內(nèi)。3.3 硬件抽象層SDK封裝成什么樣決定了換硬件的成本硬件抽象層是這套方案里最臟的部分因?yàn)椴煌瑥S商的機(jī)械臂、VR設(shè)備、傳感器都有自己的SDK接口風(fēng)格千差萬(wàn)別。如果直接把SDK的API暴露給上層那換一個(gè)硬件就要改一遍上層代碼這在教學(xué)場(chǎng)景里是不可接受的。我的做法是在SDK之上再包一層統(tǒng)一的ROS接口。以機(jī)械臂為例不管底層是哪個(gè)品牌的SDK上層只認(rèn)幾個(gè)標(biāo)準(zhǔn)話題/robot/joint_states當(dāng)前關(guān)節(jié)狀態(tài)、/robot/joint_command關(guān)節(jié)指令、/robot/end_effector_pose末端位姿。硬件抽象層負(fù)責(zé)把這些標(biāo)準(zhǔn)話題翻譯成具體SDK的調(diào)用。這樣換機(jī)械臂時(shí)只需要重寫(xiě)硬件抽象層這一個(gè)節(jié)點(diǎn)上層的IK、插補(bǔ)、VR交互全部不用動(dòng)。VR設(shè)備同理。不同VR SDK的初始化流程、數(shù)據(jù)獲取方式都不一樣但抽象層對(duì)外只暴露/vr/hand_pose和/vr/button_event兩個(gè)話題。我甚至在抽象層里做了設(shè)備自動(dòng)識(shí)別啟動(dòng)時(shí)根據(jù)連接的設(shè)備類型加載對(duì)應(yīng)的驅(qū)動(dòng)插件學(xué)生不需要關(guān)心底層用的是哪款VR設(shè)備。C和Python在這里的分工也很明確硬件抽象層和實(shí)時(shí)性要求高的模塊用C寫(xiě)因?yàn)橐苯诱{(diào)用SDK的C接口而且控制循環(huán)對(duì)性能敏感上層的算法驗(yàn)證、數(shù)據(jù)處理、可視化工具用Python寫(xiě)因?yàn)榈?、生態(tài)好。兩者之間通過(guò)ROS話題通信語(yǔ)言差異被完全隔離。4. 環(huán)境搭建的深水區(qū)從裸機(jī)到可復(fù)現(xiàn)鏡像的完整路徑4.1 系統(tǒng)版本選擇為什么我最終鎖定了Ubuntu 20.04 ROS Noetic環(huán)境搭建的第一步是選版本這一步選錯(cuò)后面全是坑。ROS的版本和Ubuntu版本是強(qiáng)綁定的Noetic對(duì)應(yīng)20.04Melodic對(duì)應(yīng)18.04再新的ROS2雖然好用但生態(tài)還沒(méi)完全跟上很多教學(xué)用的功能包還是ROS1的。我最終鎖定Ubuntu 20.04 ROS Noetic理由有三個(gè)。第一Noetic是ROS1的最后一個(gè)長(zhǎng)期支持版本支持到2025年對(duì)于教學(xué)平臺(tái)來(lái)說(shuō)生命周期夠長(zhǎng)。第二20.04的內(nèi)核版本對(duì)主流VR設(shè)備的驅(qū)動(dòng)支持比較成熟我測(cè)試過(guò)的幾款VR頭顯在20.04上都能正常識(shí)別。第三Python3是20.04的默認(rèn)Python而Noetic也是基于Python3的避免了Python2/3混用的歷史遺留問(wèn)題。這里有個(gè)細(xì)節(jié)要注意ROS Noetic的安裝如果用官方源在國(guó)內(nèi)網(wǎng)絡(luò)環(huán)境下可能會(huì)很慢。我一般會(huì)用國(guó)內(nèi)鏡像源替換具體做法是修改/etc/apt/sources.list.d/ros-latest.list里的地址。但要注意鏡像源的同步延遲有時(shí)候新版本的功能包鏡像上還沒(méi)有遇到這種情況臨時(shí)切回官方源就行。安裝完ROS之后rosdep的初始化也是個(gè)大坑。rosdep update經(jīng)常因?yàn)榫W(wǎng)絡(luò)問(wèn)題失敗我的經(jīng)驗(yàn)是多重試幾次或者配置代理注意這里說(shuō)的是網(wǎng)絡(luò)代理用于軟件包下載和前面提到的敏感內(nèi)容無(wú)關(guān)。如果實(shí)在不行可以手動(dòng)下載rosdep的索引文件放到本地具體路徑在~/.ros/rosdep/sources.cache。4.2 依賴管理Python環(huán)境和系統(tǒng)庫(kù)的沖突怎么解ROS Noetic自帶Python3但系統(tǒng)里可能還有conda或者其他Python環(huán)境這就容易出問(wèn)題。我踩過(guò)最典型的一個(gè)坑系統(tǒng)里裝了Anacondapython3命令指向的是conda的環(huán)境導(dǎo)致ROS的Python節(jié)點(diǎn)找不到rospy模塊。解決辦法是在.bashrc里把conda的初始化注釋掉或者用conda deactivate退出環(huán)境后再運(yùn)行ROS。另一個(gè)常見(jiàn)問(wèn)題是numpy版本沖突。IK解算里經(jīng)常要用numpy做矩陣運(yùn)算而ROS自帶的numpy版本可能和某些算法庫(kù)要求的版本不一致。我的做法是在系統(tǒng)層面用apt安裝numpy不用pip這樣版本和ROS保持一致。如果某個(gè)算法確實(shí)需要特定版本的numpy就把它放到獨(dú)立的虛擬環(huán)境里通過(guò)ROS的節(jié)點(diǎn)啟動(dòng)腳本切換Python解釋器。C這邊的依賴相對(duì)簡(jiǎn)單主要是Eigen矩陣運(yùn)算、urdf機(jī)器人模型解析、tf2坐標(biāo)變換這幾個(gè)。用apt安裝就行版本都是ROS配套的不會(huì)有沖突。唯一要注意的是如果自己編譯的庫(kù)和ROS的庫(kù)有同名符號(hào)鏈接時(shí)可能出問(wèn)題這種情況用LD_LIBRARY_PATH控制加載順序。4.3 Docker鏡像一次構(gòu)建到處運(yùn)行前面說(shuō)了環(huán)境標(biāo)準(zhǔn)化的重要性Docker是解決這個(gè)問(wèn)題的標(biāo)準(zhǔn)答案。我的做法是寫(xiě)一個(gè)Dockerfile把ROS、Python依賴、SDK運(yùn)行時(shí)、編譯好的工作空間全部打進(jìn)去。基礎(chǔ)鏡像用osrf/ros:noetic-desktop-full這個(gè)鏡像已經(jīng)包含了ROS的完整桌面環(huán)境。Dockerfile里幾個(gè)關(guān)鍵步驟先裝系統(tǒng)依賴apt再裝Python依賴pip然后拷貝工作空間源碼編譯最后設(shè)置entrypoint腳本。編譯這一步要注意ROS工作空間的編譯依賴環(huán)境變量source /opt/ros/noetic/setup.bash必須在catkin_make之前執(zhí)行。entrypoint腳本里要自動(dòng)source工作空間的devel/setup.bash這樣容器啟動(dòng)后ROS環(huán)境就是就緒的。VR設(shè)備的接入是Docker方案里最麻煩的部分。VR頭顯通常通過(guò)USB連接Docker容器要訪問(wèn)USB設(shè)備需要加--device參數(shù)或者用--privileged模式。我一般用--device/dev/bus/usb把整個(gè)USB總線映射進(jìn)去這樣VR設(shè)備插拔都能被容器識(shí)別。顯卡方面如果用NVIDIA顯卡做VR渲染需要裝nvidia-docker啟動(dòng)時(shí)加--gpus all。實(shí)測(cè)下來(lái)這套Docker方案在20臺(tái)機(jī)器上部署從零到能跑通VR遙操作平均每臺(tái)機(jī)器15分鐘其中大部分時(shí)間花在下載鏡像上。如果提前把鏡像導(dǎo)出成tar文件用U盤(pán)拷貝每臺(tái)機(jī)器5分鐘就能搞定。5. 核心算法模塊的定制改造IK、濾波與軌跡規(guī)劃5.1 IK解算為什么我放棄了SDK自帶的解算器原系統(tǒng)用的是機(jī)械臂SDK自帶的IK解算器優(yōu)點(diǎn)是穩(wěn)定、經(jīng)過(guò)廠商驗(yàn)證缺點(diǎn)是黑盒、不可調(diào)、不支持冗余自由度。在教學(xué)場(chǎng)景里學(xué)生需要能看到IK的中間過(guò)程比如雅可比矩陣、條件數(shù)、奇異值這些SDK都不暴露。我最終換成了自己實(shí)現(xiàn)的基于雅可比偽逆的IK解算器用C寫(xiě)依賴Eigen做矩陣運(yùn)算。核心邏輯是給定目標(biāo)末端位姿計(jì)算當(dāng)前位姿下的雅可比矩陣求偽逆得到關(guān)節(jié)速度積分一步迭代直到誤差收斂。這個(gè)算法本身不復(fù)雜但工程上有幾個(gè)坑要處理。第一個(gè)坑是奇異位形。當(dāng)機(jī)械臂接近奇異位形時(shí)雅可比矩陣條件數(shù)急劇增大偽逆解會(huì)給出巨大的關(guān)節(jié)速度。我的處理是加阻尼用阻尼最小二乘法DLS代替純偽逆阻尼系數(shù)根據(jù)條件數(shù)自適應(yīng)調(diào)整。條件數(shù)小的時(shí)候阻尼接近零退化成偽逆條件數(shù)大的時(shí)候阻尼增大犧牲一點(diǎn)精度換穩(wěn)定性。第二個(gè)坑是關(guān)節(jié)限位。迭代過(guò)程中關(guān)節(jié)角可能超出物理限位需要在每一步之后做裁剪。但簡(jiǎn)單裁剪會(huì)導(dǎo)致末端位姿跳變我的做法是把限位做成軟約束在目標(biāo)函數(shù)里加懲罰項(xiàng)讓解算器自己避開(kāi)限位。第三個(gè)坑是實(shí)時(shí)性。IK解算要在控制周期內(nèi)完成我實(shí)測(cè)下來(lái)6自由度機(jī)械臂的DLS-IK單次迭代在1ms以內(nèi)通常迭代5-10次收斂總耗時(shí)5-10ms。如果控制周期是10ms剛好夠用。如果機(jī)械臂自由度更多或者要跑更復(fù)雜的算法就得考慮用更高效的求解器或者降低控制頻率。Python這邊我也提供了一個(gè)IK的參考實(shí)現(xiàn)用numpy寫(xiě)的性能差一些但代碼更易讀適合學(xué)生理解算法原理。兩個(gè)版本通過(guò)ROS話題切換學(xué)生可以對(duì)比C和Python實(shí)現(xiàn)的差異。5.2 濾波與平滑手柄抖動(dòng)和機(jī)械臂振動(dòng)的抑制VR手柄的位姿數(shù)據(jù)噪聲主要來(lái)自兩方面光學(xué)追蹤的量化誤差和手部的生理抖動(dòng)。前者是高頻小幅噪聲后者是低頻大幅抖動(dòng)。我用的是二階低通濾波截止頻率10Hz對(duì)高頻噪聲衰減明顯對(duì)低頻抖動(dòng)也有一定抑制。但濾波會(huì)引入相位延遲截止頻率越低延遲越大。10Hz截止頻率下延遲大約在15-20ms量級(jí)。對(duì)于遙操作來(lái)說(shuō)這個(gè)延遲是可以接受的但如果做精細(xì)操作比如插孔操作者會(huì)感覺(jué)到跟手性變差。我的經(jīng)驗(yàn)是如果任務(wù)對(duì)精度要求高可以把截止頻率提到20Hz犧牲一點(diǎn)平滑性換響應(yīng)速度。機(jī)械臂這邊的振動(dòng)主要來(lái)自軌跡插補(bǔ)。如果直接把手柄位姿作為目標(biāo)點(diǎn)發(fā)給IK目標(biāo)點(diǎn)本身是跳變的解算出的關(guān)節(jié)角也會(huì)跳變。我在IK之前加了一級(jí)軌跡插補(bǔ)用五次多項(xiàng)式或者S型速度曲線做平滑。插補(bǔ)周期和控制周期一致每個(gè)周期更新一次目標(biāo)點(diǎn)這樣關(guān)節(jié)運(yùn)動(dòng)是連續(xù)的。還有一個(gè)細(xì)節(jié)手柄的抓取/釋放事件。抓取時(shí)機(jī)械臂應(yīng)該鎖定當(dāng)前位姿釋放時(shí)應(yīng)該解鎖。如果處理不好抓取瞬間機(jī)械臂會(huì)跳一下。我的做法是在抓取事件觸發(fā)時(shí)記錄當(dāng)前手柄位姿和機(jī)械臂末端位姿的偏移量后續(xù)手柄運(yùn)動(dòng)都加上這個(gè)偏移量這樣抓取瞬間機(jī)械臂不動(dòng)之后跟著手柄走。5.3 軌跡規(guī)劃從點(diǎn)到點(diǎn)的關(guān)節(jié)空間插補(bǔ)教學(xué)場(chǎng)景里經(jīng)常需要機(jī)械臂做點(diǎn)到點(diǎn)的運(yùn)動(dòng)比如從A點(diǎn)抓取放到B點(diǎn)。這種運(yùn)動(dòng)如果直接在笛卡爾空間做直線插補(bǔ)末端軌跡是直線但關(guān)節(jié)空間可能經(jīng)過(guò)奇異位形。如果直接在關(guān)節(jié)空間插補(bǔ)關(guān)節(jié)運(yùn)動(dòng)平滑但末端軌跡是曲線。我的方案是提供兩種模式學(xué)生可以切換對(duì)比。笛卡爾空間插補(bǔ)用直線每個(gè)周期算一次IK關(guān)節(jié)空間插補(bǔ)用五次多項(xiàng)式直接對(duì)關(guān)節(jié)角插值。兩種模式各有適用場(chǎng)景笛卡爾適合對(duì)末端軌跡有要求的任務(wù)關(guān)節(jié)空間適合對(duì)運(yùn)動(dòng)平滑性有要求的任務(wù)。軌跡規(guī)劃里還有個(gè)速度規(guī)劃的問(wèn)題。如果只是簡(jiǎn)單插值啟動(dòng)和停止時(shí)加速度是突變的機(jī)械臂會(huì)抖。我加了梯形速度規(guī)劃加速段、勻速段、減速段分開(kāi)處理加速度有上限。這樣機(jī)械臂啟停平穩(wěn)但運(yùn)動(dòng)時(shí)間會(huì)比理想情況長(zhǎng)一些。對(duì)于教學(xué)來(lái)說(shuō)平穩(wěn)比快更重要。6. 實(shí)訓(xùn)場(chǎng)景下的多機(jī)部署與教學(xué)管理6.1 多機(jī)通信ROS多機(jī)配置的坑與解法實(shí)訓(xùn)場(chǎng)景通常是一個(gè)實(shí)驗(yàn)室多臺(tái)機(jī)器每臺(tái)機(jī)器控制一臺(tái)機(jī)械臂。如果每臺(tái)機(jī)器獨(dú)立運(yùn)行學(xué)生之間沒(méi)法協(xié)作教師也沒(méi)法統(tǒng)一監(jiān)控。ROS的多機(jī)通信機(jī)制可以把這些機(jī)器連成一個(gè)網(wǎng)絡(luò)但配置起來(lái)坑不少。核心是ROS_MASTER_URI和ROS_IP兩個(gè)環(huán)境變量。ROS_MASTER_URI指向master節(jié)點(diǎn)的地址ROS_IP是本機(jī)在ROS網(wǎng)絡(luò)里的地址。如果配置不對(duì)會(huì)出現(xiàn)節(jié)點(diǎn)能啟動(dòng)但話題訂閱不到的情況。我的經(jīng)驗(yàn)是每臺(tái)機(jī)器的ROS_IP設(shè)成本機(jī)的局域網(wǎng)IPROS_MASTER_URI統(tǒng)一指向教師機(jī)。這樣教師機(jī)是master所有學(xué)生機(jī)的節(jié)點(diǎn)都注冊(cè)到教師機(jī)上教師可以用rosnode list看到所有節(jié)點(diǎn)用rostopic訂閱任何話題。但這樣有個(gè)問(wèn)題所有話題通信都經(jīng)過(guò)教師機(jī)網(wǎng)絡(luò)帶寬可能成為瓶頸。如果學(xué)生機(jī)之間需要大量數(shù)據(jù)傳輸比如圖像最好用ROS的machine標(biāo)簽做分布式啟動(dòng)讓節(jié)點(diǎn)在各自機(jī)器上運(yùn)行只把需要共享的話題通過(guò)master協(xié)調(diào)。還有一個(gè)坑是主機(jī)名解析。ROS默認(rèn)用主機(jī)名通信如果局域網(wǎng)里沒(méi)有DNS需要用/etc/hosts手動(dòng)配置主機(jī)名和IP的映射。我一般會(huì)在所有機(jī)器上統(tǒng)一配置hosts文件把每臺(tái)機(jī)器的主機(jī)名和IP寫(xiě)進(jìn)去避免解析失敗。6.2 教學(xué)管理如何讓學(xué)生快速上手又不搞壞系統(tǒng)教學(xué)場(chǎng)景里最怕的是學(xué)生把系統(tǒng)搞壞然后下一節(jié)課別人沒(méi)法用。我的做法是把系統(tǒng)分成只讀層和可寫(xiě)層。只讀層是Docker鏡像里的ROS工作空間學(xué)生不能改可寫(xiě)層是學(xué)生自己的home目錄可以隨便折騰。每次上課前學(xué)生從鏡像啟動(dòng)容器下課后容器銷毀所有改動(dòng)都不保留。如果學(xué)生想保存自己的代碼就掛載一個(gè)外部目錄進(jìn)去。這樣還有個(gè)好處環(huán)境永遠(yuǎn)是一致的。學(xué)生不會(huì)因?yàn)檎`刪了某個(gè)文件導(dǎo)致系統(tǒng)跑不起來(lái)教師也不用每次課后恢復(fù)環(huán)境。代價(jià)是學(xué)生不能直接改系統(tǒng)里的代碼但可以通過(guò)ROS的節(jié)點(diǎn)替換機(jī)制覆蓋默認(rèn)行為教學(xué)上足夠了。對(duì)于需要做課題的研究生我會(huì)給他們單獨(dú)的開(kāi)發(fā)環(huán)境不限制改動(dòng)但要求他們自己維護(hù)環(huán)境。這樣既保證了教學(xué)秩序又不影響科研靈活性。6.3 監(jiān)控與調(diào)試教師端能看到什么教師端我做了個(gè)簡(jiǎn)單的監(jiān)控面板用Python寫(xiě)基于rosbridge和WebSocket瀏覽器打開(kāi)就能看。面板上顯示每臺(tái)機(jī)器的在線狀態(tài)、當(dāng)前運(yùn)行的節(jié)點(diǎn)、關(guān)鍵話題的數(shù)據(jù)頻率。如果某個(gè)節(jié)點(diǎn)掛了或者話題斷流面板上會(huì)標(biāo)紅。調(diào)試方面學(xué)生最常用的是rostopic echo和rqt。rostopic echo看數(shù)據(jù)流rqt_graph看節(jié)點(diǎn)連接關(guān)系rqt_plot畫(huà)數(shù)據(jù)曲線。這幾個(gè)工具在Docker鏡像里都預(yù)裝了學(xué)生開(kāi)箱即用。我還寫(xiě)了個(gè)簡(jiǎn)單的腳本一鍵啟動(dòng)VR遙操作的全部節(jié)點(diǎn)學(xué)生不用記一堆rosrun命令。7. 踩坑實(shí)錄那些文檔里不會(huì)寫(xiě)的教訓(xùn)7.1 VR SDK初始化失敗的排查鏈路VR SDK初始化失敗是最高頻的問(wèn)題表現(xiàn)是程序啟動(dòng)后報(bào)錯(cuò)退出或者卡在初始化不動(dòng)。排查鏈路我總結(jié)成三步。第一步確認(rèn)設(shè)備連接。lsusb看設(shè)備有沒(méi)有被系統(tǒng)識(shí)別如果沒(méi)識(shí)別換USB口或者換線。VR頭顯對(duì)USB帶寬有要求USB2.0的口可能帶不動(dòng)要插USB3.0。第二步確認(rèn)驅(qū)動(dòng)。有些VR設(shè)備需要裝廠商驅(qū)動(dòng)驅(qū)動(dòng)沒(méi)裝的話lsusb能看到設(shè)備但SDK初始化會(huì)失敗。驅(qū)動(dòng)版本也要注意太新的驅(qū)動(dòng)可能和SDK不兼容我遇到過(guò)升級(jí)顯卡驅(qū)動(dòng)后VR SDK反而用不了的情況回退驅(qū)動(dòng)版本就好了。第三步確認(rèn)權(quán)限。Linux下USB設(shè)備默認(rèn)只有root能訪問(wèn)普通用戶需要配置udev規(guī)則。廠商一般會(huì)提供udev規(guī)則文件放到/etc/udev/rules.d/下然后sudo udevadm control --reload-rules重載。這一步經(jīng)常被忽略表現(xiàn)是sudo運(yùn)行程序正常普通用戶運(yùn)行就失敗。7.2 ROS話題延遲的定位方法遙操作對(duì)延遲敏感如果操作者感覺(jué)不跟手就要查延遲。定位方法是打時(shí)間戳在數(shù)據(jù)發(fā)布的節(jié)點(diǎn)記錄發(fā)布時(shí)間在接收的節(jié)點(diǎn)記錄接收時(shí)間兩者相減就是傳輸延遲。如果延遲大再細(xì)分是發(fā)布端的問(wèn)題還是傳輸?shù)膯?wèn)題。發(fā)布端的問(wèn)題通常是計(jì)算耗時(shí)太長(zhǎng)比如IK解算太慢。用rosconsole打日志看每個(gè)環(huán)節(jié)的耗時(shí)。傳輸?shù)膯?wèn)題通常是網(wǎng)絡(luò)帶寬或者序列化開(kāi)銷。大消息比如點(diǎn)云用ROS話題傳延遲很高考慮用共享內(nèi)存或者壓縮。我實(shí)測(cè)下來(lái)局域網(wǎng)內(nèi)小消息幾KB的ROS話題延遲在1-3ms大消息幾MB能到幾十毫秒。VR遙操作的控制指令是小消息延遲可以接受如果要傳VR畫(huà)面千萬(wàn)別走ROS。7.3 機(jī)械臂運(yùn)動(dòng)異常的幾種典型表現(xiàn)與對(duì)應(yīng)原因機(jī)械臂運(yùn)動(dòng)異常在教學(xué)場(chǎng)景里很常見(jiàn)學(xué)生改代碼改出問(wèn)題很正常。我總結(jié)了幾種典型表現(xiàn)和對(duì)應(yīng)原因。抖動(dòng)通常是濾波參數(shù)不對(duì)或者控制頻率不穩(wěn)定。檢查濾波截止頻率檢查控制循環(huán)是不是被其他任務(wù)阻塞了。跳變通常是IK解算出現(xiàn)多解切換或者關(guān)節(jié)限位裁剪導(dǎo)致。檢查IK的初始猜測(cè)值檢查限位處理邏輯。不動(dòng)通常是話題沒(méi)連上或者指令沒(méi)發(fā)出去。用rostopic echo看指令話題有沒(méi)有數(shù)據(jù)用rqt_graph看節(jié)點(diǎn)連接。飛車最危險(xiǎn)的情況通常是IK解算發(fā)散了。一定要有急停機(jī)制軟件急停和硬件急停都要有。軟件急停是在控制節(jié)點(diǎn)里加一個(gè)標(biāo)志位收到急停信號(hào)就停止發(fā)送指令硬件急停是物理按鈕直接切斷電機(jī)電源。8. 從這套方案能延伸出的教學(xué)與科研方向這套定制方案落地之后能支撐的教學(xué)內(nèi)容比預(yù)想的要多?;A(chǔ)層面學(xué)生可以學(xué)習(xí)ROS的基本概念節(jié)點(diǎn)、話題、服務(wù)、VR交互原理、機(jī)器人運(yùn)動(dòng)學(xué)。進(jìn)階層面可以替換IK算法做對(duì)比實(shí)驗(yàn)、改濾波參數(shù)觀察效果、加視覺(jué)傳感器做視覺(jué)伺服??蒲袑用孢@套平臺(tái)可以作為遙操作研究的基礎(chǔ)設(shè)施研究力反饋、預(yù)測(cè)控制、共享控制等方向。我特別想提的是共享控制這個(gè)方向。純遙操作里操作者的每個(gè)動(dòng)作都直接映射到機(jī)械臂操作負(fù)擔(dān)重。共享控制是操作者給高層指令比如往左移動(dòng)底層算法自動(dòng)完成細(xì)節(jié)避障、平滑。這套平臺(tái)的ROS架構(gòu)天然支持這種模式學(xué)生可以在IK層和VR層之間插入自己的共享控制算法非常靈活。另一個(gè)方向是多機(jī)協(xié)作。ROS的多機(jī)通信機(jī)制讓多臺(tái)機(jī)械臂協(xié)同成為可能學(xué)生可以研究多臂協(xié)同抓取、任務(wù)分配等課題。這套平臺(tái)的硬件抽象層設(shè)計(jì)讓每臺(tái)機(jī)械臂的接口一致多機(jī)協(xié)作的代碼可以復(fù)用。最后說(shuō)個(gè)實(shí)際的這套方案的所有代碼和配置我都整理成了文檔包括Dockerfile、ROS包結(jié)構(gòu)、IK實(shí)現(xiàn)、VR SDK封裝。學(xué)生拿到之后照著文檔一步步做基本能在一周內(nèi)跑通。帶課的教師可以直接用這套方案開(kāi)課省去了從零搭建的時(shí)間。我在實(shí)際使用中最大的體會(huì)是教學(xué)平臺(tái)的難點(diǎn)不在技術(shù)本身而在于怎么把技術(shù)包裝成不同基礎(chǔ)的人都能上手的形式。這套方案在這上面花的心思比寫(xiě)算法本身多得多。