《动手学ROS2》4.9服务实现(Python)

本系列教程作者:小鱼
公众号:鱼香ROS
QQ交流群:139707339
教学视频地址:小鱼的B站
完整文档地址:鱼香ROS官网
版权声明:如非允许禁止转载与商业用途。
公众号

4.5.2 Python服务通信实现(李三借钱)

大家好,我是小鱼。上节说完如何自定义ROS2的服务接口。

相信你已经迫不及待的想尝试一下编写代码了,让我们一起来动手,让李三成功借钱,吃上麻辣烫吧。

1.如何编写一个Python服务

开始之前,我们先说一下创建ROS2服务端基本步骤。

首先是服务端:

  1. 导入服务接口
  2. 创建服务端回调函数
  3. 声明并创建服务端
  4. 编写回调函数逻辑处理请求

2.编写服务端李四代码

我们先来创建李四这边的服务端。用VsCode打开我们的town_ws工作区。

2.1 导入服务接口

我们在上一节中自定义的服务接口这里该怎么使用呢?

需要下面两个步骤:

2.1.1 添加依赖

导入依赖是为了能够让我们的代码找到对应的接口。

因为village_li是包类型是ament_python这里只需要在package.xml中加入下面的代码即可:

  <depend>village_interfaces</depend>

image-20210816153438400

2.1.2 程序中导入

程序中导入也是只需要一行代码即可完成,打开li4.py,在文件开头加入下面一行代码。

#从村庄接口服务类中导入借钱服务
from village_interfaces.srv import BorrowMoney

2.2 创建服务端并定义服务回调函数

2.2.1创建服务端

接着创建一个服务,继承于Node之后,WriterNode也具备了创建一个服务的能力。在WriterNode__init__函数中创建成员变量borrow_server

# 新建借钱服务
self.borrow_server = self.create_service(BorrowMoney, "borrow_money", self.borrow_money_callback)

需要传入三个参数:

  • 服务接口类型,BorrowMoney,我们在2.1.2导入的

  • 服务名称,"borrow_money",具有唯一性,自己手打的

  • 回调函数,self.borrow_money_callback,我们下一步定义的。

    关于回调函数小鱼写过一篇文章:回调函数与异步执行,不理解的同学可以看一看

2.2.2 定义回调函数

def borrow_money_callback(self,request, response):
    """
    借钱回调函数
    参数:request 客户端请求对象,携带着来自客户端的数据
         response 服务端响应,返回服务端的处理结果
    返回值:response
    """
    return response

这个函数有三个入口参数,self代表本身,这个没啥好说的,类似于c++和java里的this。

  • request 是客户端请求对象,携带着来自客户端的数据。

    其结构就是上一节中我们所定义的namemoney组成

  • response 是服务端响应,返回服务端的处理结果

    其结构由successmoney组成

2.3编写回调函数

接下来开始正式编写回调函数,回调函数的输入是request和response,输出是我们处理后的reponse(当然也可以不处理,使用默认值)

def borrow_money_callback(self,request, response):
    """
    借钱回调函数
    参数:request 客户端请求
         response 服务端响应
    返回值:response
    """
    self.get_logger().info("收到来自: %s 的借钱请求,目前账户内还有%d元" % (request.name, self.account))
    #根据李四借钱规则,借出去的钱不能多于自己所有钱的十分之一,不然就不借
    if request.money <= int(self.account*0.1):
        response.success = True
        response.money = request.money
        self.account = self.account - request.money
        self.get_logger().info("借钱成功,借出%d 元 ,目前账户余额%d 元" % (response.money,self.account))
    else:
        response.success = False
        response.money = 0
        self.get_logger().info("对不起兄弟,手头紧,不能借给你")
    return response

这里代码其实并不复杂,先判断要借钱的金额是否满足要借出去的数量,如果满足则借,不然就不借。

为了测试方便,我们为李四的光头账户打赏70块钱,将__init__函数中的self.account 默认账户值改为70即可

至此,服务端的代码就编写完成了,完整版代码可以点开这个网址查看

3.测试服务端代码

3.1编译运行

在vscode中,使用Ctrl+Shift+~打开一个新的终端,在town_ws目录下输入:

colcon build --packages-select village_li

image-20210816163422495

3.2启动并查看服务列表

source,再run

source install/setup.bash
ros2 run village_li li4_node

image-20210816163704618

3.3手动调用

在VsCode中使用Ctrl+Shift+5打开一个切分终端。然后依次输入下面的指令,查看我们的服务。

ros2 service list #服务列表
ros2 service list -t #服务列表带类型

接着我们使用命令行来手动调用服务,不知道你还是否记得4.7中我们手动调用服务将两个数字相加。

这里我们手动调用服务用李三的名义来借5块钱

source install/setup.bash
ros2 service call /borrow_money village_interfaces/srv/BorrowMoney  "{name: 'li3', money: 5}"

看返回结果success为True,money的值也变成了5,说明李三借钱成功了。

image-20210816164314308

再尝试借50块钱看看李四借不借。

ros2 service call /borrow_money village_interfaces/srv/BorrowMoney  "{name: 'li3', money: 50}"

这次李四说他手头紧,不给借。返回值中的success也变成了False,money也变成了0。

image-20210816164511430

4.编写客户端代码

服务端搞定了后,我们来编写客户端李三这边的代码。

编写服务通信的客户端的一般步骤:

  1. 导入服务接口
  2. 创建请求结果接收回调函数
  3. 声明并创建客户端
  4. 编写结果接收逻辑
  5. 调用客户端发送请求

4.1导入服务接口

第一步和服务端相同,导入对应的接口,因为李四和李三是在同一个包village_li内,所以不需要再次修改package.xml

