ROS学习

1、ROS实现流程:

  1. 先创建一个工作空间;
  2. 再创建一个功能包 catkin_create_pkg ;
  3. 编辑源文件;
  4. 编辑配置文件;
  5. 编译并执行 

ROS学习_第1张图片

3. 进入 ros 包的 src 目录编辑源文件 (cd 自定义包——cd src——编写cpp)

4.配置文件(cd 自定义包——修改CMakeLists.txt)

5.编译(cd 自定义包——catkin_make);运行(终端1 roscore;终端2 cd 工作空间
——source ./devel/setup.bash——rosrun 包名 C++节点【cmakelists中的项目名称】)

打开新终端 ctrl+alt+t

2、vscode 使用

快捷键:ctrl + s 保存;ctrl+shift+k 删除;ctrl + x 剪切一行;ctrl+shift+b 编译;ctrl+/ 取消注释;ctrl+shift+p 命令面板;ctrl+~ 打开控制台; 复制一行:shift + alt + ↓ 向下复制一行;shift + alt + ↑ 向上复制一行

ROS学习_第2张图片

 4.3 vscode 中编译 ros

快捷键 ctrl + shift + B 调用编译,选择:catkin_make:build

可以点击配置设置为默认,修改配置文件.vscode/tasks.json 

4.4 创建 ROS 功能包

选定 src 右击 ---> create catkin package

设置包名(hello_vscode) 添加依赖(roscpp rospy std_msgs) 

可以在此时ctrl+shift+b进行编译,检查功能包错误

4.5在功能包的 src 下新建 cpp 文件

在ws/src/hello_vscode/src右键新建cpp文件(hello_vscode_c.cpp)

PS3: 当ROS__INFO 终端输出有中文时,会出现乱码

        解决办法:在主函数内部首行加入下面代码 setlocale(LC_ALL, "");

4.6 配置 CMakeLists.txt(功能包下的cmakelists文件)

C++ 配置:取消注释ctrl+\

        add_executable(节点名称【文件映射名称,一般取和原文件名一样的】
          src/C++源文件名.cpp
        )
        target_link_libraries(节点名称【文件映射名称】
          ${catkin_LIBRARIES}
        )

4.7 编译执行

编译: ctrl + shift + B

执行: 在 VScode 添加终端,(在ws工作空间下)执行:

roscore

添加终端,执行:

source ./devel/setup.bash

rosrun 功能包名字 映射文件名(节点名称)

4.8 launch文件

可以一次性启动多个 ROS 节点

  1. 选定功能包右击 ---> 添加 launch 文件夹

  2. 选定 launch 文件夹右击 ---> 添加 launch 文件 xx.launch

  3. 编辑 launch 文件内容

    
        //输出到屏幕
        
        
    
    

    node 包含的某个节点;pkg 功能包;type  被运行的节点文件;name 为节点命名(自定义);output= 设置日志的输出目标

  4. 运行 launch 文件

    roslaunch 功能包 launch文件名

 3、ROS通信机制

1、话题通讯

不断更新的数据

1.1理论模型

ROS学习_第3张图片

需要关注和编写的是:发布方,接受方,数据内容

1.2话题通迅流程

1,编写发布方publisher实现;2,编写订阅方subscriber实现;3,编写配置文件;4,编译并执行;

1.3 发布方

 实现流程:
        1.包含头文件        "ros/ros.h" 

                                          "std_msgs/String.h"//string类型   

                                           "功能包名称/自定义信息生成的头文件名称.h "     //自定义信息头文件       
        2.初始化 ROS 节点:命名(唯一)        ros::init(argc,argv,"结点名称")
        3.实例化 ROS 句柄        ros::NodeHandle nh;
        4.实例化 发布者 对象        

                ros::Publicher pub = nh.advertise<消息类型>("话题",队列长度)        

                消息类型:        std_msgs::String;         自定义信息:功能包名::msg文件名
        5.组织被发布的数据,并编写逻辑发布数据       

                ros::Rate r(频率);  //发布频率 

                pub.publish(消息msg);//发布消息   

                r.sleep();//自动休眠       

                ros::spinOnce();//调用一次回调函数


