Загрузка данных


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Автономный полёт по трассе на полигоне ArUco-меток (7×7, маркеры 0–48).
Платформа: Clover + Gazebo + ROS (simple_offboard).

Маршрут:
  42 (взлёт, белый LED) → 44 → 38 (розовый) →
  правый борт (оранжевый, голубой) → 12 (фиолетовый) →
  11 → 17 (красный) → 9 → 8 (жёлтый) →
  левый борт (зелёный, тёмно-синий) → 38 (розовый) →
  42 (посадка, LED выкл.)
"""

import math
import rospy
import tf2_ros
from clover import srv
from clover.srv import SetLEDEffect
from std_srvs.srv import Trigger

# --- Параметры полёта ---
FLIGHT_Z = 1.0
SPEED = 0.6
TOLERANCE = 0.3
MAP_WAIT_TIMEOUT = 30.0   # макс. ожидание фрейма aruco_map, с
ARRIVAL_TIMEOUT = 90.0    # макс. ожидание одной точки, с

# Фрейм карты меток (заполняется после взлёта)
MAP_FRAME = 'aruco_map'

rospy.init_node('autonomous_flight')

get_telemetry = rospy.ServiceProxy('get_telemetry', srv.GetTelemetry)
navigate = rospy.ServiceProxy('navigate', srv.Navigate)
land = rospy.ServiceProxy('land', Trigger)
set_effect = rospy.ServiceProxy('led/set_effect', SetLEDEffect)

tf_buffer = tf2_ros.Buffer()
tf_listener = tf2_ros.TransformListener(tf_buffer)


def set_led(r, g, b, effect='fill'):
    set_effect(effect=effect, r=r, g=g, b=b)


def led_off():
    set_led(0, 0, 0)


WHITE = (255, 255, 255)
PINK = (255, 0, 255)
ORANGE = (255, 128, 0)
CYAN = (0, 255, 255)
PURPLE = (128, 0, 255)
RED = (255, 0, 0)
YELLOW = (255, 255, 0)
GREEN = (0, 255, 0)
DARK_BLUE = (0, 0, 139)


def marker_xy(marker_id):
    """
    Координаты центра маркера в aruco_map.
    Начало — маркер №42 (левый нижний угол), шаг 1 м.
    """
    row = marker_id // 7
    col = marker_id % 7
    return col * 1.0, (6 - row) * 1.0


def midpoint(marker_a, marker_b):
    xa, ya = marker_xy(marker_a)
    xb, yb = marker_xy(marker_b)
    return (xa + xb) / 2.0, (ya + yb) / 2.0


GREEN_BLOCK = midpoint(14, 21)
DARK_BLUE_BLOCK = midpoint(22, 23)
CYAN_BLOCK = midpoint(19, 20)
ORANGE_BLOCK = midpoint(26, 33)


def wait_for_map_frame():
    """Ждём появления фрейма карты меток в TF-дереве."""
    global MAP_FRAME
    deadline = rospy.Time.now() + rospy.Duration(MAP_WAIT_TIMEOUT)
    candidates = ('aruco_map', 'aruco_map_detected')

    rospy.loginfo('Ожидание фрейма карты меток...')
    rate = rospy.Rate(5)
    while not rospy.is_shutdown() and rospy.Time.now() < deadline:
        for frame in candidates:
            try:
                tf_buffer.lookup_transform('map', frame, rospy.Time(0), rospy.Duration(0.5))
                MAP_FRAME = frame
                rospy.loginfo('Карта меток найдена: %s', MAP_FRAME)
                return
            except (tf2_ros.LookupException, tf2_ros.ConnectivityException, tf2_ros.ExtrapolationException):
                pass
        rate.sleep()

    rospy.logwarn('Фрейм aruco_map не найден, используем aruco_map без проверки')


def wait_arrival(tolerance=TOLERANCE, timeout=ARRIVAL_TIMEOUT):
    start = rospy.Time.now()
    while not rospy.is_shutdown():
        if (rospy.Time.now() - start).to_sec() > timeout:
            rospy.logwarn('Таймаут ожидания точки')
            return
        telem = get_telemetry(frame_id='navigate_target')
        if math.sqrt(telem.x ** 2 + telem.y ** 2 + telem.z ** 2) < tolerance:
            return
        rospy.sleep(0.2)


def fly_to(x, y, z=FLIGHT_Z, speed=SPEED, label=''):
    if label:
        rospy.loginfo('Полёт: %s  (%.1f, %.1f, %.1f) [%s]', label, x, y, z, MAP_FRAME)
    navigate(x=x, y=y, z=z, speed=speed, frame_id=MAP_FRAME)
    wait_arrival()


def fly_to_marker(marker_id, z=FLIGHT_Z, speed=SPEED):
    x, y = marker_xy(marker_id)
    fly_to(x, y, z=z, speed=speed, label='маркер %d' % marker_id)


def fly_to_block(xy, color, label='', z=FLIGHT_Z, speed=SPEED):
    set_led(*color)
    fly_to(xy[0], xy[1], z=z, speed=speed, label=label)


def takeoff():
    set_led(*WHITE)
    rospy.loginfo('Взлёт на %.1f м', FLIGHT_Z)
    navigate(x=0, y=0, z=FLIGHT_Z, speed=0.4, frame_id='body', auto_arm=True)
    wait_arrival()
    wait_for_map_frame()
    rospy.sleep(2.0)


def land_at_home():
    rospy.loginfo('Возврат на маркер 42 и посадка')
    fly_to_marker(42)
    land()
    while get_telemetry().armed:
        rospy.sleep(0.2)
    led_off()


def run_mission():
    takeoff()

    fly_to_marker(44)
    set_led(*PINK)
    fly_to_marker(38)

    fly_to_block(ORANGE_BLOCK, ORANGE, label='оранжевый блок')
    fly_to_block(CYAN_BLOCK, CYAN, label='голубой блок')
    set_led(*PURPLE)
    fly_to_marker(12)

    fly_to_marker(11)
    set_led(*RED)
    fly_to_marker(17)

    fly_to_marker(9)
    set_led(*YELLOW)
    fly_to_marker(8)

    fly_to_block(GREEN_BLOCK, GREEN, label='зелёный блок')
    fly_to_block(DARK_BLUE_BLOCK, DARK_BLUE, label='тёмно-синий блок')
    set_led(*PINK)
    fly_to_marker(38)

    land_at_home()
    rospy.loginfo('Миссия завершена')


if __name__ == '__main__':
    try:
        run_mission()
    except rospy.ROSInterruptException:
        pass
    except KeyboardInterrupt:
        rospy.loginfo('Прерывание — посадка')
        try:
            land()
            led_off()
        except rospy.ServiceException:
            pass