ROS学习笔记

ROS学习笔记

一、安装ROS系统

  1. 设置安装源

官方默认安装源:

sudo sh -c 'echo "deb http://packages.ros.org/ros/ubuntu $(lsb_release -sc) main" > /etc/apt/sources.list.d/ros-latest.list'

国内清华的安装源

sudo sh -c '. /etc/lsb-release && echo "deb http://mirrors.tuna.tsinghua.edu.cn/ros/ubuntu/ `lsb_release -cs` main" > /etc/apt/sources.list.d/ros-latest.list'

国内中科大的安装源

sudo sh -c '. /etc/lsb-release && echo "deb http://mirrors.ustc.edu.cn/ros/ubuntu/ `lsb_release -cs` main" > /etc/apt/sources.
  1. 设置key
sudo apt-key adv --keyserver 'hkp://keyserver.ubuntu.com:80' --recv-key C1CF6E31E6BADE8868B172B4F42ED6FBAB17C654
  1. 安装ROS
  1. 更新apt
    sudo apt update
  2. 安装ROS
    sudo apt install ros-noetic-desktop-full
  1. 配置环境变量,方便在任意终端中使用 ROS。
echo "source /opt/ros/noetic/setup.bash" >> ~/.bashrc
source ~/.bashrc
  1. 卸载
    如果需要卸载ROS可以调用如下命令:
sudo apt remove ros-noetic-*
  1. ROS测试

ROS 内置了一些小程序,可以通过运行这些小程序以检测 ROS 环境是否可以正常运行
首先启动三个命令行(ctrl + alt + T)
命令行1键入:roscore
命令行2键入:rosrun turtlesim turtlesim_node (此时会弹出图形化界面)
命令行3键入:rosrun turtlesim turtle_teleop_key(在3中可以通过上下左右控制2中乌龟的运动)

二、开发工具准备

1、安装集成终端 Terminal

  1. 安装命令
sudo apt install terminator
  1. Terminal常用快捷键
  • 关于在同一个标签内的操作
Alt+Up                          //移动到上面的终端
Alt+Down                        //移动到下面的终端
Alt+Left                        //移动到左边的终端
Alt+Right                       //移动到右边的终端
Ctrl+Shift+O                    //水平分割终端
Ctrl+Shift+E                    //垂直分割终端
Ctrl+Shift+Right                //在垂直分割的终端中将分割条向右移动
Ctrl+Shift+Left                 //在垂直分割的终端中将分割条向左移动
Ctrl+Shift+Up                   //在水平分割的终端中将分割条向上移动
Ctrl+Shift+Down                 //在水平分割的终端中将分割条向下移动
Ctrl+Shift+S                    //隐藏/显示滚动条
Ctrl+Shift+F                    //搜索
Ctrl+Shift+C                    //复制选中的内容到剪贴板
Ctrl+Shift+V                    //粘贴剪贴板的内容到此处
Ctrl+Shift+W                    //关闭当前终端
Ctrl+Shift+Q                    //退出当前窗口,当前窗口的所有终端都将被关闭
Ctrl+Shift+X                    //最大化显示当前终端
Ctrl+Shift+Z                    //最大化显示当前终端并使字体放大
Ctrl+Shift+N or Ctrl+Tab        //移动到下一个终端
Ctrl+Shift+P or Ctrl+Shift+Tab  //Crtl+Shift+Tab 移动到之前的一个终端
  • 有关各个标签之间的操作
F11                             //全屏开关
Ctrl+Shift+T                    //打开一个新的标签
Ctrl+PageDown                   //移动到下一个标签
Ctrl+PageUp                     //移动到上一个标签
Ctrl+Shift+PageDown             //将当前标签与其后一个标签交换位置
Ctrl+Shift+PageUp               //将当前标签与其前一个标签交换位置
Ctrl+Plus (+)                   //增大字体
Ctrl+Minus (-)                  //减小字体
Ctrl+Zero (0)                   //恢复字体到原始大小
Ctrl+Shift+R                    //重置终端状态
Ctrl+Shift+G                    //重置终端状态并clear屏幕
Super+g                         //绑定所有的终端,以便向一个输入能够输入到所有的终端
Super+Shift+G                   //解除绑定
Super+t                         //绑定当前标签的所有终端,向一个终端输入的内容会自动输入到其他终端
Super+Shift+T                   //解除绑定
Ctrl+Shift+I                    //打开一个窗口,新窗口与原来的窗口使用同一个进程
Super+i                         //打开一个新窗口,新窗口与原来的窗口使用不同的进程

2、安装VSCode

  • 直接到官网下载安装包,双击安装即可
https://code.visualstudio.com/docs?start=true

三、ROS功能包节点创建

  1. 打开终端,创建工作空间(文件夹)
mkdir -p demo_ws/src
  1. 进入工作空间
cd demo_ws
  1. 在工作空间内编译
catkin_make
  1. 进入 src 创建 ros 包并添加依赖
    (或者在VSCode中添加)
cd src
catkin_create_pkg 自定义ROS包名 roscpp rospy std_msgs
  1. 用vscode打开工作空间
code .
  1. 修改.vscode/c_cpp_properties.json配置文件
c++14	   改为	   c++17
  1. ctrl+shift+B设置catkin_make:build编译,点击右侧 ⚙ 设置为默认
  2. 修改.vscode/tasks.json 配置文件
{
// 有关 tasks.json 格式的文档,请参见
    // https://go.microsoft.com/fwlink/?LinkId=733558
    "version": "2.0.0",
    "tasks": [
        {
            "label": "catkin_make:debug", //代表提示的描述性信息
            "type": "shell",  //可以选择shell或者process,如果是shell代码是在shell里面运行一个命令,如果是process代表作为一个进程来运行
            "command": "catkin_make",//这个是我们需要运行的命令
            "args": [],//如果需要在命令后面加一些后缀,可以写在这里,比如-DCATKIN_WHITELIST_PACKAGES=“pac1;pac2”
            "group": {"kind":"build","isDefault":true},
            "presentation": {
                "reveal": "always"//可选always或者silence,代表是否输出信息
            },
            "problemMatcher": "$msCompile"
        }
    ]
}
  1. 右键工作空间下的src,新建功能包 create plumbing
  1. 命名功能包
  2. 添加功能包依赖
    常用依赖:
    roscpp rospy std_msgs
  1. 右键新建的功能包下的src ,hello.cpp文件 ,完成代码编写
  2. 编辑功能包下的配置文件 CMakeLists.txt
#//添加可执行节点	名字可自定义 一般与.cpp文件同名
add_executable(hello src/hello.cpp)

#//添加节点依赖
add_dependencies(hello ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})

#//链接节点与可执行文件
target_link_libraries(hello
  ${catkin_LIBRARIES}
)
  1. 调试运行
  1. Ctrl + Alt + T 打开终端 Terminal 运行命令
    roscore
  2. 打开新的终端 Terminal , 进入工作空间目录下,更新资源路径
    source ./devel/setup.bash
  3. 运行节点
    rosrun 功能包名 文件名

四、ROS通信机制

1、话题通信

概念:以相同的话题名称发布订阅的方式实现不同节点之间数据交互的通信模式。

(1)基本话题通信实现

实现流程:

  1. 编写发布方实现 publisher
  2. 编写订阅方实现 subscriber
  3. 编辑功能包下的配置文件 CMakeLists.txt
  1. 发布方实现
#include "ros/ros.h"
#include "std_msgs/String.h"
#include <sstream>

int main(int argc,char* argv[]){
    //1.init ROS node
    ros::init(argc,argv,"publisher01");
    //2.creat node handler
    ros::NodeHandle nh;
    //3.creat publisher             <data type>        ("Topic",Qeue)
    ros::Publisher pub = nh.advertise<std_msgs::String>("Home",10);
    
    //4.set rate
    ros::Rate pub_rate(10);
    
    //5.publish the messages
    int cnt=0;
    std_msgs::String str_msg;   //the message to be sent
    while(ros::ok())    //whether its exit
    {
        cnt ++;
        // str_msg.data = "weiwei";

        //connect string and number
        std::stringstream string_num;
        string_num<<"weiwei "<<cnt;
        str_msg.data = string_num.str();
        pub.publish(str_msg);   //send the message
        
        ros::spinOnce();

        pub_rate.sleep();
    }

    return 0;
}
  1. 订阅方实现
#include "ros/ros.h"
#include "std_msgs/String.h"


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

}

int main(int argc, char* argv[]){
    //1.init ROS node
    ros::init(argc,argv,"subscriber01");
    //2.creat node handler
    ros::NodeHandle nh;
    //3.creat subsciber               ("Topic",Qeue,function)
    ros::Subscriber sub = nh.subscribe("Home",10,handle_message);

    ros::spin();    //hui diao

   
    return 0;
}
  1. CMakeLists.txt 配置
