创建工作空间
工作空间(workspace)就是一个存放工程开发相关文件的文件夹。建立一个工作空间,会有以下四个文件夹:
src是用来存放功能包的,我们创建的功能包就需要在这个文件夹下创建。
build是用来存放编译过程产生的一些中间编译文件。
devel和install功能类似,有些工作空间没有install文件夹,存放已经编译完成的可执行文件。
按以下步骤来创建工作空间;
在设置环境变量,上图方法只能使当前命令窗口生效,要永久生效,最好使用:
gedit ~/.bashrc
然后在最后一行添加 source ~/catkin_ws(你的工作空间名)/devel/setup.bash 然后再:
source ~/.bashrc 使其立即生效。
环境变量就是一个使得系统在运行程序的时候找到需要依赖功能包的路径,所以必须设置好,要不然找不到包。
创建功能包
功能包就是具体实现某一机器人功能的单元,在src文件夹创建。
命令格式:catkin_create_pkg 功能包名称(自己取) 功能包需要依赖的其他功能包
然后生成功能包learning_communication中有如下文件夹:
include文件夹存放一些头文件
src存放功能包中的一些c语言源码
cmakelist和package是所有功能包都需要具备的文件
cmake文件里面包含着如何编译功能包的选项
package文件描述了功能包的具体信息,重要的是下面这些文字,以刚才安装的为例子;
这些文字描述了该功能包所依赖哪些其他功能包。
然后就是编译,记住所有的编译都是在工作空间文件夹下进行的,即在catkin_ws文件夹下进行编译,每创建一个功能包都需要编译。
最后一步设置环境变量,不需要了,因为上面已经永久设置了。结果如下:
编译是最恐怖的一步,创建功能包会报错都是在这步,每次编译都心惊肉跳!!!!
另外一个重点:
还有就是工作空间的覆盖问题,是因为环境变量设置的问题;
这就是告诉我们在改功能包时候,要注意这点。
ROS通信编程
话题编程
流程:
先实现一个发布者:
第一步:在功能包中src文件夹中创建文件talker.cpp,输入以下代码(附注释):
/**
* 该例程将发布chatter话题,消息类型String
*/
#include <sstream>
#include "ros/ros.h"
#include "std_msgs/String.h"
int main(int argc, char **argv)
{
// ROS节点初始化
ros::init(argc, argv, "talker");
// 创建节点句柄
ros::NodeHandle n;
// 创建一个Publisher,发布名为chatter的topic,消息类型为std_msgs::String
ros::Publisher chatter_pub = n.advertise<std_msgs::String>("chatter", 1000);
// 设置循环的频率
ros::Rate loop_rate(10);
int count = 0;
while (ros::ok())
{
// 初始化std_msgs::String类型的消息
std_msgs::String msg;
std::stringstream ss;
ss << "hello world " << count;
msg.data = ss.str();
// 发布消息
ROS_INFO("%s", msg.data.c_str());
chatter_pub.publish(msg);
// 循环等待回调函数
ros::spinOnce();
// 按照循环频率延时
loop_rate.sleep();
++count;
}
return 0;
}
再实现一个订阅者:
同样在src文件夹创建文件listener.cpp,代码如下:
/**
* 该例程将订阅chatter话题,消息类型String
*/
#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节点
ros::init(argc, argv, "listener");
// 创建节点句柄
ros::NodeHandle n;
// 创建一个Subscriber,订阅名为chatter的topic,注册回调函数chatterCallback
ros::Subscriber sub = n.subscribe("chatter", 1000, chatterCallback);
// 循环等待回调函数
ros::spin();
return 0;
}
编译代码
如果是python写的,就不需要加啥依赖啥的这步了,直接到工作空间目录下,直接编译即可,因为python文件本身就是可执行文件。
打开功能包中的cmakelist文件,增加以下绿色文字:
其实原先都是有的,就是被注释掉了,只需要去掉#,把名称改成自己的就好.
这个太简单,没有其他功能包需要依赖,所以不需要在package.xml文件改依赖的功能包。
编译
退回到工作空间文件夹下,执行:
catkin_make
没有报错,100%,编译成功,结果如图:
然后就可以在工作空间下的devel文件夹中的lib文件夹就可以看到功能包的名字,里面就包含了可执行文件:
运行程序
和运行小海龟一样,先roscore运行rosmaster,再rosrun一下,就可以,
这里要注意,我第一次运行发现没找到功能包,报错,如图:
是因为没把这个工作空间加入到环境变量中,把
source ~/catkin_ws/devel/setup.bash加入到.bashrc文件中就可以,两步:
然后就可以了,先运行talker,再运行listener,结果如图:
就看到listener接收的和talker发布的是同步的
自定义话题消息
步骤如下:
在功能包中创建msg文件夹存放msg消息文件
和实现话题是差不多的,但因为这里多了个其他功能包依赖,就多了几步,我自己的猜测,在cmake文件中find_package添加的应该是对着build中,而catkin_package中多的是exec_depend中的包,哎呀,实在不行,就把所有包都分别添加进去就好了,反正多了也不会咋样。
结果:
服务编程
步骤;
第一步:自定义服务
和话题自定义消息类似,步骤:
创建srv文件夹,存放自定义服务.srv文件。
编译成功:
第二步:创建服务器和客户端
和实现话题的发布者和订阅者类似,在src文件夹创建.cpp文件
代码:
/**
* AddTwoInts Server
*/
#include "ros/ros.h"
#include "learning_communication/AddTwoInts.h"
// service回调函数,输入参数req,输出参数res
bool add(learning_communication::AddTwoInts::Request &req,
learning_communication::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节点初始化
ros::init(argc, argv, "add_two_ints_server");
// 创建节点句柄
ros::NodeHandle n;
// 创建一个名为add_two_ints的server,注册回调函数add()
ros::ServiceServer service = n.advertiseService("add_two_ints", add);
// 循环等待回调函数
ROS_INFO("Ready to add two ints.");
ros::spin();
return 0;
}
客户端:
代码:
/**
* AddTwoInts Client
*/
#include <cstdlib>
#include "ros/ros.h"
#include "learning_communication/AddTwoInts.h"
int main(int argc, char **argv)
{
// ROS节点初始化
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;
// 创建一个client,请求add_two_int service,service消息类型是learning_communication::AddTwoInts
ros::ServiceClient client = n.serviceClient<learning_communication::AddTwoInts>("add_two_ints");
// 创建learning_communication::AddTwoInts类型的service消息
learning_communication::AddTwoInts srv;
srv.request.a = atoll(argv[1]);
srv.request.b = atoll(argv[2]);
// 发布service请求,等待加法运算的应答结果
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;
}
编译:
运行结果就不贴了。
在运行上述话题和服务后,我们可以用rqt工具箱看下计算图;
对话题编程和服务编程,我们发现,在编译过程中,我们需要编译两次,一是在自定义消息或者服务时,二是在定义两端时。在自定义消息和服务中的编译过程中,需要添加的东西更多,而在定义订阅者和发布者中或者在定义服务端和客户端中,编译过程中添加的更少。而什么时候需要自定义消息和服务,ros中有定义好的消息和服务,如果可以使用他们就不需要自己定义。
动作编程
和服务编程差不多,贴出过程;
这里新建一个文件夹action存放.action文件
运行结果;
计算图:
主要节点:
3 分布式通信
ROS是一种分布式软件框架,节点之间通过松耦合的方式组合
这个没啥好说的,就记住,在主机连接多个机器人的时候,记得要登录其他机器人那个设置好主机的ip地址,也就是rosmaster的地址,用以下语句: