参考《ROS机器人程序设计》一二版均有
在开始具体工作之前,首先创建工作空间。在工作空间中完成代码。
若查看ROS正在使用道工作空间:
echo $ROS_PACKAGE_PATH
创建新文件夹:
cd~
mkdir ~p dev/rosbook
将此路径添加ROS_PACKAGE_PATH.只需在~/.bashrc文件末尾添加一个新行:
echo "export ROS_PACKAGE_PATH=~/dev/rosbook:${ROS_PACKAGE_PATH}" >> ~/.bashrc
. ~/.bashrc
.bashrc文件在home中,隐藏,
ls -a ////显示隐藏文件
gedit ~/.bashrc/////在文本中打开
cat ~/.bashrc////在终端打开
功能包,指的是一种特定的文件结构和文件夹组合。这种结构如下所示:
● bin/ 这是我们编译和链接程序后,用于存储可执行文件的文件夹。
● include/package_name/ 此目录包含了你所需要库的头文件。不要忘记导出功能包清单,因为它们还会被其他功能包所使用。
● msg/ 如果你要开发非标准消息,请把文件放在这里。
● scripts/ 其中包括 Bash、Python 或任何其他脚本的可执行脚本文件。
● src/ 这是存储程序源文件的地方。你可能会为节点创建一个文件夹或按照你希望
的方式去组织它。
• srv/ 这表示的是服务(srv)类型。
● CMakeLists.txt 这是 CMake 的生成文件。
• manifest.xml 这是功能包清单文件。
可以手动创建功能包。
最好使用roscreat-pkg命令行工具。
cd ~/dev/rosbook
roscreate-pkg chapter2_tutorials std_msgs rospy roscpp
chapter2_tutorials是功能包名词,std_msgs、 rospy 、roscpp是依赖项
std_msgs包含了常见道消息类型,表示基本数据类型和其他基本道消息构造,如多维数组。
rospy 一个ros的纯Python客户端库
roscpp 使用C++ 实现ROS的各种功能。
rosmake chapter2_tutorials
编译之后会出现bin文件夹
创建两个节点:一个发布一些数据,另一个接受这些数据。两个节点之间使用最基本道方式进行通信。
$ roscd chapter2_tutorials/src/
创建两个文件并分别命名为 example1_a.cpp、example1_b.cpp。
example1_a.cpp文件将会发送带有节点名称的数据
example1_b.cpp 会把这些数据显示在 shell 窗口中。
example1_a.cpp内容:
#include "ros/ros.h"//包含使用ROS节点所有必要道文件
#include "std_msgs/String.h"//
#include ///////////包含要使用的文件类型
int main(int argc, char **argv)
{
ros::init(argc, argv, "example1_a");//启动该节点并设置其名称,名称必须唯一
ros::NodeHandle n;//设置节点进程的句柄
ros::Publisher pub = n.advertise("message", 1000);//将节点设置成发布者,并将所发布主题道类型和名称告知节点管理器。第一个参数是消息的名称:message,第二个是缓冲区道大小。如果主题发布数据速度较快,那么将缓冲区设置为1000个消息。
ros::Rate loop_rate(10);//设置发送数据道频率为10Hz
while (ros::ok())//当收到Ctrl+C的按键消息或ROS停止当前节点运行时,ros::ok()库会执行停止节点运行道命令
{
//////创建一个消息变量,变量类型必须符合发送数据的要求
std_msgs::String msg;
std::stringstream ss;
ss << " I am the example1_a node ";
msg.data = ss.str();
//ROS_INFO("%s", msg.data.c_str());
pub.publish(msg);//消息被发布
ros::spinOnce();//如果一个订阅者出现,ROS就会更新和读取所有主题
loop_rate.sleep();//按照10Hz的频率将程序挂起
}
return 0;
}
#include "ros/ros.h"
#include "std_msgs/String.h"
void messageCallback(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, "example1_b");
ros::NodeHandle n;
/////创建一个订阅者,并从主题获取以message为名称的消息数据。设置缓冲区为1000个消息,处理消息句柄的回调函数为messageCallback
ros::Subscriber sub = n.subscribe("message", 1000, messageCallback);
//// ros::spin()库是节点读取数据道消息响应循环,当消息到达的时候,回调函数就会被调用。当按下Ctrl+C时,节点会退出消息循环,于是循环结束。
ros::spin();
return 0;
}
当使用chapter2_tutorial功能包时,要自行编辑其中道CMakeList.txt文件。
$ rosed chapter2_tutorials CMakeLists.txt /////////////用VIM编辑器打开文件,但是我没有装,直接找到打开就行。
将以下命令复制到文件末尾:
rosbuild_add_executable(example1_a src/example1_a.cpp)
rosbuild_add_executable(example1_b src/example1_b.cpp)
这两个命令会在bin文件夹下创建两个可执行文件。
使用rosmake工具编译功能包就会编译全部道节点:
rosmake chapter2_tutorials
PS:如果没有启动ROS,先roscore.检查ROS是否运行:rosnode list
分别在两个终端运行:
rosrun chapter2_tutorials example1_a
rosrun chapter2_tutorials example1_b
aunch文件是ROS里一个有用的特征可以启动多个节点。在以上章节中,我们已经创建了节点、并且在不同的shell中执行这些节点。想象20个节点同时工作,并且在每个shell里执行有一个节点,这简直是噩梦!
有了launch文件,我们可以通过启动一个扩展名为.launch的配置文件在同一个shell里完成同样的工作。
为了实践此功用,我们要在我们的程序包里创建新的文件夹,如下:
$ roscd chapter2_tutorials/
$ mkdir launch
$ cd launch
$ vim chapter2.launch
现在,将以下代码放入chapter2.launch文件:
type="example1_a"/>
type="example1_b"/>
这个文件很简单,但如果需要,你可以写一个非常复杂的文件。例如,控制一个完整的机器人,如PR2或者Robonaut。它们都是真正的机器人并且在ROS中被模拟。
这个文件有一个launch标签,你可以看到node标签。node标签用来从程序包启动一个节点,例如,从chapter2_tutorials程序包启动example1_a节点。
这个launch文件会执行两个节点——本章前两个节点。如果你记得,example1_a节点发送一个消息到 example1_b节点。为了启动这个文件,你可以用以下命令:
$ roslaunch chapter2_tutorialschapter2.launch
When you launch a launch file, it is not necessary to execute it before the roscore
command; roslaunch does it for us.
Remember that the example1_b node prints in the screen the message received
from the other node. If you take a look, you won't see anything. This is because
example1_b prints the message using ROS_INFO, and when you run only a node
in a shell, you can see it, but when you run a launch file, you can't.
Now, to see the message printed in the screen, you can use the rqt_console utility.
You will learn more about this utility in the following chapters. Now, run the
following command:
$ rqt_console
用于创建自定义消息。
首先,在chapter2_tutorials功能包下创建msg文件夹,并在其中创建新的文件chapter2_msg1.msg.在文件中添加:
int32 A
int32 B
int32 C
现在编辑 CMakeList.txt,从 # rosbuild_genmsg() 这一行中删除 #,然后使用rosmake 命令编译功能包:
$ rosmake chapter2_tutorials
为了检查编译是否正确,可以使用 rosmsg 命令:
$ rosmsg show chapter2_tutorials/chapter2_msg1
如果你在 chapter2_msg1.msg 文件中看到一样的内容,说明编译正确
Now we are going to create an
现在创建一个 srv 文件。在chapter2_tutorials 文件夹下创建一个名为 srv 的文件夹,并新建文件chapter2_srv1.srv,在文件中增加以下行:
int32 A
int32 B
int32 C
---
int32 sum
编辑 CMakeList.txt 文件,从 #rosbuild_gensrv() 这一行中删除 #,并且使用
rosmake chapter2_tutorials 命令编译功能包。
你可以通过 rossrv 工具来检查编译是否正确:
$ rossrv show chapter2_tutorials/chapter2_srv1
如果你在 chapter2_srv1.srv 文件中看到相同的内容,说明编译正确。
首先,学习如何创建一个服务并在ROS中使用它。该服务对三个整数求和。我们需要两个节点,一个服务器一个客户端。
在chapter2_tutorial功能包src中创建两个节点:example2_a.cpp和example2_b.cpp
example2_a.cpp内容:
//包含必要的头文件和创建道srv文件
#include "ros/ros.h"
#include "chapter2_tutorials/chapter2_srv1.h"
//对三个变量求和,并将计算结果发送给其他节点
bool add(chapter2_tutorials::chapter2_srv1::Request &req,
chapter2_tutorials::chapter2_srv1::Response &res)
{
res.sum = req.A + req.B + req.C;
ROS_INFO("request: A=%ld, B=%ld C=%ld", (int)req.A, (int)req.B, (int)req.C);
ROS_INFO("sending back response: [%ld]", (int)res.sum);
return true;
}
int main(int argc, char **argv)
{
ros::init(argc, argv, "add_3_ints_server");
ros::NodeHandle n;
//创建服务并在ROS中发布广播
ros::ServiceServer service = n.advertiseService("add_3_ints", add);
ROS_INFO("Ready to add 3 ints.");
ros::spin();
return 0;
}
#include "ros/ros.h"
#include "chapter2_tutorials/chapter2_srv1.h"
#include
int main(int argc, char **argv)
{
ros::init(argc, argv, "add_3_ints_client");
if (argc != 4)
{
ROS_INFO("usage: add_3_ints_client A B C ");
return 1;
}
ros::NodeHandle n;
////使用add_3_ints为名称创建一个服务的客户端
ros::ServiceClient client = n.serviceClient("add_3_ints");
///创建srv文件的一个实例,并且加入需要发送的数据值
chapter2_tutorials::chapter2_srv1 srv;
srv.request.A = atoll(argv[1]);
srv.request.B = atoll(argv[2]);
srv.request.C = atoll(argv[3]);
///代码调用服务并发送数据,如果调用成功,call()函数会返回true;如果没有成功,返回false
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;
}
编译节点:
rosmake chapter2_tutorials
分别在两个终端运行:
rosrun chapter2_tutorials example2_a
rosrun chapter2_tutorials example2_b 1 2 3
同8.1,只是调用chapter2_msg1.msg。
example3_a.cpp内容:
#include "ros/ros.h"
#include "chapter2_tutorials/chapter2_msg1.h"
#include
int main(int argc, char **argv)
{
ros::init(argc, argv, "example1_a");
ros::NodeHandle n;
ros::Publisher pub = n.advertise("message", 1000);
ros::Rate loop_rate(10);
while (ros::ok())
{
chapter2_tutorials::chapter2_msg1 msg;
msg.A = 1;
msg.B = 2;
msg.C = 3;
pub.publish(msg);
ros::spinOnce();
loop_rate.sleep();
}
return 0;
}
example3_b.cpp内容:
#include "ros/ros.h"
#include "chapter2_tutorials/chapter2_msg1.h"
void messageCallback(const chapter2_tutorials::chapter2_msg1::ConstPtr& msg)
{
ROS_INFO("I heard: [%d] [%d] [%d]", msg->A, msg->B, msg->C);
}
int main(int argc, char **argv)
{
ros::init(argc, argv, "example1_b");
ros::NodeHandle n;
ros::Subscriber sub = n.subscribe("message", 1000, messageCallback);
ros::spin();
return 0;
}