#//添加可执行节点(映射名  src/文件名)
add_executable(subscribe01 src/subscribe01.cpp)
add_executable(publish01 src/(publish01 .cpp)

#//目标链接库	(映射名)
target_link_libraries(subscribe01 
  ${catkin_LIBRARIES}
)
target_link_libraries(publish01 
  ${catkin_LIBRARIES}
)

(2)自定义话题通信实现

  1. 在工程目录plumbing_pub_sub下创建新文件夹msg
  2. 在新文件夹msg中创建文件->自定义msg。(如:Person.msg)
  3. 编写相关定义类型
string name
int32 age
float32 height

注:msgs只是简单的文本文件,每行具有字段类型和字段名称,可以使用的字段类型有:

int8, int16, int32, int64 (或者无符号类型: uint*)
float32, float64
string
time, duration
other msg files
variable-length array[] and fixed-length array[C]

ROS中还有一种特殊类型:Header,标头包含时间戳和ROS中常用的坐标帧信息。会经常看到msg文件的第一行具有Header标头。

  1. 配置包目录下的 package.xml 文件。添加编译依赖与执行依赖。
  <build_depend>message_generation</build_depend>
  <exec_depend>message_runtime</exec_depend>
  <!-- 
  exce_depend 以前对应的是 run_depend 现在非法
  -->
  1. 配置包目录下的 CMakeList.txt 文件
## 添加“编译”(find_packge)依赖包  message_generation
find_package(catkin REQUIRED COMPONENTS
  roscpp
  rospy
  std_msgs
  message_generation
)
# 需要加入 message_generation,必须有 std_msgs


## 添加自定义msg文件
add_message_files(
  FILES
  # Message1.msg
  # Message2.msg
  Person.msg
)


## 添加标准依赖->标准信息格式  std_msgs
generate_messages(
  DEPENDENCIES
  std_msgs
)


#添加“运行”(catkin_package)依赖包	message_runtime
catkin_package(
#  INCLUDE_DIRS include
#  LIBRARIES demo02_talker_listener
  CATKIN_DEPENDS roscpp rospy std_msgs message_runtime
#  DEPENDS system_lib
)
  1. 编译生成中间文件 ctrl+shift+B

  2. vscode配置。 添加编译生成的头文件地址->使自定义msg可供调用

    在工作空间下.vscode目录下的c_cpp_properties.json中添加头文件路径:
    “/home/fallencity/ROS_Project/demo03_ws/devel/include/**”

{
  "configurations": [
    {
      "browse": {
        "databaseFilename": "${default}",
        "limitSymbolsToIncludedHeaders": false
      },
      "includePath": [
        "/opt/ros/noetic/include/**",
        "/usr/include/**",
        "/home/fallencity/ROS_Project/demo03_ws/devel/include/**"
      ],
      "name": "ROS",
      "intelliSenseMode": "gcc-x64",
      "compilerPath": "/usr/bin/gcc",
      "cStandard": "gnu11",
      "cppStandard": "c++17"
    }
  ],
  "version": 4
}

2、服务通信

概念:以请求响应的方式实现不同节点之间数据交互的通信模式。

(1)自定义srv步骤

  1. 在新建包目录下创建新文件夹srv
  2. 在新文件夹srv中创建文件->自定义srv(如:AddInts.srv)
  3. 编写相关定义类型
#client request
int32 num1
int32 num2
---
#server response
int32 sum
  1. 配置包目录下的package.xml文件,添加编译依赖与执行依赖
  <build_depend>message_generation</build_depend>
  <exec_depend>message_runtime</exec_depend>
  <!-- 
  exce_depend 以前对应的是 run_depend 现在非法
  -->
  1. 包目录下的CMakeLists.txt编辑 srv 相关配置
#添加“编译”(find_packge)依赖包  message_generation
find_package(catkin REQUIRED COMPONENTS
  roscpp
  rospy
  std_msgs
  message_generation
)
# 需要加入 message_generation,必须有 std_msgs
#添加自定义srv文件
add_service_files(
  FILES
  AddInts.srv
)
#添加标准依赖->标准信息格式  std_msgs
generate_messages(
  DEPENDENCIES
  std_msgs
)
#添加“运行”(catkin_package)依赖包	message_runtime
catkin_package(
#  INCLUDE_DIRS include
#  LIBRARIES plumbing_pub_sub
 CATKIN_DEPENDS roscpp rospy std_msgs message_runtime
#  DEPENDS system_lib
)
  1. 编译生成中间文件 ctrl+shift+B
  2. vscode配置。 添加编译生成的头文件地址->使自定义srv可供调用
    在工作空间.vscode目录下的c_cpp_properties.json中添加头文件路径
    “/home/fallencity/ROS_Project/demo03_ws/devel/include/**”
{
  "configurations": [
    {
      "browse": {
        "databaseFilename": "${default}",
        "limitSymbolsToIncludedHeaders": false
      },
      "includePath": [
        "/opt/ros/noetic/include/**",
        "/usr/include/**",
        "/home/fallencity/ROS_Project/demo03_ws/devel/include/**"
      ],
      "name": "ROS",
      "intelliSenseMode": "gcc-x64",
      "compilerPath": "/usr/bin/gcc",
      "cStandard": "gnu11",
      "cppStandard": "c++17"
    }
  ],
  "version": 4
}

(2)自定义srv调用(C++)

  1. 服务方实现 server.cpp
#include "ros/ros.h"
#include "plumbing_server_client/AddInts.h"


bool handle_numbers(plumbing_server_client::AddInts::Request &request,     //request messages
                    plumbing_server_client::AddInts::Response &response){   //response messages
    //1.handle the request
    int32_t num1 = request.num1;
    int32_t num2 = request.num2;
    ROS_INFO("num1=%d,num2=%d",num1,num2);
    //2.handle the response
    int32_t sum = num1+num2;
    ROS_INFO("sum=%d",sum);
    response.sum = sum;
    return true;
}


int main(int argc, char* argv[]){  
    //1.init ROS node
    ros::init(argc, argv, "company");
    //2.creat node handler
    ros::NodeHandle nh;
    //3.creat server             			        ("Topic",func)
    ros::ServiceServer server = nh.advertiseService("AddInts",handle_numbers);
    ROS_INFO("server on");

    ros::spin();
    return 0;
}
  1. 客户端实现 client
#include "ros/ros.h"
#include "plumbing_server_client/AddInts.h"

int main(int argc, char* argv[]){

    //get the order's parameters
    if(argc!=3){    // client   num1    num2    --->    3
        ROS_INFO("the number of parameter is incorrect");
        return -1;
    }

    //1.init ROS node
    ros::init(argc, argv, "me");
    //2.creat node handler
    ros::NodeHandle nh;
    //3.creat client                                         <data type>        ("Topic")
    ros::ServiceClient client = nh.serviceClient<plumbing_server_client::AddInts>("AddInts");

    //wait for the server    	hang up the client	   choose one of them
    client.waitForExistence();
    // ros::service::waitForService("AddInts");


//4.Submit a Request
    plumbing_server_client::AddInts ai; //creat a AddInts type request
    //4.1 organize the request
    ai.request.num1 = atoi(argv[1]);
    ai.request.num2 = atoi(argv[2]);
    //4.2 handle the response
    bool flag = client.call(ai);

    if(flag){
        ROS_INFO("Request executed successfully");
        ROS_INFO("sum: %d", ai.response.sum);
    }else{
        ROS_INFO("Request executed failure");
    }
    return 0;
}
  1. 包目录下的 CMakeList.txt 文件配置
#//添加可执行节点(映射名  src/文件名)
add_executable(server src/server.cpp)
add_executable(client src/client.cpp)

#//目标链接库	(映射名)
target_link_libraries(server
  ${catkin_LIBRARIES}
)
target_link_libraries(client
  ${catkin_LIBRARIES}
)

#//添加依赖关系
# add_dependencies(${PROJECT_NAME}_node ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})	
#//这是系统提示注释,下面才是添加内容
add_dependencies(server ${PROJECT_NAME}_gencpp)
add_dependencies(client ${PROJECT_NAME}_gencpp)

3、参数服务器

概念:以共享的方式实现不同节点之间数据交互的通信模式。	类似全局变量。

(1)设置参数 Set Parameters

#include "ros/ros.h"

int main(int argc,char* argv[]){
    //1.init ROS node
    ros::init(argc, argv, "set_parameter_cpp");
    //2.creat node handler
    ros::NodeHandle nh;

    /*
    Solution 1: 
    ros::NodeHande
        set_Parameter()
    */
    nh.setParam("name","Explore");  //(key,value)
    nh.setParam("radius",0.15);
    /*
    Solution 2:
    ros::para
            set()
    */
    ros::param::set("type_param","xiaobai");
    ros::param::set("radius_param",0.2);

    /*
    change parameter
    */
    nh.setParam("radius",0.5);
    ros::param::set("radius_param",0.55);
    return 0;
}

(2)删除参数 Delete Parameters