// 1.包含头文件 
#include "ros/ros.h"
#include "std_msgs/String.h" //普通文本类型的消息
#include 

int main(int argc, char  *argv[])
{   
    //设置编码
    setlocale(LC_ALL,"");

    //2.初始化 ROS 节点:命名(唯一)
    // 参数1和参数2 后期为节点传值会使用
    // 参数3 是节点名称,是一个标识符,需要保证运行后,在 ROS 网络拓扑中唯一
    ros::init(argc,argv,"talker");
    //3.实例化 ROS 句柄
    ros::NodeHandle nh;//该类封装了 ROS 中的一些常用功能

    //4.实例化 发布者 对象
    //泛型: 发布的消息类型
    //参数1: 要发布到的话题
    //参数2: 队列中最大保存的消息数,超出此阀值时,先进的先销毁(时间早的先销毁)
    ros::Publisher pub = nh.advertise("chatter",10);

    //5.组织被发布的数据,并编写逻辑发布数据
    //数据(动态组织)
    std_msgs::String msg;
    // msg.data = "你好啊!!!";
    std::string msg_front = "Hello 你好!"; //消息前缀
    int count = 0; //消息计数器

    //逻辑(一秒10次)
    ros::Rate r(1);

    //节点不死
    while (ros::ok())
    {
        //使用 stringstream 拼接字符串与编号
        std::stringstream ss;
        ss << msg_front << count;
        msg.data = ss.str();
        //发布消息
        pub.publish(msg);
        //加入调试,打印发送的消息
        ROS_INFO("发送的消息:%s",msg.data.c_str());

        //根据前面制定的发送贫频率自动休眠 休眠时间 = 1/频率;
        r.sleep();
        count++;//循环结束前,让 count 自增
        //暂无应用
        ros::spinOnce();
    }


    return 0;
}

1.4订阅方

 实现流程:
        1.包含头文件        
        2.初始化 ROS 节点:命名(唯一)        
        3.实例化 ROS 句柄
        4.实例化 订阅者 对象        

                ros::Subscriber sub = nh.subscribe<模板参数(会根据回调函数自行推导,无需填写)>("话题",队列长度,回调函数);       
        5.处理订阅的消息(回调函数) domsg函数

                  void doMsg(const std_msgs::String::ConstPtr &msg){

                                ……} 

                  自定义信息:void doMsg(const 功能包名::msg文件信息名::ConstPtr  &msg){

                                ……} 

        6.设置循环调用函数             

                ros::spin();

1.5自定义msg

1、定义msg文件 功能包下新建msg文件

2、编辑配置文件

package.xml

添加编译依赖与执行依赖

          message_generation
          message_runtime

CMakeLists.txt find_package(catkin REQUIRED COMPONENTS
  roscpp
  rospy
  std_msgs
  message_generation
## 需要加入 message_generation,必须有 std_msgs
)
CMakeLists.txt

 # 配置 msg 源文件

add_message_files(
  FILES
  Person.msg 
)

CMakeLists.txt # 生成消息时依赖于 std_msgs
generate_messages(
  DEPENDENCIES
  std_msgs
)
CMakeLists.txt #执行时依赖
catkin_package(
#  INCLUDE_DIRS include
#  LIBRARIES demo02_talker_listener
  CATKIN_DEPENDS roscpp rospy std_msgs message_runtime
#  DEPENDS system_lib
)

3、 vscode配置自定义msg文件

        C++ 需要调用的中间文件在devel/include中查看路径,为(.../工作空间/devel/include/包名/xxx.h),该head 文件路径配置进 c_cpp_properties.json 的 includepath属性

ROS学习_第4张图片

