ROS

ROS工作空间

catkin_make会创建build和devel目录和src下的CMakeLists.txt文件。

$ mkdir -p ~/catkin_ws/src
$ cd ~/catkin_ws/
$ catkin_make

使当前工作空间的包能够被找到:

# 只在当前终端生效,执行命令
$ source devel/setup.bash
# 所有终端生效,在~/.bashrc中添加
source /home/user_name/robot/study/catkin_ws/devel/setup.bash

检查是否配置好:

$ echo $ROS_PACKAGE_PATH
/home/user_name/robot/study/catkin_ws/src:/opt/ros/melodic/share

ROS文件系统

查找包的位置

$ rospack find [package_name]
$ rospack find roscpp
/opt/ros/melodic/share/roscpp

切换到指定包的目录下(前提是包的路径在ROS_PACKAGE_PATH中)

$ roscd <package-or-stack>[/subdir]
$ roscd roscpp		# 切换到/opt/ros/melodic/share/roscpp目录
$ roscd roscpp/cmake		# 切换到/opt/ros/melodic/share/roscpp/cmake目录

列出指定包下的文件

$ rosls <package-or-stack>[/subdir]
$ rosls roscpp_tutorials
cmake launch package.xml  srv

ROS Package

至少包含CMakeLists.txtpackage.xml

创建包

$ catkin_create_pkg <package_name> [depend1] [depend2] [depend3]
$ cd ~/catkin_ws/src
# 创建一个名为beginner_tutorials的包,依赖std_msgs、rospy、roscpp包
$ catkin_create_pkg beginner_tutorials std_msgs rospy roscpp

包的依赖

查看直接依赖

$ rospack depends1 beginner_tutorials
roscpp
rospy
std_msgs

它们被保存在package.xml中:

<package format="2">
...
  <buildtool_depend>catkin</buildtool_depend>
  <build_depend>roscpp</build_depend>
  <build_depend>rospy</build_depend>
  <build_depend>std_msgs</build_depend>
...
</package>

查看间接依赖

$ rospack depends1 rospy
genpy
roscpp
rosgraph
rosgraph_msgs
roslib
std_msgs

查看所有依赖

$ rospack depends beginner_tutorials

package.xml

<description>The beginner_tutorials package</description>
<maintainer email="you@yourdomain.tld">Your Name</maintainer>
 <license>TODO</license>

<buildtool_depend>catkin</buildtool_depend>
<!-- 构建时的依赖 -->
<build_depend>roscpp</build_depend>
<build_depend>rospy</build_depend>
<build_depend>std_msgs</build_depend>
<!-- 运行时的依赖 -->
<exec_depend>roscpp</exec_depend>
<exec_depend>rospy</exec_depend>
<exec_depend>std_msgs</exec_depend>

构建包

$ cd ~/catkin_ws
$ catkin_make
# 记得source
$ . ~/catkin_ws/devel/setup.bash

ROS Node

节点就是ROS包中的可执行文件,相互之间可以交流。

roscore

roscore = master (provides name service for ROS) + rosout (stdout/stderr) + parameter server

启动roscore

$ roscore

rosnode

列出正在运行的节点:

$ rosnode list
/rosout

获得节点的信息:

$ rosnode info /rosout
--------------------------------------------------------------------------------
Node [/rosout]
Publications: 
 * /rosout_agg [rosgraph_msgs/Log]

Subscriptions: 
 * /rosout [unknown type]

Services: 
 * /rosout/get_loggers
 * /rosout/set_logger_level

......

rosrun

运行一个包中的节点:

$ rosrun [package_name] [node_name]
$ rosrun turtlesim turtlesim_node
$ rosrun turtlesim turtle_teleop_key

运行时更改节点的名字:

$ rosrun turtlesim turtlesim_node __name:=my_turtle
# 此时再用rosnode list
$ rosnode list
/my_turtle
/rosout

ROS Topic

rqt_graph

打开海龟的三个终端,用rqt_graph查看话题的发布和订阅:

$ rosrun rqt_graph rqt_graph

将会看到如下窗口:

rqt_graph

rostopic

rostopic命令行帮助:

$ rostopic -h
rostopic bw     display bandwidth used by topic
rostopic echo   print messages to screen
rostopic hz     display publishing rate of topic    
rostopic list   print information about active topics
rostopic pub    publish data to topic
rostopic type   print topic type