#include "ros/ros.h"

int main(int argc,char* argv[]){
    //1.init ROS node
    ros::init(argc, argv, "get_parameter_cpp");
    //2.creat node handler
    ros::NodeHandle nh;

    /*  Solution 1:   ros::NodeHande      */
    bool flag1 = nh.deleteParam("radius");      //(key)
    if(flag1){
        ROS_INFO("radius delete success");
    }else{
        ROS_INFO("radius delete failed");
    }

    /*  Solution 2:   ros::param  */
    bool flag2 = ros::param::del("radius_param");
    if(flag2){
        ROS_INFO("radius_param delete success");
    }else{
        ROS_INFO("radius_param delete failed");
    }

    return 0;
}

(3)获取参数 Get Parameters

/*  Solution 1:   ros::NodeHande	*/
#include "ros/ros.h"

int main(int argc,char* argv[]){
    //1.init ROS node
    ros::init(argc, argv, "get_parameter_cpp");
    //2.creat node handler
    ros::NodeHandle nh;

    //1.param(key,initial value)    return a value with the key value "radius",if error,return 0.8
    double radius = nh.param("radius",0.8);
    ROS_INFO("radius = %2f",radius);

    //2.getParam(key, store)  return true: store=value ;   return false: store=store
    double radius2=0.0;
    bool err = nh.getParam("radius",radius2);
    if(err){
        ROS_INFO("get_param successful. radius = %2f",radius2);
    }else{
        ROS_INFO("get_param failed");
    }

    //3.getParamCached(key, store)     return true: store=value ;   return false: store=store ;    (as same as get_Param)
    double radius3=0.0;
    err = nh.getParamCached("radius",radius3);
    if(err){
        ROS_INFO("get_paramCached successful. radius = %2f",radius3);
    }else{
        ROS_INFO("get_paramCached failed");
    }

    //4.getParamNames(keys)     Gets all the keys and stores them in the parameter keys
    std::vector<std::string> keys;
    for(auto &key:keys){
        ROS_INFO("key = %s",key.c_str());
    }

    //5.hasParam(key)   if key exist, return true   else return false
    bool flag = nh.hasParam("radius");
    if(flag){
        ROS_INFO("radius exist");
    }else{
        ROS_INFO("radius not exist");
    }

    //6.searchParam(key,store)      search the key, if it exists, store=key
    std::string key;
    nh.searchParam("radius",key);
    ROS_INFO("%s",key.c_str());

    return 0;
}
/*  Solution 2:   ros::param         almost as same as Solution 1   */
#include "ros/ros.h"

int main(int argc,char* argv[]){
    //1.init ROS node
    ros::init(argc, argv, "get_parameter_cpp");
    //2.creat node handler
    ros::NodeHandle nh;

    ROS_INFO("++++++++++++++++++++++++++++++++++++++++");
    int res3 = ros::param::param("param_int",20); //存在
    int res4 = ros::param::param("param_int2",20); // 不存在返回默认
    ROS_INFO("param获取结果:%d,%d",res3,res4);

    // getParam 函数
    int param_int_value;
    double param_double_value;
    bool param_bool_value;
    std::string param_string_value;
    std::vector<std::string> param_stus;
    std::map<std::string, std::string> param_friends;

    ros::param::get("param_int",param_int_value);
    ros::param::get("param_double",param_double_value);
    ros::param::get("param_bool",param_bool_value);
    ros::param::get("param_string",param_string_value);
    ros::param::get("param_vector",param_stus);
    ros::param::get("param_map",param_friends);

    ROS_INFO("getParam获取的结果:%d,%.2f,%s,%d",
            param_int_value,
            param_double_value,
            param_string_value.c_str(),
            param_bool_value
            );
    for (auto &&stu : param_stus)
    {
        ROS_INFO("stus 元素:%s",stu.c_str());        
    }

    for (auto &&f : param_friends)
    {
        ROS_INFO("map 元素:%s = %s",f.first.c_str(), f.second.c_str());
    }

    // getParamCached()
    ros::param::getCached("param_int",param_int_value);
    ROS_INFO("通过缓存获取数据:%d",param_int_value);

    //getParamNames()
    std::vector<std::string> param_names2;
    ros::param::getParamNames(param_names2);
    for (auto &&name : param_names2)
    {
        ROS_INFO("名称解析name = %s",name.c_str());        
    }
    ROS_INFO("----------------------------");

    ROS_INFO("存在 param_int 吗? %d",ros::param::has("param_int"));
    ROS_INFO("存在 param_intttt 吗? %d",ros::param::has("param_intttt"));

    std::string key;
    ros::param::search("param_int",key);
    ROS_INFO("搜索键:%s",key.c_str());

    return 0;
}

五、ROS常用命令

1、rosnode 节点相关

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

2、rostopic 话题相关

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

3、rosservice 参数服务器相关

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

4、rosmsg 信息相关

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

5、rossrv 服务类型信息相关

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

6、rosparam 使用YAML编码文件在参数服务器上获取和设置ROS参数

rosparam set ${name value}  设置参数
rosparam get ${name}   		获取参数
rosparam load ${xxx.yaml}   从外部文件加载参数
rosparam dump ${yyy.yaml}   将参数写出到外部文件
rosparam delete ${name}     删除参数
rosparam list    列出所有参数

六、ROS运行管理

1、C++自定义头文件

(1)在功能包下的 include/功能包名 目录下新建头文件 myhead.h

#ifndef _MYHEAD_H
#define _MYHEAD_H

namespace myhead_ns{

class HelloWorld {
public:
    void run();
};

}
#endif

(2)工作空间.vscode 下 c_cpp_properties.json 文件中添加头文件路径

	"/home/用户/工作空间/src/功能包/include/**"
      "includePath": [
        "/opt/ros/noetic/include/**",
        "/usr/include/**",
        "/home/fallencity/ROS_Project/demo03_ws/devel/include/**",
        "/home/fallencity/ROS_Project/demo03_ws/src/plumbing_head/include/**"
      ],

(3)编辑可执行文件

#include "ros/ros.h"
#include "test_head/myhead.h"

namespace myhead_ns {

void HelloWorld::run(){
    ROS_INFO("HelloWorld!");
}

}

(4)配置包目录下CMakeLists.txt文件

## 取消注释include
include_directories(
include
  ${catkin_INCLUDE_DIRS}
)

## 声明C++add_library(myhead  		##给自定义库取名字 
  include/test_head_src/haha.h	## 添加.h文件
  src/haha.cpp			## 添加.cpp文件
)
## 添加自定义库名字于依赖中
add_dependencies(myhead${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})

## 链接库名字
target_link_libraries(myhead
  ${catkin_LIBRARIES}
)



#可执行文件配置:

## 添加可执行文件
add_executable(myheadsrc/myhead.cpp)

## 添加可执行文件依赖
add_dependencies(myhead${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})

## 链接可执行文件
#此处需要添加之前设置的 head 库
target_link_libraries(myhead
  head
  ${catkin_LIBRARIES}
)

(5)资源调用

#include "ros/ros.h"
#include "test_head_src/myhead.h"

int main(int argc, char *argv[])
{
    ros::init(argc,argv,"us_myhead");
    myhead_ns::HelloWorld helloworld;
    helloworld.run();
    return 0;
}

2、元功能包

(1)概念

MetaPackage是Linux的一个文件管理系统的概念。
是ROS中的一个虚包,里面没有实质性的内容,但是它依赖了其他的软件包,通过这种方法可以把其他包组合起来,
我们可以认为它是一本书的目录索引,告诉我们这个包集合中有哪些子包,并且该去哪里下载。

(2)配置

	1. 新建功能包,修改功能包目录下 package.xml 文件
 <exec_depend>被集成的功能包</exec_depend>
 .....
 <export>
   <metapackage />
 </export>
	2. 修改功能包目录下 CMakeLists.txt 文件	注意:CMakeLists.txt 中不可以有换行。
cmake_minimum_required(VERSION 3.0.2)
project(demo)
find_package(catkin REQUIRED)
catkin_metapackage()

3、Launch文件管理ROS节点

(1)launch标签

launch标签是所有 launch 文件的根标签,充当其他标签的容器

属性:
1. deprecated = “弃用声明”
告知用户当前 launch 文件已经弃用

<launch deprecated = "this launch had abandoned">
	<node pkg="turtlesim" type="turtlesim_node"     name="myTurtle" output="screen" />
	<node pkg="turtlesim" type="turtle_teleop_key"  name="myTurtleContro" output="screen" />
</launch>

(2)node标签

用于指定 ROS 节点,是最常见的标签。
需要注意的是: roslaunch 命令不能保证按照 node 的声明顺序来启动节点(节点的启动是多进程的)

