ROS學習筆記(二)---Beginner Level常用操作

初學ROS做的筆記,權當備忘錄

旨在梳理beginner level的常用操作,以便後續參考。

1.安裝並配置ROS環境

1.1安裝

  • 設置source.list
    首先Ctrl+Alt+T打開終端,輸入以下指令:
sudo sh -c 'echo "deb http://packages.ros.org/ros/ubuntu $(lsb_release -sc) main" > /etc/apt/sources.list.d/ros-latest.list'
  • 設置keys
sudo apt-key adv --keyserver 'hkp://keyserver.ubuntu.com:80' --recv-key C1CF6E31E6BADE8868B172B4F42ED6FBAB17C654

也可以使用另外的倉庫hkp://pgp.mit.edu:80 or hkp://keyserver.ubuntu.com:80代替

  • 安裝
    首先update源:
sudo apt-get update

然後安裝全功能包:

sudo apt-get install ros-kinetic-desktop-full
  • 設置環境變量
echo "source /opt/ros/kinetic/setup.bash" >> ~/.bashrc
source ~/.bashrc
  • 安裝依賴
sudo apt install python-rosdep python-rosinstall python-rosinstall-generator python-wstool build-essential
  • 安裝ros-dep
sudo apt install python-rosdep
  • 初始化ros-dep
sudo rosdep init
rosdep update

1.2 管理環境

確認ROS的環境變量是否設置好了,ROS_PACKAGE_PATH.

printenv | grep ROS

如果沒有設置,需要source(根據實際安裝的版本來)

source /opt/ros/kinetic/setup.bash

1.3 創建ROS空間

創建一個catkin workspace空間

mkdir -p ~/catkin_ws/src
cd ~/catkin_ws/
catkin_make               #編譯指令

2.ROS文件系統介紹

3.創建並編譯ROS程序包

在當前目錄創建一個package的命令如下,也可以同時指明這個package所依賴的其他package:

roscreate-pkg [package_name] [depend1] [depend2] [depend3]

3.1 創建新的ROS package

切換到~/catkin_ws 並創建package

cd ~/ros_workspace
roscreate-pkg beginner_tutorials std_msgs rospy roscpp

創建package成功以後可以使用roscd切換到package目錄

roscd beginner_tutorials 

如果不能使用roscd命令,則需要再次source

source ./devel/setup.sh

3.2編譯package

在catkin_ws目錄下

catkin_make

build 目錄是build space的默認所在位置,同時cmake 和 make也是在這裏被調用來配置並編譯你的程序包。devel 目錄是devel space的默認所在位置, 同時也是在你安裝程序包之前存放可執行文件和庫文件的地方。

4.理解ROS節點和話題

安裝一個輕量級的模擬器:

sudo apt-get install ros-kinetic-ros-tutorials

4.1圖的概念

  • Nodes:節點,一個節點即爲一個可執行文件,它可以通過ROS與其它節點進行通信。

  • Messages:消息,消息是一種ROS數據類型,用於訂閱或發佈到一個話題。

  • Topics:話題,節點可以發佈消息到話題,也可以訂閱話題以接收消息。

  • Master:節點管理器,ROS名稱服務 (比如幫助節點找到彼此)。

  • rosout: ROS中相當於stdout/stderr。

  • roscore: 主機+ rosout + 參數服務器 (參數服務器會在後面介紹)。

4.2節點

一個節點其實只不過是ROS程序包中的一個可執行文件。ROS節點可以使用ROS客戶庫與其他節點通信。節點可以發佈或接收一個話題。節點也可以提供或使用某種服務。

(節點是ros中非常重要的一個概念,爲了幫助初學者理解這個概念,這裏舉一個通俗的例子:

例如,咱們有一個機器人,和一個遙控器,那麼這個機器人和遙控器開始工作後,就是兩個節點。遙控器起到了下達指 令的作用;機器人負責監聽遙控器下達的指令,完成相應動作。從這裏我們可以看出,節點是一個能執行特定工作任 務的工作單元,並且能夠相互通信,從而實現一個機器人系統整體的功能。在這裏我們把遙控器和機器人簡單定義爲兩個節點,實際上在機器人中根據控制器、傳感器、執行機構等不同組成模塊,還可以將其進一步細分爲更多的節點,這個是根據用戶編寫的程序來定義的。)

4.3客戶端庫

ROS客戶端庫允許使用不同編程語言編寫的節點之間互相通信:

  • rospy = python 客戶端庫
  • roscpp = c++ 客戶端庫