echo

$ rostopic echo [topic]
$ rostopic echo /turtle1/cmd_vel
# 在键盘终端中控制海龟移动会看到类似如下输出
linear: 
  x: 2.0
  y: 0.0
  z: 0.0
angular: 
  x: 0.0
  y: 0.0
  z: 0.0
---

现在再用rqt_graph:

rqt_graph

list

$ rostopic list -h
Usage: rostopic list [/namespace]

Options:
  -h, --help            show this help message and exit
  -b BAGFILE, --bag=BAGFILE
                        list topics in .bag file
  -v, --verbose         list full details about each topic
  -p                    list only publishers
  -s                    list only subscribers
  --host                group by host name
$ rostopic list -v

Published topics:
 * /turtle1/color_sensor [turtlesim/Color] 1 publisher
 * /turtle1/cmd_vel [geometry_msgs/Twist] 1 publisher
 * /rosout [rosgraph_msgs/Log] 2 publishers
 * /rosout_agg [rosgraph_msgs/Log] 1 publisher
 * /turtle1/pose [turtlesim/Pose] 1 publisher

Subscribed topics:
 * /turtle1/cmd_vel [geometry_msgs/Twist] 1 subscriber
 * /rosout [rosgraph_msgs/Log] 1 subscriber

type

$ rostopic type [topic]
$ rostopic type /turtle1/cmd_vel
geometry_msgs/Twist
$ rosmsg show geometry_msgs/Twist		# for detail
geometry_msgs/Vector3 linear
  float64 x
  float64 y
  float64 z
geometry_msgs/Vector3 angular
  float64 x
  float64 y
  float64 z
  $ rostopic type /turtle1/cmd_vel | rosmsg show		# 结合以上两行命令

pub

$ rostopic pub [topic] [msg_type] [args]
$ rostopic pub /turtle1/cmd_vel geometry_msgs/Twist -1 -- '[2.0, 0.0, 0.0]' '[0.0, 0.0, 1.8]'
# -1表示只发布一次
# --表示之后的内容没有参数(避免将负数识别为参数)
$ rostopic pub /turtle1/cmd_vel geometry_msgs/Twist -r 1 -- '[2.0, 0.0, 0.0]' '[0.0, 0.0, -1.8]'
# -r表示发布话题的频率
$ rostopic echo /turtle1/pose		# 查看turtlesim发布的pose话题信息

hz

查看话题发布的频率。

$ rostopic hz [topic]
$ rostopic hz /turtle1/pose
subscribed to [/turtle1/pose]
average rate: 62.469
	min: 0.015s max: 0.016s std dev: 0.00045s window: 60
average rate: 62.486
	min: 0.015s max: 0.016s std dev: 0.00046s window: 122
average rate: 62.490
	min: 0.015s max: 0.016s std dev: 0.00047s window: 185
......

rqt_plot

用图像展示数据。

$ rosrun rqt_plot rqt_plot

在文本框中输入/turtle1/pose/x然后点加号,再添加/turtle1/pose/y,就能看到这两个值的实时变化。

ROS Service

rosservice list         print information about active services
rosservice call         call the service with the provided args
rosservice type         print service type
rosservice find         find services by service type
rosservice uri          print service ROSRPC uri

list

$ rosservice list
/clear
/kill
/reset
/rosout/get_loggers
/rosout/set_logger_level
/spawn
/teleop_turtle/get_loggers
/teleop_turtle/set_logger_level
/turtle1/set_pen
/turtle1/teleport_absolute
/turtle1/teleport_relative
/turtlesim/get_loggers
/turtlesim/set_logger_level

type

$  rosservice type [service]
$ rosservice type /clear
std_srvs/Empty		# Empty意味着request和response都没有参数

call

$ rosservice call [service] [args]
$ rosservice call /clear		# 清除海龟的轨迹

$ rosservice type /spawn | rossrv show		# 打印spawn服务的参数
float32 x
float32 y
float32 theta
string name
---
string name
$ rosservice call /spawn 2 2 0.2 ""		# 再造一只海龟
name: "turtle2"		# 返回海龟的名字

ROS parameter

rosparam set            set parameter
rosparam get            get parameter
rosparam load           load parameters from file
rosparam dump           dump parameters to file
rosparam delete         delete parameter
rosparam list           list parameter names

