实验二 ROS基础 - RLi43/Baxter-Experimental-Guide GitHub Wiki
- 了解基本的ROS概念
- 了解如何编写并编译基于ROS的程序
- 了解基本的Linux操作
阅读并掌握实验内容。
建议阅读一遍官方的教程,或者实验楼的教程。也可以在ROS教程(上) ROS教程(下)找到一些参考
ROS支持C++/Python编写代码。节点的意义就是跨平台的通讯,所以即使Baxter API是Python的,用C++写节点(不是控制Baxter的)也完全可以。
-
src
里面放源文件,一般都在这里写东西,其他的都不动 -
build
里面是编译出来的内容 -
install
是安装内容
catkin_make
约等于编译,catkin_make install
约等于生成执行。都在工作空间的根目录操作。
C++文件需要编译链接;Python不需要,但写完后记得添加执行权限chmod +x xxx.py
。另外注意python文件中不能包含非ASCII字符(或者在头部加# -*- coding:utf-8 -*-
)。
所有的命令基本上都可以用在其后加上 --help
查看帮助
- rospack 程序包查看
- roscd 到程序包目录
- rosrun 运行程序包里的程序
rosrun <package_name> <XXX.py>
- roslaunch 运行多个节点
roslaunch <package_name> <XXX.py>
需要.launch文件配置 详见官方教程 - rosbak 备份。详见官方教程。
使用ROS运行一个程序应该指定其所在的程序包,即
rosrun package_name program_name
各个程序包需要注册之后才能被索引到。
自动生成一个程序包
catkin_create_pkg <package_name> <package_depend1> <package_depend2>
这条命令相当于自动生成了一个<package_name>
文件夹,里面包含CMakelist.txt
和packages.xml
。
*<package_name>
** CMakelist.txt
是编译相关的配置
** packages.xml
是程序包依赖相关的内容
这两个文件相关的配置可以在官网学习。
ROS为各种服务提供通用的通信架构,将工程分为若干节点,通过话题进行消息的通信。所有节点必须先主节点roscore
注册。
建立一个chat
话题,节点chatter
在这个话题上发布(publish)消息,节点listenner
订阅(subscribe)了这个话题,它就可以收到所有这个话题上发布的内容。
- rosnode
- list 显示当前节点
- rostopic
- list
- type
- pub
- rosmsg
- show XXX 显示消息信息
- rossrv
- rosparam 参数服务器,可以获取/修改
get/set
参数,通常在.launch文件中定义
ros提供了一些基本的消息类型,
int8, int16, int32, int64 (plus uint*)
float32, float64
string
time, duration
other msg files
variable-length array[] and fixed-length array[C]
Header,它含有时间戳和坐标系信息。
也可以自定义自己的消息类型WORKSPACE/src/YOUR_PACKAGE/msg/MSG_NAME.msg
。格式如:
float32 height
string name
安装这一消息:
-
package.xml
中增加依赖
<build_depend>message_generation</build_depend>
<exec_depend>message_runtime</exec_depend>
<!--在format2中应该使用exec而不是run,官方教程有错-->
- 在
CMakelist.txt
中
2.1 增加生成消息服务的依赖
find_package(catkin REQUIRED COMPONENTS roscpp rospy std_msgs message_generation)
2.2 增加运行依赖
catkin_package(
...
CATKIN_DEPENDS message_runtime ...
...)
2.3 声明消息类型
add_message_files(
FILES
Message1.msg
Message2.msg
)
2.4 生成消息
generate_messages()
加在catkin_package
之前
服务需要自己定义类型,就是要指明传入的消息类型和传出的消息类型。格式如:
float32 height
string name
---
bool ok
#!/usr/bin/env python #所有ROS node都需要这个声明 保证作为Python程序执行
# -*- coding:utf-8 -*
import rospy #使用Python写ros节点必须调用
from std_msgs.msg import String #std_msg里有输出显示需要的string类型
def talker():
# 定义接口 即消息类型为String,主题为chatter 队列数量在hydro版本里
pub # rospy.Publisher('chatter', String, queue_size#10)
# 节点声明 必须声明,不然无法与ROS Master交流
# anonymous 节点名字#'talker'+随机数字
rospy.init_node('talker', anonymous#True)
# 循环频率 只要处理速度没有超过0.1s就能保证这个运行频率
rate # rospy.Rate(10) # 10hz
while not rospy.is_shutdown(): #检查是否退出
hello_str # "hello world %s" % rospy.get_time()
rospy.loginfo(hello_str) #loginfo会1.显示在屏幕上
#2.写在这个node的log file里 3.写在rosout里,对debug很有用
pub.publish(hello_str) #在主题上发布消息
rate.sleep() #直到下一个循环时间开始
# 以上是一个很典型的rospy结构
if __name__ '__main__':
try:
talker()
except rospy.ROSInterruptException:
# 意外/Ctrl+C 退出 会有来自sleep()的异常
pass
#include "ros/ros.h"
#include "std_msgs/String.h"
#include <sstream>
/**
* This tutorial demonstrates simple sending of messages over the ROS system.
*/
int main(int argc, char **argv)
{
/**
* The ros::init() function needs to see argc and argv so that it can perform
* any ROS arguments and name remapping that were provided at the command line. For programmatic
* remappings you can use a different version of init() which takes remappings
* directly, but for most command-line programs, passing argc and argv is the easiest
* way to do it. The third argument to init() is the name of the node.
*
* You must call one of the versions of ros::init() before using any other
* part of the ROS system.
*/
ros::init(argc, argv, "talker");//初始化ROS,指定节点名字
/**
* NodeHandle is the main access point to communications with the ROS system.
* The first NodeHandle constructed will fully initialize this node, and the last
* NodeHandle destructed will close down the node.
*/
ros::NodeHandle n;//句柄
/**
* The advertise() function is how you tell ROS that you want to
* publish on a given topic name. This invokes a call to the ROS
* master node, which keeps a registry of who is publishing and who
* is subscribing. After this advertise() call is made, the master
* node will notify anyone who is trying to subscribe to this topic name,
* and they will in turn negotiate a peer-to-peer connection with this
* node. advertise() returns a Publisher object which allows you to
* publish messages on that topic through a call to publish(). Once
* all copies of the returned Publisher object are destroyed, the topic
* will be automatically unadvertised.
*
* The second parameter to advertise() is the size of the message queue
* used for publishing messages. If messages are published more quickly
* than we can send them, the number here specifies how many messages to
* buffer up before throwing some away.
*/
ros::Publisher chatter_pub # n.advertise<std_msgs::String>("chatter", 1000);
ros::Rate loop_rate(10);
/**
* A count of how many messages we have sent. This is used to create
* a unique string for each message.
*/
int count # 0;
while (ros::ok())//1.Ctrl+C 2.被同名节点踢出ROS网络 3.ros::shutdown() 4.节点中所有NodeHandles都被销毁 : 会使ok()返回false
{
/**
* This is a message object. You stuff it with data, and then publish it.
*/
std_msgs::String msg;
std::stringstream ss;
ss << "hello world " << count;
msg.data # ss.str();
ROS_INFO("%s", msg.data.c_str());
/**
* The publish() function is how you send messages. The parameter
* is the message object. The type of this object must agree with the type
* given as a template parameter to the advertise<>() call, as was done
* in the constructor above.
*/
chatter_pub.publish(msg);
ros::spinOnce();
loop_rate.sleep();
++count;
}
return 0;
}
#!/usr/bin/env python
# -*- coding:utf-8 -*
import rospy
from std_msgs.msg import String
def callback(data):
rospy.loginfo(rospy.get_caller_id() + "I heard %s", data.data)
def listener():
# 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)
# spin() simply keeps python from exiting until this node is stopped 而且它拥有自己的线程,不受回调函数影响
rospy.spin()
if __name__ '__main__':
listener()
#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;
/**
* The subscribe() call is how you tell ROS that you want to receive messages
* on a given topic. This invokes a call to the ROS
* master node, which keeps a registry of who is publishing and who
* is subscribing. Messages are passed to a callback function, here
* called chatterCallback. subscribe() returns a Subscriber object that you
* must hold on to until you want to unsubscribe. When all copies of the Subscriber
* object go out of scope, this callback will automatically be unsubscribed from
* this topic.
*
* The second parameter to the subscribe() function is the size of the message
* queue. If messages are arriving faster than they are being processed, this
* is the number of messages that will be buffered up before beginning to throw
* away the oldest ones.
*/
ros::Subscriber sub # n.subscribe("chatter", 1000, chatterCallback);//当这个话题有发布内容时就会调用chatterCallback函数
//ros::Subscriber 被销毁时会自动退订话题
/**
* ros::spin() will enter a loop, pumping callbacks. With this version, all
* callbacks will be called from within this thread (the main one). ros::spin()
* will exit when Ctrl-C is pressed, or the node is shutdown by the 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})
- 在可执行文件
WS/devel/lib/<package name>/<file name>
中加入对消息文件的依赖
add_dependencies(talker beginner_tutorials_generate_messages_cpp)
- 编译
catkin_make
#!/usr/bin/env python
from beginner_tutorials.srv import *
import rospy
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)
def add_two_ints_server():
rospy.init_node('add_two_ints_server')
s # rospy.Service('add_two_ints', AddTwoInts, handle_add_two_ints) #声明一个服务,其名字为'add_two_ints',其服务类型为AddTwoInts,所有请求都被送到handle_add_two_ints这个函数
print "Ready to add two ints."
rospy.spin()#spin函数保持服务在运行中,直到被结束
if __name__ "__main__":
add_two_ints_server()
#include "ros/ros.h"
#include "beginner_tutorials/AddTwoInts.h"
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;
}
#!/usr/bin/env python
import sys
import rospy
from beginner_tutorials.srv import *
def add_two_ints_client(x, y):
rospy.wait_for_service('add_two_ints')#客户端不需要新建节点,直接调用服务
try: #如果调用失败可能会抛出异常
add_two_ints # rospy.ServiceProxy('add_two_ints', AddTwoInts) # 直接调用函数 服务名,服务类型
resp1 # add_two_ints(x, y)
return resp1.sum #AddTwoIntsResponse对象 也就是服务类型定义的下面的数据类型
except rospy.ServiceException, e:
print "Service call failed: %s"%e
def usage():
return "%s [x y]"%sys.argv[0]
if __name__ "__main__":
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))
#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))//可能会调用失败 call()返回false
{
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)
编译
catkin_make
rqt包含了很多ROS内容的查询。入门时推荐使用rqt_graph
观察节点之间的关系。
掌握实验目标的要求