打开li3.py我们直接导入对应接口

from village_interfaces.srv import BorrowMoney

4.2创建请求结果接收回调函数

编写borrow_respoonse_callback借钱结果回调函数,该函数的只有一个入口参数response

def borrow_respoonse_callback(self,response):
    """
    借钱结果回调
    """
    pass

4.3创建客户端并定义结果回调函数

李三继承于Node,也具备了创建客户端的能力

class BaiPiaoNode(Node): #BaiPiaoNode是继承于Node

创建客户端

#在__init__函数中创建一个服务的客户端
self.borrow_money_client_ = self.create_client(BorrowMoney, "borrow_money")

创建客户端使用函数create_client该函数有两个入口参数,一个是服务接口类型,一个是服务名称。

这里的两个参数需和服务端的完全一致,方可通信。名字不一致,会找不到对应服务,数据类型不一致会导致无法通信。

4.4 编写结果回调函数处理逻辑

根据结果说不同的话

    def borrow_respoonse_callback(self,response):
        """
        借钱结果回调
        """
        # 打印一下信息
        result = response.result()
        if result.success == True:
            self.get_logger().info("果然是亲弟弟,借到%d,吃麻辣烫去了" % result.money)
        else:
            self.get_logger().info("害,连几块钱都不借,我还是不是他亲哥了,世态炎凉呀")

4.5 编写发送请求逻辑

4.5.1创建发送请求函数

接着我们在BaiPiaoNode中编写一个函数用于创建发送的数据,并发送请求。

def borrow_money_eat(self):
    """
    借钱吃麻辣烫函数
    """
    #打印一句话
    self.get_logger().info("找我弟借钱吃麻辣烫喽")
    #等待服务启动,每1s检查一次,如果服务没有启动,则一直循环
    while not self.borrow_money_client_.wait_for_service(1.0):
        self.get_logger().warn("我弟不在线,我再等等。")
    # 构建请求内容
    request = BorrowMoney.Request()
    #将当前节点名称作为借钱者姓名
    request.name = self.get_name()
    #借钱金额10元
    request.money = 10
    #发送异步借钱请求,借钱成功后就调用borrow_respoonse_callback()函数
    self.borrow_money_client_.call_async(request).add_done_callback(self.borrow_respoonse_callback)

小鱼来讲一讲这个代码

  • wait_for_service(1.0)用于等待服务上线,这是一种很优雅的做法,调用之前检测一下服务是否在线
  • call_async(request).add_done_callback这里是代码的核心部分,用于发送请求,并且添加了一个任务完成时的回调函数borrow_respoonse_callback

4.5.2修改main函数调用发送请求函数

因为发送请求的函数是BaiPiaoNode的成员函数,所以我们直接调用BaiPiaoNode来发送请求即可,可以将main函数做如下修改(其实只增加了一行代码而已)。

def main(args=None):
    """
    ros2运行该节点的入口函数,可配置函数名称
    """
    rclpy.init(args=args) # 初始化rclpy
    node = BaiPiaoNode()  # 新建一个节点
    node.borrow_money_eat() #增加一行,李三借钱
    rclpy.spin(node) # 保持节点运行,检测是否收到退出指令(Ctrl+C)  
    rclpy.shutdown() # rcl关闭

编写好客户端之后,我们就可以做整体的测试了,但要记得编译程序哦。完整的li3.py代码可以访问这里获取

除了使用call_async(request)异步调用,还有一种同步调用的方式,但小鱼并不推荐,原因这里小鱼mark@鱼香ROS一下,后面在公众号中单独写一篇文章介绍。

5.整体测试

嘀嘀嘀,终于可以开始最终的测试了。

5.1编译功能包

在vscode中,使用Ctrl+Shift+~打开一个新的终端,在town_ws目录下输入:

colcon build --packages-select village_li

image-20210816163422495

5.2运行客户端李三程序

5.2.1 source

source install/setup.bash

5.2.2 运行客户端代码

ros2 run village_li li3_node

image-20210817104713043

5.3运行服务端代码

5.3.1 切分终端并source

vscode中使用Ctrl+Shift+5重新切分出一个终端,然后source

source install/setup.bash

5.3.2 运行服务端李四程序

ros2 run village_li li4_node

5.3.3 运行结果

image-20210817105329996

从图片中可以看到,李三借钱失败了,原因80*0.1=8<10,不能借给李三十块钱,符合李四做人原则,那为了李三能够吃上麻辣烫,我们可以帮助李四赚钱——让王二过来进行知识付费。

5.4运行王二过来知识付费

同样的再切分出一个终端,然后source运行王二节点。

source install/setup.bash
ros2 run village_wang wang2_node 

重新运行李三节点,点击李三运行的终端,先输入Ctrl+C使其退出,再重新运行节点。

ros2 run village_li li3_node

image-20210817110416711

可以看到,此时李四账户里已经有了六百多块了,很轻松的借给了李三10块钱,这多亏了王二的知识付费。

6.结束

至此,我们帮助李三成功借钱,吃上了麻辣烫,下一步就是编写C++程序,努力帮助张三看上二手书。

如果还有不明白的地方,欢迎加入鱼群交流。

作者介绍:

我是小鱼,机器人领域资深玩家,现深圳某独脚兽机器人算法工程师一枚
初中学习编程,高中开始接触机器人,大学期间打机器人相关比赛实现月入2W+(比赛奖金)
目前在输出机器人学习指南、论文注解、工作经验,欢迎大家关注小智,一起交流技术,学习机器人

猜你喜欢

转载自blog.csdn.net/qq_27865227/article/details/121206358