4、配置CMakeList.txt

 add_executable        ->         add_dependencies        ->        target_link_libraries

需要添加 add_dependencies 用以设置所依赖的消息相关的中间文件

add_dependencies(映射名字  ${PROJECT_NAME}_generate_messages_cpp)
 

2、服务通信

请求一次、响应一次

2.1理论模型

ROS学习_第5张图片

2.2自定义svr

2.2.1定义svr文件

功能包下新建 srv 目录,添加 xxx.srv 文件,内容:

# 客户端请求时发送的两个数字
int32 num1
int32 num2
---
# 服务器响应发送的数据
int32 sum

 2.2.2 编辑配置文件

package.xml

添加编译依赖与执行依赖

          message_generation
          message_runtime

CMakeLists.txt find_package(catkin REQUIRED COMPONENTS
  roscpp
  rospy
  std_msgs
  message_generation
## 需要加入 message_generation,必须有 std_msgs
)
CMakeLists.txt

 # 配置 svr 源文件

add_message_files(
  FILES
  AddInts.svr
)

CMakeLists.txt # 生成消息时依赖于 std_msgs
generate_messages(
  DEPENDENCIES
  std_msgs
)
CMakeLists.txt #没有在 catkin_package 中配置 message_runtime,经测试配置也可以
catkin_package(
#  INCLUDE_DIRS include
#  LIBRARIES demo02_talker_listener
  CATKIN_DEPENDS roscpp rospy std_msgs message_runtime
#  DEPENDS system_lib
)

2.2.3 vscode配置自定义msg文件

        C++ 需要调用的中间文件在devel/include中查看路径,为(.../工作空间/devel/include/包名/xxx.h),该head 文件路径配置进 c_cpp_properties.json 的 includepath属性

2.3编写流程

1,编写服务端server实现;2,编写客户端client实现;3,编写配置文件;4,编译并执行;

2.4服务端server

    服务器实现:
        1.包含头文件
        2.初始化 ROS 节点
        3.创建 ROS 句柄
        4.创建 服务 对象

ros::ServiceServer server = nh.advertiseService("话题",回调函数);//不用写模板参数,可以自动推导
        5.回调函数处理请求并产生响应 回调函数的返回值为bool
        6.由于请求有多个,需要调用 ros::spin()

/*
    需求: 
        编写两个节点实现服务通信,客户端节点需要提交两个整数到服务器
        服务器需要解析客户端提交的数据,相加后,将结果响应回客户端,
        客户端再解析

*/
#include "ros/ros.h"
#include "demo03_server_client/AddInts.h"

//注意这里 bool 返回值由于标志是否处理成功
bool doReq(demo03_server_client::AddInts::Request& req,
          demo03_server_client::AddInts::Response& resp){
    int num1 = req.num1;
    int num2 = req.num2;

    ROS_INFO("服务器接收到的请求数据为:num1 = %d, num2 = %d",num1, num2);

    //逻辑处理
    if (num1 < 0 || num2 < 0)
    {
        ROS_ERROR("提交的数据异常:数据不可以为负数");
        return false;
    }

    //如果没有异常,那么相加并将结果赋值给 resp
    resp.sum = num1 + num2;
    return true;


}

int main(int argc, char *argv[])
{
    setlocale(LC_ALL,"");
    // 2.初始化 ROS 节点
    ros::init(argc,argv,"AddInts_Server");
    // 3.创建 ROS 句柄
    ros::NodeHandle nh;
    // 4.创建 服务 对象
    ros::ServiceServer server = nh.advertiseService("AddInts",doReq);
    ROS_INFO("服务已经启动....");
    //     5.回调函数处理请求并产生响应
    //     6.由于请求有多个,需要调用 ros::spin()
    ros::spin();
    return 0;
}

