|
@@ -2,34 +2,24 @@
|
|
|
|
|
|
看到张三买书就突然想起`华强买瓜`,但张三是真心买书不是存心找茬。开始编写代码之前,我们先梳理一下买书任务流程。
|
|
|
|
|
|
-**一句话:张三拿多少钱钱给王二,王二凑够多少个章节的艳娘传奇给他**
|
|
|
-
|
|
|
-## 1.如何编写C++服务
|
|
|
-
|
|
|
-### 1.1编写C++服务端
|
|
|
+## 1.任务流程
|
|
|
|
|
|
-- 创建服务节点
|
|
|
-- 添加服务接口与依赖
|
|
|
-- 创建服务与回调函数
|
|
|
-- 实例化服务端并编写回调函数处理请求
|
|
|
-- 修改`CMakeLists.txt`并编译
|
|
|
-
|
|
|
-### 1.2编写C++客户端
|
|
|
+**一句话:张三拿多少钱钱给王二,王二凑够多少个章节的艳娘传奇给他**
|
|
|
|
|
|
-- 创建客户端节点
|
|
|
-- 添加服务接口与依赖
|
|
|
-- 创建客户端、请求函数和请求结果回调函数
|
|
|
-- 实例化客户端、编写请求函数和回调函数
|
|
|
-- 修改`CMakeLists.txt`并编译
|
|
|
|
|
|
## 2.服务端(王二)实现
|
|
|
|
|
|
首先是作为二手书提供者的服务端王二节点代码的编写,我们按照上面所说的步骤开始编写程序。
|
|
|
|
|
|
-### 2.1 创建服务节点
|
|
|
+### 2.1 创建C++服务通信服务端的步骤
|
|
|
|
|
|
-服务肯定是由节点提供,所以肯定要有节点,在3.6章节中,王二已经在前面的章节中被创建了出来,这里无需再次创建节点。
|
|
|
+1. 导入服务接口
|
|
|
|
|
|
+2. 创建服务端回调函数
|
|
|
+
|
|
|
+3. 声明并创建服务端
|
|
|
+
|
|
|
+4. 编写回调函数逻辑处理请求
|
|
|
|
|
|
### 2.2 添加服务接口与依赖
|
|
|
|
|
@@ -80,17 +70,13 @@ ament_target_dependencies(wang2_node
|
|
|
|
|
|
|
|
|
|
|
|
-### 2.3 声明服务与回调函数
|
|
|
+### 2.3 声明回调函数
|
|
|
|
|
|
-#### 2.3.1 声明服务与回调函数
|
|
|
+#### 2.3.1 声明回调函数
|
|
|
|
|
|
-添加完服务接口接着就可以声明服务和回调函数。
|
|
|
-
|
|
|
-在Wang2Node中声明一个服务和一个**卖书请求回调函数**。
|
|
|
+添加完服务接口接着就可以声明一个**卖书请求回调函数**。
|
|
|
|
|
|
```c++
|
|
|
-// 声明一个服务端
|
|
|
-rclcpp::Service<village_interfaces::srv::SellNovel>::SharedPtr server_;
|
|
|
// 声明一个回调函数,当收到买书请求时调用该函数,用于处理数据
|
|
|
void sell_book_callback(const village_interfaces::srv::SellNovel::Request::SharedPtr request,
|
|
|
const village_interfaces::srv::SellNovel::Response::SharedPtr response)
|
|
@@ -127,24 +113,24 @@ std::queue<std::string> novels_queue;
|
|
|
|
|
|
#### 2.3.5 回调函数组
|
|
|
|
|
|
-ROS2中要使用多线程执行器和回调组来实现多线程,我们先在`Wang2Node`中声明一个回调组成员变量。
|
|
|
+ROS2中要使用多线程执行器和回调组来实现多线程,我们先在`SingleDogNode`中声明一个回调组成员变量。
|
|
|
|
|
|
```c++
|
|
|
// 声明一个服务回调组
|
|
|
rclcpp::CallbackGroup::SharedPtr callback_group_service_;
|
|
|
```
|
|
|
|
|
|
-完成 声明之后,我们的`Wang2Node`新增内容如下:
|
|
|
+完成 声明之后,我们的`SingleDogNode`新增内容如下:
|
|
|
|
|
|
#### 2.3.6 最终结果
|
|
|
|
|
|
```c++
|
|
|
-class Wang2Node : public rclcpp::Node
|
|
|
+class SingleDogNode : public rclcpp::Node
|
|
|
{
|
|
|
|
|
|
public:
|
|
|
// 构造函数
|
|
|
- Wang2Node() : Node("wang2")
|
|
|
+ SingleDogNode(std::string name) : Node(name)
|
|
|
{
|
|
|
}
|
|
|
|
|
@@ -164,21 +150,19 @@ private:
|
|
|
};
|
|
|
```
|
|
|
|
|
|
-
|
|
|
-
|
|
|
### 2.4 实例化服务端并编写回调函数处理请求
|
|
|
|
|
|
#### 2.4.1 实例化回调组
|
|
|
|
|
|
在`ROS2`中,回调函数组也是一个对象,通过实例化`create_callback_group`类即可创建一个callback_group_service的对象。
|
|
|
|
|
|
-在Wang2Node的构造函数中添加下面这行代码,即可完成实例化
|
|
|
+在SingleDogNode的构造函数中添加下面这行代码,即可完成实例化
|
|
|
|
|
|
```
|
|
|
callback_group_service_ = this->create_callback_group(rclcpp::CallbackGroupType::MutuallyExclusive);
|
|
|
```
|
|
|
|
|
|
-#### 2.4.2 实例化服务端
|
|
|
+#### 2.4.2 声明并实例化服务端
|
|
|
|
|
|
我们使用成员函数作为回调函数,这里要根据回调函数中参数个数,设置占位符,即告诉编译器,这个函数需要传入的参数个数。
|
|
|
|
|
@@ -189,12 +173,19 @@ using std::placeholders::_1;
|
|
|
using std::placeholders::_2;
|
|
|
```
|
|
|
|
|
|
-**实例化服务端**
|
|
|
+在`private:`下**声明服务端**
|
|
|
+
|
|
|
+```
|
|
|
+// 声明一个服务端
|
|
|
+rclcpp::Service<village_interfaces::srv::SellNovel>::SharedPtr server_;
|
|
|
+```
|
|
|
+
|
|
|
+在构造函数中**实例化服务端**
|
|
|
|
|
|
```C++
|
|
|
// 实例化卖二手书的服务
|
|
|
server_ = this->create_service<village_interfaces::srv::SellNovel>("sell_novel",
|
|
|
- std::bind(&Wang2Node::sell_book_callback,this,_1,_2),
|
|
|
+ std::bind(&SingleDogNode::sell_book_callback,this,_1,_2),
|
|
|
rmw_qos_profile_services_default,
|
|
|
callback_group_service_);
|
|
|
```
|
|
@@ -202,7 +193,7 @@ server_ = this->create_service<village_interfaces::srv::SellNovel>("sell_novel",
|
|
|
实例化服务端可以直接使用`create_service`函数,该函数是一个模版函数,需要输入要创建的服务类型,这里我们使用的是`<village_interfaces::srv::SellNovel>`,这个函数有四个参数需要输入,小鱼接下来进行一一介绍
|
|
|
|
|
|
- `"sell_novel"`服务名称,没啥好说的,要唯一哦,因为服务只能有一个
|
|
|
-- `std::bind(&Wang2Node::sell_book_callback,this,_1,_2)`回调函数,这里指向了我们2.3.1中我们声明的`sell_book_callback`
|
|
|
+- `std::bind(&SingleDogNode::sell_book_callback,this,_1,_2)`回调函数,这里指向了我们2.3.1中我们声明的`sell_book_callback`
|
|
|
- `rmw_qos_profile_services_default` 通信质量,这里使用服务默认的通信质量
|
|
|
- `callback_group_service_`,回调组,我们前面创建回调组就是在这里使用的,告诉ROS2,当你要调用回调函数处理请求时,请把它放到单独线程的回调组中
|
|
|
|
|
@@ -260,18 +251,18 @@ server_ = this->create_service<village_interfaces::srv::SellNovel>("sell_novel",
|
|
|
|
|
|
```
|
|
|
// 收到话题数据的回调函数
|
|
|
- void topic_callback(const std_msgs::msg::String::SharedPtr msg){
|
|
|
- // 新建一张人民币
|
|
|
- std_msgs::msg::UInt32 money;
|
|
|
- money.data = 10;
|
|
|
+ void topic_callback(const std_msgs::msg::String::SharedPtr msg){
|
|
|
+ // 新建一张人民币
|
|
|
+ std_msgs::msg::UInt32 money;
|
|
|
+ money.data = 10;
|
|
|
|
|
|
- // 发送人民币给李四
|
|
|
- pub_->publish(money);
|
|
|
- RCLCPP_INFO(this->get_logger(), "王二:我收到了:'%s' ,并给了李四:%d 元的稿费", msg->data.c_str(),money.data);
|
|
|
+ // 发送人民币给李四
|
|
|
+ pub_->publish(money);
|
|
|
+ RCLCPP_INFO(this->get_logger(), "王二:我收到了:'%s' ,并给了李四:%d 元的稿费", msg->data.c_str(),money.data);
|
|
|
|
|
|
- //将小说放入novels_queue中
|
|
|
- novels_queue.push(msg->data);
|
|
|
- };
|
|
|
+ //将小说放入novels_queue中
|
|
|
+ novels_queue.push(msg->data);
|
|
|
+};
|
|
|
```
|
|
|
|
|
|
### 2.5 修改`main`函数
|
|
@@ -285,7 +276,7 @@ int main(int argc, char **argv)
|
|
|
{
|
|
|
rclcpp::init(argc, argv);
|
|
|
/*产生一个Wang2的节点*/
|
|
|
- auto node = std::make_shared<Wang2Node>();
|
|
|
+ auto node = std::make_shared<SingleDogNode>("wang2");
|
|
|
/* 运行节点,并检测退出信号*/
|
|
|
rclcpp::executors::MultiThreadedExecutor exector;
|
|
|
exector.add_node(node);
|
|
@@ -297,11 +288,7 @@ int main(int argc, char **argv)
|
|
|
|
|
|
王二节点完整代码见:https://fishros.com/code/ros2/ros2_town/village_wang/src/wang2.cpp
|
|
|
|
|
|
-### 2.6 修改CmkeLists.txt并编译
|
|
|
-
|
|
|
-很开心的告诉你,因为之前我们已经添加了王二节点,使其生成可执行文件并安装到指定目录,这里就不需要做任何修改。
|
|
|
-
|
|
|
-#### 2.6.1 直接编译
|
|
|
+### 2.6 编译
|
|
|
|
|
|
在工作空间下:输入下面的指令
|
|
|
|
|
@@ -372,12 +359,22 @@ ros2 run village_li li4_node
|
|
|
|
|
|
|
|
|
|
|
|
+
|
|
|
+
|
|
|
## 3.客户端(张三)实现
|
|
|
|
|
|
编写完服务端的程序,接下来我们就可以编写客户端张三了。
|
|
|
|
|
|
-### 3.1 创建客户端节点
|
|
|
+### 3.1 编写ROS2服务通信客户端步骤
|
|
|
+
|
|
|
+
|
|
|
|
|
|
+
|
|
|
+
|
|
|
+### 3.2 添加服务接口与依赖
|
|
|
+
|
|
|
+
|
|
|
+#### 3.2.1 创建客户端节点
|
|
|
因为张家村和张三之前不存在,这里我们需要新创建出来,命令如下:
|
|
|
|
|
|
打开终端到src文件夹下:
|
|
@@ -393,8 +390,7 @@ ros2 pkg create village_zhang --build-type ament_cmake --dependencies rclcpp
|
|
|
/imgs/image-20210831125824511.png)
|
|
|
|
|
|
|
|
|
-
|
|
|
-### 3.2 添加服务接口与依赖
|
|
|
+#### 3.2.2 创建客户端节点
|
|
|
|
|
|
因为张三要找王二请求小说,所以一定依赖通信接口`village_interfaces`
|
|
|
|
|
@@ -441,7 +437,7 @@ ament_target_dependencies(zhang3_node
|
|
|
|
|
|
#### 3.3.1 创建客户端
|
|
|
|
|
|
-接着我们就可以编写客户端了,张三也是C++,我们可以参考王二的代码,建立一个C++节点的基本的框架。
|
|
|
+接着我们就可以编写客户端了,穷光蛋张三也是C++,我们可以参考王二的代码,建立一个C++节点的基本的框架。
|
|
|
|
|
|
```
|
|
|
#include "rclcpp/rclcpp.hpp"
|
|
@@ -451,14 +447,14 @@ ament_target_dependencies(zhang3_node
|
|
|
using std::placeholders::_1;
|
|
|
|
|
|
/*
|
|
|
- 创建一个类节点,名字叫做Zhang3Node,继承自Node.
|
|
|
+ 创建一个类节点,名字叫做PoorManNode,继承自Node.
|
|
|
*/
|
|
|
-class Zhang3Node : public rclcpp::Node
|
|
|
+class PoorManNode : public rclcpp::Node
|
|
|
{
|
|
|
|
|
|
public:
|
|
|
/* 构造函数 */
|
|
|
- Zhang3Node() : Node("wang2")
|
|
|
+ PoorManNode(std::string name) : Node(name)
|
|
|
{
|
|
|
// 打印一句自我介绍
|
|
|
RCLCPP_INFO(this->get_logger(), "大家好,我是得了穷病的张三.");
|
|
@@ -471,7 +467,8 @@ int main(int argc, char **argv)
|
|
|
{
|
|
|
rclcpp::init(argc, argv);
|
|
|
/*产生一个Zhang3的节点*/
|
|
|
- auto node = std::make_shared<Zhang3Node>();
|
|
|
+ auto node = std::make_shared<PoorManNode>("zhang3");
|
|
|
+ /* 运行节点,并检测rclcpp状态*/
|
|
|
rclcpp::spin(node);
|
|
|
rclcpp::shutdown();
|
|
|
return 0;
|
|
@@ -516,14 +513,14 @@ int main(int argc, char **argv)
|
|
|
using std::placeholders::_1;
|
|
|
|
|
|
/*
|
|
|
- 创建一个类节点,名字叫做Zhang3Node,继承自Node.
|
|
|
+ 创建一个类节点,名字叫做PoorManNode,继承自Node.
|
|
|
*/
|
|
|
-class Zhang3Node : public rclcpp::Node
|
|
|
+class PoorManNode : public rclcpp::Node
|
|
|
{
|
|
|
|
|
|
public:
|
|
|
/* 构造函数 */
|
|
|
- Zhang3Node() : Node("zhang3")
|
|
|
+ PoorManNode() : Node("zhang3")
|
|
|
{
|
|
|
// 打印一句自我介绍
|
|
|
RCLCPP_INFO(this->get_logger(), "大家好,我是得了穷病的张三.");
|
|
@@ -551,7 +548,7 @@ int main(int argc, char **argv)
|
|
|
{
|
|
|
rclcpp::init(argc, argv);
|
|
|
/*产生一个Zhang3的节点*/
|
|
|
- auto node = std::make_shared<Zhang3Node>();
|
|
|
+ auto node = std::make_shared<PoorManNode>();
|
|
|
rclcpp::spin(node);
|
|
|
rclcpp::shutdown();
|
|
|
return 0;
|
|
@@ -606,7 +603,7 @@ client_ = this->create_client<village_interfaces::srv::SellNovel>("sell_novel");
|
|
|
request->money = 5;
|
|
|
|
|
|
//3.发送异步请求,然后等待返回,返回时调用回调函数
|
|
|
- client_->async_send_request(request,std::bind(&Zhang3Node::novels_callback, this, _1));
|
|
|
+ client_->async_send_request(request,std::bind(&PoorManNode::novels_callback, this, _1));
|
|
|
};
|
|
|
```
|
|
|
|
|
@@ -664,7 +661,7 @@ int main(int argc, char **argv)
|
|
|
{
|
|
|
rclcpp::init(argc, argv);
|
|
|
/*产生一个Zhang3的节点*/
|
|
|
- auto node = std::make_shared<Zhang3Node>();
|
|
|
+ auto node = std::make_shared<PoorManNode>();
|
|
|
node->buy_novel();
|
|
|
/* 运行节点,并检测rclcpp状态*/
|
|
|
rclcpp::spin(node);
|