4.4 roscore

roscore 是你在運行所有ROS程序前首先要運行的命令。

roscore

4.5使用rosnode

rosnode 顯示當前運行的ROS節點信息。rosnode list 指令列出活躍的節點:

rosnode list

rosnode info 命令返回的是關於一個特定節點的信息。

$ rosnode info /rosout

4.6使用rosrun

rosrun 允許你使用包名直接運行一個包內的節點(而不需要知道這個包的路徑)。用法:

rosrun [package_name] [node_name]

用一下命令運行一個turtlesim_node

rosrun turtlesim turtlesim_node

在這裏插入圖片描述

5.理解ROS服務和參數

5.1 ROS Services

服務(services)是節點之間通訊的另一種方式。服務允許節點發送請求(request) 並獲得一個響應(response)
rosservice可以很輕鬆的使用 ROS 客戶端/服務器框架提供的服務。rosservice提供了很多可以在topic上使用的命令,如下所示:
使用方法:

rosservice list         #輸出可用服務的信息
rosservice call         #調用帶參數的服務
rosservice type         #輸出服務類型
rosservice find         #依據類型尋找服務find services by service type
rosservice uri          #輸出服務的ROSRPC uri
rosservice call [service] [args]
rosservice type [service]

5.2 Using rosparam

rosparam使得我們能夠存儲並操作ROS 參數服務器(Parameter Server)上的數據。參數服務器能夠存儲整型、浮點、布爾、字符串、字典和列表等數據類型。rosparam使用YAML標記語言的語法。一般而言,YAML的表述很自然:1 是整型, 1.0 是浮點型, one是字符串, true是布爾, [1, 2, 3]是整型列表, {a: b, c: d}是字典. rosparam有很多指令可以用來操作參數,如下所示:
使用方法:

rosparam set            #設置參數
rosparam get            #獲取參數
rosparam load           #從文件讀取參數
rosparam dump           #向文件中寫入參數
rosparam delete         #刪除參數
rosparam list           #列出參數名
rosparam set [param_name]
rosparam get [param_name]
rosparam dump [file_name]
rosparam load [file_name] [namespace]

6. 使用rqt_console和roslaunch

安裝:

sudo apt-get install ros-<distro>-rqt ros-<distro>-rqt-common-plugins ros-<distro>-turtlesim

6.1使用rqt_console和rqt_logger_level

開兩個終端分別運行:

rosrun rqt_console rqt_console
rosrun rqt_logger_level rqt_logger_level

再開一個新的終端運行:

rosrun turtlesim turtlesim_node

6.2使用roslaunch

roslaunch可以用來啓動定義在launch文件中的多個節點。用法:

roslaunch [package] [filename.launch]

先切換到beginner_tutorials程序包目錄下:

roscd beginner_tutorials

如果roscd執行失敗了,記得設置你當前終端下的ROS_PACKAGE_PATH環境變量,設置方法如下

export ROS_PACKAGE_PATH=~/catkin_ws/:$ROS_PACKAGE_PATH

創建一個launch文件夾

mkdir launch
cd launch

創建一個名爲turtlemimic.launch的launch文件,件裏面

touch turtlemimic.launch

並複製粘貼以下內容到該文件

   <launch>  <!--以launch標籤開頭以表明這是一個launch文件 -->
   <!--
   在這裏我們創建了兩個節點分組並以'命名空間(namespace)'標籤來區分,其中一個名爲turtulesim1,另一個名爲turtlesim2,兩個組裏面都使用相同的turtlesim節點並命名爲'sim'。這樣可以讓我們同時啓動兩個turtlesim模擬器而不會產生命名衝突。 
   -->
     <group ns="turtlesim1">
       <node pkg="turtlesim" name="sim" type="turtlesim_node"/>
     </group>
   
     <group ns="turtlesim2">
       <node pkg="turtlesim" name="sim" type="turtlesim_node"/>
     </group>
   <!--
   在這裏我們啓動模仿節點,並將所有話題的輸入和輸出分別重命名爲turtlesim1和turtlesim2,這樣就會使turtlesim2模仿turtlesim1。
   -->
     <node pkg="turtlesim" name="mimic" type="mimic">
       <remap from="input" to="turtlesim1/turtle1"/>
       <remap from="output" to="turtlesim2/turtle1"/>
     </node>
   
   </launch>

現在讓我們通過roslaunch命令來啓動launch文件:

roslaunch beginner_tutorials turtlemimic.launch

在將會有兩個turtlesims被啓動,然後我們在一個新終端中使用rostopic命令發送速度設定消息

rostopic pub /turtlesim1/turtle1/command_velocity turtlesim/Velocity -r 1 -- 2.0  -1.8

7.創建ROS消息和ROS服務

  • 消息(msg): msg文件就是一個描述ROS中所使用消息類型的簡單文本。它們會被用來生成不同語言的源代碼。

  • 服務(srv): 一個srv文件描述一項服務。它包含兩個部分:請求和響應。

msg文件存放在package的msg目錄下,srv文件則存放在srv目錄下。

msg文件實際上就是每行聲明一個數據類型和變量名。可以使用的數據類型如下:

  • int8, int16, int32, int64 (plus uint*)
  • float32, float64
  • string
  • time, duration
  • other msg files
  • variable-length array[] and fixed-length array[C]
    在ROS中有一個特殊的數據類型:Header,它含有時間戳和座標系信息。在msg文件的第一行經常可以看到Header header的聲明.

7.1 使用msg

在之前創建的package裏定義新的消息

cd ~/catkin_ws/src/beginner_tutorials
mkdir msg
echo "int64 num" > msg/Num.msg

接下來,還有關鍵的一步:我們要確保msg文件被轉換成爲C++,Python和其他語言的源代碼:
查看package.xml, 確保它包含一下兩條語句:

  <build_depend>message_generation</build_depend>
  <exec_depend>message_runtime</exec_depend>

在 CMakeLists.txt文件中,利用find_packag函數,增加對message_generation的依賴,這樣就可以生成消息了。 你可以直接在COMPONENTS的列表裏增加message_generation,就像這樣:

find_package(catkin REQUIRED COMPONENTS
  roscpp
  std_msgs
  message_generation
)

還有運行依賴

catkin_package(
#  INCLUDE_DIRS include
#  LIBRARIES beginner_tutorials
  CATKIN_DEPENDS roscpp std_msgs
#  DEPENDS system_lib
  message_runtime
)

add_message_files(
  FILES
  Num.msg
)

手動添加.msg文件後,我們要確保CMake知道在什麼時候重新配置我們的project。 確保添加了如下代碼:

generate_messages()

具體文件見GitHub:CMakeList.txt

使用
rosmsg show [message type]

具體方法

rosmsg show beginner_tutorials/Num  #rosmsg show Num

7.2 使用msv

在剛剛那個package中創建一個服務:

roscd beginner_tutorials
mkdir srv

這次我們不再手動創建服務,而是從其他的package中複製一個服務。 roscp是一個很實用的命令行工具,它實現了將文件從一個package複製到另外一個package的功能。
使用方法:

 roscp [package_name] [file_to_copy_path] [copy_path]

從rospy_tutorials package中複製一個服務文件了:

roscp rospy_tutorials AddTwoInts.srv srv/AddTwoInts.srv

還有很關鍵的一步:我們要確保srv文件被轉換成C++,Python和其他語言的源代碼。
還是再CMakeLists.txt中增加:

add_service_files(
  FILES
  AddTwoInts.srv
)
使用
 rossrv show <service type>
rossrv show beginner_tutorials/AddTwoInts #  rossrv show AddTwoInts

修改MakeLists.txt

generate_messages(
  DEPENDENCIES
  std_msgs
)

編譯

cd ~/catkin_ws/
catkin_make

8.編寫簡單的消息發佈器和訂閱器並測試(C++)

8.1 編寫消息發佈器

『節點』(Node) 是指 ROS 網絡中可執行文件。接下來,我們將會創建一個發佈器節點(“talker”),它將不斷的在 ROS 網絡中廣播消息
切換到之前創建的 beginner_tutorials package 路徑下:

cd ~/catkin_ws/src/beginner_tutorials

創建一個src文件

mkdir -p ~/catkin_ws/src/beginner_tutorials/src

在src中新建一個talker.cpp文件,複製talker.cpp的內容

//ros/ros.h 是一個實用的頭文件,它引用了 ROS 系統中大部分常用的頭文件。
#include "ros/ros.h"
//這引用了 std_msgs/String 消息, 它存放在 std_msgs package 裏,是由 String.msg 文件自動生成的頭文件。需要關於消息的定義,可以參考 msg 頁面。
#include "std_msgs/String.h"

#include <sstream>