2.5 客户端client

    服务器实现:
        1.包含头文件
        2.初始化 ROS 节点
        3.创建 ROS 句柄
        4.创建 客户端 对象

                ros::ServiceClient client = nh.serviceClient<功能包::srv数据文件名>("话题");
        5.请求服务,接收响应

        (1)等待服务端启动端
                    ros::service::waitForService("话题")                  client.waitForExistence();

        (2)组织请求数据 创建数据实例接受输入信息

        (3)标记是否传输成功,并设置判断是否传输成功

                     bool flag = client.call(ai);

/*
    需求: 
        编写两个节点实现服务通信,客户端节点需要提交两个整数到服务器
        服务器需要解析客户端提交的数据,相加后,将结果响应回客户端,
        客户端再解析

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

*/
// 1.包含头文件
#include "ros/ros.h"
#include "demo03_server_client/AddInts.h"

int main(int argc, char *argv[])
{
    setlocale(LC_ALL,"");

    // 利用argc,argv,动态传值,如果通过 launch 的 args 传参,需要传递的参数个数 +3
    if (argc != 3)
    // if (argc != 5)//launch 传参(0-文件路径 1传入的参数 2传入的参数 3节点名称 4日志路径)
    {
        ROS_ERROR("请提交两个整数");
        return 1;
    }


    // 2.初始化 ROS 节点
    ros::init(argc,argv,"AddInts_Client");
    // 3.创建 ROS 句柄
    ros::NodeHandle nh;
    // 4.创建 客户端 对象
    ros::ServiceClient client = nh.serviceClient("AddInts");
    //等待服务启动成功
    //方式1
    ros::service::waitForService("AddInts");
    //方式2
    // client.waitForExistence();
    // 5.组织请求数据
    demo03_server_client::AddInts ai;
    ai.request.num1 = atoi(argv[1]);
    ai.request.num2 = atoi(argv[2]);
    // 6.发送请求,返回 bool 值,标记是否成功
    bool flag = client.call(ai);
    // 7.处理响应
    if (flag)
    {
        ROS_INFO("请求正常处理,响应结果:%d",ai.response.sum);
    }
    else
    {
        ROS_ERROR("请求处理失败....");
        return 1;
    }

    return 0;
}

6、配置CMakeList.txt

 add_executable        ->         add_dependencies        ->        target_link_libraries

3、参数服务器

独立于所有结点的公用容器,用于存储静态的非二进制的简单数据

在 C++ 中实现参数服务器数据的增删改查,可以通过两套 API 实现:

ros::NodeHandle

ros::param

//修改    
ros::NodeHandle
    setParam("键",值)
ros::param
    set("键","值")

//查询
    在 roscpp 中提供了两套 API 实现参数操作
    ros::NodeHandle

        param(键,默认值) 
            存在,返回对应结果,否则返回默认值

        getParam(键,存储结果的变量)
            存在,返回 true,且将值赋值给参数2
            若果键不存在,那么返回值为 false,且不为参数2赋值

        getParamCached(键,存储结果的变量)--提高变量获取效率
            存在,返回 true,且将值赋值给参数2
            若果键不存在,那么返回值为 false,且不为参数2赋值

        getParamNames(std::vector)
            获取所有的键,并存储在参数 vector 中 

        hasParam(键)
            是否包含某个键,存在返回 true,否则返回 false

        searchParam(参数1,参数2)
            搜索键,参数1是被搜索的键,参数2存储搜索结果的变量

//删除
    ros::param ----- 与 NodeHandle 类似

    ros::NodeHandle
        deleteParam("键")
        根据键删除参数,删除成功,返回 true,否则(参数不存在),返回 false

    ros::param
        del("键")
        根据键删除参数,删除成功,返回 true,否则(参数不存在),返回 false

4、常用命令

4.1 rosnode

rosnode ping    测试到节点的连接状态
rosnode list    列出活动节点
rosnode info    打印节点信息
rosnode machine    列出指定设备上节点
rosnode kill    杀死某个节点
rosnode cleanup    清除不可连接的节点

4.2 rostopic