list

$ rosparam list
/rosdistro
/roslaunch/uris/host_user__33313
/rosversion
/run_id
/turtlesim/background_b
/turtlesim/background_g
/turtlesim/background_r

set, get

$ rosparam set [param_name]
$ rosparam get [param_name]
$ rosparam set /turtlesim/background_r 150
$ rosservice call /clear		# 使更改生效

$ rosparam get /turtlesim/background_g
86

$ rosparam get /		# 查看所有参数
rosdistro: 'melodic

  '
roslaunch:
  uris: {host_user__33313: 'http://user:33313/'}
rosversion: '1.14.13

  '
run_id: 34cdf4f2-0aef-11ed-8d55-342eb7b06bcf
turtlesim: {background_b: 255, background_g: 86, background_r: 150}

dump, load

$ rosparam dump [file_name] [namespace]
$ rosparam load [file_name] [namespace]
$ rosparam dump params.yaml		# 保存到文件
$ rosparam load params.yaml copy_turtle		# 从文件加载并更改名字空间
$ rosparam get /copy_turtle/turtlesim/background_b
255

Rqt

之前使用过rqt_graphrqt_plot

rqt_console

显示节点的输出。

$ rosrun rqt_console rqt_console

rqt_logger_level

更改节点的详细级别。

$ rosrun rqt_logger_level rqt_logger_level

日志记录级别(从高到低):

Fatal
Error
Warn
Info
Debug

通过设置记录器级别,将获得该优先级或更高优先级的所有消息。例如,通过将级别设置为Warn,将获得所有WarnErrorFatal的日志记录消息。

如海龟撞墙会发出Warn,如果将级别设置为Error(在rqt_logger_level中依次选择/turtlesim->ros->Error)则不会在rqt_console中看到输出。

ROS Launch

$ roslaunch [package] [filename.launch]
$ roscd beginner_tutorials
$ mkdir launch
$ cd launch

创建turtlemimic.launch文件,写入:

<launch>
	<!-- 创建两个组,使用不同的名字空间 -->
    <group ns="turtlesim1">
        <!-- 
			pkg:包名
			type:节点对应的可执行文件名
 			name:运行时显示的节点名称
		-->
        <node pkg="turtlesim" name="sim" type="turtlesim_node"/>
    </group>

	<group ns="turtlesim2">
        <node pkg="turtlesim" name="sim" type="turtlesim_node"/>
    </group>
	<!-- 启动mimic节点 -->
    <node pkg="turtlesim" name="mimic" type="mimic">
        <!-- 修改发布者/订阅着发布/订阅的话题,将from的话题修改为to的话题 -->
        <remap from="input" to="turtlesim1/turtle1"/>
        <remap from="output" to="turtlesim2/turtle1"/>
    </node>

</launch>

运行launch文件:

$ roslaunch beginner_tutorials turtlemimic.launch		# 会打开两个海龟仿真器
$ rostopic pub /turtlesim1/turtle1/cmd_vel geometry_msgs/Twist -r 1 -- '[2.0, 0.0, 0.0]' '[0.0, 0.0, -1.8]'		# 向turtlesim1发布一个话题

运行rqt_graph(命令行rqt->Plugins->Introspection->Node Graph 或 命令行rqt_graph):

roslaunch_rqt_graph

ROS msg和srv

msg文件

$ roscd beginner_tutorials
$ mkdir msg
$ echo "int64 num" > msg/Num.msg

在package.xml中取消注释以下两行:

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

在包下的CMakeLists.txt中进行如下修改:

find_package(catkin REQUIRED COMPONENTS
  roscpp
  rospy
  std_msgs
  message_generation		# 加上
)

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

# 取消注释以下行并将自己的msg文件加入
add_message_files(
  FILES
  Num.msg
)

# 取消注释以下行
generate_messages(
  DEPENDENCIES
  std_msgs		# msg文件的依赖
)

rosmsg

$ rosmsg show [message type]		# 查看定义的msg文件中的内容
$ rosmsg show beginner_tutorials/Num		# 包名/msg名
int64 num

$ rosmsg show Num		# 不写包名会去查找
[beginner_tutorials/Num]:
int64 num

srv文件

$ roscd beginner_tutorials
$ mkdir srv

