一、ROS基础

本文章所用例程:

链接:https://pan.baidu.com/s/1QBhcQgk525YflBug8SmjOg 
提取码:c93y 
 

1、创建工作空间

1)何为工作空间(workspace):用于存放工程文件

src:代码空间,放置功能包源码;

build:编译空间,编译过程中的中间文件;

devel:开发空间,编译完成后的可执行文件,配置脚本之类的文件;

install: 安装空间,同上,比较重复

2)创建工作空间

  • 比如在home/ 目录下创建工作空间:

  • 新建两个文件夹,比如 mkdir -p ~/catkin_ws/src

  • cd catkin_ws/src

  • 通过命令 catkin_init_workspace  创建出CMakeList.txt告知系统这里是ros的一个工作空间

  • 回到根目录下(catkin_ws) 使用 catkin_make 编译

  • 这时生成了上文的两个文件夹

3)设置环境变量

方法一:(仅在当前终端有效)

catkin_ws$    source devel/setup.bash 

这时系统便知道功能包在 catkin_ws 下

方法二: 

vim  .bashrc 添加source 

保存退出 

env | grep ros 查找和ros相关的环境变量

4)创建功能包

cd ~/catkin_ws/src

catkin_create_pkg <package_name> [depend1] [depend2] ......

这时会生成 src文件夹放置代码,include文件夹放置头文件 以及CMakeLists.txt  编译选项 和 package.xml 描述具体信息,声明依赖等

2、ROS通讯编程

1)话题编程

流程:

创建发布者-创建订阅者-添加编译选项-运行可执行程序

a)如何实现一个发布者

  • 初始化ROS节点

  • 向ROS Master 注册节点信息,包括发布的话题名和话题中的消息类型;

  • 按照一定频率循环发布消息

/**
* 该例程将发布chatter话题,消息类型String
* author:maxwang
*/
#include <sstream>
#include "ros/ros.h"
#include "std_msgs/String.h"

int main(int argc, char **argv)
{
  // ROS节点初始化
  ros::init(argc, argv, "talker");//使用ros::init API初始化节点,前两个是输入参数 与main函数保持一致,第三个定义节点运行名称

  // 创建节点句柄 句柄可以让你通过构造函数指定命名空间,便于管理资源,就是一个指针

  ros::NodeHandle n;

  // 创建一个Publisher(发布者),发布名为chatter的topic(节点),n.advertise 句柄接口 定义发布者消息类型为std_msgs::String,1000为队列长度,用于缓存数据,当满了后会把时间戳早的数据删掉。

  ros::Publisher chatter_pub = n.advertise<std_msgs::String>("chatter", 1000);

  // 设置循环的频率 单位 Hz

  ros::Rate loop_rate(10);
  int count = 0;

  //进入循环 ros::ok() 检查节点
  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);//使用public这个API接口发布

    // 循环等待回调函数(循环一次)
    ros::spinOnce();

    // 按照循环频率延时 Hz  否则会导致CPU不断循环 占用资源
    loop_rate.sleep();
    ++count;
  }

  return 0;
}

b)如何实现一个订阅者

  • 初始化ROS节点

  • 订阅所需话题

  • 循环等待话题消息,接收到消息后进入回调函数

  • 在回调函数中完成消息处理

/**
* 该例程将订阅chatter话题,消息类型String
*/
#include "ros/ros.h"
#include "std_msgs/String.h"

// 接收到订阅的消息后,会进入消息回调函数 传入msg (通过指针访问)
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);


  // 循环等待回调函数 无需while(1);
  ros::spin();

  return 0;
}

c)如何编译代码

  • 设置需要编译的代码和可执行文件;

  • 设置链接库

生成可执行文件, 确定所需源码

链接所需要的库

  • 设置依赖

最后回到工作空间,编译

d)运行

启动roscore

rosrun <功能包> <可执行文件>

e)自定义话题消息

一般放置在功能包文件夹下,创建msg文件夹,进入后创建person.msg 定义(类似于#define)

string name
uint8  sex
uint8  age

uint8 unknown = 0
uint8 male    = 1
uint8 female  = 2

package.xml  :

  <build_depend>message_generation</build_depend>

  <run_depend>message_runtime</run_depend>

CMakeLists.txt

find_package(catkin REQUIRED COMPONENTS
  roscpp
  rospy
  std_msgs
  message_generation #所依赖的功能包
)

catkin_package(
  CATKIN_DEPENDS roscpp rospy std_msgs message_runtime #添加编译时的依赖
)


add_message_files(FILES Person.msg) #添加msg文件
generate_messages(DEPENDENCIES std_msgs)

然后开始编译

2)服务编程