int main(int argc,char **argv)
{
/*
初始化 ROS 。它允許 ROS 通過命令行進行名稱重映射——然而這並不是現在討論的重點。在這裏,我們也可以指定節點的名稱——運行過程中,節點的名稱必須唯一。

這裏的名稱必須是一個 base name ,也就是說,名稱內不能包含 / 等符號
*/
  ros::init(argc,argv,"talker");
  /*
爲這個進程的節點創建一個句柄。第一個創建的 NodeHandle 會爲節點進行初始化,最後一個銷燬的 NodeHandle 則會釋放該節點所佔用的所有資源。
*/
  ros::NodeHandle n;
/*
chatter 話題的節點,將要有數據發佈。第二個參數是發佈序列的大小。如果我們發佈的消息的頻率太高,緩衝區中的消息在大於 1000 個的時候就會開始丟棄先前發佈的消息。

NodeHandle::advertise() 返回一個 ros::Publisher 對象,它有兩個作用: 1) 它有一個 publish() 成員函數可以讓你在topic上發佈消息; 2) 如果消息類型不對,它會拒絕發佈。
*/  
  ros::Publisher chatter_pub = n.advertise<std_msgs::String>("chatter",1000);
/*
ros::Rate 對象可以允許你指定自循環的頻率。它會追蹤記錄自上一次調用 Rate::sleep() 後時間的流逝,並休眠直到一個頻率週期的時間。

在這個例子中,我們讓它以 10Hz 的頻率運行。
*/
  ros::Rate loop_rate(10);
/*
roscpp 會默認生成一個 SIGINT 句柄,它負責處理 Ctrl-C 鍵盤操作——使得 ros::ok() 返回 false。

如果下列條件之一發生,ros::ok() 返回false:

SIGINT 被觸發 (Ctrl-C)
被另一同名節點踢出 ROS 網絡
ros::shutdown() 被程序的另一部分調用

節點中的所有 ros::NodeHandles 都已經被銷燬

一旦 ros::ok() 返回 false, 所有的 ROS 調用都會失效。
*/
  int count=0;
  while(ros::ok())
  {
    /*
    我們使用一個由 msg file 文件產生的『消息自適應』類在 ROS 網絡中廣播消息。現在我們使用標準的String消息,它只有一個數據成員 "data"。當然,你也可以發佈更復雜的消息類型。
    */
    std_msgs::String msg;
    std::stringstream ss;
    ss<<"hello world"<<count;
    msg.data=ss.str();
   /*
ROS_INFO 和其他類似的函數可以用來代替 printf/cout 等函數
*/ 
    ROS_INFO("%s",msg.data.c_str());

    chatter_pub.publish(msg);//這裏,我們向所有訂閱 chatter 話題的節點發送消息。
/*
在這個例子中並不是一定要調用 ros::spinOnce(),因爲我們不接受回調。然而,如果你的程序裏包含其他回調函數,最好在這裏加上 ros::spinOnce()這一語句,否則你的回調函數就永遠也不會被調用了。
*/
    ros::spinOnce();
    loop_rate.sleep();
    ++count;
  }

  return 0;
}

8.2 編寫訂閱器

在src下新建listener.cpp文件,複製listener.cpp

#include "ros/ros.h"
#include "std_msgs/String.h"
/*
這是一個回調函數,當接收到 chatter 話題的時候就會被調用。消息是以 boost shared_ptr 指針的形式傳輸,這就意味着你可以存儲它而又不需要複製數據。
*/
void chatterCallback(const std_msgs::String::ConstPtr& msg)
{
    ROS_INFO("I heard: [%s]",msg->data.c_str());
}

int main(int argc,char **argv)
{

    ros::init(argc,argv,"listener");
    ros::NodeHandle n;
/*
告訴 master 我們要訂閱 chatter 話題上的消息。當有消息發佈到這個話題時,ROS 就會調用 chatterCallback() 函數。第二個參數是隊列大小,以防我們處理消息的速度不夠快,當緩存達到 1000 條消息後,再有新的消息到來就將開始丟棄先前接收的消息。

NodeHandle::subscribe() 返回 ros::Subscriber 對象,你必須讓它處於活動狀態直到你不再想訂閱該消息。當這個對象銷燬時,它將自動退訂 chatter 話題的消息。

有各種不同的 NodeHandle::subscribe() 函數,允許你指定類的成員函數,甚至是 Boost.Function 對象可以調用的任何數據類型。roscpp overview 提供了更爲詳盡的信息。
*/
    ros::Subscriber sub = n.subscribe("chatter",1000,chatterCallback);
/*
ros::spin() 進入自循環,可以儘可能快的調用消息回調函數。如果沒有消息到達,它不會佔用很多 CPU,所以不用擔心。一旦 ros::ok() 返回 false,ros::spin() 就會立刻跳出自循環。這有可能是 ros::shutdown() 被調用,或者是用戶按下了 Ctrl-C,使得 master 告訴節點要終止運行。也有可能是節點被人爲關閉的。
*/
    ros::spin();



    return 0;
}

