ros2学习(04)—掷骰子游戏1.0版本
Published: Post views:ros2学习日志(4)—掷骰子游戏1.0版
3.0 写在前面
上节我们创建了一个自定义服务接口,那么这一节可以尝试来实现掷骰子游戏了
3.1 服务端程序
服务端程序其实就是在节点的基础上,增加一个服务。主要流程如下:
- 创建功能包—(
ros2 pkg create
) - 创建节点—(新建一个.py或者cpp文件)
- 在package.xml添加依赖—(主要增加自定义接口的依赖)
- 在setup.py中添加节点入口—(如
"god1=god.god:main"
) - 编写服务端主程序—(在创建的节点文件中写入程序)
前面我们已经创建了god功能包,那么我们就不重新创建了。
3.1.1 新建god_service.py
在god/god文件夹内,建立god_service.py文件,并写入以下代码:
import rclpy #导入ros的客户端库,必备步骤
from rclpy.node import Node #rclpy.node是ros客户端库自带的Node类,用于创建节点
import random #python自带的随机数库
from std_msgs.msg import UInt32,String,Bool
from interfaces.srv import DiceGameService #导入自定义的服务接口,interfaces是package的名称
# .srv是指srv文件夹下
class GodServiceNode(Node): #采用面向对象(OOP)的方式建立node; 定义了一个GodServiceNode的类,继承rclpy.node中的Node;
def __init__(self,name): #定义初始化方法;或者叫构造函数。。(构造函数是个人的理解,源自C++)
super().__init__(name) #调用父类初始化,需要传入参数name,通常name是实例化的Node的名称,即节点名字;比如本例子中节点名字是 God
self.get_logger().info("GodServiceNode初始化成功!") #get_logger().info是rclpy.node中的方法;
self.i=random.randint(1,6) #产生一个随机数,表示骰子点数
self.get_logger().info("掷骰子游戏已经准备好了,快来猜吧") #发布日志消息,表明骰子点数已经准备好
self.create_service(DiceGameService,"diceGameService",self.diceGame_callback)
# create_service是创建服务的命令
# 第一个参数是接口类型,我们这里使用自定义服务类型,取名为diceGame
# 第二个参数是服务的名字,需要与客户端使用名称相同
# 第三个参数是服务回调函数
# 第四个参数是消息队列长度
def diceGame_callback(self,request,response): #掷骰子服务回调函数
#request 客户端请求对象,携带着来自客户端的数据。这这里我们收到的应该是一个uint32的数据
#response 服务端响应,返回服务端的处理结果;我们暂时不返回数据;
#返回值:response
if request.input_num == self.i: #判断从player收到的数字是不是骰子的点数
response.guess_result= True #如果猜正确,输出结果
else:
response.guess_result= False #如果猜错误,输出结果
return response #记得将response返回
def main(args=None): #main函数,程序执行的主入口
rclpy.init(args=args) # 初始化客户端库,必备步骤
node=GodServiceNode("diceGameServiceGod") # 新建节点对象,必备步骤;传入God,将GodNode实例化
rclpy.spin(node) # spin循环节点,保持节点运行,检测是否收到退出指令(Ctrl+C),必备步骤
rclpy.shutdown() # 关闭客户端库,必备步骤
- create_service是创建服务的意思。
- 服务回调函数diceGame_callback中的两个参数,分别是request和response。在本例中,request是客户端发出的请求,也就是uint32的数值;response是服务端发出的响应,也就是bool类型的标识符。
- 在读取request和response的数据时,要用
.
命令,后面的参数与自定义服务接口中的变量名称相同;如requset.input_num
,response.guess_result
3.1.2 修改package.xml
修改package.xml,添加我们自定义接口interfaces的依赖。
3.1.3 修改setup.py
修改setup.py,添加新的入口点,并取名为diceGameService,注意这个名字就是编译后的节点名称。
3.2 客户端程序
客户端程序与服务端程序的建立程序类似
3.2.1 新建player_cilent.py
在player/player文件夹内,建立player_client.py文件,并写入以下代码:
import rclpy #导入ros的客户端库,必备步骤
from rclpy.node import Node #rclpy.node是ros客户端库自带的Node类,用于创建节点
from interfaces.srv import DiceGameService #interfaces是package的名字
# .srv是服务接口的意思
# #DiceGameService是服务的类型
class PlayerClientNode(Node): #采用面向对象(OOP)的方式建立node; 定义了一个PlayerClientNode的类,继承rclpy.node中的Node;
def __init__(self,name): #定义初始化方法;或者叫构造函数。。(构造函数是个人的理解,源自C++)
super().__init__(name) #调用父类初始化,需要传入参数name,通常name是实例化的Node的名称,即节点名字;
self.get_logger().info("PlayerClientNode初始化成功!") #get_logger().info是rclpy.node中的方法;
#在这里意思是,当这个layerClientNode节点实例化的时候,输出一句日志;
self.playerClient=self.create_client(DiceGameService,"diceGameService") #create_client创建客户端
#第一个参数是接口类型,也就是程序开头from interfaces.srv import DiceGameService
#第二个参数是服务的名字,需要与服务端服务名字一致
self.req=DiceGameService.Request() #初始化的时候定义一个req来存放request数据,使用Request()方法
def diceGame_callback(self,response): #回调函数
if response.result().guess_result==True: #注意使用.result()方法来获取response的响应,然后用.guess_result获取数据
self.get_logger().info("恭喜你,猜对了")
else:
self.get_logger().info("很遗憾,猜错了")
def diceGame(self): #创建一个diceGame的方法,在主程序中调用即可,OOP的思想。
while not self.playerClient.wait_for_service(1.0): #等待服务上线,如果不在线则等待1.0s
self.get_logger().warn("掷骰子游戏未上线")
num=int(input("请输入点数:")) #手动输入骰子点数
self.req.input_num=num #将手动输入的骰子点数赋值给input_num,也就是咱们自定义服务接口中的request;注意用法
self.playerClient.call_async(self.req).add_done_callback(self.diceGame_callback)
# 异步调用,参考的fishros.com
#self.playerClient是创建的客户端
def main(args=None): #main函数,程序执行的主入口
rclpy.init(args=args) # 初始化客户端库,必备步骤
node=PlayerClientNode("playerClient") # 新建节点对象,必备步骤;传入playerClient,将playerClientNode实例化
node.diceGame() #调用diceGame方法
rclpy.spin(node) # spin循环节点,保持节点运行,检测是否收到退出指令(Ctrl+C),必备步骤
rclpy.shutdown() # 关闭客户端库,必备步骤
- create_client是创建客服端的命令
- 获取response的数据时,要使用
response.result().guess_result
,相较于服务端程序,多了一个.result()
方法 - 多使用.result()的原因可能在于使用了异步调用,导致与服务端程序不同(瞎猜的)
3.2.2 修改package.xml
与服务端程序相同,修改package.xml,添加我们自定义接口interfaces的依赖。
3.2.3 修改setup.py
修改setup.py,添加新的入口点,并取名为playerClient,注意这个名字就是编译后的节点名称。
3.3 编译运行
依次colcon build, source install/setup.bash之后,运行服务端节点:
ros2 run god diceGameService
可以看到运行成功,在输出的第二个info中出现了一个数值5,这其实就是god服务端随机出来的点数5,这是为了方便我们测试使用
重新开一个终端,source install/setup.bash之后,运行客户端节点:
ros2 run player playerClient
手动输入点数5之后,显示我们猜对了。:)
3.4 小结
- 可以看到无论是发布、订阅、服务端、客户端,创建的逻辑都是类似的;
- 1.创建package;或使用已有的package
- 2.创建xxx.py文件,写入代码
- 3.修改package.xml,添加依赖
- 4.修改setup.py,添加入口点
Categories: 机器人技术
Comments