rostopic bw     显示主题使用的带宽
rostopic delay  显示带有 header 的主题延迟
rostopic echo   打印消息到屏幕
rostopic find   根据类型查找主题
rostopic hz     显示主题的发布频率
rostopic info   显示主题相关信息
rostopic list   显示所有活动状态下的主题
rostopic pub    将数据发布到主题
rostopic type   打印主题类型

4.3 rosmsg

rosmsg show    显示消息描述
rosmsg info    显示消息信息
rosmsg list    列出所有消息
rosmsg md5    显示 md5 加密后的消息
rosmsg package    显示某个功能包下的所有消息
rosmsg packages    列出包含消息的功能包

4.4 rosservice

rosservice args 打印服务参数
rosservice call    使用提供的参数调用服务
rosservice find    按照服务类型查找服务
rosservice info    打印有关服务的信息
rosservice list    列出所有活动的服务
rosservice type    打印服务类型
rosservice uri    打印服务的 ROSRPC uri

4.5 rossrv

rossrv show    显示服务消息详情
rossrv info    显示服务消息相关信息
rossrv list    列出所有服务信息
rossrv md5    显示 md5 加密后的服务消息
rossrv package    显示某个包下所有服务消息
rossrv packages    显示包含服务消息的所有包

4.6 rosparam

rosparam set    设置参数
rosparam get    获取参数
rosparam load    从外部文件加载参数
rosparam dump    将参数写出到外部文件
rosparam delete    删除参数
rosparam list    列出所有参数

5、话题通信与服务通信比较

5.1 相同点

二者的实现流程是比较相似的,都是涉及到四个要素:

  • 要素1: 消息的发布方/客户端(Publisher/Client)
  • 要素2: 消息的订阅方/服务端(Subscriber/Server)
  • 要素3: 话题名称(Topic/Service)
  • 要素4: 数据载体(msg/srv)

5.2 区别

ROS学习_第6张图片

ROS节点管理

roROS学习笔记9 —— launch文件 | 码农家园

补充

1、NodeHandle常用成员函数

//创建话题的publisher
ros::Publisher advertise(const string &topic, uint32_t queue_size, bool latch=false);
//第一个参数为发布话题的名称
//第二个是消息队列的最大长度,如果发布的消息超过这个长度而没有被接收,那么就的消息就会出队。通常设为一个较小的数即可。
//第三个参数是是否锁存。某些话题并不是会以某个频率发布,比如/map这个topic,只有在初次订阅或者地图更新这两种情况下,/map才会发布消息。这里就用到了锁存。

//创建话题的subscriber
ros::Subscriber subscribe(const string &topic, uint32_t queue_size, void(*)(M));
//第一个参数是订阅话题的名称
//第二个参数是订阅队列的长度,如果受到的消息都没来得及处理,那么新消息入队,就消息就会出队
//第三个参数是回调函数指针,指向回调函数来处理接收到的消息

//创建服务的server,提供服务
ros::ServiceServer advertiseService(const string &service, bool(*srv_func)(Mreq &, Mres &));
//第一个参数是service名称
//第二个参数是服务函数的指针,指向服务函数。指向的函数应该有两个参数,分别接受请求和响应。

//创建服务的client
ros::ServiceClient serviceClient(const string &service_name, bool persistent=false);
//第一个函数式service名称
//第二个参数用于设置服务的连接是否持续,如果为true,client将会保持与远程主机的连接,这样后续的请求会快一些。通常我们设为flase

//查询某个参数的值
bool getParam(const string &key, std::string &s);
bool getParam (const std::string &key, double &d) const;
bool getParam (const std::string &key, int &i) const;
//从参数服务器上获取key对应的值,已重载了多个类型
//给参数赋值
void setParam (const std::string &key, const std::string &s) const;
void setParam (const std::string &key, const char *s) const;
void setParam (const std::string &key, int i) const;
//给key对应的val赋值,重载了多个类型的val

你可能感兴趣的:(学习)