属性:
1. pkg=“包名”
节点所属的功能包
2. type=“nodeType”
节点类型(与之相同名称的可执行文件)
3. name=“nodeName”
节点名称(在 ROS 网络拓扑中节点的名称)
4. args=“xxx xxx xxx” (可选)
将参数传递给节点
5. machine=“机器名”
在指定机器上启动节点
6. respawn=“true | false” (可选)
如果节点退出,是否自动重启
7. respawn_delay=" N" (可选)
如果 respawn 为 true, 那么延迟 N 秒后启动节点
8. required=“true | false” (可选)
该节点是否必须,如果为 true,那么如果该节点退出,将杀死整个 roslaunch
9. ns=“xxx” (可选)
在指定命名空间 xxx 中启动节点
10. clear_params=“true | false” (可选)
在启动前,删除节点的私有空间的所有参数
11. output=“log | screen” (可选)
日志发送目标,可以设置为 log 日志文件,或 screen 屏幕,默认是 log

(3)include标签

将另一个 xml 格式的 launch 文件导入到当前文件,在当前launch文件中运行导入的launch文件

属性:
1. file=“$(find 包名)/xxx/xxx.launch”
要包含的文件路径

例:<include file="$(find launch_basic)/launch/start_turtle.launch"/>
2. ns=“namespace” (可选)
在指定命名空间导入文件

(4)remap标签 (话题通信相关)

用于话题重命名(node子节点)

属性:
1. from=“original_name”
原始话题名称
2. to=“target_name”
目标名称

例:

<launch>
	<node pkg="turtlesim" type="turtlesim_node"     name="myTurtle" output="screen">
		<remap from="/turtle1/cmd_vel"  to="/cmd_vel" />
	</node>
	<node pkg="turtlesim" type="turtle_teleop_key"  name="my_key" output="screen" />
</launch>

(5)param标签 (参数服务器相关)

主要用于在参数服务器上设置参数,参数源可以在标签中通过 value 指定,
也可以通过外部文件加载,在标签中时,相当于私有命名空间。

属性:
1. name=“命名空间/参数名”
参数名称,可以包含命名空间
2. value=“xxx” (可选)
定义参数值,如果此处省略,必须指定外部文件作为参数源
3. type=“str | int | double | bool | yaml” (可选)
指定参数类型,如果未指定,roslaunch 会尝试确定参数类型,规则如下:
①如果包含 ‘.’ 的数字解析未浮点型,否则为整型
②"true" 和 “false” 是 bool 值(不区分大小写)
③其他是字符串

例:
格式Ⅰ(launch下,node外):

<launch>
	<param name="param_A"  type="int"  value="100" />
	<node pkg="turtlesim" type="turtlesim_node"     name="myTurtle" output="screen">
		<remap from="/turtle1/cmd_vel"  to="/cmd_vel" />
	</node>
	<node pkg="turtlesim" type="turtle_teleop_key"  name="my_key" output="screen" />
</launch>

格式Ⅱ(node下) 参数在节点命名空间下

<launch>
	<node pkg="turtlesim" type="turtlesim_node"     name="myTurtle" output="screen">
		<remap from="/turtle1/cmd_vel"  to="/cmd_vel" />
		<param name="param_B"  type="flaot"  value="10.23" />
	</node>
	<node pkg="turtlesim" type="turtle_teleop_key"  name="my_key" output="screen" />
</launch>

(6)rosparam标签 (参数服务器相关)

可以从 YAML 文件导入参数,或将参数导出到 YAML 文件,
也可以用来删除参数,标签在标签中时被视为私有(在节点命名空间下)。

属性:
1. command=“load | dump | delete” (可选,默认 load)
加载、导出或删除参数

例:

<launch>
	<node pkg="turtlesim" type="turtlesim_node"     name="myTurtle" output="screen">
		<!-- 导出参数数据 -->
		<rosparam  command="dump"  file="$(find launch_basic"/launch/params_out.yaml" />
		<!-- 删除参数-->
		<rosparam  command="delete"  param="bg_B" />
	</node>
	<node pkg="turtlesim" type="turtle_teleop_key"  name="my_key" output="screen" />
</launch>

2. file=“$(find xxxxx)/xxx/yyy…”
加载或导出到的 yaml 文件
3. param=“参数名称”
ns=“命名空间” (可选)

例: (导入参数数据)

格式Ⅰ(launch下,node外):

<launch>
	<param name="param_A"  type="int"  value="100" />
	<rosparam  command="load"  file="$(find launch_basic"/launch/params.yaml" />
	<node pkg="turtlesim" type="turtlesim_node"     name="myTurtle" output="screen">
		<remap from="/turtle1/cmd_vel"  to="/cmd_vel" />
	</node>
	<node pkg="turtlesim" type="turtle_teleop_key"  name="my_key" output="screen" />
</launch>

格式Ⅱ(node下) 参数在节点命名空间下(私有)

<launch>
	<node pkg="turtlesim" type="turtlesim_node"     name="myTurtle" output="screen">
		<remap from="/turtle1/cmd_vel"  to="/cmd_vel" />
		<param name="param_B"  type="flaot"  value="10.23" />
		<rosparam  command="load"  file="$(find launch_basic"/launch/params.yaml" />
	</node>
	<node pkg="turtlesim" type="turtle_teleop_key"  name="my_key" output="screen" />
</launch>

注:执行优先级高于节点,如需使用,最好单独放在一个launch文件中

(7)group标签

对节点分组,具有 ns 属性,可以让节点归属某个命名空间

属性:
1. ns=“名称空间” (可选)

2. clear_params=“true | false” (可选)
启动前,是否删除组名称空间的所有参数(慎用…此功能危险)

例:

<launch>
	<group ns="first_turtle">
		<node pkg="turtlesim" type="turtlesim_node"     name="myTurtle" output="screen" />
		<node pkg="turtlesim" type="turtle_teleop_key"  name="myTurtleContro" output="screen" />
	</group>
	<group ns="second_turtle">
		<node pkg="turtlesim" type="turtlesim_node"     name="myTurtle" output="screen" />
		<node pkg="turtlesim" type="turtle_teleop_key"  name="myTurtleContro" output="screen" />
	</group>
</launch>

(8)arg标签

用于动态传参,类似于函数的参数,函数内可以对其修改调用,可以增强launch文件的灵活性

属性:

  1. name=“参数名称”
  2. default=“默认值” (可选)
  3. value=“数值” (可选)
    不可以与 default 并存
  4. doc=“描述”
    参数说明

例:

<launch>
	<arg name="car_length" defalt="0.55"  />
	<param name="param_A"  value="$(arg car_length)" />
	<param name="param_B"  value="$(arg car_length)" />
	<param name="param_C"  value="$(arg car_length)" />
</launch>

(9)调用launch文件

命令:roslaunch 包名 xxx.launch

执行launch文件时,可直接调用命令修改参数
roslaunch launch_basic arg.launch car_length:=0.6 #修改参数值为0.6

注意:roslaunch 命令执行launch文件时,首先会判断是否启动了 roscore,如果启动了,则不再启动,否则,会自动调用 roscore

4、重名解决办法

  1. 添加命名空间 (命名前缀)
  2. 名称重映射 (起别名)

(1)节点重名

1. 命令行rosrun设置

添加命名空间(注意有两个_)
rosrun 包名 节点名 __ns:=新名称
例如:rosrun turtlesim turtlesim_node __ns:=/xxx

重映射(不适用于Python)
rosrun 包名 节点名 __name:=新名称
例如:rosrun turtlesim turtlesim_node __name:=t1
或者:rosrun turtlesim turtlesim_node /turtlesim:=t1

两个都用
rosrun 包名 节点名 __ns:=新名称 __name:=新名称
例如:rosrun turtlesim turtlesim_node __ns:=/xxx __name:=tn

2. launch文件设置

添加命名空间
例如:<node pkg="turtlesim" type="turtlesim_node" ns="good" />

重映射
例如:<node pkg="turtlesim" type="turtlesim_node" name="t2" />

两个都用
例如:<node pkg="turtlesim" type="turtlesim_node" name="t1" ns="hello"/>

注意:在 node 标签中,name 属性是必须的,ns 可选。

3. C++编码设置

//添加命名空间
 std::map<std::string, std::string> map;
 map["__ns"] = "xxxx";
 ros::init(map,"wangqiang");
//重映射	调用初始化API时,加入映射名
ros::init(argc,argv,"zhangsan",ros::init_options::AnonymousName);

(2)话题重名

1. 命令行rosrun设置

rorun 包名 节点名 话题名:=节点命名空间/新话题名称
修改话题名称或将话题置于节点命名空间下
例如:rosrun turtlesim turtlesim_node /turtle1/cmd_vel:=node_name/cmd_vel

2. launch文件设置

<!--添加命名空间/新话题名称-->
<node pkg="xxx" type="xxx" name="xxx">
	<remap from="original_topic" to="namspace/new_topic" />
</node>

3. C++编码设置
话题的名称与节点的命名空间、节点的名称是有一定关系的,话题名称大致可以分为三种类型:

