ros2学习(04)—掷骰子游戏1.0版本

Published: 1 minute read, with 235 words. 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的依赖。

image-20220412145205928

3.1.3 修改setup.py

修改setup.py,添加新的入口点,并取名为diceGameService,注意这个名字就是编译后的节点名称。

image-20220412145310724

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的依赖。

image-20220412145205928

3.2.3 修改setup.py

修改setup.py,添加新的入口点,并取名为playerClient,注意这个名字就是编译后的节点名称。

image-20220413092312008

3.3 编译运行

依次colcon build, source install/setup.bash之后,运行服务端节点:

ros2 run god diceGameService

image-20220413092558904

可以看到运行成功,在输出的第二个info中出现了一个数值5,这其实就是god服务端随机出来的点数5,这是为了方便我们测试使用

重新开一个终端,source install/setup.bash之后,运行客户端节点:

ros2 run player playerClient

image-20220413093106507

手动输入点数5之后,显示我们猜对了。:)

3.4 小结

  • 可以看到无论是发布、订阅、服务端、客户端,创建的逻辑都是类似的;
    • 1.创建package;或使用已有的package
    • 2.创建xxx.py文件,写入代码
    • 3.修改package.xml,添加依赖
    • 4.修改setup.py,添加入口点

Tags: ,

Categories:

Comments