• ROS2——动作(十一)


    ROS2机器人操作系统


    前言

    机器人是一个复杂的智能系统,并不仅仅是键盘遥控运动、识别某个目标这么简单,我们需要实现的是送餐、送货、分拣等满足具体场景需求的机器人。

    在这些应用功能的实现中,另外一种ROS通信机制也会被常常用到——那就是动作。从这个名字上就可以很好理解这个概念的含义,这种通信机制的目的就是便于对机器人某一完整行为的流程进行管理。

    一、pandas是什么?

    在这里插入图片描述
    举个例子,比如我们想让机器人转个圈,这肯定不是一下就可以完成的,机器人得一点一点旋转,直到360度才能结束,假设机器人并不在我们眼前,发出指令后,我们根本不知道机器人到底有没有开始转圈,转到哪里了?OK,现在我们需要的是一个反馈,比如每隔1s,告诉我们当前转到多少度了,10度、20度、30度,一段时间之后,到了360度,再发送一个信息,表示动作执行完成。
    这样一个需要执行一段时间的行为,使用动作的通信机制就更为合适,就像装了一个进度条,我们可以随时把控进度,如果运动过程当中,我们还可以随时发送一个取消运动的命令。

    客户端/服务器模型

    动作和服务类似,使用的也是客户端和服务器模型,客户端发送动作的目标,想让机器人干什么,服务器端执行动作过程, 控制机器人达到运动的目标,同时周期反馈动作执行过程中的状态。
    在这里插入图片描述
    客户端发送一个运动的目标,想让机器人动起来,服务器端收到之后,就开始控制机器人运动,一边运动,一边反馈当前的状态,如果是一个导航动作,这个反馈可能是当前所处的坐标,如果是机械臂抓取,这个反馈可能又是机械臂的实时姿态。当运动执行结束后,服务器再反馈一个动作结束的信息。整个通信过程就此结束。

    一对多通信

    和服务一样,动作通信中的客户端可以有多个,大家都可以发送运动命令,但是服务器端只能有一个,毕竟只有一个机器人,先执行完成一个动作,才能执行下一个动作。

    同步通信

    既然有反馈,那动作也是一种同步通信机制,之前我们也介绍过,动作过程中的数据通信接口,使用.action文件进行定义。

    由服务和话题合成

    动作的三个通信模块,竟然有两个是服务,一个是话题,当客户端发送运动目标时,使用的是服务的请求调用,服务器端也会反馈一个应带,表示收到命令。动作的反馈过程,其实就是一个话题的周期发布,服务器端是发布者,客户端是订阅者。

    没错,动作是一种应用层的通信机制,其底层就是基于话题和服务来实现的。

    二、使用案例

    1.小海龟的动作

    按照以下命令启动小海龟仿真器,接下来使用action命令控制小海龟的动作,可以让海龟运动到某一指定的姿态:

    ros2 run turtlesim turtlesim_node
    ros2 run turtlesim turtle_teleop_key
    ros2 action info /turtle1/rotate_absolute
    ros2 action send_goal /turtle1/rotate_absolute turtlesim/action/RotateAbsolute "{theta: -1.57}"
    ros2 action send_goal /turtle1/rotate_absolute turtlesim/action/RotateAbsolute "{theta: -1.57}" --feedback
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在这里插入图片描述

    2.机器人画圆

    在这里插入图片描述

    ros2 run learning_action action_move_server 
    ros2 run learning_action action_move_client
    
    • 1
    • 2

    终端中,我们可以看到客户端发送动作目标之后,服务器端开始模拟机器人运动,每30度发送一次反馈信息,最终完成运动,并反馈结束运动的信息。
    在这里插入图片描述

    接口定义

    bool enable     # 定义动作的目标,表示动作开始的指令
    ---
    bool finish     # 定义动作的结果,表示是否成功执行
    ---
    int32 state     # 定义动作的反馈,表示当前执行到的位置
    
    • 1
    • 2
    • 3
    • 4
    • 5

    第一块是动作的目标,enable为true时,表示开始运动;
    第二块是动作的执行结果,finish为true,表示动作执行完成;
    第三块是动作的周期反馈,表示当前机器人旋转到的角度。
    完成定义后,还需要在功能包的CMakeLists.txt中配置编译选项,让编译器在编译过程中,根据接口定义,自动生成不同语言的代码:

    ...
    
    find_package(rosidl_default_generators REQUIRED)
    
    rosidl_generate_interfaces(${PROJECT_NAME}
      "action/MoveCircle.action"
    )
    ...
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    通信模型

    通信模型就是这样,客户端发送给一个动作目标,服务器控制机器人开始运动,并周期反馈,结束后反馈结束信息。
    在这里插入图片描述
    服务端代码

    import time
    
    import rclpy                                      # ROS2 Python接口库
    from rclpy.node   import Node                     # ROS2 节点类
    from rclpy.action import ActionServer             # ROS2 动作服务器类
    from learning_interface.action import MoveCircle  # 自定义的圆周运动接口
    
    class MoveCircleActionServer(Node):
        def __init__(self, name):
            super().__init__(name)                   # ROS2节点父类初始化
            self._action_server = ActionServer(      # 创建动作服务器(接口类型、动作名、回调函数)
                self,
                MoveCircle,
                'move_circle',
                self.execute_callback)
    
        def execute_callback(self, goal_handle):            # 执行收到动作目标之后的处理函数
            self.get_logger().info('Moving circle...')
            feedback_msg = MoveCircle.Feedback()            # 创建一个动作反馈信息的消息
    
            for i in range(0, 360, 30):                     # 从0360度,执行圆周运动,并周期反馈信息
                feedback_msg.state = i                      # 创建反馈信息,表示当前执行到的角度
                self.get_logger().info('Publishing feedback: %d' % feedback_msg.state)
                goal_handle.publish_feedback(feedback_msg)  # 发布反馈信息
                time.sleep(0.5)
    
            goal_handle.succeed()                           # 动作执行成功
            result = MoveCircle.Result()                    # 创建结果消息
            result.finish = True                            
            return result                                   # 反馈最终动作执行的结果
    
    def main(args=None):                                    # ROS2节点主入口main函数
        rclpy.init(args=args)                               # ROS2 Python接口初始化
        node = MoveCircleActionServer("action_move_server") # 创建ROS2节点对象并进行初始化
        rclpy.spin(node)                                    # 循环等待ROS2退出
        node.destroy_node()                                 # 销毁节点对象
        rclpy.shutdown()                                    # 关闭ROS2 Python接口
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    客户端代码

    import rclpy                                      # ROS2 Python接口库
    from rclpy.node   import Node                     # ROS2 节点类
    from rclpy.action import ActionClient             # ROS2 动作客户端类
    
    from learning_interface.action import MoveCircle  # 自定义的圆周运动接口
    
    class MoveCircleActionClient(Node):
        def __init__(self, name):
            super().__init__(name)                   # ROS2节点父类初始化
            self._action_client = ActionClient(      # 创建动作客户端(接口类型、动作名)
                self, MoveCircle, 'move_circle') 
    
        def send_goal(self, enable):                 # 创建一个发送动作目标的函数
            goal_msg = MoveCircle.Goal()             # 创建一个动作目标的消息
            goal_msg.enable = enable                 # 设置动作目标为使能,希望机器人开始运动
    
            self._action_client.wait_for_server()    # 等待动作的服务器端启动
            self._send_goal_future = self._action_client.send_goal_async(   # 异步方式发送动作的目标
                goal_msg,                                                   # 动作目标
                feedback_callback=self.feedback_callback)                   # 处理周期反馈消息的回调函数
    
            self._send_goal_future.add_done_callback(self.goal_response_callback) # 设置一个服务器收到目标之后反馈时的回调函数
    
        def goal_response_callback(self, future):           # 创建一个服务器收到目标之后反馈时的回调函数
            goal_handle = future.result()                   # 接收动作的结果
            if not goal_handle.accepted:                    # 如果动作被拒绝执行
                self.get_logger().info('Goal rejected :(')
                return
    
            self.get_logger().info('Goal accepted :)')                            # 动作被顺利执行
    
            self._get_result_future = goal_handle.get_result_async()              # 异步获取动作最终执行的结果反馈
            self._get_result_future.add_done_callback(self.get_result_callback)   # 设置一个收到最终结果的回调函数 
    
        def get_result_callback(self, future):                                    # 创建一个收到最终结果的回调函数
            result = future.result().result                                       # 读取动作执行的结果
            self.get_logger().info('Result: {%d}' % result.finish)                # 日志输出执行结果
    
        def feedback_callback(self, feedback_msg):                                # 创建处理周期反馈消息的回调函数
            feedback = feedback_msg.feedback                                      # 读取反馈的数据
            self.get_logger().info('Received feedback: {%d}' % feedback.state) 
    
    def main(args=None):                                       # ROS2节点主入口main函数
        rclpy.init(args=args)                                  # ROS2 Python接口初始化
        node = MoveCircleActionClient("action_move_client")    # 创建ROS2节点对象并进行初始化
        node.send_goal(True)                                   # 发送动作目标
        rclpy.spin(node)                                       # 循环等待ROS2退出
        node.destroy_node()                                    # 销毁节点对象
        rclpy.shutdown()                                       # 关闭ROS2 Python接口
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50

    动作命令行

    $ ros2 action list                  # 查看服务列表
    $ ros2 action info <action_name>    # 查看服务数据类型
    $ ros2 action send_goal <action_name> <action_type> <action_data>   # 发送服务请求
    
    • 1
    • 2
    • 3

    在这里插入图片描述

  • 相关阅读:
    【微信小程序】全局样式文件app.wxss、页面的根元素page、 app.json中的window配置项
    零基础学Java(6)控制流程
    Mockito -- 如何Mock Util类中的static 方法?
    【Linux】进程(6):环境变量
    前端面试总结
    net-java-php-python-医药库存管理系统计算机毕业设计程序
    一文搞懂 == 、equals和hashCode
    Spark SQL----CSV文件
    Chrome浏览器是如何工作的?(一)
    5. 最长回文子串
  • 原文地址:https://blog.csdn.net/qq_51963216/article/details/125619749