編譯:
在CMakeLists.txt中增加以下內容

include_directories(include ${catkin_INCLUDE_DIRS})

add_executable(talker src/talker.cpp)
target_link_libraries(talker ${catkin_LIBRARIES})

add_executable(listener src/listener.cpp)
target_link_libraries(listener ${catkin_LIBRARIES})

切換到catkin_ws目錄編譯

cd ~/catkin_ws
catkin_make

8.3 測試

確保roscore可用,並運行:

roscore

catkin specific 如果使用catkin,確保你在調用catkin_make後,在運行你自己的程序前,已經source了catkin工作空間下的setup.sh文件:

cd ~/catkin_ws
source ./devel/setup.bash

運行發佈器

rosrun beginner_tutorials talker

運行訂閱器

rosrun beginner_tutorials listener

9.編寫簡單的服務器和客戶端並測試(C++)

9.1 服務器

進入先前你在catkin workspace教程中所創建的beginner_tutorials包所在的目錄:

cd ~/catkin_ws/src/beginner_tutorials

創建src/add_two_ints_server.cpp文件

#include "ros/ros.h"
//beginner_tutorials/AddTwoInts.h是由編譯系統自動根據我們先前創建的srv文件生成的對應該srv文件的頭文件。
#include "beginner_tutorials/AddTwoInts.h"
/*
這個函數提供兩個int值求和的服務,int值從request裏面獲取,而返回數據裝入response內,這些數據類型都定義在srv文件內部,函數返回一個boolean值。
*/
bool add(beginner_tutorials::AddTwoInts::Request  &req,
         beginner_tutorials::AddTwoInts::Response &res)
{
  res.sum = req.a + req.b;
  ROS_INFO("request: x=%ld, y=%ld", (long int)req.a, (long int)req.b);
  ROS_INFO("sending back response: [%ld]", (long int)res.sum);
  return true;
}

int main(int argc, char **argv)
{
  ros::init(argc, argv, "add_two_ints_server");
  ros::NodeHandle n;

  ros::ServiceServer service = n.advertiseService("add_two_ints", add);
  ROS_INFO("Ready to add two ints.");
  ros::spin();

  return 0;
}

9.2 客戶端

創建src/add_two_ints_client.cpp文件


#include "ros/ros.h"
#include "beginner_tutorials/AddTwoInts.h"
#include <cstdlib>

int main(int argc, char **argv)
{
  ros::init(argc, argv, "add_two_ints_client");
  if (argc != 3)
  {
      ROS_INFO("usage: add_two_ints_client X Y");
      return 1;
    }
  
    ros::NodeHandle n;
    ros::ServiceClient client = n.serviceClient<beginner_tutorials::AddTwoInts>("add_two_ints");
    beginner_tutorials::AddTwoInts srv;
    srv.request.a = atoll(argv[1]);
    srv.request.b = atoll(argv[2]);
    if (client.call(srv))
    {
      ROS_INFO("Sum: %ld", (long int)srv.response.sum);
    }
    else
    {
      ROS_ERROR("Failed to call service add_two_ints");
      return 1;
    }
  
    return 0;
  }

編輯一下beginner_tutorials裏面的CMakeLists.txt
添加下面代碼

add_executable(add_two_ints_server src/add_two_ints_server.cpp)
target_link_libraries(add_two_ints_server ${catkin_LIBRARIES})
add_dependencies(add_two_ints_server beginner_tutorials_gencpp)

add_executable(add_two_ints_client src/add_two_ints_client.cpp)
target_link_libraries(add_two_ints_client ${catkin_LIBRARIES})
add_dependencies(add_two_ints_client beginner_tutorials_gencpp)

編譯

cd ~/catkin_ws
catkin_make

9.3 測試

  • 運行service
rosrun beginner_tutorials add_two_ints_server
  • 運行client
rosrun beginner_tutorials add_two_ints_client 1 3

參考

ROS 官方教程
github源碼

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章