roscp

$ roscp [package_name] [file_to_copy_path] [copy_path]		# 从一个包下复制一个文件
$ roscp rospy_tutorials AddTwoInts.srv srv/AddTwoInts.srv

在package.xml中取消注释以下两行(同上):

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

在包下的CMakeLists.txt中进行如下修改:

find_package(catkin REQUIRED COMPONENTS
  roscpp
  rospy
  std_msgs
  message_generation		# msg和srv都要用
)

# 取消注释以下行并将自己的srv文件加入
add_service_files(
  FILES
  AddTwoInts.srv
)

rossrv

$ rossrv show <service type>		# 查看定义的srv文件中的内容
$ rossrv show beginner_tutorials/AddTwoInts
int64 a
int64 b
---
int64 sum

$ rossrv show AddTwoInts		# 省略包名就会去查找
[beginner_tutorials/AddTwoInts]:
int64 a
int64 b
---
int64 sum

[rospy_tutorials/AddTwoInts]:
int64 a
int64 b
---
int64 sum

生成文件

$ roscd beginner_tutorials
$ cd ../..
$ catkin_make 或 $ catkin build

C++文件将会生成到:/catkin_ws/devel/include/beginner_tutorials/

python文件将会生成到:/catkin_ws/devel/lib/python2.7/dist-packages/beginner_tutorials/

Publisher and Subscriber

C++

Publisher

#include "ros/ros.h"
#include "std_msgs/String.h"
#include <sstream>

int main(int argc, char **argv)
{
    ros::init(argc, argv, "talker");		// 初始化ros节点,节点名不能带'/'
    ros::NodeHandle n;		// 创建一个handle

    // NodeHandle.advertise()方法会返回ros::Publisher对象,第二个参数是发布队列的大小
    ros::Publisher chatter_pub = n.advertise<std_msgs::String>("chatter", 1000);

    ros::Rate loop_rate(10);		// 设置循环的频率:10Hz

    int count = 0;
    // ros::ok()在以下情况会返回false
    // 按Ctrl+C,被同名节点替换,ros::shutdown(),ros::NodeHandles被销毁
    while (ros::ok())
    {
        std_msgs::String msg;
        std::stringstream ss;
        ss << "hello world " << count;
        msg.data = ss.str();		// std_msgs::String类有一个data属性

        ROS_INFO("%s", msg.data.c_str());

        chatter_pub.publish(msg);		// 发布话题

        ros::spinOnce();		// 获取回调函数

        loop_rate.sleep();		// 等待指定的时间
        ++count;
    }
    
    return 0;
}

Subscriber

#include "ros/ros.h"
#include "std_msgs/String.h"

// 回调函数,收到发布的话题时调用。收到的消息可以存储起来,不会因为函数结束而销毁
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;

    // 订阅消息,第二个参数是队列长度
	ros::Subscriber sub = n.subscribe("chatter", 1000, chatterCallback);

	ros::spin();	// 循环等待,直到ros::ok()返回false

	return 0;
}

CMakeLists.txt

加上之前修改的应该是这样的:

cmake_minimum_required(VERSION 2.8.3)
project(beginner_tutorials)

## Find catkin and any catkin packages
find_package(catkin REQUIRED COMPONENTS roscpp rospy std_msgs genmsg)

## Declare ROS messages and services
add_message_files(FILES Num.msg)
add_service_files(FILES AddTwoInts.srv)

## Generate added messages and services
generate_messages(DEPENDENCIES std_msgs)

## Declare a catkin package
catkin_package()

## Build talker and listener
include_directories(include ${catkin_INCLUDE_DIRS})

# 需要添加的内容
add_executable(talker src/talker.cpp)
target_link_libraries(talker ${catkin_LIBRARIES})
add_dependencies(talker beginner_tutorials_generate_messages_cpp)

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

python

注意设置py文件权限为可执行。

程序第一行应当指定python的解释器:

  • 未安装conda
    • #!/usr/bin/env python:使用ubuntu的python2
    • #!/usr/bin/env python3:使用ubuntu的python3
  • 安装了conda
    • #!/usr/bin/env python:使用conda的python3
    • #!/usr/bin/env python3:使用conda的python3
    • #!/usr/bin/env python2:使用ubuntu的python2

Publisher

