iRobotEducation / create3_examples

Example nodes to drive the iRobot® Create® 3 Educational Robot
BSD 3-Clause "New" or "Revised" License
51 stars 12 forks source link

Actions won't work in sequence #36

Open skemp117 opened 1 year ago

skemp117 commented 1 year ago

Hello,

I am trying to use actions to move the robot. I only need to perform simple tasks. Turn, drive a few feet, turn. They don't need to be done quickly or with great position. Why can I not string actions together like this? For some reason I cannot run one after the other, they always hang. This is extremely frustrating, and there is very little support online for anything other than the most basic action (which both of the action clients I have work perfectly on their own). I don't really know ROS2 extremely well, but I have been programming for several years. I understand the basics of multi-threading, blocking operations, and asyncio. As far as I understand, rclpy.spin is a blocking operation (without a multi threaded executor) which is fine because I want to wait for the action client to finish before it moves on. Why is this not working?

#!/usr/bin/env python3
import rclpy
from irobot_logger.action_undock import UndockingActionClient
from irobot_logger.action_drive_distance import DriveDistanceActionClient

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

    try:
        # Initialize action clients
        undock_action_client = UndockingActionClient()

        future = undock_action_client.send_goal("{}")
        rclpy.spin_once(undock_action_client) # i've tried spin, spin_once, and spin_until_future_complete

        action_client = DriveDistanceActionClient()
        dist  = 1.0
        speed = 0.5
        future = action_client.send_goal(dist, speed)
        rclpy.spin_once(action_client)

    except KeyboardInterrupt:
        pass

    rclpy.shutdown()

if __name__ == '__main__':
    main()

Here is the drive action client


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

from irobot_create_msgs.action import DriveDistance

class DriveDistanceActionClient(Node):
    '''
    An example of an action client that will cause the iRobot 
    Create3 to drive a specific distance. Subclass of Node.
    '''

    def __init__(self):
        '''
        Purpose
        -------
        initialized by calling the Node constructor, naming our node 
        'drive_distance_action_client'
        '''
        super().__init__('drive_distance_action_client')
        self._action_client = ActionClient(
            self, DriveDistance, 'drive_distance')

    def send_goal(self, distance=0.5, max_translation_speed=0.15):
        '''
        Purpose
        -------
        This method waits for the action server to be available, then sends a 
        goal to the server. It returns a future that we can later wait on.
        '''
        goal_msg = DriveDistance.Goal()
        goal_msg.distance = distance
        goal_msg.max_translation_speed = max_translation_speed

        self._action_client.wait_for_server()

        self._send_goal_future = self._action_client.send_goal_async(goal_msg)

        self._send_goal_future.add_done_callback(self.goal_response_callback)
        return self._send_goal_future

    def goal_response_callback(self, future):
        '''
        Purpose
        -------
        A callback that is executed when the future is complete.
        The future is completed when an action server accepts or rejects the goal request.
        '''
        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):
        '''
        Purpose
        -------
        Similar to sending the goal, we will get a future that will complete when the result is ready.
        '''
        result = future.result().result
        self.get_logger().info('Result: {0}'.format(result))