本系列教程作者:小鱼
公众号:鱼香ROS
QQ交流群:139707339
教学视频地址:小鱼的B站
完整文档地址:鱼香ROS官网
版权声明:如非允许禁止转载与商业用途。
4.5.2 Python服务通信实现(李三借钱)
大家好,我是小鱼。上节说完如何自定义ROS2的服务接口。
相信你已经迫不及待的想尝试一下编写代码了,让我们一起来动手,让李三成功借钱,吃上麻辣烫吧。
1.如何编写一个Python服务
开始之前,我们先说一下创建ROS2服务端基本步骤。
首先是服务端:
- 导入服务接口
- 创建服务端回调函数
- 声明并创建服务端
- 编写回调函数逻辑处理请求
2.编写服务端李四代码
我们先来创建李四这边的服务端。用VsCode打开我们的town_ws
工作区。
2.1 导入服务接口
我们在上一节中自定义的服务接口这里该怎么使用呢?
需要下面两个步骤:
2.1.1 添加依赖
导入依赖是为了能够让我们的代码找到对应的接口。
因为village_li
是包类型是ament_python
这里只需要在package.xml
中加入下面的代码即可:
<depend>village_interfaces</depend>
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 是客户端请求对象,携带着来自客户端的数据。
其结构就是上一节中我们所定义的
name
和money
组成 -
response 是服务端响应,返回服务端的处理结果
其结构由
success
和money
组成
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
3.2启动并查看服务列表
先source
,再run
source install/setup.bash
ros2 run village_li li4_node
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,说明李三借钱成功了。
再尝试借50块钱看看李四借不借。
ros2 service call /borrow_money village_interfaces/srv/BorrowMoney "{name: 'li3', money: 50}"
这次李四说他手头紧,不给借。返回值中的success也变成了False,money也变成了0。
4.编写客户端代码
服务端搞定了后,我们来编写客户端李三这边的代码。
编写服务通信的客户端的一般步骤:
- 导入服务接口
- 创建请求结果接收回调函数
- 声明并创建客户端
- 编写结果接收逻辑
- 调用客户端发送请求
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
5.2运行客户端李三程序
5.2.1 source
source install/setup.bash
5.2.2 运行客户端代码
ros2 run village_li li3_node
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 运行结果
从图片中可以看到,李三借钱失败了,原因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
可以看到,此时李四账户里已经有了六百多块了,很轻松的借给了李三10块钱,这多亏了王二的知识付费。
6.结束
至此,我们帮助李三成功借钱,吃上了麻辣烫,下一步就是编写C++程序,努力帮助张三看上二手书。
如果还有不明白的地方,欢迎加入鱼群交流。
作者介绍:
我是小鱼,机器人领域资深玩家,现深圳某独脚兽机器人算法工程师一枚
初中学习编程,高中开始接触机器人,大学期间打机器人相关比赛实现月入2W+(比赛奖金)
目前在输出机器人学习指南、论文注解、工作经验,欢迎大家关注小智,一起交流技术,学习机器人