流程

创建服务器-创建客户端-添加编译选项-运行可执行文件

自定义服务请求与应答:

a)定义srv

创建srv文件夹,进入后创建AddTeolnts.srv 

横线以上是服务的请求数据,以下是应答数据。即客户端把a,b 发给服务端,处理后服务端将sum 发回客户端。

int64 a
int64 b
---
int64 sum

修改两个文件

#补充

add_service_files(FILES AddTwoInts.srv)

b)创建服务器

/**
* 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;

}

c)创建客户端

/**
* 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;
}

d)编译设置

3)动作编程

a)定义动作消息

# Define the goal 目标数据
uint32 dishwasher_id  # Specify which dishwasher we want to use
---
# Define the result 结果数据
uint32 total_dishes_cleaned
---
# Define a feedback message 周期反馈消息
float32 percent_complete

b)动作服务器

#include <ros/ros.h>
#include <actionlib/server/simple_action_server.h>
#include "learning_communication/DoDishesAction.h"

typedef actionlib::SimpleActionServer<learning_communication::DoDishesAction> Server;

// 收到action的goal后调用该回调函数
void execute(const learning_communication::DoDishesGoalConstPtr& goal, Server* as)
{
    ros::Rate r(1);
    learning_communication::DoDishesFeedback feedback;
    ROS_INFO("Dishwasher %d is working.", goal->dishwasher_id);
    // 假设洗盘子的进度,并且按照1hz的频率发布进度feedback
    for(int i=1; i<=10; i++)
    {
        feedback.percent_complete = i * 10;
        as->publishFeedback(feedback);
        r.sleep();//休眠一秒
    }

    // 当action完成后,向客户端返回结果
    ROS_INFO("Dishwasher %d finish working.", goal->dishwasher_id);
    as->setSucceeded();
}

int main(int argc, char** argv)

{
    ros::init(argc, argv, "do_dishes_server");
    ros::NodeHandle n;

    // 定义一个服务器
    Server server(n, "do_dishes", boost::bind(&execute, _1, &server), false);

    // 服务器开始运行
    server.start();
    ros::spin();
    return 0;
}

c)动作客户端

#include <actionlib/client/simple_action_client.h>
#include "learning_communication/DoDishesAction.h"



typedef actionlib::SimpleActionClient<learning_communication::DoDishesAction> Client;


// 当action完成后会调用该回调函数一次
void doneCb(const actionlib::SimpleClientGoalState& state,
        const learning_communication::DoDishesResultConstPtr& result)

{
    ROS_INFO("Yay! The dishes are now clean");
    ros::shutdown();
}

// 当action激活后会调用该回调函数一次
void activeCb()
{
    ROS_INFO("Goal just went active");
}

// 收到feedback后调用该回调函数

void feedbackCb(const learning_communication::DoDishesFeedbackConstPtr& feedback)
{
    ROS_INFO(" percent_complete : %f ", feedback->percent_complete);
}



int main(int argc, char** argv)

{
    ros::init(argc, argv, "do_dishes_client");

    // 定义一个客户端
    Client client("do_dishes", true);

    // 等待服务器端
    ROS_INFO("Waiting for action server to start.");
    client.waitForServer();
    ROS_INFO("Action server started, sending goal.");

    // 创建一个action的goal
    learning_communication::DoDishesGoal goal;
    goal.dishwasher_id = 1;

    // 发送action的goal给服务器端,并且设置回调函数
    client.sendGoal(goal,  &doneCb, &activeCb, &feedbackCb);
    ros::spin();
    return 0;

}

d)编译

3、实现分布式通信

4、ROS中的关键组件

  • launch文件:通过XML文件试下多节点的配置和启动

  • TF坐标变换

小海龟跟随实验:

sudo apt-get install ros-kinect-turtle-tf
roslaunch turtle_tf turtle_tf_demo.launch
rosrun turtlesim turtle_teleop_key
rosrun tf view_frames

  • Qt工具箱

  • Rviz可视化平台

  • Gazebo物理仿真平台

猜你喜欢

转载自blog.csdn.net/qq_36754438/article/details/86656233