ROS 基础入门指南

ROS 基础入门指南

ROS 概述与环境搭建

ROS(Robot Operating System)是一个用于编写机器人软件的灵活框架。它提供了硬件抽象、设备驱动、库、可视化工具、消息传递和包管理等功能。

环境搭建

个人没有在虚拟机装 VSCode,使用 VSCode 的远程连接进行开发,体验也不错,就是有些模块还是得到虚拟机操作,比如小乌龟模块启动运行。引出的虚拟机如何固定 IP 之后做详细记录。


一、话题通信(Topic)

话题通信是 ROS 中最常用的通信方式,采用发布/订阅模型,实现节点间异步通信。

话题通信 C++ 实现流程

发布方逻辑:

  1. 包含头文件
  2. 初始化 ROS 节点:命名(唯一)
  3. 实例化 ROS 句柄
  4. 实例化 发布者 对象
  5. 组织被发布的数据,并编写逻辑发布数据

订阅方逻辑:

  1. 包含头文件
  2. 初始化 ROS 节点:命名(唯一)
  3. 实例化 ROS 句柄
  4. 实例化 订阅者 对象
  5. 处理订阅的消息(回调函数)
  6. 设置循环调用回调函数

C++ 代码示例

发布方:

#include "ros/ros.h"
#include "std_msgs/String.h"
#include <sstream>

int a=1,b=1;

int feibo(int x)
{
    int tmp=b;
    if(x==1||x==2)
        return 1;
    else
    {
        b+=a;
        a=tmp;
        return b;
    }
}

int main(int argc,char *argv[])
{
    setlocale(LC_ALL,"");
    ros::init(argc,argv,"fabuzhe");
    ros::NodeHandle nh;
    ros::Publisher pub=nh.advertise<std_msgs::String>("fibonacci",10);
    std_msgs::String msg;
    ros::Rate rate(10);
    int count=0;
    ros::Duration(1).sleep();// 留时间来在master中注册
    while(ros::ok())
    {
        count++;
        std::stringstream ss;
        ss<<feibo(count);
        msg.data=ss.str();
        pub.publish(msg);
        ROS_INFO("发布的数据是%s",ss.str().c_str());
        rate.sleep();
        ros::spinOnce();//官方建议的处理回调函数
    }
    return 0;
}

订阅方:

#include "ros/ros.h"
#include "std_msgs/String.h"

void doMSG(const std_msgs::String::ConstPtr &msg)
{
    ROS_INFO("%s ",msg->data.c_str());
}

int main(int argc,char *argv[])
{
    setlocale(LC_ALL,"");
    ros::init(argc,argv,"jieshouzhe");
    ros::NodeHandle nh;
    ros::Subscriber sub=nh.subscribe<std_msgs::String>("fibonacci",10,doMSG);
    ros::spin();
    return 0;
}

话题通信 Python 实现流程

发布者逻辑:

  1. 导包
  2. 初始化 ROS 节点:命名(唯一)
  3. 实例化 发布者 对象
  4. 组织被发布的数据,并编写逻辑发布数据

订阅者逻辑:

  1. 导包
  2. 初始化 ROS 节点:命名(唯一)
  3. 实例化 订阅者 对象
  4. 处理订阅的消息(回调函数)
  5. 设置循环调用回调函数

Python 代码示例

发布者:

#! /usr/bin/env python
import rospy
from std_msgs.msg import String

a=1
b=1

def js(x):
    global a,b
    if(x==1 or x==2):
        return 1
    else:
        tmp=b
        b+=a
        a=tmp
        return b

if __name__ == "__main__":
    rospy.init_node("tolker")
    pub=rospy.Publisher("fibonacci",String,queue_size=10)
    msg=String()
    rate=rospy.Rate(1)
    count=0
    rospy.sleep(1)
    while not rospy.is_shutdown():
        count+=1
        ans=js(count)
        msg.data=str(ans)
        pub.publish(msg)
        rospy.loginfo("输出的数据为:%s",msg.data)
        rate.sleep()

订阅者:

#! /usr/bin/env python
import rospy
from std_msgs.msg import String

def doMSG(msg):
    rospy.loginfo("%s ",msg.data)

if __name__ == "__main__":
    rospy.init_node("listener")
    sub=rospy.Subscriber("fibonacci",String,doMSG,queue_size=10)
    rospy.spin()

二、服务通信(Service)

服务通信采用请求/响应模型,实现节点间同步通信。

服务通信 C++ 实现

服务端逻辑:

  1. 包含头文件
  2. 初始化 ROS 节点
  3. 创建 ROS 句柄
  4. 创建 服务 对象
  5. 回调函数处理请求并产生响应
  6. 由于请求有多个,需要调用 ros::spin()

客户端逻辑:

  1. 包含头文件
  2. 初始化 ROS 节点
  3. 创建 ROS 句柄
  4. 创建 客户端 对象
  5. 请求服务,接收响应

C++ 代码示例

服务端:

#include "ros/ros.h"
#include "plubing_server_client/addint.h"

bool doADD(plubing_server_client::addint::Request &req,plubing_server_client::addint::Response &res)
{
    int num1=req.num1;
    int num2=req.num2;
    ROS_INFO("收到数据:%d,%d",num1,num2);
    int sum=num1+num2;
    res.sum=sum;
    ROS_INFO("求得的数据:%d",sum);
    return true;
}

int main(int argc,char *argv[])
{
    setlocale(LC_ALL,"");
    ros::init(argc,argv,"server");
    ros::NodeHandle nh;
    ROS_INFO("服务已经启动------");
    ros::ServiceServer server=nh.advertiseService("addint",doADD);
    ros::spin();
    return 0;
}