全局(话题参考ROS系统,与节点命名空间平级)
格式:以 / 开头的名称,和节点名称无关
示例1:ros::Publisher pub = nh.advertise<std_msgs::String>("/chatter",1000);
结果1:/chatter
示例2:ros::Publisher pub = nh.advertise<std_msgs::String>("/chatter/money",1000);
结果2:/chatter/money

相对(话题参考的是节点的命名空间,与节点名称平级)
格式:非 / 开头的名称,参考命名空间(与节点名称平级)来确定话题名称
示例1:ros::Publisher pub = nh.advertise<std_msgs::String>("chatter",1000);
结果1:xxx/chatter
示例2:ros::Publisher pub = nh.advertise<std_msgs::String>("chatter/money",1000);
结果2:xxx/chatter/money

私有(话题参考节点名称,是节点名称的子级)
格式:以~开头的名称

	//	示例1:
	ros::NodeHandle nh("~");
	ros::Publisher pub = nh.advertise<std_msgs::String>("chatter",1000);
	//	结果1:/xxx/hello/chatter


	//	示例2:
	ros::NodeHandle nh("~");
	ros::Publisher pub = nh.advertise<std_msgs::String>("chatter/money",1000)
	//	结果2:/xxx/hello/chatter/money
	//	PS:当使用~,而话题名称有时/开头时,那么话题名称是绝对的(全局话题优先级更高)


	//	示例3:
	ros::NodeHandle nh("~");
	ros::Publisher pub = nh.advertise<std_msgs::String>("/chatter/money",1000);
	//	结果3:/chatter/money

(3)参数重名

1. 命令行rosrun设置

rosrun 包名 节点名称 _参数名:=参数值
启动节点时设置参数
例如:rosrun turtlesim turtlesim_node _A:=100

2. launch文件设置
在 node 标签外设置的参数是全局性质的,参考的是 /
在 node 标签中设置的参数是私有性质的,参考的是 /命名空间/节点名称。

<!--添加命名空间/新话题名称-->
<launch>
	<param name="p1" value="100" />				#全局
	<node pkg="turtlesim" type="turtlesim_node" name="t1">
		<param name="p2" value="100" />			#私有
	</node>
</launch>

3. C++编码设置
在 C++ 中,可以使用 ros::param 或者 ros::NodeHandle 来设置参数。

//ros::param设置参数
ros::param::set("/set_A",100); //全局,和命名空间以及节点名称无关
ros::param::set("set_B",100); //相对,参考命名空间
ros::param::set("~set_C",100); //私有,参考命名空间与节点名称

//ros::NodeHandle设置参数
ros::NodeHandle nh;
nh.setParam("/nh_A",100); //全局,和命名空间以及节点名称无关
nh.setParam("nh_B",100); //相对,参考命名空间
ros::NodeHandle nh_private("~");
nh_private.setParam("nh_C",100);//私有,参考命名空间与节点名称

七、ROS常用组件

1、TF坐标变换

(1)坐标msg消息概念

  1. geometry_msgs/TransformStamped

命令行键入:rosmsg info geometry_msgs/TransformStamped

std_msgs/Header header                     #头信息
  uint32 seq                                #|-- 序列号
  time stamp                                #|-- 时间戳
  string frame_id                            #|-- 坐标 ID
string child_frame_id                    #子坐标系的 id
geometry_msgs/Transform transform        #坐标信息
  geometry_msgs/Vector3 translation        #偏移量
    float64 x                                #|-- X 方向的偏移量
    float64 y                                #|-- Y 方向的偏移量
    float64 z                                #|-- Z 方向上的偏移量
  geometry_msgs/Quaternion rotation        #四元数
    float64 x                                
    float64 y                                
    float64 z                                
    float64 w

四元数用于表示坐标的相对姿态

  1. geometry_msgs/PointStamped

命令行键入:rosmsg info geometry_msgs/PointStamped

std_msgs/Header header                    #头
  uint32 seq                                #|-- 序号
  time stamp                                #|-- 时间戳
  string frame_id                            #|-- 所属坐标系的 id
geometry_msgs/Point point                #点坐标
  float64 x                                    #|-- x y z 坐标
  float64 y
  float64 z

(2)静态坐标变换C++实现

举例:
项目功能包依赖于 tf2、tf2_ros、tf2_geometry_msgs、roscpp rospy std_msgs geometry_msgs

发布方实现

/* 
    静态坐标变换发布方:
        发布关于 laser 坐标系的位置信息 

    实现流程:
        1.包含头文件
        2.初始化 ROS 节点
        3.创建静态坐标转换广播器
        4.创建坐标系信息
        5.广播器发布坐标系信息
        6.spin()
*/


// 1.包含头文件
#include "ros/ros.h"
#include "tf2_ros/static_transform_broadcaster.h"
#include "geometry_msgs/TransformStamped.h"
#include "tf2/LinearMath/Quaternion.h"

int main(int argc, char *argv[])
{
    setlocale(LC_ALL,"");
    // 2.初始化 ROS 节点
    ros::init(argc,argv,"static_brocast");
    // 3.创建静态坐标转换广播器
    tf2_ros::StaticTransformBroadcaster broadcaster;
    // 4.创建坐标系信息
    geometry_msgs::TransformStamped ts;
    //----设置头信息
    ts.header.seq = 100;
    ts.header.stamp = ros::Time::now();
    ts.header.frame_id = "base_link";
    //----设置子级坐标系
    ts.child_frame_id = "laser";
    //----设置子级相对于父级的偏移量
    ts.transform.translation.x = 0.2;
    ts.transform.translation.y = 0.0;
    ts.transform.translation.z = 0.5;
    //----设置四元数:将 欧拉角数据转换成四元数
    tf2::Quaternion qtn;
    qtn.setRPY(0,0,0);
    ts.transform.rotation.x = qtn.getX();
    ts.transform.rotation.y = qtn.getY();
    ts.transform.rotation.z = qtn.getZ();
    ts.transform.rotation.w = qtn.getW();
    // 5.广播器发布坐标系信息
    broadcaster.sendTransform(ts);
    ros::spin();
    return 0;
}

订阅方实现

/*  
    订阅坐标系信息,生成一个相对于 子级坐标系的坐标点数据,转换成父级坐标系中的坐标点

    实现流程:
        1.包含头文件
        2.初始化 ROS 节点
        3.创建 TF 订阅节点
        4.生成一个坐标点(相对于子级坐标系)
        5.转换坐标点(相对于父级坐标系)
        6.spin()
*/
//1.包含头文件
#include "ros/ros.h"
#include "tf2_ros/transform_listener.h"
#include "tf2_ros/buffer.h"
#include "geometry_msgs/PointStamped.h"
#include "tf2_geometry_msgs/tf2_geometry_msgs.h" //注意: 调用 transform 必须包含该头文件

int main(int argc, char *argv[])
{
    setlocale(LC_ALL,"");
    // 2.初始化 ROS 节点
    ros::init(argc,argv,"tf_sub");
    ros::NodeHandle nh;
    // 3.创建 TF 订阅节点
    tf2_ros::Buffer buffer;
    tf2_ros::TransformListener listener(buffer);

    ros::Rate r(1);
    while (ros::ok())
    {
    // 4.生成一个坐标点(相对于子级坐标系)
        geometry_msgs::PointStamped point_laser;
        point_laser.header.frame_id = "laser";
        point_laser.header.stamp = ros::Time::now();
        point_laser.point.x = 1;
        point_laser.point.y = 2;
        point_laser.point.z = 7.3;
    // 5.转换坐标点(相对于父级坐标系)
        //新建一个坐标点,用于接收转换结果  
        //--------------使用 try 语句或休眠,否则可能由于缓存接收延迟而导致坐标转换失败------------------------
        try
        {
            geometry_msgs::PointStamped point_base;
            point_base = buffer.transform(point_laser,"base_link");
            ROS_INFO("转换后的数据:(%.2f,%.2f,%.2f),参考的坐标系是:",point_base.point.x,point_base.point.y,point_base.point.z,point_base.header.frame_id.c_str());
        }
        catch(const std::exception& e)
        {
            // std::cerr << e.what() << '\n';
            ROS_INFO("程序异常.....");
        }
        r.sleep();  
        ros::spinOnce();
    }
    return 0;
}

补充1:
当坐标系之间的相对位置固定时,那么所需参数也是固定的: 父系坐标名称、子级坐标系名称、x偏移量、y偏移量、z偏移量、x 翻滚角度、y俯仰角度、z偏航角度,实现逻辑相同,参数不同,ROS 系统就已经封装好了专门的节点,使用方式如下:

rosrun tf2_ros static_transform_publisher x偏移量 y偏移量 z偏移量 z偏航角度 y俯仰角度 x翻滚角度 父级坐标系 子级坐标系

示例:rosrun tf2_ros static_transform_publisher 0.2 0 0.5 0 0 0 /baselink /laser
也建议使用该种方式直接实现静态坐标系相对信息发布。