#!/usr/bin/env python3
import rospy
from std_msgs.msg import String

def talker():
    pub = rospy.Publisher('chatter', String, queue_size=10)		# 创建Publisher
    rospy.init_node('talker', anonymous=True)		# anonymous=True:保证节点名字唯一(加随机数)
    rate = rospy.Rate(10)		# 10hz
    while not rospy.is_shutdown():
        hello_str = "hello world %s" % rospy.get_time()
        rospy.loginfo(hello_str)
        pub.publish(hello_str)
        rate.sleep()

if __name__ == '__main__':
    try:
        talker()
    except rospy.ROSInterruptException:
        pass

Subscriber

#!/usr/bin/env python3
import rospy
from std_msgs.msg import String

def callback(data):
    rospy.loginfo(rospy.get_caller_id() + "I heard %s", data.data)
    
def listener():
	# anonymous=True的作用:
    # In ROS, nodes are uniquely named. If two nodes with the same
    # name are launched, the previous one is kicked off. The
    # anonymous=True flag means that rospy will choose a unique
    # name for our 'listener' node so that multiple listeners can
    # run simultaneously.
    rospy.init_node('listener', anonymous=True)

    rospy.Subscriber("chatter", String, callback)		# String将作为callback的参数

    # spin()只是用来阻止python退出直到这个节点停止运行(因为callback有自己的线程)
    rospy.spin()

if __name__ == '__main__':
    listener()

CMakeLists.txt

取消注释以下行并修改内容:

catkin_install_python(PROGRAMS
  scripts/talker.py
  scripts/listener.py
  DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
)

Service and Client

C++

Service

#include "ros/ros.h"
#include "beginner_tutorials/AddTwoInts.h"		// 从srv中生成的

// 传入request和response对象,返回bool
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;		// 执行完毕返回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;
}

Client

#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");
    
    // 自动生成的service类,有request和response两个成员
    beginner_tutorials::AddTwoInts srv;
    srv.request.a = atoll(argv[1]);
    srv.request.b = atoll(argv[2]);
    
    // 阻塞发送服务请求:
    // 成功则返回true,获得的值存在srv.response中
    // 失败则返回false,srv.response无效
    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;
}

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)

python

记得改权限。

Service

#!/usr/bin/env python3

from beginner_tutorials.srv import AddTwoInts,AddTwoIntsResponse
import rospy

# 获得的参数为AddTwoIntsRequest类型
def handle_add_two_ints(req):
    print("Returning [%s + %s = %s]"%(req.a, req.b, (req.a + req.b)))
    return AddTwoIntsResponse(req.a + req.b)		# 返回AddTwoIntsResponse的对象

def add_two_ints_server():
    rospy.init_node('add_two_ints_server')
    #第二个参数为service的类型,第三个参数为处理函数
    s = rospy.Service('add_two_ints', AddTwoInts, handle_add_two_ints)
    print("Ready to add two ints.")
    rospy.spin()		# 阻止python退出

if __name__ == "__main__":
    add_two_ints_server()

Client

#!/usr/bin/env python3

import sys
import rospy
from beginner_tutorials.srv import *

def add_two_ints_client(x, y):
    rospy.wait_for_service('add_two_ints')		# 阻塞直到有service
    try:
        # 创建一个handle用来处理请求,可以像函数一样使用它
        add_two_ints = rospy.ServiceProxy('add_two_ints', AddTwoInts)
        resp1 = add_two_ints(x, y)		# 返回AddTwoIntsResponse对象
        return resp1.sum
    except rospy.ServiceException as e:
        print("Service call failed: %s"%e)

def usage():
    return "%s [x y]"%sys.argv[0]

if __name__ == "__main__":
    # 无需ros_init
    if len(sys.argv) == 3:
        x = int(sys.argv[1])
        y = int(sys.argv[2])
    else:
        print(usage())
        sys.exit(1)
    print("Requesting %s+%s"%(x, y))
    print("%s + %s = %s"%(x, y, add_two_ints_client(x, y)))

CMakeLists.txt

catkin_install_python(PROGRAMS 
	scripts/add_two_ints_server.py
	scripts/add_two_ints_client.py
	DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
)

ROS
https://shuusui.site/blog/2022/09/20/ros/
作者
Shuusui
发布于
2022年9月20日
更新于
2022年9月20日
许可协议