Untitled

Anonymous
plain_text
02/23/2026 9:59 AM
8.2 KB
10
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.cb_group = ReentrantCallbackGroup()
        
        self._action_server = ActionServer(
            self, FindHiddenGift, 'find_hidden_gift',
            self.execute_callback, callback_group=self.cb_group
        )
        
        self.pub = self.create_publisher(Twist, '/turtle1/cmd_vel', 10)
        
        self.create_subscription(
            Pose, '/turtle1/pose', self.pose_cb, 10, callback_group=self.cb_group
        )
        self.create_subscription(
            Color, '/turtle1/color_sensor', self.color_cb, 10, callback_group=self.cb_group
        )

        self.pose = None
        self.gift_found = False
        self.gift_coords = None
        self.traveled = 0.0
        self.grid_step = 0.5
        
        self.get_logger().info('Сервер запущен')

    def pose_cb(self, msg):
        self.pose = msg

    def color_cb(self, msg):
        if msg.r == 0 and msg.g == 255 and msg.b == 0:
            if not self.gift_found and self.pose:
                self.gift_found = True
                self.gift_coords = (self.pose.x, self.pose.y)
                self.get_logger().info(f'🎁 Найден: ({self.pose.x:.2f}, {self.pose.y:.2f})')

    def wait_pose(self):
        for _ in range(100):  # 5 сек максимум
            if self.pose:
                return True
            time.sleep(0.05)
        return False

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

    def stop(self):
        self.pub.publish(Twist())

    def rotate_to(self, target_angle):
        """Поворот к заданному углу. Только угловая скорость."""
        twist = Twist()
        
        while rclpy.ok() and not self.gift_found:
            error = self.norm_angle(target_angle - self.pose.theta)
            
            if abs(error) < 0.03:  # ~1.7 градуса
                break
            
            # Простой P-регулятор с ограничением
            twist.angular.z = max(-2.0, min(2.0, 3.0 * error))
            self.pub.publish(twist)
            time.sleep(0.02)
        
        self.stop()

    def move_forward(self, distance):
        """Движение вперёд на заданное расстояние. Только линейная скорость."""
        start_x, start_y = self.pose.x, self.pose.y
        twist = Twist()
        twist.linear.x = 1.5
        
        while rclpy.ok() and not self.gift_found:
            dx = self.pose.x - start_x
            dy = self.pose.y - start_y
            traveled = math.sqrt(dx*dx + dy*dy)
            
            if traveled >= distance:
                break
            
            self.pub.publish(twist)
            time.sleep(0.02)
        
        self.stop()
        self.traveled += traveled

    def go_to(self, x, y):
        """Едем к точке: сначала поворот, потом прямо."""
        if not self.wait_pose() or self.gift_found:
            return
        
        dx, dy = x - self.pose.x, y - self.pose.y
        dist = math.sqrt(dx*dx + dy*dy)
        
        if dist < 0.1:  # Уже на месте
            return
        
        # Шаг 1: поворачиваемся к цели
        angle = math.atan2(dy, dx)
        self.rotate_to(angle)
        
        if self.gift_found:
            return
        
        # Шаг 2: едем прямо
        self.move_forward(dist)

    def search(self, area, goal_handle):
        """Поиск змейкой."""
        x_min = area.bottom_left.x + 0.3
        x_max = area.top_right.x - 0.3
        y_min = area.bottom_left.y + 0.3
        y_max = area.top_right.y - 0.3
        
        # Начинаем с левого нижнего угла
        self.go_to(x_min, y_min)
        
        y = y_min
        right = True
        
        while y <= y_max and rclpy.ok() and not self.gift_found:
            # Горизонтальная линия
            target_x = x_max if right else x_min
            self.go_to(target_x, y)
            
            # Feedback
            fb = FindHiddenGift.Feedback()
            fb.gift_ever_detected = self.gift_found
            fb.traveled_distance = self.traveled
            goal_handle.publish_feedback(fb)
            
            # Следующий ряд
            y += self.grid_step
            right = not right
            
            if y <= y_max and not self.gift_found:
                self.go_to(self.pose.x, y)

    def execute_callback(self, goal_handle):
        self.get_logger().info('Начинаю поиск')
        
        self.gift_found = False
        self.gift_coords = None
        self.traveled = 0.0
        
        area = goal_handle.request.search_area
        
        if not self.wait_pose():
            goal_handle.abort()
            return FindHiddenGift.Result()
        
        self.search(area, goal_handle)
        
        result = FindHiddenGift.Result()
        if self.gift_found:
            result.gift_found = True
            result.gift_coordinates.x = self.gift_coords[0]
            result.gift_coordinates.y = self.gift_coords[1]
        else:
            result.gift_found = False
        
        self.get_logger().info(f'Пройдено: {self.traveled:.1f} м')
        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