我花了一个稍微简单但细节上又不太准确的图:
节点的功能就是参与数据运算或者对数据进行操作。比如我们可以由一个节点计算图片坐标,然后通过控制下位机的节点订阅坐标,实现获取坐标并与下位机通信,实现“识图-移动"。这只是个简单的例子。那么结合上图我们看看一个节点的核心要素(这里就先用C++展示了):
#include "ros/ros.h"
导入头文件,确保能基于ROS编程。int main(int argc, char **argv)
主函数需要写成这样的标准化入口。ros::init(argc, argv, "节点名")
初始化节点这个才是我们最真实的节点名,系统内显示的是这个。ros::NodeHandle n
创建一个句柄,后续如果有对节点有其他操作,需通过句柄,所以不可或缺。对于句柄的理解可以百度,简单来说,你大概可以理解为一个为了方便外部调用其代码而生成的"地址"。ros::Rate loop_rate(int 频率)
如果有循环体且,要控制循环运行的频率,需要调用,单位Hz。ROS_INFO()
会被log记录的printf()ros::ok()
用户判断该节点是否可用,出现以下情况会返回false:(反正能用就是true)
ros::spinOnce()
如果有回调函数,需要添加此语句,否则回调函数不能被触发。loop_rate.sleep()
通过调用 ros::Rate 对象来休眠一段时间控制运行频率这大约就是一个节点所需要用到的东西。那么我们来构成一个最小代码看看:
#include "ros/ros.h"
int main(int argc, char **argv)
{
ros::init(argc, argv, "timer");
ros::NodeHandle n;
ros::Rate loop_rate(1);
while (ros::ok())
{
ROS_INFO("1s passed");
// ros::spinOnce();
loop_rate.sleep();
}
return 0;
}
然后是常见的4个方法的参数说明:
基类ros::Publisher
,方法: advertise(const string &topic, uint32_t queue_size, bool latch=false)
向 topic 发送数据
基类ros::Subscriber
,方法:subscribe(const string &topic, uint32_t queue_size, void(*)(M))
接受 topic 中的数据
advertiseService(const string &service, bool(*srv_func)(Mreq &, Mres &))
创建服务的server端
serviceClient(const string &service_name, bool persistent=false)
创建服务的 client端
好了,那我们来看看节点编程里常用的话题节点与服务节点吧:
这个就拿前两篇的文章来说明吧:
#include "ros/ros.h"
#include "top/Name.h"
// 引用消息的头文件,格式为 "功能包名/消息名.h"
#include "sstream"
// 基于C++编程,不解释
int main(int argc, char **argv)
{
std::stringstream ss;
// 基于C++编程,不解释
top::Name msg;
//使用消息类,实例化一个变量
ros::init(argc, argv, "publisher");
ros::NodeHandle h;
ros::Publisher puber = h.advertise<top::Name>("Name_Info", 10);
//实例化发布器类,创建且使用<包名::消息>的发布"Name_Info"这个话题
ros::Rate loop_rate(1);
ss << "A B";
// 基于C++编程,不解释
while (ros::ok())
{
ss >> msg.first_name;
// 基于C++编程,不解释
ss >> msg.last_name;
// 基于C++编程,不解释
ROS_INFO("I Send [%s %s]", msg.first_name.c_str(), msg.last_name.c_str());
// 在控制台上显示信息,使用ROS_INFO,可以查看日志输出来复原操作
puber.publish(msg);
loop_rate.sleep();
}
return 0;
}
#include "ros/ros.h"
#include "top/Name.h"
void infoCallback(const top::Name::ConstPtr& msg)
// 回调函数名(常量 包名::消息::常量指针 变量名), 用于接收消息,且防止被修改
{
ROS_INFO("Name Changed [%s %s]", msg->last_name.c_str(), msg->first_name.c_str());
}
int main(int argc, char **argv)
{
ros::init(argc, argv, "suber");
ros::NodeHandle n;
ros::Subscriber sub = n.subscribe("Name_Info", 1000, infoCallback);
// 实例化订阅器,且订阅"Name_Info"这个话题,并调用回调函数infoCallback
ros::spin();
// 有回调函数记得加上这句话
return 0;
}
#!/usr/bin/env python
import rospy
from egpy.msg import Name #
def talker():
pub = rospy.Publisher('Name_Info', Name, queue_size=10) # 实例化一个发布者
rospy.init_node('pyPub', anonymous=True) # 初始化节点信息
rate = rospy.Rate(10)
a = Name() # 实例化消息
while not rospy.is_shutdown():
a.first_name = "A"
a.last_name = "B"
rospy.loginfo("%s %s"%(a.first_name, a.last_name)) # 等同ROS_INFO
pub.publish(a)
rate.sleep()
if __name__ == '__main__':
try:
talker()
except rospy.ROSInterruptException:
pass
#!/usr/bin/env python
import rospy
from egpy.msg import Name
def callback(data):
rospy.loginfo("pyname:%s %s"%(data.first_name, data.last_name))
def listener():
rospy.init_node('pySub', anonymous=True)
rospy.Subscriber("Name_Info", Name, callback)
rospy.spin()
if __name__ == '__main__':
listener()
#include "ros/ros.h"
#include "server/add.h"
//定义服务的类型 包名/服务文件.h
bool add(server::add::Request &req, server::add::Response &res)
//定义服务的回调函数,且请求与响应是同一个文件里的不同两类,所以分开声明
{
res.sum = req.a + req.b;
ROS_INFO("Get request: x=%ld, y=%ld", (long int)req.a, (long int)req.b);
return true;
}
int main(int argc, char **argv)
{
ros::init(argc, argv, "server");
ros::NodeHandle n;
ROS_INFO("The server is start running!");
ros::ServiceServer service = n.advertiseService("add", add);
//实例化服务
ros::spin();
// spin函数是永远执行回调函数,而不管它以下的代码,所以要放至末尾
return 0;
}
#include "ros/ros.h"
#include "server/add.h"
#include
int main(int argc, char **argv)
{
ros::init(argc, argv, "client");
ros::NodeHandle n;
ros::ServiceClient client = n.serviceClient<server::add>("add");
//实例化客户端
server::add srv;
//实例化add的服务类,且srv是Request与Response的多继承
ros::Rate loop_rate(1);
long int a,b;
while(ros::ok)
{
a = rand()%100;
b = rand()%100;
srv.request.a = a;
srv.request.b = b;
ROS_INFO("Send a request: %ld + %ld = ?", a, b);
if (client.call(srv))
//调用服务是否成功
{
ROS_INFO("Sum: %ld", (long int)srv.response.sum);
}
else
{
ROS_ERROR("Failed to call service add");
return 1;
}
loop_rate.sleep();
}
return 0;
}
#!/usr/bin/env python
from serpy.srv import *
import rospy
def handle_add_two_ints(req): # 服务的回调函数
print "Returning [%s + %s = %s]"%(req.a, req.b, (req.a + req.b))
return addResponse(req.a + req.b)
def add_two_ints_server():
rospy.init_node('add_server')
s = rospy.Service('add', add, handle_add_two_ints)
# 实例化服务端,并设置服务名,数据类型与回调函数
print "Ready to add two ints."
rospy.spin() # 进入死循环只处理回调
if __name__ == "__main__":
add_two_ints_server()
#!/usr/bin/env python
import sys
import rospy
import random
from serpy.srv import *
def sendrequest(x, y):
rospy.wait_for_service('add')
# 等待服务端启动,会阻塞在这里,直到有add这个服务出现
try:
req = rospy.ServiceProxy('add', add) # 实例化一个请求
resp1 = req(x, y) # 进行请求并返回
return resp1.sum
except rospy.ServiceException, e:
print "Service call failed: %s"%e
def runforever():
rospy.init_node('add_client', anonymous=True)
rate = rospy.Rate(1) # 控制一秒一个请求
while not rospy.is_shutdown():
x = int(random.random()*100)
y = int(random.random()*100)
print "Requesting %d+%d"%(x, y)
print "%s + %s = %s"%(x, y, sendrequest(x, y))
rate.sleep()
if __name__ == "__main__":
runforever()