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.txt
和package.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
将会看到如下窗口:
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:
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_console
显示节点的输出。
$ rosrun rqt_console rqt_console
rqt_logger_level
更改节点的详细级别。
$ rosrun rqt_logger_level rqt_logger_level
日志记录级别(从高到低):
Fatal
Error
Warn
Info
Debug
通过设置记录器级别,将获得该优先级或更高优先级的所有消息。例如,通过将级别设置为Warn
,将获得所有Warn
、Error
、Fatal
的日志记录消息。
如海龟撞墙会发出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
):
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}
)