Untitled

Anonymous
plain_text
02/22/2026 10:02 AM
9.7 KB
13
Indexable
import rclpy
from rclpy.node import Node
from rclpy.action import ActionServer
from rclpy.callback_groups import ReentrantCallbackGroup
from rclpy.executors import MultiThreadedExecutor
from turtlesim_contest_interface.action import FindHiddenGift
from geometry_msgs.msg import Twist
from turtlesim.msg import Pose, Color
import math
import time


class GiftFinderActionServer(Node):

    def __init__(self):
        super().__init__('gift_finder_action_server')
        
        self.callback_group = ReentrantCallbackGroup()
        
        self._action_server = ActionServer(
            self, FindHiddenGift, 'find_hidden_gift',
            self.execute_callback, callback_group=self.callback_group
        )
        
        self.cmd_vel_publisher = self.create_publisher(Twist, '/turtle1/cmd_vel', 10)
        
        self.create_subscription(
            Pose, '/turtle1/pose', self.pose_callback, 10,
            callback_group=self.callback_group
        )
        
        self.create_subscription(
            Color, '/turtle1/color_sensor', self.color_callback, 10,
            callback_group=self.callback_group
        )

        self.current_pose = None
        self.gift_found = False
        self.gift_coordinates = None
        self.traveled_distance = 0.0
        
        self.linear_speed = 2.0
        self.grid_step = 0.5
        self.angle_tolerance = 0.02
        self.position_tolerance = 0.05
        
        self.get_logger().info('Action Server запущен')

    def pose_callback(self, msg):
        self.current_pose = msg

    def color_callback(self, msg):
        if msg.r == 0 and msg.g == 255 and msg.b == 0:
            if not self.gift_found and self.current_pose:
                self.gift_found = True
                self.gift_coordinates = (self.current_pose.x, self.current_pose.y)
                self.get_logger().info(
                    f'🎁 Подарок найден: ({self.gift_coordinates[0]:.2f}, {self.gift_coordinates[1]:.2f})'
                )

    def wait_for_pose(self, timeout=5.0):
        start = time.time()
        while self.current_pose is None and rclpy.ok():
            if time.time() - start > timeout:
                return False
            time.sleep(0.05)
        return True

    def normalize_angle(self, angle):
        while angle > math.pi:
            angle -= 2 * math.pi
        while angle < -math.pi:
            angle += 2 * math.pi
        return angle

    def stop(self):
        twist = Twist()
        self.cmd_vel_publisher.publish(twist)

    def rotate_to_angle(self, target_angle):
        if not self.wait_for_pose():
            return
        
        Kp = 4.0
        min_speed, max_speed = 0.1, 2.0
        twist = Twist()
        
        while rclpy.ok() and not self.gift_found:
            error = self.normalize_angle(target_angle - self.current_pose.theta)
            
            if abs(error) < self.angle_tolerance:
                break
            
            speed = max(min_speed, min(max_speed, abs(Kp * error)))
            twist.angular.z = speed if error > 0 else -speed
            self.cmd_vel_publisher.publish(twist)
            time.sleep(0.02)
        
        self.stop()

    def move_to_point(self, target_x, target_y):
        if not self.wait_for_pose():
            return False
        
        start_x, start_y = self.current_pose.x, self.current_pose.y
        
        target_angle = math.atan2(target_y - start_y, target_x - start_x)
        self.rotate_to_angle(target_angle)
        
        if self.gift_found:
            return False
        
        twist = Twist()
        
        while rclpy.ok() and not self.gift_found:
            dx = target_x - self.current_pose.x
            dy = target_y - self.current_pose.y
            distance = math.sqrt(dx*dx + dy*dy)
            
            if distance < self.position_tolerance:
                break
            
            target_angle = math.atan2(dy, dx)
            angle_error = self.normalize_angle(target_angle - self.current_pose.theta)
            
            twist.linear.x = min(self.linear_speed, max(0.3, self.linear_speed * distance / 0.5))
            twist.angular.z = 2.0 * angle_error
            self.cmd_vel_publisher.publish(twist)
            time.sleep(0.02)
        
        self.stop()
        
        dx = self.current_pose.x - start_x
        dy = self.current_pose.y - start_y
        self.traveled_distance += math.sqrt(dx*dx + dy*dy)
        
        return not self.gift_found

    def search_snake_pattern(self, search_area, goal_handle):
        x_min = search_area.bottom_left.x + self.grid_step / 2
        x_max = search_area.top_right.x - self.grid_step / 2
        y_min = search_area.bottom_left.y + self.grid_step / 2
        y_max = search_area.top_right.y - self.grid_step / 2
        
        self.move_to_point(x_min, y_min)
        
        current_y = y_min
        moving_right = True
        
        while current_y <= y_max and rclpy.ok() and not self.gift_found:
            target_x = x_max if moving_right else x_min
            
            if not self.move_to_point(target_x, current_y):
                return
            
            feedback = FindHiddenGift.Feedback()
            feedback.gift_ever_detected = self.gift_found
            feedback.traveled_distance = self.traveled_distance
            goal_handle.publish_feedback(feedback)
            
            current_y += self.grid_step
            moving_right = not moving_right
            
            if current_y <= y_max and not self.gift_found:
                if not self.move_to_point(self.current_pose.x, current_y):
                    return

    def execute_callback(self, goal_handle):
        self.get_logger().info('Получен запрос на поиск подарка')
        
        self.gift_found = False
        self.gift_coordinates = None
        self.traveled_distance = 0.0
        
        search_area = goal_handle.request.search_area
        self.get_logger().info(
            f'Область: ({search_area.bottom_left.x:.1f}, {search_area.bottom_left.y:.1f}) - '
            f'({search_area.top_right.x:.1f}, {search_area.top_right.y:.1f})'
        )
        
        if not self.wait_for_pose():
            goal_handle.abort()
            result = FindHiddenGift.Result()
            result.gift_found = False
            return result
        
        self.search_snake_pattern(search_area, goal_handle)
        
        result = FindHiddenGift.Result()
        
        if self.gift_found:
            result.gift_found = True
            result.gift_coordinates.x = self.gift_coordinates[0]
            result.gift_coordinates.y = self.gift_coordinates[1]
            self.get_logger().info(f'Подарок: ({self.gift_coordinates[0]:.2f}, {self.gift_coordinates[1]:.2f})')
        else:
            result.gift_found = False
            self.get_logger().info('Подарок не найден')
        
        self.get_logger().info(f'Пройдено: {self.traveled_distance:.2f} м')
        goal_handle.succeed()
        return result


def main(args=None):
    rclpy.init(args=args)
    node = GiftFinderActionServer()
    
    executor = MultiThreadedExecutor()
    executor.add_node(node)
    
    try:
        executor.spin()
    except KeyboardInterrupt:
        pass
    finally:
        node.stop()
        node.destroy_node()
        rclpy.shutdown()


if __name__ == '__main__':
    main()
Editor is loading...
Leave a Comment