with-RL
로봇 / ROS2024년 1월 27일

ROS2 Minimal Tutorial - Action

이 포스트는 이전 포스트에 이어서 ROS2의 Action에 대한 간단한 설명입니다.

이 포스트는 다음 과정을 완료한 후에 참고하시길 바랍니다.

1. Action

(img src) https://docs.ros.org/en/foxy/Tutorials/Beginner-CLI-Tools/Understanding-ROS2-Actions/Understanding-ROS2-Actions.html
$ cd ~/Workspace/ros_ws
$ source install/setup.bash
$ ros2 launch ros_tutorial turtlesim.launch.py
$ ros2 action send_goal /turtle1/rotate_absolute turtlesim/action/RotateAbsolute "{theta: 1.57}"
$ ros2 action list -t
$ ros2 action info /turtle1/rotate_absolute
$ ros2 interface show turtlesim/action/RotateAbsolute

2. Action - Client

'turtlesim_rot_client = ros_tutorial.turtlesim_rot_client:main',
#!/usr/bin/env python3

import rclpy
from rclpy.node import Node
from rclpy.action import ActionClient
from turtlesim.action import RotateAbsolute


class TurtlesimRotateClient(Node):
    def __init__(self):
        super().__init__('turtlesim_rot_client')

        self.client = ActionClient(self, RotateAbsolute, '/turtle1/rotate_absolute')
        self.get_logger().info('action client started...')
    
    def send_goal(self, theta):
        goal_req = RotateAbsolute.Goal()
        goal_req.theta = theta

        if self.client.wait_for_server(10) is False:
            self.get_logger().info('service not available...')
            return
        
        goal_future = self.client.send_goal_async(goal_req, feedback_callback=self.feedback_callback)
        goal_future.add_done_callback(self.goal_callback)
    
    def goal_callback(self, future):
        self.goal_handle = future.result()

        if not self.goal_handle.accepted:
            self.get_logger().info('gole rejected...')
            return

        self.get_logger().info('gole accepted...')
        goal_future = self.goal_handle.get_result_async()
        goal_future.add_done_callback(self.result_callback)
        # cancel test
        # self.timer= self.create_timer(1.0, self.timer_callback)

    def feedback_callback(self, msg):
        feedback = msg.feedback
        self.get_logger().info(f'recv feedback: {feedback.remaining}')

    def result_callback(self, future):
        result_handle = future.result()
        res = result_handle.result
        self.get_logger().info(f'recv result: {res.delta}')

        self.destroy_node()
        rclpy.shutdown()
    
    def timer_callback(self):
        self.get_logger().info(f'canceling goal...')

        cancel_future = self.goal_handle.cancel_goal_async()
        cancel_future.add_done_callback(self.cancel_callback)
        # stop timer
        self.timer.cancel()
    
    def cancel_callback(self, future):
        result_handle = future.result()
        if len(result_handle.goals_canceling) > 0:
            self.get_logger().info('canceling goal success...')
        else:
            self.get_logger().info('canceling goal fail...')

        self.destroy_node()
        rclpy.shutdown()


def main(args=None):
    rclpy.init(args=args)

    client = TurtlesimRotateClient()
    client.send_goal(3.14)

    rclpy.spin(client)


if __name__ == '__main__':
    main()
$ cd ~/Workspace/ros_ws
$ colcon build --symlink-install
$ source install/setup.bash
$ ros2 launch ros_tutorial turtlesim.launch.py
$ cd ~/Workspace/ros_ws
$ source install/setup.bash
$ ros2 run ros_tutorial turtlesim_rot_client

3. Action - Server

'turtlesim_rot_server = ros_tutorial.turtlesim_rot_server:main',
#!/usr/bin/env python3

import time
import rclpy
from rclpy.node import Node
from rclpy.action import ActionServer, GoalResponse, CancelResponse
from rclpy.callback_groups import ReentrantCallbackGroup
from rclpy.executors import MultiThreadedExecutor
from turtlesim.action import RotateAbsolute

class TurtlesimRotateServer(Node):
    def __init__(self):
        super().__init__('turtlesim_rot_server')

        self.server = ActionServer(self, RotateAbsolute,
                                   '/turtle1/rotate_absolute',
                                   callback_group=ReentrantCallbackGroup(),
                                   execute_callback=self.execute_callback,
                                   goal_callback=self.goal_callback,
                                   cancel_callback=self.cancel_callback)
        self.get_logger().info('action server started...')
    
   
    def goal_callback(self, goal_request):
        self.get_logger().info(f'recv goal request: {goal_request}')
        return GoalResponse.ACCEPT
    
    def cancel_callback(self, cancel_request):
        self.get_logger().info(f'recv cancel request: {cancel_request}')
        return CancelResponse.ACCEPT


    async def execute_callback(self, goal_handle):
        feedback = RotateAbsolute.Feedback()
        feedback.remaining = 10.0
        for i in range(10):
            if goal_handle.is_cancel_requested:
                goal_handle.canceled()
                self.get_logger().info('action canceled...')
                return RotateAbsolute.Result()

            feedback.remaining -= 1
            goal_handle.publish_feedback(feedback)
            time.sleep(1)

        goal_handle.succeed()
        self.get_logger().info('action succeed...')

        res = RotateAbsolute.Result()
        res.delta = 0.0
        return res


def main(args=None):
    rclpy.init(args=args)

    server = TurtlesimRotateServer()
    executor = MultiThreadedExecutor()
    rclpy.spin(server, executor=executor)

    server.destroy()
    rclpy.shutdown()


if __name__ == '__main__':
    main()
$ cd ~/Workspace/ros_ws
$ colcon build --symlink-install
$ source install/setup.bash
$ ros2 run ros_tutorial turtlesim_rot_server
$ cd ~/Workspace/ros_ws
$ source install/setup.bash
$ ros2 run ros_tutorial turtlesim_rot_client