补充2:
可以借助于rviz显示坐标系关系,具体操作:

新建窗口输入命令:`rviz`
在启动的 rviz 中设置Fixed Frame 为 base_link;
点击左下的 add 按钮,在弹出的窗口中选择 TF 组件,即可显示坐标关系。

(2)动态坐标变换C++实现

举例:
创建项目功能包依赖于 tf2、tf2_ros、tf2_geometry_msgs、roscpp rospy std_msgs geometry_msgs、turtlesim

发布方实现

/*  
    动态的坐标系相对姿态发布(一个坐标系相对于另一个坐标系的相对姿态是不断变动的)

    需求: 启动 turtlesim_node,该节点中窗体有一个世界坐标系(左下角为坐标系原点),乌龟是另一个坐标系,键盘
    控制乌龟运动,将两个坐标系的相对位置动态发布

    实现分析:
        1.乌龟本身不但可以看作坐标系,也是世界坐标系中的一个坐标点
        2.订阅 turtle1/pose,可以获取乌龟在世界坐标系的 x坐标、y坐标、偏移量以及线速度和角速度
        3.将 pose 信息转换成 坐标系相对信息并发布

    实现流程:
        1.包含头文件
        2.初始化 ROS 节点
        3.创建 ROS 句柄
        4.创建订阅对象
        5.回调函数处理订阅到的数据(实现TF广播)
            5-1.创建 TF 广播器
            5-2.创建 广播的数据(通过 pose 设置)
            5-3.广播器发布数据
        6.spin
*/
// 1.包含头文件
#include "ros/ros.h"
#include "turtlesim/Pose.h"
#include "tf2_ros/transform_broadcaster.h"
#include "geometry_msgs/TransformStamped.h"
#include "tf2/LinearMath/Quaternion.h"

void doPose(const turtlesim::Pose::ConstPtr& pose){
    //  5-1.创建 TF 广播器
    static tf2_ros::TransformBroadcaster broadcaster;
    //  5-2.创建 广播的数据(通过 pose 设置)
    geometry_msgs::TransformStamped tfs;
    //  |----头设置
    tfs.header.frame_id = "world";
    tfs.header.stamp = ros::Time::now();
    //  |----坐标系 ID
    tfs.child_frame_id = "turtle1";
    //  |----坐标系相对信息设置
    tfs.transform.translation.x = pose->x;
    tfs.transform.translation.y = pose->y;
    tfs.transform.translation.z = 0.0; // 二维实现,pose 中没有z,z 是 0
    //  |--------- 四元数设置
    tf2::Quaternion qtn;
    qtn.setRPY(0,0,pose->theta);
    tfs.transform.rotation.x = qtn.getX();
    tfs.transform.rotation.y = qtn.getY();
    tfs.transform.rotation.z = qtn.getZ();
    tfs.transform.rotation.w = qtn.getW();
    
    //  5-3.广播器发布数据
    broadcaster.sendTransform(tfs);
}

int main(int argc, char *argv[])
{
    setlocale(LC_ALL,"");
    // 2.初始化 ROS 节点
    ros::init(argc,argv,"dynamic_tf_pub");
    // 3.创建 ROS 句柄
    ros::NodeHandle nh;
    // 4.创建订阅对象
    ros::Subscriber sub = nh.subscribe<turtlesim::Pose>("/turtle1/pose",1000,doPose);
    //     5.回调函数处理订阅到的数据(实现TF广播)
    //        
    // 6.spin
    ros::spin();
    return 0;
}

订阅方实现

//1.包含头文件
#include "ros/ros.h"
#include "tf2_ros/transform_listener.h"
#include "tf2_ros/buffer.h"
#include "geometry_msgs/PointStamped.h"
#include "tf2_geometry_msgs/tf2_geometry_msgs.h" //注意: 调用 transform 必须包含该头文件

int main(int argc, char *argv[])
{
    setlocale(LC_ALL,"");
    // 2.初始化 ROS 节点
    ros::init(argc,argv,"dynamic_tf_sub");
    ros::NodeHandle nh;
    // 3.创建 TF 订阅节点
    tf2_ros::Buffer buffer;
    tf2_ros::TransformListener listener(buffer);

    ros::Rate r(1);
    while (ros::ok())
    {
    // 4.生成一个坐标点(相对于子级坐标系)
        geometry_msgs::PointStamped point_laser;
        point_laser.header.frame_id = "turtle1";
        point_laser.header.stamp = ros::Time();
        point_laser.point.x = 1;
        point_laser.point.y = 1;
        point_laser.point.z = 0;
    // 5.转换坐标点(相对于父级坐标系)
        //新建一个坐标点,用于接收转换结果  
        //--------------使用 try 语句或休眠,否则可能由于缓存接收延迟而导致坐标转换失败------------------------
        try
        {
            geometry_msgs::PointStamped point_base;
            point_base = buffer.transform(point_laser,"world");
            ROS_INFO("坐标点相对于 world 的坐标为:(%.2f,%.2f,%.2f)",point_base.point.x,point_base.point.y,point_base.point.z);
        }
        catch(const std::exception& e)
        {
            // std::cerr << e.what() << '\n';
            ROS_INFO("程序异常:%s",e.what());
        }
        r.sleep();  
        ros::spinOnce();
    }
    return 0;
}

(2)坐标系关系查看

安装tf2_tools坐标系查看工具
sudo apt install ros-noetic-tf2-tools
启动坐标系广播程序之后,运行如下命令
rosrun tf2_tools view_frames.py
会产生类似于下面的日志信息:
[INFO] [1592920556.827549]: Listening to tf data during 5 seconds…
[INFO] [1592920561.841536]: Generating graph in frames.pdf file…
查看当前目录会生成一个 frames.pdf 文件
在这里插入图片描述

2、rosbag 数据的留存与读取

(1)命令行方式

  1. 创建目录保存录制的文件mkdir ./xxx
    进入文件目录cd xxx
  2. 开始录制
    rosbag record -a -O 目标文件

操作小乌龟一段时间,结束录制使用 ctrl + c,在创建的目录中会生成bag文件。

  1. 查看文件
    rosbag info 文件名
  2. 回放文件
    rosbag play 文件名

重启乌龟节点,会发现,乌龟按照录制时的轨迹运动。

(2)C++实现

  1. 写rosbag
#include "ros/ros.h"
#include "rosbag/bag.h"
#include "std_msgs/String.h"

int main(int argc, char *argv[])
{
    ros::init(argc,argv,"bag_write");
    ros::NodeHandle nh;
    //创建bag对象
    rosbag::Bag bag;
    //打开
    bag.open("/home/rosdemo/demo/test.bag",rosbag::BagMode::Write);
    //写
    std_msgs::String msg;
    msg.data = "hello world";
    bag.write("/chatter",ros::Time::now(),msg);
    bag.write("/chatter",ros::Time::now(),msg);
    bag.write("/chatter",ros::Time::now(),msg);
    bag.write("/chatter",ros::Time::now(),msg);
    //关闭
    bag.close();
    return 0;
}

  1. 读rosbag
#include "ros/ros.h"
#include "rosbag/bag.h"
#include "rosbag/view.h"
#include "std_msgs/String.h"
#include "std_msgs/Int32.h"

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

    //创建 bag 对象
    rosbag::Bag bag;
    //打开 bag 文件
    bag.open("/home/rosdemo/demo/test.bag",rosbag::BagMode::Read);
    //读数据
    for (rosbag::MessageInstance const m : rosbag::View(bag))
    {
        std_msgs::String::ConstPtr p = m.instantiate<std_msgs::String>();
        if(p != nullptr){
            ROS_INFO("读取的数据:%s",p->data.c_str());
        }
    }
    //关闭文件流
    bag.close();
    return 0;
}

3、rqt界面工具

1.安装
sudo apt-get install ros-noetic-rqt
sudo apt-get install ros-noetic-rqt-common-plugins
2. 启动
方式1:rqt
方式2:rosrun rqt_gui rqt_gui

(1)rqt_graph 显示简化节点与话题关系

在这里插入图片描述椭圆形—节点
矩形—话题

(2)rqt_console 显示和过滤日志的图形化插件

//输出各种级别的日志信息
     ROS_DEBUG("Debug message d");
     ROS_INFO("Info message oooooooooooooo");
     ROS_WARN("Warn message wwwww");
     ROS_ERROR("Erroe message EEEEEEEEEEEEEEEEEEEE");
     ROS_FATAL("Fatal message FFFFFFFFFFFFFFFFFFFFFFFFFFFFF");

启动:可以在 rqt 的 plugins 中添加,或者使用rqt_console启动

(3)rqt_plot 图形绘制插件

可以以 2D 绘图的方式绘制发布在 topic 上的数据
启动:调用命令 rqt_plot

(4)rqt_bag 录制和重放 bag 文件的图形化插件

在这里插入图片描述