客户端:

#include "ros/ros.h"
#include "plubing_server_client/addint.h"

int main(int argc,char *argv[])
{
    setlocale(LC_ALL,"");
    if(argc!=3)
    {
        ROS_INFO("提交的参数个数错误");
    }
    else
    {
        ros::init(argc,argv,"client");
        ros::NodeHandle nh;
        ros::ServiceClient client=nh.serviceClient<plubing_server_client::addint>("addint");
        plubing_server_client::addint ai;
        ai.request.num1=atoi(argv[1]);
        ai.request.num2=atoi(argv[2]);
        client.waitForExistence();//判断服务端是否启动,从而决定是否挂起
        bool flag=client.call(ai);
        if(flag)
        {
            ROS_INFO("响应成功");
            ROS_INFO("响应结果为:%d",ai.response.sum);
        }
        else
        {
            ROS_INFO("响应失败");
        }
    }
    return 0;
}

服务通信 Python 实现

服务端:

#! /usr/bin/env python
import rospy
from plubing_server_client.srv import addint,addintRequest,addintResponse

def doreq(req):
    sum=req.num1+req.num2
    response=addintResponse()
    response.sum=sum
    rospy.loginfo("服务器响应:%d+%d=%d",req.num1,req.num2,response.sum)
    return response

if __name__ =="__main__":
    rospy.init_node("pyserver")
    server=rospy.Service("addint",addint,doreq)
    rospy.loginfo("服务已启动--------")
    rospy.spin()

客户端:

#! /usr/bin/env python
import rospy
from plubing_server_client.srv import *
import sys

if __name__ == "__main__":
    if len(sys.argv)!=3:
        rospy.logerr("请正确提交参数")
        sys.exit(1)

    rospy.init_node("pyclient")
    client=rospy.ServiceProxy("addint",addint)
    client.wait_for_service()
    req=addintRequest()
    req.num1=int(sys.argv[1])
    req.num2=int(sys.argv[2])
    response=client.call(req.num1,req.num2)
    rospy.loginfo("收到回复:%d",response.sum)

三、参数服务器

参数服务器就是一个存放参数的容器,相当于全局变量的设置。

设置参数(C++)

#include "ros/ros.h"

int main(int argc,char *argv[])
{
    ros::init(argc,argv,"setnode");
    ros::NodeHandle nh;
    nh.setParam("type","xiaohua");
    nh.setParam("radiu",0.5);
    nh.setParam("radiu",0.7);//可直接覆盖
    //ros::param::set() 另一种可用方法
    return 0;
}

获取和删除参数(C++)

#include "ros/ros.h"

int main(int argc,char *argv[])
{
    setlocale(LC_ALL,"");
    ros::init(argc,argv,"param_get");
    ros::NodeHandle nh;

    double radiu=nh.param("radiu",0.5);
    ROS_INFO("radiu=%.2f",radiu);

    // getParam(键,存储结果的变量)
    //         存在,返回 true,且将值赋值给参数2
    //         如果键不存在,那么返回值为 false,且不为参数2赋值
    //     getParamCached键(比getparam更高性能,功能相同)
    std::vector<std::string> names;
    nh.getParamNames(names); //遍历元素
    for (auto &&name : names)
    {
        ROS_INFO("遍历的元素为:%s",name.c_str());
    }

    bool flag;
    flag=nh.deleteParam("audiu");
    // deleteParam("键")
    //     根据键删除参数,删除成功,返回 true,否则(参数不存在),返回 false

    return 0;
}

设置参数(Python)

#! /usr/bin/env python
import rospy

if __name__ =="__main__":
    rospy.init_node("param_set")
    rospy.set_param("shuzi",10)
    rospy.set_param("xiaoshu",10.5)
    rospy.set_param("shuzi",10)

获取和删除参数(Python)

#! /usr/bin/env python
import rospy

if __name__ == "__main__":
    rospy.init_node("param_get")

    int_value=rospy.get_param("shuzi")#获取键值
    double_value=rospy.get_param("xiaoshu")
    rospy.loginfo("intvalue=%d",int_value)
    rospy.loginfo("intvalue=%.2f",double_value)

    #rospy.get_param_cached()有缓存效率更高

    names=rospy.get_param_names()#获取所有元素
    for i in names:
        rospy.loginfo("name=%s",i)

    flag=rospy.has_param("shuzi")#判断是否包含某个键
    if(flag):
        rospy.loginfo("存在")

    shuzi=rospy.search_param("shuzi")#查找键
    rospy.loginfo("%s",shuzi)

    try:
        rospy.delete_param("shuzi")#删除键
    except Exception as e:
        rospy.loginfo("删除失败")

常用 API

初始化

// C++
ros::init(argc,argv,"name");

// Python
rospy.init_node("name");

话题与服务相关对象

略(具体实现见上述代码示例)


总结

ROS 提供了三种主要的通信机制:

  1. 话题通信(Topic) - 异步通信,适用于连续数据流
  2. 服务通信(Service) - 同步通信,适用于请求/响应模式
  3. 参数服务器(Parameter Server) - 全局参数存储

掌握这三种通信机制是 ROS 开发的基础,后续的机器人开发都是基于这些通信机制实现的。




Enjoy Reading This Article?

Here are some more articles you might like to read next:

  • 灵巧手遥操方案调研
  • lerobot探索
  • 提问的智慧
  • Markdown 功能使用指南
  • ROS 机器人开发实践