启动:启动:可以在 rqt 的 plugins 中添加,或者使用rqt_bag启动

八、机器人系统仿真 URDF

1、URDF语法

URDF 不能单独使用,需要结合 Rviz 或 Gazebo
URDF 只是一个文件,需要在 Rviz 或 Gazebo 中渲染成图形化的机器人模型

(1)robot标签

urdf 中为了保证 xml 语法的完整性,使用了robot标签作为根标签,所有的 link 和 joint 以及其他标签都必须包含在 robot 标签内,在该标签内可以通过 name 属性设置机器人模型的名称

属性:

  1. name=“robot_name”
    指定机器人模型的名称

(2)link标签

用于描述机器人某个部件(也即刚体部分)的外观和物理属性,比如: 机器人底座、轮子、激光雷达、摄像头…每一个部件都对应一个 link, 在 link 标签内,可以设计该部件的形状、尺寸、颜色、惯性矩阵、碰撞参数等一系列属性。

属性:

name = "base_link"
为连杆命名

子标签:

visual ---> 描述外观(对应的数据是可视的)
    geometry 设置连杆的形状
        标签1: box(盒状)
            属性:size=长(x) 宽(y) 高(z)
        标签2: cylinder(圆柱)
            属性:radius=半径 length=高度
        标签3: sphere(球体)
            属性:radius=半径
        标签4: mesh(为连杆添加皮肤)
            属性: filename=资源路径(格式:package://<packagename>/<path>/文件)
    origin 设置偏移量与倾斜弧度
        属性1: xyz=x偏移 y便宜 z偏移
        属性2: rpy=x翻滚 y俯仰 z偏航 (单位是弧度)
    metrial 设置材料属性(颜色)
        属性: name
        标签: color
            属性: rgba=红绿蓝权重值与透明度 (每个权重值以及透明度取值[0,1])
collision ---> 连杆的碰撞属性
Inertial ---> 连杆的惯性矩阵

例:

    <link name="base_link">
        <visual>
            <!-- 形状 -->
            <geometry>
                <!-- 长方体的长宽高 -->
                <!-- <box size="0.5 0.3 0.1" /> -->
                <!-- 圆柱,半径和长度 -->
                <!-- <cylinder radius="0.5" length="0.1" /> -->
                <!-- 球体,半径-->
                <!-- <sphere radius="0.3" /> -->
            </geometry>
            <!-- xyz坐标 rpy翻滚俯仰与偏航角度(3.14=180度 1.57=90度) -->
            <origin xyz="0 0 0" rpy="0 0 0" />
            <!-- 颜色: r=red g=green b=blue a=alpha -->
            <material name="black">
                <color rgba="0.7 0.5 0 0.5" />
            </material>
        </visual>
    </link>

(3)joint标签

urdf 中的 joint 标签用于描述机器人关节的运动学和动力学属性,还可以指定关节运动的安全极限,机器人的两个部件(分别称之为 parent link 与 child link)以"关节"的形式相连接

属性:

name ---> 为关节命名
type ---> 关节运动形式
    continuous: 旋转关节,可以绕单轴无限旋转
    revolute: 旋转关节,类似于 continues,但是有旋转角度限制
    prismatic: 滑动关节,沿某一轴线移动的关节,有位置极限
    planer: 平面关节,允许在平面正交方向上平移或旋转
    floating: 浮动关节,允许进行平移、旋转运动
    fixed: 固定关节,不允许运动的特殊关节

子标签:

parent(必需的)
parent link的名字是一个强制的属性:
    link:父级连杆的名字,是这个link在机器人结构树中的名字。
child(必需的)
child link的名字是一个强制的属性:
    link:子级连杆的名字,是这个link在机器人结构树中的名字。
origin
    属性: xyz=各轴线上的偏移量 rpy=各轴线上的偏移弧度。
axis
    属性: xyz用于设置围绕哪个关节轴运动。

例:

<!-- 
    需求: 创建机器人模型,底盘为长方体,
         在长方体的前面添加一摄像头,
         摄像头可以沿着 Z 轴 360 度旋转
 -->
<robot name="mycar">
    <!-- 底盘 -->
    <link name="base_link">
        <visual>
            <geometry>
                <box size="0.5 0.2 0.1" />
            </geometry>
            <origin xyz="0 0 0" rpy="0 0 0" />
            <material name="blue">
                <color rgba="0 0 1.0 0.5" />
            </material>
        </visual>
    </link>

    <!-- 摄像头 -->
    <link name="camera">
        <visual>
            <geometry>
                <box size="0.02 0.05 0.05" />
            </geometry>
            <origin xyz="0 0 0" rpy="0 0 0" />
            <material name="red">
                <color rgba="1 0 0 0.5" />
            </material>
        </visual>
    </link>

    <!-- 关节 -->
    <joint name="camera2baselink" type="continuous">
        <parent link="base_link"/>
        <child link="camera" />
        <!-- 需要计算两个 link 的物理中心之间的偏移量 -->
        <origin xyz="0.2 0 0.075" rpy="0 0 0" />
        <axis xyz="0 0 1" />
    </joint>

</robot>

2、launch文件调用

例:

<launch>
	<param name="robot_description" textfile="$(find urdf_rviz_demo)/urdf/urdf/urdf03_joint.urdf" />
	<node pkg="rviz" type="rviz" name="rviz" args="-d $(find urdf_rviz_demo)/config/helloworld.rviz" /> 
	
	<!-- 添加关节状态发布节点 -->
	<node pkg="joint_state_publisher" type="joint_state_publisher" name="joint_state_publisher" />
	<!-- 添加机器人状态发布节点 -->
	<node pkg="robot_state_publisher" type="robot_state_publisher" name="robot_state_publisher" />
	<!-- 可选:用于控制关节运动的节点 -->
	<node pkg="joint_state_publisher_gui" type="joint_state_publisher_gui" name="joint_state_publisher_gui" />

	<!-- 注意joint_state_publisher节点和joint_state_publisher_gui节点不应重复调用 -->
</launch>

3、base_footprint优化urdf

将初始 link 设置为一个尺寸极小的 link(比如半径为 0.001m 的球体),然后再在初始 link 上添加底盘等刚体,可使得机器人有一个整体的基坐标。
例:

<!-- 设置一个原点(机器人中心点的投影) -->
    <link name="base_footprint">
        <visual>
            <geometry>
                <sphere radius="0.001" />
            </geometry>
        </visual>
    </link>

    <!-- 底盘与原点连接的关节 -->
    <joint name="base_link2base_footprint" type="fixed">
        <parent link="base_footprint" />
        <child link="base_link" />
        <origin xyz="0 0 0.05" />
    </joint>
    <!-- 此处origin设置z轴高度为base_link几何中心距离地面高度-->

4、URDF辅助工具

  1. 辅助工具安装
    sudo apt install liburdfdom-tools
  2. 检查复杂的 urdf 文件是否存在语法问题
    check_urdf test.urdf
  3. 在当前目录下会生成 pdf 文件,查看 urdf 模型结构,显示不同 link 的层级关系
    urdf_to_graphiz test.urdf

5、xacro优化urdf

Xacro 是 XML Macros 的缩写,Xacro 是一种 XML 宏语言,是可编程的 XML。
可以声明变量,可以通过数学运算求解,使用流程控制控制执行顺序,还可以通过类似函数的实现,封装固定的逻辑,将逻辑中需要的可变的数据以参数的方式暴露出去,从而提高代码复用率以及程序的安全性。

注意:

(1)在终端下打开xacro文件,通过调用命令 rosrun xacro xacro xxx.xacro > xxx.urdf 运行节点将xcro文件转换为urdf文件
(2)xacro 提供了可编程接口,类似于计算机语言,包括变量声明调用、函数声明与调用等语法实现。在使用 xacro 生成 urdf 时,根标签robot中必须包含命名空间声明:xmlns:xacro="http://wiki.ros.org/xacro"

  1. 属性与算数运算
    用于封装 URDF 中的一些字段,比如: PAI 值,小车的尺寸,轮子半径 …
    <!-- 属性定义-->
	<xacro:property name="length" value="10" />
	<xacro:property name="width" value="5" />
	
	<!-- 属性调用-->
	${length}
	
	<!-- 算数运算-->
	${length*width+10}

  1. 类似于函数实现,提高代码复用率,优化代码结构,提高安全性
<!--宏定义-->
<xacro:macro name="宏名称" params="参数列表(多参数之间使用空格分隔)">

<!--参数调用格式: ${参数名}-->
<!--宏内容-->

</xacro:macro>


<!--宏调用-->
<xacro:宏名称 参数1=xxx 参数2=xxx/>

示例:

<robot name="mycar" xmlns:xacro="http://wiki.ros.org/xacro">
    <!-- 属性封装 -->
    <xacro:property name="wheel_radius" value="0.0325" />
    <xacro:property name="wheel_length" value="0.0015" />
    <xacro:property name="PI" value="3.1415927" />
    <xacro:property name="base_link_length" value="0.08" />
    <xacro:property name="lidi_space" value="0.015" />

    <!-- 宏 -->
    <xacro:macro name="wheel_func" params="wheel_name flag" >
        <link name="${wheel_name}_wheel">
            <visual>
                <geometry>
                    <cylinder radius="${wheel_radius}" length="${wheel_length}" />
                </geometry>

                <origin xyz="0 0 0" rpy="${PI / 2} 0 0" />

                <material name="wheel_color">
                    <color rgba="0 0 0 0.3" />
                </material>
            </visual>
        </link>

        <!-- 3-2.joint -->
        <joint name="${wheel_name}2link" type="continuous">
            <parent link="base_link"  />
            <child link="${wheel_name}_wheel" />
            <!-- 
                x 无偏移
                y 车体半径
                z z= 车体高度 / 2 + 离地间距 - 车轮半径
            -->
            <origin xyz="0 ${0.1*flag} ${(base_link_length/2+lidi_space-wheel_radius)*-1}" rpy="0 0 0" />
            <axis xyz="0 1 0" />
        </joint>

    </xacro:macro>

    <!--宏调用-->
    <xacro:wheel_func wheel_name="left" flag="1" />
    <xacro:wheel_func wheel_name="right" flag="-1" />
</robot>
  1. 文件包含
    机器人由多部件组成,不同部件封装为单独的 xacro 文件,最后再将不同的文件集成,组合为完整机器人,可以使用文件包含实现多xacro文件的一次性调用。

例:

<robot name="xxx" xmlns:xacro="http://wiki.ros.org/xacro">
      <xacro:include filename="my_base.xacro" />
      <xacro:include filename="my_camera.xacro" />
      <xacro:include filename="my_laser.xacro" />
</robot>
  1. Launch文件直接启动xacro文件
    例:
<launch>
	<!-- 加载robot_description时使用command属性,属性值就是调用 xacro 功能包的 xacro 程序直接解析 xacro 文件。 -->
    <param name="robot_description" command="$(find xacro)/xacro $(find demo01_urdf_helloworld)/urdf/xacro/my_base.urdf.xacro" />

    <node pkg="rviz" type="rviz" name="rviz" args="-d $(find demo01_urdf_helloworld)/config/helloworld.rviz" />
    <node pkg="joint_state_publisher" type="joint_state_publisher" name="joint_state_publisher" output="screen" />
    <node pkg="robot_state_publisher" type="robot_state_publisher" name="robot_state_publisher" output="screen" />
    <node pkg="joint_state_publisher_gui" type="joint_state_publisher_gui" name="joint_state_publisher_gui" output="screen" />

</launch>

6、Arbotix控制器 (只能在rviz中使用)

Arbotix 是一款控制电机、舵机的控制板,并提供相应的 ros 功能包,帮助我们实现机器人在 rviz 中的运动。

  1. 安装Arbotix功能包

方式1:命令行调用
sudo apt-get install ros-novatic-arbotix
如果提示功能包无法定位,请采用方式2。
方式2:源码安装
先从 github 下载源码,然后调用 catkin_make 编译
git clone https://github.com/vanadiumlabs/arbotix_ros.git

  1. 配置文件
    在config中添加arbotix的.yaml文件配置
# 该文件是控制器配置,一个机器人模型可能有多个控制器,比如: 底盘、机械臂、夹持器(机械手)....
# 因此,根 name 是 controller
controllers: {
   # 单控制器设置
   base_controller: {
          #类型: 差速控制器
       type: diff_controller,
       #参考坐标
       base_frame_id: base_footprint, 
       #两个轮子之间的间距
       base_width: 0.2,
       #控制频率
       ticks_meter: 2000, 
       #PID控制参数,使机器人车轮快速达到预期速度
       Kp: 12, 
       Kd: 12, 
       Ki: 0, 
       Ko: 50, 
       #加速限制
       accel_limit: 1.0 
    }
}
  1. 添加launch文件调用arbotix节点,可放在小车建模launch文件中一同启动 control.launch
<launch>
    <param name="robot_description" command="$(find xacro)/xacro $(find urdf_rviz)/urdf/xacro/my_car.xacro" />

    <node pkg="rviz" type="rviz" name="rviz" args="-d $(find urdf_rviz)/config/show_mycar.rviz" />

    <node pkg="joint_state_publisher" type="joint_state_publisher" name="joint_state_publisher" output="screen" />
    <node pkg="robot_state_publisher" type="robot_state_publisher" name="robot_state_publisher" output="screen" />

    <!-- <node pkg="joint_state_publisher_gui" type="joint_state_publisher_gui" name="joint_state_publisher_gui" output="screen" /> -->

<!-- 运行arbotix控制器 -->
    <node name="arbotix" pkg="arbotix_python" type="arbotix_driver" output="screen">
        <rosparam file="$(find urdf_rviz)/config/control.yaml" command="load" />
        <param name="sim" value="true" />
    </node>

</launch>
  1. 运行arbotix
    此时调用 rostopic list 会发现一个熟悉的话题: /cmd_vel
    也就说我们可以发布 cmd_vel 话题消息控制车运动了,该实现策略有多种,可以另行编写节点,或者更简单些可以直接通过如下命令发布消息:
    rostopic pub -r 10 /cmd_vel geometry_msgs/Twist '{linear: {x: 0.2, y: 0, z: 0}, angular: {x: 0, y: 0, z: 0.5}}'

注意: 在rviz中,参考坐标系应选odom,而不是footprint
在rviz中可添加Odometry查看小车运动状态

7、URDF集成Gazebo

依赖包: urdf xacro gazebo_ros gazebo_ros_control gazebo_plugins

在常规urdf文件中,添加以下内容:

  1. 必须使用 collision 标签,因为既然是仿真环境,那么必然涉及到碰撞检测,collision 提供碰撞检测的依据。
<!-- 直接复制visual标签下的内容(除去颜色设置部分) -->
<!-- 与visual同级 -->
<collision>
    <geometry>
        <box size="0.5 0.2 0.1" />
    </geometry>
    <origin xyz="0.0 0.0 0.0" rpy="0.0 0.0 0.0" />
</collision>
  1. 必须使用 inertial 标签,此标签标注了当前机器人某个刚体部分的惯性矩阵,用于一些力学相关的仿真计算。
<!-- 与visual同级 通常采用xacro封装 -->
<!-- 圆柱惯性矩阵 -->
    <xacro:macro name="cylinder_inertial_matrix" params="m r h">
        <inertial>
            <mass value="${m}" />
            <inertia ixx="${m*(3*r*r+h*h)/12}" ixy = "0" ixz = "0"
                iyy="${m*(3*r*r+h*h)/12}" iyz = "0"
                izz="${m*r*r/2}" /> 
        </inertial>
    </xacro:macro>

<!-- 调用-->
<xacro:cylinder_inertial_matrix m="${wheel_m}" r="${wheel_radius}" h="${wheel_length}" />
  1. 颜色设置,也需要重新使用 gazebo 标签标注,因为之前的颜色设置为了方便调试包含透明度,仿真环境下没有此选项。
<!-- 颜色设置  标签与link同级-->
<gazebo reference="link_name">
	<material>Gazebo/Red</material>
</gazebo>
  1. Launch文件设置
<launch>
    <!-- 将 Urdf 文件的内容加载到参数服务器 -->
    <param name="robot_description" textfile="$(find urdf_gazebo)/urdf/hello.urdf" />
    <!-- 或将 xacro文件的内容加载到参数服务器 
    <param name="robot_description" command="$(find xacro)/xacro $(find urdf_gazebo)/urdf/my_car.xacro" />
	 -->
	 
    <!-- 启动 gazebo 
    <include file="$(find gazebo_ros)/launch/empty_world.launch" />
     启动 Gazebo 的仿真环境,当前环境为空环境 -->
    <include file="$(find gazebo_ros)/launch/empty_world.launch">
        <arg name="world_name" value="$(find demo02_urdf_gazebo)/worlds/hello.world" />
    </include>
    <!-- 启动已集成仿真环境  添加arg参数  name="world_name"是固定的-->

    <!-- 在 gazebo 中显示机器人模型 -->
    <node pkg="gazebo_ros" type="spawn_model" name="model" args="-urdf -model mycar -param robot_description"  />
    <!-- 
    在 Gazebo 中加载一个机器人模型,该功能由 gazebo_ros 下的 spawn_model 提供:
    -urdf 加载的是 urdf 文件
    -model mycar 模型名称是 mycar
    -param robot_description 从参数 robot_description 中载入模型
    -x 模型载入的 x 坐标
    -y 模型载入的 y 坐标
    -z 模型载入的 z 坐标
-->
</launch>

注意:打开Gazebo后发现模型透明不可见,可通过选择菜单行中的Camera下的Orthographic使机器人显形。

参考:ROS入门教程

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

梦星越

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值