Skip to main content

СИСТЕМЫ УПРАВЛЕНИЯ

🎛️ МОДУЛЬ 6: “СИСТЕМЫ УПРАВЛЕНИЯ” (10 часов)

“От хаоса к порядку: создаем интеллектуальные системы управления полетом”


🧠 ФИЛОСОФИЯ СИСТЕМ УПРАВЛЕНИЯ

Эволюция от компонентов к системе:

🔄 ПУТЬ ТЕХНИЧЕСКОГО МАСТЕРСТВА:

МОДУЛЬ 4: "МЕХАНИК" 
- Понимание физики полета
- Знание характеристик компонентов
- Сборка и балансировка

МОДУЛЬ 5: "ПРОГРАММИСТ"
- Программирование микроконтроллеров
- Работа с датчиками и протоколами
- Создание базовых алгоритмов

МОДУЛЬ 6: "СИСТЕМНЫЙ ИНЖЕНЕР"
- Проектирование поведения системы
- Настройка сложных алгоритмов управления
- Создание интеллектуальных автопилотов
- Диагностика и оптимизация производительности

Концепция “Думающего дрона”:

🤖 ОТ РЕАКТИВНОГО К ИНТЕЛЛЕКТУАЛЬНОМУ:

УРОВЕНЬ 1: РЕАКТИВНОЕ УПРАВЛЕНИЕ
- Простая реакция на команды пилота
- Базовая стабилизация
- Фиксированные алгоритмы

УРОВЕНЬ 2: АДАПТИВНОЕ УПРАВЛЕНИЕ  
- Автоматическая подстройка параметров
- Компенсация внешних возмущений
- Обучение на основе опыта

УРОВЕНЬ 3: ПРЕДИКТИВНОЕ УПРАВЛЕНИЕ
- Предвидение развития ситуации
- Упреждающие корректировки
- Оптимизация траекторий

УРОВЕНЬ 4: ИНТЕЛЛЕКТУАЛЬНОЕ УПРАВЛЕНИЕ
- Принятие решений высокого уровня
- Планирование миссий
- Взаимодействие с другими системами

📐 УРОК 1: ТЕОРИЯ УПРАВЛЕНИЯ НА ПРАКТИКЕ (2.5 часа)

1.1 PID регулятор: от математики к реальности

ИНТУИТИВНОЕ ПОНИМАНИЕ PID:

🎯 PID БЕЗ СЛОЖНЫХ ФОРМУЛ:

P - ПРОПОРЦИОНАЛЬНАЯ СОСТАВЛЯЮЩАЯ:
Аналогия: Резиновая лента между желаемым и текущим положением

Представь дрон на веревочке:
- Большое отклонение → сильное "натяжение" → большая корректировка
- Маленькое отклонение → слабое "натяжение" → слабая корректировка

Практический эффект:
✅ Быстрая реакция на ошибки
❌ Может вызвать колебания
❌ Не убирает постоянную ошибку

I - ИНТЕГРАЛЬНАЯ СОСТАВЛЯЮЩАЯ:
Аналогия: Накопительный "штраф" за долгую ошибку

Представь копилку ошибок:
- Ошибка есть → монетки накапливаются
- Чем дольше ошибка → тем больше коррекция
- Ошибка исчезла → копилка медленно пустеет

Практический эффект:
✅ Убирает постоянные ошибки (дрейф)
✅ Компенсирует систематические возмущения
❌ Может вызвать "раскачку"

D - ДИФФЕРЕНЦИАЛЬНАЯ СОСТАВЛЯЮЩАЯ:
Аналогия: Предсказатель будущего

Представь провидца:
- Ошибка растет быстро → "Стой! Будет хуже!"
- Ошибка уменьшается → "Притормози коррекцию"
- Предвидит куда движется система

Практический эффект:
✅ Сглаживает колебания
✅ Повышает стабильность
❌ Усиливает шум датчиков

ПРАКТИЧЕСКАЯ НАСТРОЙКА PID:

// =====================================
// ИНТЕРАКТИВНАЯ НАСТРОЙКА PID
// =====================================

class InteractivePIDTuner {
private:
  float kp, ki, kd;
  float integral, lastError;
  float setpoint, measurement;
  
  // История для анализа
  float errorHistory[100];
  float outputHistory[100];
  int historyIndex = 0;
  
public:
  InteractivePIDTuner() : kp(1.0), ki(0.1), kd(0.05) {}
  
  float update(float target, float current, float dt) {
    setpoint = target;
    measurement = current;
    
    float error = target - current;
    
    // P составляющая
    float proportional = kp * error;
    
    // I составляющая
    integral += error * dt;
    integral = constrain(integral, -10, 10);  // Anti-windup
    float integralTerm = ki * integral;
    
    // D составляющая  
    float derivative = (error - lastError) / dt;
    float derivativeTerm = kd * derivative;
    lastError = error;
    
    float output = proportional + integralTerm + derivativeTerm;
    
    // Сохранение истории для анализа
    errorHistory[historyIndex] = error;
    outputHistory[historyIndex] = output;
    historyIndex = (historyIndex + 1) % 100;
    
    return constrain(output, -1.0, 1.0);
  }
  
  void tunePID() {
    Serial.println("=== ИНТЕРАКТИВНАЯ НАСТРОЙКА PID ===");
    Serial.println("Команды:");
    Serial.println("p+/p- : увеличить/уменьшить Kp");
    Serial.println("i+/i- : увеличить/уменьшить Ki");  
    Serial.println("d+/d- : увеличить/уменьшить Kd");
    Serial.println("r     : сброс интегратора");
    Serial.println("s     : показать статистику");
    Serial.println("a     : автонастройка");
    
    while (true) {
      if (Serial.available()) {
        String command = Serial.readString();
        command.trim();
        
        if (command == "p+") kp += 0.1;
        else if (command == "p-") kp -= 0.1;
        else if (command == "i+") ki += 0.01;
        else if (command == "i-") ki -= 0.01;
        else if (command == "d+") kd += 0.01;
        else if (command == "d-") kd -= 0.01;
        else if (command == "r") integral = 0;
        else if (command == "s") showStatistics();
        else if (command == "a") autoTune();
        else if (command == "exit") break;
        
        kp = max(0, kp);
        ki = max(0, ki);
        kd = max(0, kd);
        
        Serial.printf("PID: P=%.2f I=%.3f D=%.3f\n", kp, ki, kd);
      }
      
      // Симуляция системы для настройки
      static float simulatedPosition = 0;
      static float simulatedVelocity = 0;
      float targetPosition = 10.0 * sin(millis() / 3000.0);  // Синусоида
      
      float pidOutput = update(targetPosition, simulatedPosition, 0.02);
      
      // Простая модель системы
      simulatedVelocity += pidOutput * 0.02;
      simulatedVelocity *= 0.95;  // Трение
      simulatedPosition += simulatedVelocity * 0.02;
      
      Serial.printf("Target: %6.2f | Current: %6.2f | Error: %6.2f | Output: %6.2f\n",
        targetPosition, simulatedPosition, targetPosition - simulatedPosition, pidOutput);
      
      delay(20);
    }
  }
  
  void showStatistics() {
    // Анализ производительности
    float avgError = 0, maxError = 0, oscillations = 0;
    
    for (int i = 0; i < 100; i++) {
      avgError += abs(errorHistory[i]);
      maxError = max(maxError, abs(errorHistory[i]));
    }
    avgError /= 100;
    
    // Подсчет колебаний
    for (int i = 1; i < 99; i++) {
      if ((outputHistory[i-1] < outputHistory[i]) && (outputHistory[i] > outputHistory[i+1])) {
        oscillations++;
      }
    }
    
    Serial.println("\n=== СТАТИСТИКА ПРОИЗВОДИТЕЛЬНОСТИ ===");
    Serial.printf("Средняя ошибка: %.3f\n", avgError);
    Serial.printf("Максимальная ошибка: %.3f\n", maxError);
    Serial.printf("Колебания за 100 семплов: %.0f\n", oscillations);
    
    // Рекомендации
    if (avgError > 1.0) {
      Serial.println("💡 Большая ошибка - увеличьте Kp");
    }
    if (oscillations > 20) {
      Serial.println("💡 Много колебаний - уменьшите Kp или увеличьте Kd");  
    }
    if (maxError > 0.5 && oscillations < 5) {
      Serial.println("💡 Медленная реакция - увеличьте Kp или уменьшите Kd");
    }
  }
  
  void autoTune() {
    Serial.println("Автонастройка PID по методу Ziegler-Nichols...");
    
    // Упрощенная автонастройка
    float bestKp = 1.0;
    float bestPerformance = 999;
    
    // Поиск критического Kp
    for (float testKp = 0.1; testKp <= 5.0; testKp += 0.1) {
      kp = testKp;
      ki = 0;
      kd = 0;
      
      float performance = testPerformance(3.0);  // Тест 3 секунды
      
      if (performance < bestPerformance) {
        bestPerformance = performance;
        bestKp = testKp;
      }
      
      Serial.printf("Тест Kp=%.1f, производительность: %.2f\n", testKp, performance);
    }
    
    // Установка оптимальных параметров по Ziegler-Nichols
    kp = bestKp * 0.6;
    ki = 2.0 * kp / 1.0;  // Ti = 1 секунда
    kd = kp * 0.125;      // Td = 0.125 секунды
    
    Serial.printf("Автонастройка завершена: P=%.2f I=%.3f D=%.3f\n", kp, ki, kd);
  }
  
private:
  float testPerformance(float testDuration) {
    // Тестируем производительность PID за заданное время
    float totalError = 0;
    int samples = testDuration * 50;  // 50Hz
    
    float testPosition = 0, testVelocity = 0;
    integral = 0;
    lastError = 0;
    
    for (int i = 0; i < samples; i++) {
      float target = 5.0;  // Ступенчатое воздействие
      float pidOut = update(target, testPosition, 0.02);
      
      testVelocity += pidOut * 0.02;
      testVelocity *= 0.95;
      testPosition += testVelocity * 0.02;
      
      totalError += abs(target - testPosition);
    }
    
    return totalError / samples;
  }
};

// Пример использования
InteractivePIDTuner pidTuner;

void setupPIDTuning() {
  pidTuner.tunePID();
}

1.2 Многоконтурные системы управления

КАСКАДНЫЕ РЕГУЛЯТОРЫ:

🏗️ АРХИТЕКТУРА МНОГОУРОВНЕВОГО УПРАВЛЕНИЯ:

УРОВЕНЬ 1: УПРАВЛЕНИЕ ПОЛОЖЕНИЕМ
┌─────────────────────────────────────┐
│ POSITION CONTROLLER                 │
│ Вход: Желаемая позиция (X, Y, Z)   │
│ Выход: Желаемые углы (Roll, Pitch)  │
│ Частота: 10-50 Hz                   │
└─────────────────────────────────────┘
                │
                ▼
УРОВЕНЬ 2: УПРАВЛЕНИЕ УГЛАМИ
┌─────────────────────────────────────┐
│ ATTITUDE CONTROLLER                 │
│ Вход: Желаемые углы                 │
│ Выход: Желаемые угловые скорости    │
│ Частота: 50-100 Hz                  │
└─────────────────────────────────────┘
                │
                ▼
УРОВЕНЬ 3: УПРАВЛЕНИЕ УГЛОВЫМИ СКОРОСТЯМИ
┌─────────────────────────────────────┐
│ RATE CONTROLLER                     │
│ Вход: Желаемые угловые скорости     │
│ Выход: Команды моторам              │
│ Частота: 100-1000 Hz                │
└─────────────────────────────────────┘

ПРАКТИЧЕСКАЯ РЕАЛИЗАЦИЯ:

// =====================================
// КАСКАДНАЯ СИСТЕМА УПРАВЛЕНИЯ
// =====================================

class CascadeController {
public:
  struct Position {
    float x, y, z;
  };
  
  struct Attitude {
    float roll, pitch, yaw;
  };
  
  struct Rates {
    float rollRate, pitchRate, yawRate;
  };
  
private:
  // PID контроллеры для каждого уровня
  InteractivePIDTuner positionXPID, positionYPID, positionZPID;
  InteractivePIDTuner attitudeRollPID, attitudePitchPID, attitudeYawPID;
  InteractivePIDTuner rateRollPID, ratePitchPID, rateYawPID;
  
  // Ограничения между уровнями
  float maxTiltAngle = 30.0;      // Максимальный наклон ±30°
  float maxYawRate = 180.0;       // Максимальная скорость поворота ±180°/с
  float maxRotationRate = 720.0;  // Максимальная угловая скорость ±720°/с
  
public:
  CascadeController() {
    // Настройка PID параметров для каждого уровня
    setupPositionControllers();
    setupAttitudeControllers(); 
    setupRateControllers();
  }
  
  // Главная функция управления
  void update(Position desiredPos, Position currentPos,
              Attitude currentAtt, Rates currentRates,
              float dt, float motorOutputs[4]) {
    
    // УРОВЕНЬ 1: Управление позицией → желаемые углы
    Attitude desiredAttitude = positionControl(desiredPos, currentPos, dt);
    
    // УРОВЕНЬ 2: Управление углами → желаемые угловые скорости  
    Rates desiredRates = attitudeControl(desiredAttitude, currentAtt, dt);
    
    // УРОВЕНЬ 3: Управление угловыми скоростями → команды моторам
    rateControl(desiredRates, currentRates, dt, motorOutputs);
  }
  
private:
  void setupPositionControllers() {
    // Позиционные контроллеры - медленные, интегральные
    // Настройки для плавного полета
  }
  
  void setupAttitudeControllers() {
    // Угловые контроллеры - средняя скорость
    // Баланс между точностью и стабильностью
  }
  
  void setupRateControllers() {
    // Скоростные контроллеры - быстрые, без интегральной составляющей
    // Максимальная отзывчивость
  }
  
  Attitude positionControl(Position desired, Position current, float dt) {
    Attitude output;
    
    // X позиция → Roll угол
    float xError = desired.x - current.x;
    float rollCommand = positionXPID.update(0, xError, dt);
    output.roll = constrain(rollCommand, -maxTiltAngle, maxTiltAngle);
    
    // Y позиция → Pitch угол  
    float yError = desired.y - current.y;
    float pitchCommand = positionYPID.update(0, yError, dt);
    output.pitch = constrain(pitchCommand, -maxTiltAngle, maxTiltAngle);
    
    // Z позиция → Throttle (обрабатывается отдельно)
    // Yaw остается от пилота или навигации
    output.yaw = 0;  // Или from external source
    
    return output;
  }
  
  Rates attitudeControl(Attitude desired, Attitude current, float dt) {
    Rates output;
    
    // Roll угол → Roll rate
    float rollError = desired.roll - current.roll;
    output.rollRate = attitudeRollPID.update(0, rollError, dt);
    output.rollRate = constrain(output.rollRate, -maxRotationRate, maxRotationRate);
    
    // Pitch угол → Pitch rate
    float pitchError = desired.pitch - current.pitch;
    output.pitchRate = attitudePitchPID.update(0, pitchError, dt);
    output.pitchRate = constrain(output.pitchRate, -maxRotationRate, maxRotationRate);
    
    // Yaw угол → Yaw rate
    float yawError = desired.yaw - current.yaw;
    // Обработка перехода через ±180°
    if (yawError > 180) yawError -= 360;
    if (yawError < -180) yawError += 360;
    
    output.yawRate = attitudeYawPID.update(0, yawError, dt);
    output.yawRate = constrain(output.yawRate, -maxYawRate, maxYawRate);
    
    return output;
  }
  
  void rateControl(Rates desired, Rates current, float dt, float motors[4]) {
    // Контроль угловых скоростей → PWM моторов
    
    float rollOutput = rateRollPID.update(desired.rollRate, current.rollRate, dt);
    float pitchOutput = ratePitchPID.update(desired.pitchRate, current.pitchRate, dt);
    float yawOutput = rateYawPID.update(desired.yawRate, current.yawRate, dt);
    
    // Микширование на квадрокоптер (X конфигурация)
    float throttle = 0.5;  // Базовый газ (от altitude controller)
    
    motors[0] = throttle + pitchOutput - rollOutput - yawOutput;  // Front Right
    motors[1] = throttle - pitchOutput - rollOutput + yawOutput;  // Rear Right
    motors[2] = throttle - pitchOutput + rollOutput - yawOutput;  // Rear Left  
    motors[3] = throttle + pitchOutput + rollOutput + yawOutput;  // Front Left
    
    // Нормализация (если сумма превышает максимум)
    float maxMotor = max({motors[0], motors[1], motors[2], motors[3]});
    if (maxMotor > 1.0) {
      float scale = 1.0 / maxMotor;
      for (int i = 0; i < 4; i++) {
        motors[i] *= scale;
      }
    }
    
    // Ограничение минимума
    for (int i = 0; i < 4; i++) {
      motors[i] = constrain(motors[i], 0.0, 1.0);
    }
  }
};

// Пример интеграции с системой
CascadeController flightController;

void flightControlLoop() {
  // Получение данных датчиков
  CascadeController::Position currentPos = getCurrentPosition();
  CascadeController::Attitude currentAtt = getCurrentAttitude();
  CascadeController::Rates currentRates = getCurrentRates();
  
  // Желаемая позиция (от навигации или пилота)
  CascadeController::Position targetPos = getTargetPosition();
  
  // Управление
  float motorCommands[4];
  flightController.update(targetPos, currentPos, currentAtt, currentRates, 0.01, motorCommands);
  
  // Вывод на моторы
  setMotorOutputs(motorCommands);
}

🚁 УРОК 2: РЕЖИМЫ ПОЛЕТА И АВТОПИЛОТ (3.5 часа)

2.1 Иерархия режимов полета

АРХИТЕКТУРА РЕЖИМОВ:

🎯 КЛАССИФИКАЦИЯ РЕЖИМОВ ПО СЛОЖНОСТИ:

БАЗОВЫЕ РЕЖИМЫ (Ручное управление):
├── ACRO - Прямое управление угловыми скоростями
├── STABILIZE - Стабилизация углов
└── ALT_HOLD - Стабилизация + удержание высоты

НАВИГАЦИОННЫЕ РЕЖИМЫ (Полуавтоматические):
├── LOITER - Удержание позиции GPS
├── GUIDED - Полет к указанной точке
└── CIRCLE - Полет по кругу

АВТОНОМНЫЕ РЕЖИМЫ (Полная автоматизация):
├── AUTO - Полет по заданному маршруту
├── RTL - Автоматический возврат домой
├── LAND - Автоматическая посадка
└── MISSION - Выполнение сложных миссий

АВАРИЙНЫЕ РЕЖИМЫ (Безопасность):
├── FAILSAFE - Аварийные процедуры
├── BRAKE - Экстренное торможение
└── EMERGENCY_LAND - Аварийная посадка

УНИВЕРСАЛЬНАЯ СИСТЕМА РЕЖИМОВ:

// =====================================
// ДИСПЕТЧЕР РЕЖИМОВ ПОЛЕТА
// =====================================

class FlightModeManager {
public:
  enum FlightMode {
    DISARMED = 0,
    ACRO = 1,
    STABILIZE = 2,
    ALT_HOLD = 3,
    LOITER = 4,
    GUIDED = 5,
    AUTO = 6,
    RTL = 7,
    LAND = 8,
    FAILSAFE = 9
  };
  
  struct ControlState {
    // Входы от пилота
    float rollInput, pitchInput, yawInput, throttleInput;
    
    // Навигационные команды
    float targetLat, targetLon, targetAlt;
    float targetVelX, targetVelY, targetVelZ;
    
    // Состояние системы
    bool isArmed;
    bool gpsHealthy;
    bool altitudeHealthy;
    bool radioHealthy;
    
    // Выходы для контроллера
    float rollCommand, pitchCommand, yawCommand, throttleCommand;
    bool autoControlActive;
  };
  
private:
  FlightMode currentMode = DISARMED;
  FlightMode previousMode = DISARMED;
  ControlState state;
  
  // Для режимов с памятью
  float altHoldTarget = 0;
  float loiterLat = 0, loiterLon = 0;
  bool targetSet = false;
  
  // Таймеры и безопасность
  unsigned long modeChangeTime = 0;
  unsigned long lastRadioUpdate = 0;
  
public:
  bool setMode(FlightMode newMode) {
    // Проверка возможности перехода
    if (!canTransitionTo(newMode)) {
      Serial.printf("Cannot transition from %s to %s\n", 
        getModeString(currentMode), getModeString(newMode));
      return false;
    }
    
    // Выход из текущего режима
    exitMode(currentMode);
    
    // Смена режима
    previousMode = currentMode;
    currentMode = newMode;
    modeChangeTime = millis();
    
    // Вход в новый режим
    enterMode(newMode);
    
    Serial.printf("Mode changed: %s → %s\n", 
      getModeString(previousMode), getModeString(currentMode));
    
    return true;
  }
  
  void update(ControlState& inputState) {
    state = inputState;
    
    // Проверка безопасности
    checkSafetyConditions();
    
    // Обработка текущего режима
    switch (currentMode) {
      case DISARMED:
        handleDisarmed();
        break;
      case ACRO:
        handleAcro();
        break;
      case STABILIZE:
        handleStabilize();
        break;
      case ALT_HOLD:
        handleAltHold();
        break;
      case LOITER:
        handleLoiter();
        break;
      case GUIDED:
        handleGuided();
        break;
      case AUTO:
        handleAuto();
        break;
      case RTL:
        handleRTL();
        break;
      case LAND:
        handleLand();
        break;
      case FAILSAFE:
        handleFailsafe();
        break;
    }
    
    // Обновление выходного состояния
    inputState = state;
  }
  
  FlightMode getCurrentMode() { return currentMode; }
  String getModeString() { return getModeString(currentMode); }
  
private:
  bool canTransitionTo(FlightMode newMode) {
    switch (newMode) {
      case DISARMED:
        return true;  // Всегда можно disarm
        
      case ACRO:
      case STABILIZE:
        return state.isArmed && state.radioHealthy;
        
      case ALT_HOLD:
        return state.isArmed && state.radioHealthy && state.altitudeHealthy;
        
      case LOITER:
      case GUIDED:
      case AUTO:
      case RTL:
        return state.isArmed && state.radioHealthy && 
               state.altitudeHealthy && state.gpsHealthy;
        
      case LAND:
        return state.isArmed;
        
      case FAILSAFE:
        return true;  // Failsafe можно всегда
    }
    return false;
  }
  
  void enterMode(FlightMode mode) {
    switch (mode) {
      case ALT_HOLD:
        altHoldTarget = getCurrentAltitude();
        targetSet = true;
        Serial.printf("Altitude hold target set: %.1fm\n", altHoldTarget);
        break;
        
      case LOITER:
        if (state.gpsHealthy) {
          loiterLat = getCurrentLatitude();
          loiterLon = getCurrentLongitude();
          altHoldTarget = getCurrentAltitude();
          targetSet = true;
          Serial.printf("Loiter target: %.6f, %.6f, %.1fm\n", 
            loiterLat, loiterLon, altHoldTarget);
        }
        break;
        
      case RTL:
        state.targetLat = getHomeLat();
        state.targetLon = getHomeLon();
        state.targetAlt = getHomeAlt() + 20.0;  // +20m безопасная высота
        targetSet = true;
        Serial.println("Return to Launch initiated");
        break;
    }
  }
  
  void exitMode(FlightMode mode) {
    // Очистка состояния при выходе из режима
    targetSet = false;
    state.autoControlActive = false;
  }
  
  void handleDisarmed() {
    // В режиме DISARMED все моторы выключены
    state.rollCommand = 0;
    state.pitchCommand = 0;
    state.yawCommand = 0;
    state.throttleCommand = 0;
    state.autoControlActive = false;
  }
  
  void handleAcro() {
    // Прямое управление угловыми скоростями
    state.rollCommand = state.rollInput * 720.0;      // ±720°/s
    state.pitchCommand = state.pitchInput * 720.0;
    state.yawCommand = state.yawInput * 360.0;        // ±360°/s
    state.throttleCommand = state.throttleInput;
    state.autoControlActive = false;
  }
  
  void handleStabilize() {
    // Стабилизация углов
    state.rollCommand = state.rollInput * 45.0;       // ±45° max angle
    state.pitchCommand = state.pitchInput * 45.0;
    state.yawCommand = state.yawInput * 180.0;        // ±180°/s yaw rate
    state.throttleCommand = state.throttleInput;
    state.autoControlActive = false;
  }
  
  void handleAltHold() {
    // Стабилизация + удержание высоты
    handleStabilize();  // Базовая стабилизация
    
    // Автоматическое управление высотой
    if (targetSet) {
      // Изменение целевой высоты стиком газа
      if (abs(state.throttleInput - 0.5) > 0.1) {
        altHoldTarget += (state.throttleInput - 0.5) * 2.0;  // 2 м/с max
        Serial.printf("New altitude target: %.1fm\n", altHoldTarget);
      }
      
      state.targetAlt = altHoldTarget;
      state.autoControlActive = true;
    }
  }
  
  void handleLoiter() {
    // Удержание позиции GPS + высоты
    if (targetSet && state.gpsHealthy) {
      state.targetLat = loiterLat;
      state.targetLon = loiterLon;
      state.targetAlt = altHoldTarget;
      
      // Пилот может сместить target стиками
      if (abs(state.rollInput) > 0.1 || abs(state.pitchInput) > 0.1) {
        // Переключаемся в режим "nudge" - смещение позиции
        float nudgeDistance = 0.00001;  // ~1 метр в градусах
        loiterLat += state.pitchInput * nudgeDistance;
        loiterLon += state.rollInput * nudgeDistance;
      }
      
      state.autoControlActive = true;
    } else {
      // Fallback к ALT_HOLD если GPS потерян
      setMode(ALT_HOLD);
    }
  }
  
  void handleGuided() {
    // Полет к заданной точке
    if (targetSet) {
      // Команды устанавливаются извне (GCS, companion computer)
      state.autoControlActive = true;
    }
  }
  
  void handleAuto() {
    // Выполнение автономной миссии
    // Здесь должна быть интеграция с mission planner
    state.autoControlActive = true;
    
    // Простая заглушка - лететь по квадрату
    executeSimpleMission();
  }
  
  void handleRTL() {
    // Возврат домой
    if (!targetSet) {
      enterMode(RTL);  // Переустановка target если потерян
    }
    
    float distanceToHome = calculateDistance(
      getCurrentLatitude(), getCurrentLongitude(),
      state.targetLat, state.targetLon);
    
    if (distanceToHome > 5.0) {
      // Лететь домой
      state.autoControlActive = true;
    } else {
      // Достигли дома - переключаемся на посадку
      setMode(LAND);
    }
  }
  
  void handleLand() {
    // Автоматическая посадка
    state.targetVelZ = -1.0;  // Снижение 1 м/с
    state.autoControlActive = true;
    
    // Проверка касания земли
    if (getCurrentAltitude() < 0.5 && getCurrentVerticalSpeed() < 0.1) {
      Serial.println("Landing detected - disarming");
      setMode(DISARMED);
    }
  }
  
  void handleFailsafe() {
    // Аварийные процедуры
    Serial.println("FAILSAFE MODE - Emergency procedures");
    
    static unsigned long failsafeStartTime = 0;
    if (failsafeStartTime == 0) {
      failsafeStartTime = millis();
    }
    
    unsigned long failsafeTime = millis() - failsafeStartTime;
    
    if (failsafeTime < 5000) {
      // Первые 5 секунд - попытка стабилизации
      state.rollCommand = 0;
      state.pitchCommand = 0;
      state.yawCommand = 0;
      state.throttleCommand = 0.4;  // Низкий газ
    } else {
      // После 5 секунд - аварийная посадка
      setMode(LAND);
      failsafeStartTime = 0;
    }
    
    state.autoControlActive = true;
  }
  
  void checkSafetyConditions() {
    // Проверка потери радиосвязи
    if (millis() - lastRadioUpdate > 2000) {  // 2 секунды без сигнала
      state.radioHealthy = false;
      if (currentMode != FAILSAFE && currentMode != DISARMED) {
        Serial.println("Radio failsafe triggered!");
        setMode(FAILSAFE);
      }
    }
    
    // Проверка низкого заряда батареи
    float batteryVoltage = getBatteryVoltage();
    if (batteryVoltage < 10.5 && state.isArmed) {  // Критично для 3S LiPo
      Serial.println("Low battery failsafe!");
      setMode(RTL);
    }
    
    // Проверка потери GPS в навигационных режимах
    if (!state.gpsHealthy && 
        (currentMode == LOITER || currentMode == GUIDED || 
         currentMode == AUTO || currentMode == RTL)) {
      Serial.println("GPS failsafe - switching to ALT_HOLD");
      setMode(ALT_HOLD);
    }
  }
  
  void executeSimpleMission() {
    // Простая миссия - полет по квадрату
    static int waypointIndex = 0;
    static float waypoints[][2] = {
      {0.0001, 0.0001},   // +10m North, +10m East
      {0.0001, -0.0001},  // +10m North, -10m East  
      {-0.0001, -0.0001}, // -10m North, -10m East
      {-0.0001, 0.0001}   // -10m North, +10m East
    };
    
    float homeLat = getHomeLat();
    float homeLon = getHomeLon();
    
    state.targetLat = homeLat + waypoints[waypointIndex][0];
    state.targetLon = homeLon + waypoints[waypointIndex][1];
    state.targetAlt = getHomeAlt() + 10.0;
    
    // Проверка достижения waypoint
    float distance = calculateDistance(
      getCurrentLatitude(), getCurrentLongitude(),
      state.targetLat, state.targetLon);
    
    if (distance < 2.0) {  // Достигли waypoint с точностью 2м
      waypointIndex = (waypointIndex + 1) % 4;
      Serial.printf("Waypoint %d reached, proceeding to waypoint %d\n", 
        waypointIndex-1, waypointIndex);
    }
  }
  
  // Вспомогательные функции
  String getModeString(FlightMode mode) {
    switch (mode) {
      case DISARMED: return "DISARMED";
      case ACRO: return "ACRO";
      case STABILIZE: return "STABILIZE";
      case ALT_HOLD: return "ALT_HOLD";
      case LOITER: return "LOITER";
      case GUIDED: return "GUIDED";
      case AUTO: return "AUTO";
      case RTL: return "RTL";
      case LAND: return "LAND";
      case FAILSAFE: return "FAILSAFE";
      default: return "UNKNOWN";
    }
  }
  
  float calculateDistance(float lat1, float lon1, float lat2, float lon2) {
    // Упрощенный расчет расстояния (для небольших дистанций)
    float dlat = lat2 - lat1;
    float dlon = lon2 - lon1;
    return sqrt(dlat*dlat + dlon*dlon) * 111319.5;  // Примерно в метрах
  }
  
  // Заглушки для получения данных датчиков
  float getCurrentAltitude() { return 10.0; }  // Получить из барометра
  float getCurrentLatitude() { return 0.0; }   // Получить из GPS
  float getCurrentLongitude() { return 0.0; }  // Получить из GPS
  float getCurrentVerticalSpeed() { return 0.0; }
  float getBatteryVoltage() { return 12.0; }
  float getHomeLat() { return 0.0; }
  float getHomeLon() { return 0.0; }
  float getHomeAlt() { return 0.0; }
};

// Интеграция в основной цикл
FlightModeManager flightModes;

void flightLoop() {
  // Получение входных данных
  FlightModeManager::ControlState controlState;
  controlState.rollInput = getRollInput();
  controlState.pitchInput = getPitchInput();
  controlState.yawInput = getYawInput();
  controlState.throttleInput = getThrottleInput();
  
  controlState.isArmed = getArmStatus();
  controlState.gpsHealthy = getGPSStatus();
  controlState.altitudeHealthy = getAltitudeStatus();
  controlState.radioHealthy = getRadioStatus();
  
  // Обработка режимов
  flightModes.update(controlState);
  
  // Использование результатов
  if (controlState.autoControlActive) {
    // Передача в автономный контроллер
    setAutonomousTarget(controlState.targetLat, controlState.targetLon, controlState.targetAlt);
  } else {
    // Передача в ручной контроллер
    setManualCommands(controlState.rollCommand, controlState.pitchCommand, 
                     controlState.yawCommand, controlState.throttleCommand);
  }
}

2.2 Системы навигации и планирования миссий

WAYPOINT NAVIGATION:

// =====================================
// СИСТЕМА НАВИГАЦИИ ПО WAYPOINTS
// =====================================

class WaypointNavigator {
public:
  struct Waypoint {
    double latitude, longitude;
    float altitude;
    float speed;              // Желаемая скорость м/с
    float acceptanceRadius;   // Радиус принятия waypoint
    int waitTime;            // Время ожидания в секундах
    int action;              // Действие в точке (фото, сброс груза и т.д.)
  };
  
  struct NavigationState {
    double currentLat, currentLon;
    float currentAlt;
    float currentSpeed;
    float bearing;           // Направление к цели
    float distance;          // Расстояние до цели
    float crossTrackError;   // Отклонение от линии пути
  };
  
private:
  std::vector<Waypoint> mission;
  int currentWaypointIndex = 0;
  bool missionActive = false;
  unsigned long waypointReachTime = 0;
  
  NavigationState navState;
  
public:
  void loadMission(const std::vector<Waypoint>& waypoints) {
    mission = waypoints;
    currentWaypointIndex = 0;
    missionActive = false;
    
    Serial.printf("Mission loaded: %d waypoints\n", mission.size());
    for (int i = 0; i < mission.size(); i++) {
      Serial.printf("WP%d: %.6f, %.6f, %.1fm\n", 
        i, mission[i].latitude, mission[i].longitude, mission[i].altitude);
    }
  }
  
  void startMission() {
    if (mission.empty()) {
      Serial.println("No mission loaded!");
      return;
    }
    
    missionActive = true;
    currentWaypointIndex = 0;
    waypointReachTime = 0;
    
    Serial.println("Mission started!");
    printCurrentWaypoint();
  }
  
  void stopMission() {
    missionActive = false;
    Serial.println("Mission stopped");
  }
  
  bool update(double currentLat, double currentLon, float currentAlt) {
    if (!missionActive || mission.empty()) {
      return false;
    }
    
    navState.currentLat = currentLat;
    navState.currentLon = currentLon;
    navState.currentAlt = currentAlt;
    
    if (currentWaypointIndex >= mission.size()) {
      // Миссия завершена
      missionActive = false;
      Serial.println("🎉 Mission completed!");
      return false;
    }
    
    Waypoint& currentWP = mission[currentWaypointIndex];
    
    // Расчет навигационных параметров
    calculateNavigation(currentWP);
    
    // Проверка достижения waypoint
    if (navState.distance <= currentWP.acceptanceRadius) {
      if (waypointReachTime == 0) {
        waypointReachTime = millis();
        Serial.printf("Waypoint %d reached! Distance: %.1fm\n", 
          currentWaypointIndex, navState.distance);
        
        // Выполнение действия в точке
        executeWaypointAction(currentWP.action);
      }
      
      // Ожидание в точке
      if (millis() - waypointReachTime >= currentWP.waitTime * 1000) {
        currentWaypointIndex++;
        waypointReachTime = 0;
        
        if (currentWaypointIndex < mission.size()) {
          Serial.printf("Proceeding to waypoint %d\n", currentWaypointIndex);
          printCurrentWaypoint();
        }
      }
    }
    
    return true;
  }
  
  NavigationState getNavigationState() { return navState; }
  
  // Генерация стандартных миссий
  void generateSurveyMission(double centerLat, double centerLon, 
                           float width, float height, float spacing, float altitude) {
    mission.clear();
    
    // Сетка для съемки
    int lines = height / spacing;
    bool leftToRight = true;
    
    for (int i = 0; i < lines; i++) {
      float y = -height/2 + i * spacing;
      
      if (leftToRight) {
        // Слева направо
        addWaypoint(centerLat + y/111319.5, centerLon - width/2/111319.5, altitude);
        addWaypoint(centerLat + y/111319.5, centerLon + width/2/111319.5, altitude);
      } else {
        // Справа налево
        addWaypoint(centerLat + y/111319.5, centerLon + width/2/111319.5, altitude);
        addWaypoint(centerLat + y/111319.5, centerLon - width/2/111319.5, altitude);
      }
      
      leftToRight = !leftToRight;
    }
    
    Serial.printf("Survey mission generated: %d waypoints\n", mission.size());
  }
  
  void generateCircleMission(double centerLat, double centerLon, 
                           float radius, int points, float altitude) {
    mission.clear();
    
    for (int i = 0; i <= points; i++) {
      float angle = i * 2 * PI / points;
      float lat = centerLat + (radius * cos(angle)) / 111319.5;
      float lon = centerLon + (radius * sin(angle)) / 111319.5;
      
      addWaypoint(lat, lon, altitude);
    }
    
    Serial.printf("Circle mission generated: %d waypoints\n", mission.size());
  }
  
private:
  void addWaypoint(double lat, double lon, float alt, 
                  float speed = 5.0, float radius = 3.0, int wait = 0, int action = 0) {
    Waypoint wp;
    wp.latitude = lat;
    wp.longitude = lon;
    wp.altitude = alt;
    wp.speed = speed;
    wp.acceptanceRadius = radius;
    wp.waitTime = wait;
    wp.action = action;
    
    mission.push_back(wp);
  }
  
  void calculateNavigation(const Waypoint& target) {
    // Расстояние до цели
    navState.distance = calculateDistance(
      navState.currentLat, navState.currentLon,
      target.latitude, target.longitude);
    
    // Направление к цели
    navState.bearing = calculateBearing(
      navState.currentLat, navState.currentLon,
      target.latitude, target.longitude);
    
    // Cross-track error (отклонение от линии пути)
    if (currentWaypointIndex > 0) {
      Waypoint& prevWP = mission[currentWaypointIndex - 1];
      navState.crossTrackError = calculateCrossTrackError(
        prevWP.latitude, prevWP.longitude,
        target.latitude, target.longitude,
        navState.currentLat, navState.currentLon);
    } else {
      navState.crossTrackError = 0;
    }
  }
  
  float calculateDistance(double lat1, double lon1, double lat2, double lon2) {
    // Haversine formula для точного расчета
    double R = 6371000; // Радиус Земли в метрах
    double phi1 = lat1 * PI / 180.0;
    double phi2 = lat2 * PI / 180.0;
    double deltaPhi = (lat2 - lat1) * PI / 180.0;
    double deltaLambda = (lon2 - lon1) * PI / 180.0;
    
    double a = sin(deltaPhi/2) * sin(deltaPhi/2) +
               cos(phi1) * cos(phi2) *
               sin(deltaLambda/2) * sin(deltaLambda/2);
    double c = 2 * atan2(sqrt(a), sqrt(1-a));
    
    return R * c;
  }
  
  float calculateBearing(double lat1, double lon1, double lat2, double lon2) {
    double phi1 = lat1 * PI / 180.0;
    double phi2 = lat2 * PI / 180.0;
    double deltaLambda = (lon2 - lon1) * PI / 180.0;
    
    double y = sin(deltaLambda) * cos(phi2);
    double x = cos(phi1) * sin(phi2) - sin(phi1) * cos(phi2) * cos(deltaLambda);
    
    double bearing = atan2(y, x) * 180.0 / PI;
    return fmod(bearing + 360.0, 360.0);  // Нормализация 0-360°
  }
  
  float calculateCrossTrackError(double lat1, double lon1, double lat2, double lon2,
                               double currentLat, double currentLon) {
    // Упрощенный расчет для небольших расстояний
    double d13 = calculateDistance(lat1, lon1, currentLat, currentLon);
    double bearing13 = calculateBearing(lat1, lon1, currentLat, currentLon) * PI / 180.0;
    double bearing12 = calculateBearing(lat1, lon1, lat2, lon2) * PI / 180.0;
    
    return asin(sin(d13/6371000) * sin(bearing13 - bearing12)) * 6371000;
  }
  
  void executeWaypointAction(int action) {
    switch (action) {
      case 1:
        Serial.println("📸 Taking photo");
        // triggerCamera();
        break;
      case 2:
        Serial.println("📦 Dropping payload");
        // dropPayload();
        break;
      case 3:
        Serial.println("📡 Recording telemetry");
        // recordTelemetry();
        break;
      default:
        // Никакого действия
        break;
    }
  }
  
  void printCurrentWaypoint() {
    if (currentWaypointIndex < mission.size()) {
      Waypoint& wp = mission[currentWaypointIndex];
      Serial.printf("Current target - WP%d: %.6f, %.6f, %.1fm\n",
        currentWaypointIndex, wp.latitude, wp.longitude, wp.altitude);
    }
  }
};

// Интеграция с системой управления
WaypointNavigator navigator;

void missionLoop() {
  // Получение текущей позиции
  double currentLat = getCurrentLatitude();
  double currentLon = getCurrentLongitude();
  float currentAlt = getCurrentAltitude();
  
  // Обновление навигации
  if (navigator.update(currentLat, currentLon, currentAlt)) {
    WaypointNavigator::NavigationState nav = navigator.getNavigationState();
    
    // Передача команд в систему управления
    setNavigationTarget(nav.bearing, nav.distance, nav.crossTrackError);
    
    // Отладочная информация
    Serial.printf("Nav: bearing=%.1f° distance=%.1fm crosstrack=%.1fm\n",
      nav.bearing, nav.distance, nav.crossTrackError);
  }
}

📡 УРОК 3: ТЕЛЕМЕТРИЯ И МОНИТОРИНГ (2.5 часа)

3.1 Системы телеметрии реального времени

АРХИТЕКТУРА ТЕЛЕМЕТРИИ:

📊 МНОГОУРОВНЕВАЯ СИСТЕМА МОНИТОРИНГА:

УРОВЕНЬ 1: СБОР ДАННЫХ (1000Hz)
├── IMU данные (углы, скорости, ускорения)
├── Данные моторов (обороты, ток, температура)
├── Состояние батареи (напряжение, ток, заряд)
└── Системные метрики (CPU, память, температура)

УРОВЕНЬ 2: ОБРАБОТКА И ФИЛЬТРАЦИЯ (100Hz)
├── Sensor fusion (объединение датчиков)
├── State estimation (оценка состояния)
├── Performance metrics (показатели производительности)
└── Health monitoring (контроль исправности)

УРОВЕНЬ 3: ПЕРЕДАЧА ДАННЫХ (10Hz)
├── Critical data (критически важные данные)
├── Navigation data (навигационная информация)
├── Status messages (сообщения о состоянии)
└── Error reports (отчеты об ошибках)

УРОВЕНЬ 4: ВИЗУАЛИЗАЦИЯ (1Hz)
├── Real-time dashboard (панель в реальном времени)
├── Historical plots (графики истории)
├── 3D visualization (3D визуализация)
└── Alert notifications (уведомления о проблемах)

УНИВЕРСАЛЬНАЯ СИСТЕМА ТЕЛЕМЕТРИИ:

// =====================================
// КОМПЛЕКСНАЯ СИСТЕМА ТЕЛЕМЕТРИИ
// =====================================

#include <WiFi.h>
#include <WebServer.h>
#include <WebSocketsServer.h>
#include <ArduinoJson.h>
#include <SPIFFS.h>

class TelemetrySystem {
public:
  struct FlightData {
    // Временная метка
    unsigned long timestamp;
    
    // Ориентация и движение
    float roll, pitch, yaw;
    float rollRate, pitchRate, yawRate;
    float accelX, accelY, accelZ;
    
    // Позиция и навигация
    double latitude, longitude;
    float altitude, altitudeGPS;
    float velocityN, velocityE, velocityD;
    float groundSpeed, airSpeed;
    
    // Система управления
    float motor1, motor2, motor3, motor4;
    float rollCommand, pitchCommand, yawCommand, throttleCommand;
    int flightMode;
    bool armed;
    
    // Система питания
    float batteryVoltage, batteryCurrent;
    float batteryCapacityUsed, batteryRemaining;
    int batteryCells;
    
    // Датчики окружения
    float temperature, pressure, humidity;
    float windSpeed, windDirection;
    
    // Системные данные
    float cpuUsage, memoryUsage;
    int wifiSignal, gpsStatus;
    int loopFrequency;
    
    // Статус и ошибки
    uint32_t statusFlags;
    uint32_t errorFlags;
  };
  
private:
  WebServer httpServer;
  WebSocketsServer webSocketServer;
  
  FlightData currentData;
  std::vector<FlightData> dataBuffer;
  static const int BUFFER_SIZE = 1000;
  
  // Настройки передачи
  unsigned long lastTelemetryUpdate = 0;
  unsigned long lastLogWrite = 0;
  int telemetryRate = 10;  // Hz
  int logRate = 50;        // Hz
  
  bool loggingEnabled = true;
  File logFile;
  
public:
  TelemetrySystem() : httpServer(80), webSocketServer(81) {}
  
  void begin() {
    SPIFFS.begin();
    setupWebServer();
    setupWebSocket();
    startLogging();
    
    httpServer.begin();
    webSocketServer.begin();
    
    Serial.println("Telemetry system started");
    Serial.printf("Web interface: http://%s\n", WiFi.localIP().toString().c_str());
  }
  
  void update(const FlightData& data) {
    currentData = data;
    currentData.timestamp = millis();
    
    // Добавление в буфер
    dataBuffer.push_back(currentData);
    if (dataBuffer.size() > BUFFER_SIZE) {
      dataBuffer.erase(dataBuffer.begin());
    }
    
    // Обработка веб-сервера
    httpServer.handleClient();
    webSocketServer.loop();
    
    // Отправка телеметрии
    if (millis() - lastTelemetryUpdate >= 1000 / telemetryRate) {
      sendTelemetryUpdate();
      lastTelemetryUpdate = millis();
    }
    
    // Запись в лог
    if (loggingEnabled && millis() - lastLogWrite >= 1000 / logRate) {
      writeToLog(currentData);
      lastLogWrite = millis();
    }
  }
  
private:
  void setupWebServer() {
    // Главная страница
    httpServer.on("/", [this]() {
      httpServer.send(200, "text/html", getMainPage());
    });
    
    // API для получения текущих данных
    httpServer.on("/api/current", [this]() {
      DynamicJsonDocument doc(2048);
      serializeFlightData(currentData, doc);
      
      String json;
      serializeJson(doc, json);
      httpServer.send(200, "application/json", json);
    });
    
    // API для получения истории
    httpServer.on("/api/history", [this]() {
      DynamicJsonDocument doc(8192);
      JsonArray array = doc.to<JsonArray>();
      
      int startIndex = max(0, (int)dataBuffer.size() - 100);
      for (int i = startIndex; i < dataBuffer.size(); i++) {
        JsonObject obj = array.createNestedObject();
        serializeFlightData(dataBuffer[i], obj);
      }
      
      String json;
      serializeJson(doc, json);
      httpServer.send(200, "application/json", json);
    });
    
    // Скачивание логов
    httpServer.on("/api/download_log", [this]() {
      if (SPIFFS.exists("/flight.log")) {
        File file = SPIFFS.open("/flight.log", "r");
        httpServer.streamFile(file, "application/octet-stream");
        file.close();
      } else {
        httpServer.send(404, "text/plain", "Log file not found");
      }
    });
  }
  
  void setupWebSocket() {
    webSocketServer.onEvent([this](uint8_t num, WStype_t type, uint8_t* payload, size_t length) {
      switch (type) {
        case WStype_DISCONNECTED:
          Serial.printf("WebSocket client %u disconnected\n", num);
          break;
          
        case WStype_CONNECTED:
          Serial.printf("WebSocket client %u connected\n", num);
          break;
          
        case WStype_TEXT:
          processWebSocketCommand(num, (char*)payload);
          break;
      }
    });
  }
  
  void sendTelemetryUpdate() {
    DynamicJsonDocument doc(1024);
    serializeFlightData(currentData, doc);
    
    String json;
    serializeJson(doc, json);
    
    webSocketServer.broadcastTXT(json);
  }
  
  void serializeFlightData(const FlightData& data, JsonDocument& doc) {
    doc["timestamp"] = data.timestamp;
    doc["roll"] = data.roll;
    doc["pitch"] = data.pitch;
    doc["yaw"] = data.yaw;
    doc["altitude"] = data.altitude;
    doc["latitude"] = data.latitude;
    doc["longitude"] = data.longitude;
    doc["battery_voltage"] = data.batteryVoltage;
    doc["battery_current"] = data.batteryCurrent;
    doc["flight_mode"] = data.flightMode;
    doc["armed"] = data.armed;
    doc["motor1"] = data.motor1;
    doc["motor2"] = data.motor2;
    doc["motor3"] = data.motor3;
    doc["motor4"] = data.motor4;
  }
  
  void startLogging() {
    if (loggingEnabled) {
      logFile = SPIFFS.open("/flight.log", "w");
      if (logFile) {
        // Заголовок CSV
        logFile.println("timestamp,roll,pitch,yaw,altitude,lat,lon,battery_v,battery_a,mode,armed,m1,m2,m3,m4");
        Serial.println("Flight logging started");
      }
    }
  }
  
  void writeToLog(const FlightData& data) {
    if (logFile && loggingEnabled) {
      logFile.printf("%lu,%.2f,%.2f,%.2f,%.1f,%.6f,%.6f,%.2f,%.2f,%d,%d,%.3f,%.3f,%.3f,%.3f\n",
        data.timestamp, data.roll, data.pitch, data.yaw, data.altitude,
        data.latitude, data.longitude, data.batteryVoltage, data.batteryCurrent,
        data.flightMode, data.armed, data.motor1, data.motor2, data.motor3, data.motor4);
      logFile.flush();
    }
  }
  
  void processWebSocketCommand(uint8_t clientNum, const char* command) {
    DynamicJsonDocument doc(512);
    deserializeJson(doc, command);
    
    String cmd = doc["command"];
    
    if (cmd == "set_telemetry_rate") {
      telemetryRate = doc["rate"];
      Serial.printf("Telemetry rate set to %d Hz\n", telemetryRate);
    }
    else if (cmd == "toggle_logging") {
      loggingEnabled = !loggingEnabled;
      Serial.printf("Logging %s\n", loggingEnabled ? "enabled" : "disabled");
    }
    else if (cmd == "clear_log") {
      if (logFile) {
        logFile.close();
        SPIFFS.remove("/flight.log");
        startLogging();
        Serial.println("Flight log cleared");
      }
    }
  }
  
  String getMainPage() {
    return R"HTML(
<!DOCTYPE html>
<html>
<head>
    <title>Drone Telemetry Dashboard</title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <style>
        body { 
            font-family: 'Segoe UI', Arial, sans-serif; 
            margin: 0; 
            padding: 20px; 
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            color: white;
        }
        .container { 
            max-width: 1400px; 
            margin: 0 auto; 
        }
        .header {
            text-align: center;
            margin-bottom: 30px;
        }
        .header h1 {
            font-size: 2.5em;
            margin: 0;
            text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
        }
        .status-bar {
            display: flex;
            justify-content: center;
            gap: 20px;
            margin-bottom: 30px;
        }
        .status-item {
            background: rgba(255,255,255,0.2);
            padding: 10px 20px;
            border-radius: 20px;
            backdrop-filter: blur(10px);
        }
        .grid {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
            gap: 20px;
            margin-bottom: 30px;
        }
        .card { 
            background: rgba(255,255,255,0.15);
            backdrop-filter: blur(10px);
            padding: 20px; 
            border-radius: 15px; 
            box-shadow: 0 8px 32px rgba(0,0,0,0.1);
            border: 1px solid rgba(255,255,255,0.2);
        }
        .card h3 {
            margin-top: 0;
            font-size: 1.2em;
            border-bottom: 2px solid rgba(255,255,255,0.3);
            padding-bottom: 10px;
        }
        .metric { 
            display: flex;
            justify-content: space-between;
            margin: 10px 0;
            padding: 8px 0;
            border-bottom: 1px solid rgba(255,255,255,0.1);
        }
        .metric-label { 
            font-weight: 500; 
        }
        .metric-value { 
            font-weight: bold; 
            font-family: 'Courier New', monospace;
        }
        .attitude-indicator {
            width: 200px;
            height: 200px;
            margin: 20px auto;
            position: relative;
            border-radius: 50%;
            background: radial-gradient(circle, #87CEEB 0%, #4682B4 100%);
            border: 5px solid rgba(255,255,255,0.3);
        }
        .horizon {
            position: absolute;
            width: 100%;
            height: 50%;
            bottom: 0;
            background: linear-gradient(to bottom, #8B4513 0%, #A0522D 100%);
            border-radius: 0 0 50% 50%;
            transform-origin: center top;
            transition: transform 0.3s ease;
        }
        .aircraft-symbol {
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            width: 60px;
            height: 4px;
            background: #FFD700;
            border-radius: 2px;
        }
        .aircraft-symbol::before {
            content: '';
            position: absolute;
            top: -8px;
            left: 50%;
            transform: translateX(-50%);
            width: 20px;
            height: 4px;
            background: #FFD700;
            border-radius: 2px;
        }
        .controls {
            display: flex;
            gap: 10px;
            margin-top: 20px;
            justify-content: center;
        }
        .btn {
            padding: 10px 20px;
            background: rgba(255,255,255,0.2);
            border: none;
            border-radius: 25px;
            color: white;
            cursor: pointer;
            transition: all 0.3s ease;
            backdrop-filter: blur(10px);
        }
        .btn:hover {
            background: rgba(255,255,255,0.3);
            transform: translateY(-2px);
        }
        .chart-container {
            grid-column: 1 / -1;
            height: 300px;
            background: rgba(255,255,255,0.1);
            border-radius: 15px;
            padding: 20px;
        }
        #chart {
            width: 100%;
            height: 100%;
        }
        .connection-status {
            position: fixed;
            top: 20px;
            right: 20px;
            padding: 10px 15px;
            border-radius: 20px;
            font-weight: bold;
            transition: all 0.3s ease;
        }
        .connected {
            background: rgba(76, 175, 80, 0.9);
        }
        .disconnected {
            background: rgba(244, 67, 54, 0.9);
        }
    </style>
    <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
</head>
<body>
    <div id="connectionStatus" class="connection-status disconnected">
         Disconnected
    </div>
    
    <div class="container">
        <div class="header">
            <h1>🚁 Drone Telemetry Dashboard</h1>
        </div>
        
        <div class="status-bar">
            <div class="status-item">
                <span id="flightMode">DISARMED</span>
            </div>
            <div class="status-item">
                <span id="armedStatus">DISARMED</span>
            </div>
            <div class="status-item">
                GPS: <span id="gpsStatus">No Fix</span>
            </div>
        </div>
        
        <div class="grid">
            <div class="card">
                <h3>🧭 Attitude</h3>
                <div class="attitude-indicator">
                    <div class="horizon" id="horizon"></div>
                    <div class="aircraft-symbol"></div>
                </div>
                <div class="metric">
                    <span class="metric-label">Roll:</span>
                    <span class="metric-value" id="roll">0.0°</span>
                </div>
                <div class="metric">
                    <span class="metric-label">Pitch:</span>
                    <span class="metric-value" id="pitch">0.0°</span>
                </div>
                <div class="metric">
                    <span class="metric-label">Yaw:</span>
                    <span class="metric-value" id="yaw">0.0°</span>
                </div>
            </div>
            
            <div class="card">
                <h3>📍 Position</h3>
                <div class="metric">
                    <span class="metric-label">Latitude:</span>
                    <span class="metric-value" id="latitude">0.000000</span>
                </div>
                <div class="metric">
                    <span class="metric-label">Longitude:</span>
                    <span class="metric-value" id="longitude">0.000000</span>
                </div>
                <div class="metric">
                    <span class="metric-label">Altitude:</span>
                    <span class="metric-value" id="altitude">0.0 m</span>
                </div>
                <div class="metric">
                    <span class="metric-label">Ground Speed:</span>
                    <span class="metric-value" id="groundSpeed">0.0 m/s</span>
                </div>
            </div>
            
            <div class="card">
                <h3>🔋 Power System</h3>
                <div class="metric">
                    <span class="metric-label">Battery Voltage:</span>
                    <span class="metric-value" id="batteryVoltage">0.0 V</span>
                </div>
                <div class="metric">
                    <span class="metric-label">Current Draw:</span>
                    <span class="metric-value" id="batteryCurrent">0.0 A</span>
                </div>
                <div class="metric">
                    <span class="metric-label">Power:</span>
                    <span class="metric-value" id="power">0.0 W</span>
                </div>
                <div class="metric">
                    <span class="metric-label">Remaining:</span>
                    <span class="metric-value" id="batteryRemaining">100%</span>
                </div>
            </div>
            
            <div class="card">
                <h3>🚁 Motors</h3>
                <div class="metric">
                    <span class="metric-label">Motor 1:</span>
                    <span class="metric-value" id="motor1">0%</span>
                </div>
                <div class="metric">
                    <span class="metric-label">Motor 2:</span>
                    <span class="metric-value" id="motor2">0%</span>
                </div>
                <div class="metric">
                    <span class="metric-label">Motor 3:</span>
                    <span class="metric-value" id="motor3">0%</span>
                </div>
                <div class="metric">
                    <span class="metric-label">Motor 4:</span>
                    <span class="metric-value" id="motor4">0%</span>
                </div>
            </div>
            
            <div class="card chart-container">
                <h3>📊 Real-time Data</h3>
                <canvas id="chart"></canvas>
            </div>
        </div>
        
        <div class="controls">
            <button class="btn" onclick="toggleLogging()">📝 Toggle Logging</button>
            <button class="btn" onclick="downloadLogs()">📥 Download Logs</button>
            <button class="btn" onclick="clearLogs()">🗑️ Clear Logs</button>
            <button class="btn" onclick="changeTelemetryRate()">⚡ Change Rate</button>
        </div>
    </div>
    
    <script>
        let ws;
        let chart;
        let dataPoints = [];
        const maxDataPoints = 50;
        
        function initWebSocket() {
            const wsUrl = `ws://${window.location.hostname}:81`;
            ws = new WebSocket(wsUrl);
            
            ws.onopen = function() {
                document.getElementById('connectionStatus').className = 'connection-status connected';
                document.getElementById('connectionStatus').innerHTML = '🟢 Connected';
                console.log('WebSocket connected');
            };
            
            ws.onclose = function() {
                document.getElementById('connectionStatus').className = 'connection-status disconnected';
                document.getElementById('connectionStatus').innerHTML = '🔴 Disconnected';
                console.log('WebSocket disconnected, attempting reconnect...');
                setTimeout(initWebSocket, 2000);
            };
            
            ws.onmessage = function(event) {
                try {
                    const data = JSON.parse(event.data);
                    updateDashboard(data);
                    updateChart(data);
                } catch (e) {
                    console.error('Error parsing telemetry data:', e);
                }
            };
            
            ws.onerror = function(error) {
                console.error('WebSocket error:', error);
            };
        }
        
        function updateDashboard(data) {
            // Attitude
            document.getElementById('roll').textContent = data.roll.toFixed(1) + '°';
            document.getElementById('pitch').textContent = data.pitch.toFixed(1) + '°';
            document.getElementById('yaw').textContent = data.yaw.toFixed(1) + '°';
            
            // Update attitude indicator
            const horizon = document.getElementById('horizon');
            horizon.style.transform = `rotate(${-data.roll}deg) translateY(${data.pitch}px)`;
            
            // Position
            document.getElementById('latitude').textContent = data.latitude.toFixed(6);
            document.getElementById('longitude').textContent = data.longitude.toFixed(6);
            document.getElementById('altitude').textContent = data.altitude.toFixed(1) + ' m';
            
            // Power
            document.getElementById('batteryVoltage').textContent = data.battery_voltage.toFixed(2) + ' V';
            document.getElementById('batteryCurrent').textContent = data.battery_current.toFixed(1) + ' A';
            document.getElementById('power').textContent = (data.battery_voltage * data.battery_current).toFixed(1) + ' W';
            
            // Motors
            document.getElementById('motor1').textContent = (data.motor1 * 100).toFixed(0) + '%';
            document.getElementById('motor2').textContent = (data.motor2 * 100).toFixed(0) + '%';
            document.getElementById('motor3').textContent = (data.motor3 * 100).toFixed(0) + '%';
            document.getElementById('motor4').textContent = (data.motor4 * 100).toFixed(0) + '%';
            
            // Status
            const modes = ['DISARMED', 'ACRO', 'STABILIZE', 'ALT_HOLD', 'LOITER', 'GUIDED', 'AUTO', 'RTL', 'LAND', 'FAILSAFE'];
            document.getElementById('flightMode').textContent = modes[data.flight_mode] || 'UNKNOWN';
            document.getElementById('armedStatus').textContent = data.armed ? 'ARMED' : 'DISARMED';
            document.getElementById('armedStatus').style.color = data.armed ? '#ff4444' : '#44ff44';
        }
        
        function initChart() {
            const ctx = document.getElementById('chart').getContext('2d');
            chart = new Chart(ctx, {
                type: 'line',
                data: {
                    labels: [],
                    datasets: [
                        {
                            label: 'Roll (°)',
                            data: [],
                            borderColor: '#ff6384',
                            backgroundColor: 'rgba(255, 99, 132, 0.1)',
                            tension: 0.1,
                            fill: false
                        },
                        {
                            label: 'Pitch (°)',
                            data: [],
                            borderColor: '#36a2eb',
                            backgroundColor: 'rgba(54, 162, 235, 0.1)',
                            tension: 0.1,
                            fill: false
                        },
                        {
                            label: 'Altitude (m)',
                            data: [],
                            borderColor: '#4bc0c0',
                            backgroundColor: 'rgba(75, 192, 192, 0.1)',
                            tension: 0.1,
                            fill: false,
                            yAxisID: 'y1'
                        }
                    ]
                },
                options: {
                    responsive: true,
                    maintainAspectRatio: false,
                    plugins: {
                        legend: {
                            labels: {
                                color: 'white'
                            }
                        }
                    },
                    scales: {
                        x: {
                            ticks: { color: 'white' },
                            grid: { color: 'rgba(255,255,255,0.1)' }
                        },
                        y: {
                            type: 'linear',
                            display: true,
                            position: 'left',
                            ticks: { color: 'white' },
                            grid: { color: 'rgba(255,255,255,0.1)' }
                        },
                        y1: {
                            type: 'linear',
                            display: true,
                            position: 'right',
                            ticks: { color: 'white' },
                            grid: { drawOnChartArea: false }
                        }
                    }
                }
            });
        }
        
        function updateChart(data) {
            const time = new Date().toLocaleTimeString();
            
            chart.data.labels.push(time);
            chart.data.datasets[0].data.push(data.roll);
            chart.data.datasets[1].data.push(data.pitch);
            chart.data.datasets[2].data.push(data.altitude);
            
            if (chart.data.labels.length > maxDataPoints) {
                chart.data.labels.shift();
                chart.data.datasets.forEach(dataset => dataset.data.shift());
            }
            
            chart.update('none');
        }
        
        function sendCommand(command, params = {}) {
            if (ws && ws.readyState === WebSocket.OPEN) {
                const message = { command, ...params };
                ws.send(JSON.stringify(message));
            }
        }
        
        function toggleLogging() {
            sendCommand('toggle_logging');
        }
        
        function downloadLogs() {
            window.open('/api/download_log');
        }
        
        function clearLogs() {
            if (confirm('Clear all flight logs? This cannot be undone.')) {
                sendCommand('clear_log');
            }
        }
        
        function changeTelemetryRate() {
            const rate = prompt('Enter telemetry rate (1-100 Hz):', '10');
            if (rate && !isNaN(rate)) {
                sendCommand('set_telemetry_rate', { rate: parseInt(rate) });
            }
        }
        
        // Initialize everything
        document.addEventListener('DOMContentLoaded', function() {
            initChart();
            initWebSocket();
        });
    </script>
</body>
</html>
    )HTML";
  }
};

// Использование системы телеметрии
TelemetrySystem telemetry;

void setup() {
  Serial.begin(115200);
  WiFi.begin("YourWiFi", "password");
  
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.println("Connecting to WiFi...");
  }
  
  telemetry.begin();
}

void loop() {
  // Сбор данных полета
  TelemetrySystem::FlightData flightData;
  
  // Заполнение данных из датчиков
  flightData.roll = getCurrentRoll();
  flightData.pitch = getCurrentPitch();
  flightData.yaw = getCurrentYaw();
  flightData.altitude = getCurrentAltitude();
  flightData.latitude = getCurrentLatitude();
  flightData.longitude = getCurrentLongitude();
  flightData.batteryVoltage = getBatteryVoltage();
  flightData.batteryCurrent = getBatteryCurrent();
  flightData.flightMode = getCurrentFlightMode();
  flightData.armed = isArmed();
  
  // Обновление телеметрии
  telemetry.update(flightData);
  
  delay(20); // 50Hz основной цикл
}

3.2 Продвинутая диагностика системы

СИСТЕМА МОНИТОРИНГА ЗДОРОВЬЯ:

// =====================================
// ДИАГНОСТИКА И МОНИТОРИНГ ЗДОРОВЬЯ СИСТЕМЫ
// =====================================

class SystemHealthMonitor {
public:
  enum HealthStatus {
    HEALTHY = 0,
    WARNING = 1,
    CRITICAL = 2,
    FAILURE = 3
  };
  
  struct ComponentHealth {
    String componentName;
    HealthStatus status;
    float healthScore;      // 0.0 - 1.0
    String statusMessage;
    unsigned long lastUpdate;
    std::vector<String> symptoms;
  };
  
  struct SystemMetrics {
    float overallHealth;
    int healthyComponents;
    int warningComponents;
    int criticalComponents;
    int failedComponents;
    
    // Performance metrics
    float averageLoopTime;
    float cpuUsage;
    float memoryUsage;
    int droppedPackets;
    
    // Flight metrics
    float flightTime;
    float totalDistance;
    int landingCount;
    float averageBatteryLife;
  };
  
private:
  std::map<String, ComponentHealth> components;
  SystemMetrics metrics;
  
  // Thresholds for health scoring
  struct HealthThresholds {
    float batteryVoltageMin = 10.5;    // 3S LiPo critical
    float batteryVoltageWarning = 11.1; // 3S LiPo warning
    float temperatureMax = 85.0;        // °C critical
    float temperatureWarning = 75.0;    // °C warning
    float vibrationMax = 5.0;           // m/s² critical
    float vibrationWarning = 3.0;       // m/s² warning
    float gpsHdopMax = 5.0;            // HDOP critical
    float gpsHdopWarning = 2.0;        // HDOP warning
  } thresholds;
  
  unsigned long lastHealthCheck = 0;
  const unsigned long healthCheckInterval = 1000; // 1 second
  
public:
  void begin() {
    // Initialize component health tracking
    registerComponent("IMU", "Inertial Measurement Unit");
    registerComponent("GPS", "Global Positioning System");
    registerComponent("BARO", "Barometric Altimeter");
    registerComponent("BATTERY", "Power System");
    registerComponent("MOTORS", "Motor Control System");
    registerComponent("RADIO", "Radio Communication");
    registerComponent("STORAGE", "Data Storage System");
    registerComponent("THERMAL", "Thermal Management");
    
    Serial.println("System Health Monitor initialized");
  }
  
  void update() {
    if (millis() - lastHealthCheck >= healthCheckInterval) {
      checkAllComponents();
      calculateOverallHealth();
      updateMetrics();
      checkCriticalConditions();
      lastHealthCheck = millis();
    }
  }
  
  SystemMetrics getMetrics() { return metrics; }
  
  ComponentHealth getComponentHealth(const String& componentName) {
    return components[componentName];
  }
  
  void printHealthReport() {
    Serial.println("\n=== SYSTEM HEALTH REPORT ===");
    Serial.printf("Overall Health: %.1f%% (%s)\n", 
      metrics.overallHealth * 100, getHealthStatusString(getOverallStatus()).c_str());
    
    Serial.printf("Components: %d healthy, %d warning, %d critical, %d failed\n",
      metrics.healthyComponents, metrics.warningComponents, 
      metrics.criticalComponents, metrics.failedComponents);
    
    Serial.println("\nComponent Details:");
    for (const auto& pair : components) {
      const ComponentHealth& health = pair.second;
      Serial.printf("%-10s: %s (%.1f%%) - %s\n",
        health.componentName.c_str(),
        getHealthStatusString(health.status).c_str(),
        health.healthScore * 100,
        health.statusMessage.c_str());
    }
    
    if (metrics.criticalComponents > 0 || metrics.failedComponents > 0) {
      Serial.println("\n⚠️  CRITICAL ISSUES DETECTED:");
      for (const auto& pair : components) {
        const ComponentHealth& health = pair.second;
        if (health.status >= CRITICAL) {
          Serial.printf("- %s: %s\n", health.componentName.c_str(), health.statusMessage.c_str());
          for (const String& symptom : health.symptoms) {
            Serial.printf("  • %s\n", symptom.c_str());
          }
        }
      }
    }
  }
  
  bool isFlightSafe() {
    return metrics.criticalComponents == 0 && metrics.failedComponents == 0;
  }
  
private:
  void registerComponent(const String& id, const String& name) {
    ComponentHealth health;
    health.componentName = name;
    health.status = HEALTHY;
    health.healthScore = 1.0;
    health.statusMessage = "Initialized";
    health.lastUpdate = millis();
    
    components[id] = health;
  }
  
  void checkAllComponents() {
    checkIMUHealth();
    checkGPSHealth();
    checkBarometerHealth();
    checkBatteryHealth();
    checkMotorHealth();
    checkRadioHealth();
    checkStorageHealth();
    checkThermalHealth();
  }
  
  void checkIMUHealth() {
    ComponentHealth& health = components["IMU"];
    health.symptoms.clear();
    
    // Simulate IMU health checks
    float vibrationLevel = getVibrationLevel();
    float temperature = getIMUTemperature();
    float dataRate = getIMUDataRate();
    
    float score = 1.0;
    HealthStatus status = HEALTHY;
    String message = "Operating normally";
    
    // Check vibration levels
    if (vibrationLevel > thresholds.vibrationMax) {
      status = CRITICAL;
      score *= 0.3;
      health.symptoms.push_back("Excessive vibration detected");
      message = "High vibration levels";
    } else if (vibrationLevel > thresholds.vibrationWarning) {
      status = max(status, WARNING);
      score *= 0.7;
      health.symptoms.push_back("Elevated vibration levels");
      message = "Moderate vibration";
    }
    
    // Check temperature
    if (temperature > thresholds.temperatureMax) {
      status = max(status, CRITICAL);
      score *= 0.4;
      health.symptoms.push_back("IMU overheating");
    } else if (temperature > thresholds.temperatureWarning) {
      status = max(status, WARNING);
      score *= 0.8;
      health.symptoms.push_back("IMU running hot");
    }
    
    // Check data rate
    if (dataRate < 800) { // Expected 1000Hz
      status = max(status, WARNING);
      score *= 0.6;
      health.symptoms.push_back("Reduced data rate");
    }
    
    health.status = status;
    health.healthScore = score;
    health.statusMessage = message;
    health.lastUpdate = millis();
  }
  
  void checkGPSHealth() {
    ComponentHealth& health = components["GPS"];
    health.symptoms.clear();
    
    int satellites = getGPSSatellites();
    float hdop = getGPSHDOP();
    bool fix3D = hasGPS3DFix();
    
    float score = 1.0;
    HealthStatus status = HEALTHY;
    String message = "GPS fix acquired";
    
    if (!fix3D) {
      status = CRITICAL;
      score = 0.2;
      health.symptoms.push_back("No 3D GPS fix");
      message = "No GPS fix";
    } else {
      if (satellites < 6) {
        status = max(status, WARNING);
        score *= 0.7;
        health.symptoms.push_back("Low satellite count");
        message = "Few satellites";
      }
      
      if (hdop > thresholds.gpsHdopMax) {
        status = max(status, CRITICAL);
        score *= 0.4;
        health.symptoms.push_back("Poor GPS accuracy");
        message = "Poor GPS accuracy";
      } else if (hdop > thresholds.gpsHdopWarning) {
        status = max(status, WARNING);
        score *= 0.8;
        health.symptoms.push_back("Reduced GPS accuracy");
      }
    }
    
    health.status = status;
    health.healthScore = score;
    health.statusMessage = message;
    health.lastUpdate = millis();
  }
  
  void checkBatteryHealth() {
    ComponentHealth& health = components["BATTERY"];
    health.symptoms.clear();
    
    float voltage = getBatteryVoltage();
    float current = getBatteryCurrent();
    float temperature = getBatteryTemperature();
    float capacity = getRemainingCapacity();
    
    float score = 1.0;
    HealthStatus status = HEALTHY;
    String message = "Battery healthy";
    
    // Voltage check
    if (voltage < thresholds.batteryVoltageMin) {
      status = CRITICAL;
      score *= 0.2;
      health.symptoms.push_back("Critical low voltage");
      message = "Low battery - land immediately";
    } else if (voltage < thresholds.batteryVoltageWarning) {
      status = max(status, WARNING);
      score *= 0.6;
      health.symptoms.push_back("Low voltage warning");
      message = "Battery getting low";
    }
    
    // Current check (overcurrent protection)
    if (current > 60.0) { // Amp limit
      status = max(status, CRITICAL);
      score *= 0.3;
      health.symptoms.push_back("Overcurrent detected");
    }
    
    // Temperature check
    if (temperature > 60.0) { // °C
      status = max(status, WARNING);
      score *= 0.7;
      health.symptoms.push_back("Battery overheating");
    }
    
    // Capacity check
    if (capacity < 0.2) { // 20%
      status = max(status, WARNING);
      score *= 0.5;
      health.symptoms.push_back("Low remaining capacity");
    }
    
    health.status = status;
    health.healthScore = score;
    health.statusMessage = message;
    health.lastUpdate = millis();
  }
  
  void checkMotorHealth() {
    ComponentHealth& health = components["MOTORS"];
    health.symptoms.clear();
    
    float motor1Temp = getMotorTemperature(1);
    float motor2Temp = getMotorTemperature(2);
    float motor3Temp = getMotorTemperature(3);
    float motor4Temp = getMotorTemperature(4);
    
    float maxTemp = max({motor1Temp, motor2Temp, motor3Temp, motor4Temp});
    float avgTemp = (motor1Temp + motor2Temp + motor3Temp + motor4Temp) / 4.0;
    
    // Check for current imbalance
    float motorImbalance = getMotorCurrentImbalance();
    
    float score = 1.0;
    HealthStatus status = HEALTHY;
    String message = "Motors operating normally";
    
    if (maxTemp > 100.0) { // °C critical
      status = CRITICAL;
      score *= 0.3;
      health.symptoms.push_back("Motor overheating");
      message = "Motor overheating detected";
    } else if (maxTemp > 85.0) { // °C warning
      status = max(status, WARNING);
      score *= 0.7;
      health.symptoms.push_back("Motors running hot");
      message = "Motors running hot";
    }
    
    if (motorImbalance > 0.3) { // 30% imbalance
      status = max(status, WARNING);
      score *= 0.6;
      health.symptoms.push_back("Motor performance imbalance");
    }
    
    health.status = status;
    health.healthScore = score;
    health.statusMessage = message;
    health.lastUpdate = millis();
  }
  
  void checkRadioHealth() {
    ComponentHealth& health = components["RADIO"];
    health.symptoms.clear();
    
    int rssi = getRadioRSSI();
    int packetLoss = getPacketLossRate();
    unsigned long lastPacket = getLastPacketTime();
    
    float score = 1.0;
    HealthStatus status = HEALTHY;
    String message = "Radio link good";
    
    // Signal strength check
    if (rssi < -90) { // dBm
      status = CRITICAL;
      score *= 0.4;
      health.symptoms.push_back("Very weak signal");
      message = "Weak radio signal";
    } else if (rssi < -80) {
      status = max(status, WARNING);
      score *= 0.7;
      health.symptoms.push_back("Weak signal");
    }
    
    // Packet loss check
    if (packetLoss > 10) { // 10%
      status = max(status, WARNING);
      score *= 0.6;
      health.symptoms.push_back("High packet loss");
    }
    
    // Connection timeout check
    if (millis() - lastPacket > 2000) { // 2 seconds
      status = FAILURE;
      score = 0.0;
      health.symptoms.push_back("Radio connection lost");
      message = "No radio contact";
    }
    
    health.status = status;
    health.healthScore = score;
    health.statusMessage = message;
    health.lastUpdate = millis();
  }
  
  void checkStorageHealth() {
    ComponentHealth& health = components["STORAGE"];
    health.symptoms.clear();
    
    float freeSpace = getStorageFreeSpace(); // Percentage
    int writeErrors = getStorageWriteErrors();
    
    float score = 1.0;
    HealthStatus status = HEALTHY;
    String message = "Storage healthy";
    
    if (freeSpace < 10.0) { // 10%
      status = WARNING;
      score *= 0.7;
      health.symptoms.push_back("Low storage space");
      message = "Storage almost full";
    }
    
    if (writeErrors > 5) {
      status = max(status, WARNING);
      score *= 0.6;
      health.symptoms.push_back("Storage write errors");
    }
    
    health.status = status;
    health.healthScore = score;
    health.statusMessage = message;
    health.lastUpdate = millis();
  }
  
  void checkThermalHealth() {
    ComponentHealth& health = components["THERMAL"];
    health.symptoms.clear();
    
    float cpuTemp = getCPUTemperature();
    float ambientTemp = getAmbientTemperature();
    
    float score = 1.0;
    HealthStatus status = HEALTHY;
    String message = "Temperature normal";
    
    if (cpuTemp > 85.0) { // °C
      status = CRITICAL;
      score *= 0.3;
      health.symptoms.push_back("CPU overheating");
      message = "System overheating";
    } else if (cpuTemp > 75.0) {
      status = max(status, WARNING);
      score *= 0.7;
      health.symptoms.push_back("Elevated CPU temperature");
      message = "System running hot";
    }
    
    health.status = status;
    health.healthScore = score;
    health.statusMessage = message;
    health.lastUpdate = millis();
  }
  
  void calculateOverallHealth() {
    float totalScore = 0;
    int componentCount = 0;
    
    metrics.healthyComponents = 0;
    metrics.warningComponents = 0;
    metrics.criticalComponents = 0;
    metrics.failedComponents = 0;
    
    for (const auto& pair : components) {
      const ComponentHealth& health = pair.second;
      totalScore += health.healthScore;
      componentCount++;
      
      switch (health.status) {
        case HEALTHY: metrics.healthyComponents++; break;
        case WARNING: metrics.warningComponents++; break;
        case CRITICAL: metrics.criticalComponents++; break;
        case FAILURE: metrics.failedComponents++; break;
      }
    }
    
    metrics.overallHealth = componentCount > 0 ? totalScore / componentCount : 0;
  }
  
  void updateMetrics() {
    metrics.averageLoopTime = getAverageLoopTime();
    metrics.cpuUsage = getCPUUsage();
    metrics.memoryUsage = getMemoryUsage();
    metrics.droppedPackets = getDroppedPacketCount();
  }
  
  void checkCriticalConditions() {
    if (metrics.failedComponents > 0 || metrics.criticalComponents > 1) {
      Serial.println("🚨 CRITICAL SYSTEM CONDITION DETECTED!");
      Serial.println("Recommend immediate landing!");
      
      // Trigger emergency procedures
      triggerEmergencyMode();
    }
  }
  
  HealthStatus getOverallStatus() {
    if (metrics.failedComponents > 0) return FAILURE;
    if (metrics.criticalComponents > 0) return CRITICAL;
    if (metrics.warningComponents > 0) return WARNING;
    return HEALTHY;
  }
  
  String getHealthStatusString(HealthStatus status) {
    switch (status) {
      case HEALTHY: return "HEALTHY";
      case WARNING: return "WARNING";
      case CRITICAL: return "CRITICAL";
      case FAILURE: return "FAILURE";
      default: return "UNKNOWN";
    }
  }
  
  // Mock functions for sensor data (replace with real implementations)
  float getVibrationLevel() { return random(0, 300) / 100.0; }
  float getIMUTemperature() { return random(2000, 4000) / 100.0; }
  float getIMUDataRate() { return random(950, 1000); }
  int getGPSSatellites() { return random(4, 12); }
  float getGPSHDOP() { return random(100, 500) / 100.0; }
  bool hasGPS3DFix() { return random(0, 100) > 20; }
  float getBatteryVoltage() { return random(1050, 1260) / 100.0; }
  float getBatteryCurrent() { return random(500, 3000) / 100.0; }
  float getBatteryTemperature() { return random(2000, 4500) / 100.0; }
  float getRemainingCapacity() { return random(20, 100) / 100.0; }
  float getMotorTemperature(int motor) { return random(3000, 7000) / 100.0; }
  float getMotorCurrentImbalance() { return random(0, 50) / 100.0; }
  int getRadioRSSI() { return random(-100, -40); }
  int getPacketLossRate() { return random(0, 20); }
  unsigned long getLastPacketTime() { return millis() - random(0, 3000); }
  float getStorageFreeSpace() { return random(1000, 9000) / 100.0; }
  int getStorageWriteErrors() { return random(0, 10); }
  float getCPUTemperature() { return random(4000, 8000) / 100.0; }
  float getAmbientTemperature() { return random(1500, 3500) / 100.0; }
  float getAverageLoopTime() { return random(18, 22) / 1000.0; }
  float getCPUUsage() { return random(30, 80) / 100.0; }
  float getMemoryUsage() { return random(40, 90) / 100.0; }
  int getDroppedPacketCount() { return random(0, 5); }
  
  void triggerEmergencyMode() {
    // Implementation for emergency mode
    Serial.println("Emergency mode activated!");
  }
};

// Использование системы мониторинга
SystemHealthMonitor healthMonitor;

void setup() {
  Serial.begin(115200);
  healthMonitor.begin();
}

void loop() {
  healthMonitor.update();
  
  // Периодический вывод отчета (каждые 10 секунд)
  static unsigned long lastReport = 0;
  if (millis() - lastReport > 10000) {
    healthMonitor.printHealthReport();
    lastReport = millis();
  }
  
  delay(100);
}

ИНТЕГРАЦИЯ С НАЗЕМНЫМИ СТАНЦИЯМИ:

// =====================================
// MAVLINK ТЕЛЕМЕТРИЯ
// =====================================

#include <common/mavlink.h>

class MAVLinkTelemetry {
private:
  HardwareSerial* mavSerial;
  uint8_t systemId = 1;
  uint8_t componentId = MAV_COMP_ID_AUTOPILOT1;
  uint8_t targetSystem = 255;
  uint8_t targetComponent = 0;
  
  // Heartbeat timing
  unsigned long lastHeartbeat = 0;
  const unsigned long heartbeatInterval = 1000; // 1Hz
  
  // Telemetry timing
  unsigned long lastAttitude = 0;
  unsigned long lastGPS = 0;
  unsigned long lastSysStatus = 0;
  const unsigned long attitudeRate = 100;   // 10Hz
  const unsigned long gpsRate = 200;        // 5Hz
  const unsigned long sysStatusRate = 1000; // 1Hz
  
  // Mission management
  std::vector<mavlink_mission_item_t> currentMission;
  int currentWaypointIndex = 0;
  bool missionActive = false;
  
public:
  MAVLinkTelemetry(HardwareSerial* serial) : mavSerial(serial) {}
  
  void begin() {
    mavSerial->begin(57600);
    Serial.println("MAVLink telemetry started on 57600 baud");
    Serial.println("Compatible with Mission Planner, QGroundControl");
  }
  
  void update() {
    // Send heartbeat
    if (millis() - lastHeartbeat >= heartbeatInterval) {
      sendHeartbeat();
      lastHeartbeat = millis();
    }
    
    // Send attitude data
    if (millis() - lastAttitude >= attitudeRate) {
      sendAttitude();
      sendRCChannels();
      lastAttitude = millis();
    }
    
    // Send GPS data
    if (millis() - lastGPS >= gpsRate) {
      sendGPS();
      sendGlobalPosition();
      lastGPS = millis();
    }
    
    // Send system status
    if (millis() - lastSysStatus >= sysStatusRate) {
      sendSystemStatus();
      sendBatteryStatus();
      sendVFRHUD();
      lastSysStatus = millis();
    }
    
    // Process incoming messages
    processIncomingMessages();
    
    // Send mission status if active
    if (missionActive) {
      sendMissionCurrent();
    }
  }
  
private:
  void sendHeartbeat() {
    mavlink_message_t msg;
    mavlink_heartbeat_t heartbeat;
    
    heartbeat.type = MAV_TYPE_QUADROTOR;
    heartbeat.autopilot = MAV_AUTOPILOT_GENERIC;
    
    // Set base mode based on current state
    heartbeat.base_mode = 0;
    if (isArmed()) heartbeat.base_mode |= MAV_MODE_FLAG_SAFETY_ARMED;
    if (hasManualInput()) heartbeat.base_mode |= MAV_MODE_FLAG_MANUAL_INPUT_ENABLED;
    if (isGuidedMode()) heartbeat.base_mode |= MAV_MODE_FLAG_GUIDED_ENABLED;
    if (isAutoMode()) heartbeat.base_mode |= MAV_MODE_FLAG_AUTO_ENABLED;
    
    heartbeat.custom_mode = getCurrentFlightMode();
    heartbeat.system_status = getSystemStatus();
    heartbeat.mavlink_version = 3;
    
    mavlink_msg_heartbeat_encode(systemId, componentId, &msg, &heartbeat);
    sendMessage(msg);
  }
  
  void sendAttitude() {
    mavlink_message_t msg;
    mavlink_attitude_t attitude;
    
    attitude.time_boot_ms = millis();
    attitude.roll = getCurrentRoll() * DEG_TO_RAD;
    attitude.pitch = getCurrentPitch() * DEG_TO_RAD;
    attitude.yaw = getCurrentYaw() * DEG_TO_RAD;
    attitude.rollspeed = getCurrentRollRate() * DEG_TO_RAD;
    attitude.pitchspeed = getCurrentPitchRate() * DEG_TO_RAD;
    attitude.yawspeed = getCurrentYawRate() * DEG_TO_RAD;
    
    mavlink_msg_attitude_encode(systemId, componentId, &msg, &attitude);
    sendMessage(msg);
  }
  
  void sendGPS() {
    mavlink_message_t msg;
    mavlink_gps_raw_int_t gps;
    
    gps.time_usec = millis() * 1000;
    gps.fix_type = getGPSFixType();
    gps.lat = getCurrentLatitude() * 1e7;  // Convert to 1e7 degrees
    gps.lon = getCurrentLongitude() * 1e7;
    gps.alt = getGPSAltitude() * 1000;     // Convert to mm
    gps.eph = getGPSHDOP() * 100;          // Convert to cm
    gps.epv = getGPSVDOP() * 100;          // Convert to cm
    gps.vel = getGroundSpeed() * 100;      // Convert to cm/s
    gps.cog = getCourseOverGround() * 100; // Convert to cdeg
    gps.satellites_visible = getGPSSatellites();
    
    mavlink_msg_gps_raw_int_encode(systemId, componentId, &msg, &gps);
    sendMessage(msg);
  }
  
  void sendGlobalPosition() {
    mavlink_message_t msg;
    mavlink_global_position_int_t pos;
    
    pos.time_boot_ms = millis();
    pos.lat = getCurrentLatitude() * 1e7;
    pos.lon = getCurrentLongitude() * 1e7;
    pos.alt = getCurrentAltitude() * 1000;        // AMSL altitude in mm
    pos.relative_alt = getRelativeAltitude() * 1000; // Relative altitude in mm
    pos.vx = getVelocityX() * 100;                // cm/s
    pos.vy = getVelocityY() * 100;                // cm/s
    pos.vz = getVelocityZ() * 100;                // cm/s
    pos.hdg = getCurrentYaw() * 100;              // cdeg
    
    mavlink_msg_global_position_int_encode(systemId, componentId, &msg, &pos);
    sendMessage(msg);
  }
  
  void sendSystemStatus() {
    mavlink_message_t msg;
    mavlink_sys_status_t sysStatus;
    
    // Sensors present and enabled
    sysStatus.onboard_control_sensors_present = 
      MAV_SYS_STATUS_SENSOR_3D_GYRO |
      MAV_SYS_STATUS_SENSOR_3D_ACCEL |
      MAV_SYS_STATUS_SENSOR_3D_MAG |
      MAV_SYS_STATUS_SENSOR_ABSOLUTE_PRESSURE |
      MAV_SYS_STATUS_SENSOR_GPS |
      MAV_SYS_STATUS_SENSOR_MOTOR_OUTPUTS |
      MAV_SYS_STATUS_SENSOR_RC_RECEIVER |
      MAV_SYS_STATUS_SENSOR_BATTERY;
    
    sysStatus.onboard_control_sensors_enabled = sysStatus.onboard_control_sensors_present;
    
    // Health status based on actual sensor states
    sysStatus.onboard_control_sensors_health = 0;
    if (isIMUHealthy()) sysStatus.onboard_control_sensors_health |= MAV_SYS_STATUS_SENSOR_3D_GYRO | MAV_SYS_STATUS_SENSOR_3D_ACCEL;
    if (isMagHealthy()) sysStatus.onboard_control_sensors_health |= MAV_SYS_STATUS_SENSOR_3D_MAG;
    if (isBaroHealthy()) sysStatus.onboard_control_sensors_health |= MAV_SYS_STATUS_SENSOR_ABSOLUTE_PRESSURE;
    if (isGPSHealthy()) sysStatus.onboard_control_sensors_health |= MAV_SYS_STATUS_SENSOR_GPS;
    if (areMotorsHealthy()) sysStatus.onboard_control_sensors_health |= MAV_SYS_STATUS_SENSOR_MOTOR_OUTPUTS;
    if (isRadioHealthy()) sysStatus.onboard_control_sensors_health |= MAV_SYS_STATUS_SENSOR_RC_RECEIVER;
    if (isBatteryHealthy()) sysStatus.onboard_control_sensors_health |= MAV_SYS_STATUS_SENSOR_BATTERY;
    
    sysStatus.load = getCPUUsage() * 1000;         // Convert to per mille
    sysStatus.voltage_battery = getBatteryVoltage() * 1000; // Convert to mV
    sysStatus.current_battery = getBatteryCurrent() * 100;  // Convert to cA
    sysStatus.battery_remaining = getRemainingCapacity() * 100; // Convert to %
    
    sysStatus.drop_rate_comm = getCommDropRate();
    sysStatus.errors_comm = getCommErrors();
    sysStatus.errors_count1 = getSystemError1();
    sysStatus.errors_count2 = getSystemError2();
    sysStatus.errors_count3 = getSystemError3();
    sysStatus.errors_count4 = getSystemError4();
    
    mavlink_msg_sys_status_encode(systemId, componentId, &msg, &sysStatus);
    sendMessage(msg);
  }
  
  void sendBatteryStatus() {
    mavlink_message_t msg;
    mavlink_battery_status_t battery;
    
    battery.id = 0;  // Battery ID
    battery.battery_function = MAV_BATTERY_FUNCTION_ALL;
    battery.type = MAV_BATTERY_TYPE_LIPO;
    battery.temperature = getBatteryTemperature() * 100; // cdegC
    
    // Individual cell voltages (mV)
    int cellCount = getBatteryCellCount();
    for (int i = 0; i < 10; i++) {
      if (i < cellCount) {
        battery.voltages[i] = getCellVoltage(i) * 1000;
      } else {
        battery.voltages[i] = UINT16_MAX; // Invalid/not present
      }
    }
    
    battery.current_battery = getBatteryCurrent() * 100; // cA
    battery.current_consumed = getConsumedCapacity();    // mAh
    battery.energy_consumed = getConsumedEnergy();       // hJ
    battery.battery_remaining = getRemainingCapacity() * 100; // %
    
    mavlink_msg_battery_status_encode(systemId, componentId, &msg, &battery);
    sendMessage(msg);
  }
  
  void sendRCChannels() {
    mavlink_message_t msg;
    mavlink_rc_channels_t rc;
    
    rc.time_boot_ms = millis();
    rc.chancount = 8;  // Number of channels
    rc.chan1_raw = getRCChannel(1);
    rc.chan2_raw = getRCChannel(2);
    rc.chan3_raw = getRCChannel(3);
    rc.chan4_raw = getRCChannel(4);
    rc.chan5_raw = getRCChannel(5);
    rc.chan6_raw = getRCChannel(6);
    rc.chan7_raw = getRCChannel(7);
    rc.chan8_raw = getRCChannel(8);
    rc.chan9_raw = 0;
    rc.chan10_raw = 0;
    rc.chan11_raw = 0;
    rc.chan12_raw = 0;
    rc.chan13_raw = 0;
    rc.chan14_raw = 0;
    rc.chan15_raw = 0;
    rc.chan16_raw = 0;
    rc.chan17_raw = 0;
    rc.chan18_raw = 0;
    rc.rssi = getRadioRSSI();
    
    mavlink_msg_rc_channels_encode(systemId, componentId, &msg, &rc);
    sendMessage(msg);
  }
  
  void sendVFRHUD() {
    mavlink_message_t msg;
    mavlink_vfr_hud_t hud;
    
    hud.airspeed = getAirSpeed();
    hud.groundspeed = getGroundSpeed();
    hud.heading = getCurrentYaw();
    hud.throttle = getThrottlePercent();
    hud.alt = getCurrentAltitude();
    hud.climb = getClimbRate();
    
    mavlink_msg_vfr_hud_encode(systemId, componentId, &msg, &hud);
    sendMessage(msg);
  }
  
  void sendMissionCurrent() {
    mavlink_message_t msg;
    mavlink_mission_current_t missionCurrent;
    
    missionCurrent.seq = currentWaypointIndex;
    
    mavlink_msg_mission_current_encode(systemId, componentId, &msg, &missionCurrent);
    sendMessage(msg);
  }
  
  void sendMessage(mavlink_message_t& msg) {
    uint8_t buffer[MAVLINK_MAX_PACKET_LEN];
    uint16_t len = mavlink_msg_to_send_buffer(buffer, &msg);
    
    mavSerial->write(buffer, len);
  }
  
  void processIncomingMessages() {
    while (mavSerial->available()) {
      uint8_t byte = mavSerial->read();
      mavlink_message_t msg;
      mavlink_status_t status;
      
      if (mavlink_parse_char(MAVLINK_COMM_0, byte, &msg, &status)) {
        handleIncomingMessage(msg);
      }
    }
  }
  
  void handleIncomingMessage(mavlink_message_t& msg) {
    switch (msg.msgid) {
      case MAVLINK_MSG_ID_PARAM_REQUEST_LIST:
        handleParamRequestList(msg);
        break;
        
      case MAVLINK_MSG_ID_PARAM_REQUEST_READ:
        handleParamRequestRead(msg);
        break;
        
      case MAVLINK_MSG_ID_PARAM_SET:
        handleParamSet(msg);
        break;
        
      case MAVLINK_MSG_ID_COMMAND_LONG:
        handleCommandLong(msg);
        break;
        
      case MAVLINK_MSG_ID_SET_MODE:
        handleSetMode(msg);
        break;
        
      case MAVLINK_MSG_ID_MISSION_REQUEST_LIST:
        handleMissionRequestList(msg);
        break;
        
      case MAVLINK_MSG_ID_MISSION_COUNT:
        handleMissionCount(msg);
        break;
        
      case MAVLINK_MSG_ID_MISSION_ITEM:
        handleMissionItem(msg);
        break;
        
      case MAVLINK_MSG_ID_MISSION_REQUEST:
        handleMissionRequest(msg);
        break;
        
      case MAVLINK_MSG_ID_MISSION_CLEAR_ALL:
        handleMissionClearAll(msg);
        break;
        
      case MAVLINK_MSG_ID_MISSION_SET_CURRENT:
        handleMissionSetCurrent(msg);
        break;
        
      case MAVLINK_MSG_ID_RC_CHANNELS_OVERRIDE:
        handleRCOverride(msg);
        break;
        
      default:
        Serial.printf("Unknown MAVLink message: %d\n", msg.msgid);
        break;
    }
  }
  
  void handleParamRequestList(mavlink_message_t& msg) {
    Serial.println("Parameter list requested");
    
    // Send all flight controller parameters
    const char* params[][3] = {
      {"PID_ROLL_P", "1.500", "Real32"},
      {"PID_ROLL_I", "0.100", "Real32"},
      {"PID_ROLL_D", "0.050", "Real32"},
      {"PID_PITCH_P", "1.500", "Real32"},
      {"PID_PITCH_I", "0.100", "Real32"},
      {"PID_PITCH_D", "0.050", "Real32"},
      {"PID_YAW_P", "2.000", "Real32"},
      {"PID_YAW_I", "0.200", "Real32"},
      {"PID_YAW_D", "0.000", "Real32"},
      {"ALT_HOLD_P", "0.500", "Real32"},
      {"ALT_HOLD_I", "0.100", "Real32"},
      {"ALT_HOLD_D", "0.200", "Real32"},
      {"MAX_ANGLE", "30.000", "Real32"},
      {"MAX_YAW_RATE", "180.000", "Real32"},
      {"BATT_CELLS", "3", "Int32"},
      {"BATT_CAPACITY", "5000", "Int32"},
      {"FAILSAFE_BATT", "10.5", "Real32"},
      {"FAILSAFE_RC", "2.0", "Real32"},
      {"GPS_TYPE", "1", "Int32"},
      {"COMPASS_ENABLE", "1", "Int32"}
    };
    
    int paramCount = sizeof(params) / sizeof(params[0]);
    
    for (int i = 0; i < paramCount; i++) {
      sendParameter(params[i][0], atof(params[i][1]), 
                   strcmp(params[i][2], "Real32") == 0 ? MAV_PARAM_TYPE_REAL32 : MAV_PARAM_TYPE_INT32, 
                   i, paramCount);
      delay(10); // Small delay to prevent flooding
    }
  }
  
  void handleParamSet(mavlink_message_t& msg) {
    mavlink_param_set_t paramSet;
    mavlink_msg_param_set_decode(&msg, &paramSet);
    
    String paramName = String(paramSet.param_id);
    float paramValue = paramSet.param_value;
    
    Serial.printf("Setting parameter %s = %.3f\n", paramName.c_str(), paramValue);
    
    // Update parameter in flight controller
    bool success = updateParameter(paramName, paramValue);
    
    if (success) {
      // Send parameter value back to confirm
      sendParameter(paramName, paramValue, paramSet.param_type, 0, 1);
    } else {
      Serial.printf("Failed to set parameter %s\n", paramName.c_str());
    }
  }
  
  void handleCommandLong(mavlink_message_t& msg) {
    mavlink_command_long_t cmd;
    mavlink_msg_command_long_decode(&msg, &cmd);
    
    uint8_t result = MAV_RESULT_UNSUPPORTED;
    
    Serial.printf("Received command: %d\n", cmd.command);
    
    switch (cmd.command) {
      case MAV_CMD_COMPONENT_ARM_DISARM:
        if (cmd.param1 == 1.0) {
          // Arm command
          if (isSystemArmable()) {
            armSystem();
            result = MAV_RESULT_ACCEPTED;
            Serial.println("System ARMED via MAVLink");
          } else {
            result = MAV_RESULT_FAILED;
            Serial.println("ARM command rejected - system not armable");
          }
        } else {
          // Disarm command
          disarmSystem();
          result = MAV_RESULT_ACCEPTED;
          Serial.println("System DISARMED via MAVLink");
        }
        break;
        
      case MAV_CMD_NAV_TAKEOFF:
        if (isArmed()) {
          float targetAltitude = cmd.param7;
          startTakeoff(targetAltitude);
          result = MAV_RESULT_ACCEPTED;
          Serial.printf("Takeoff to %.1fm initiated\n", targetAltitude);
        } else {
          result = MAV_RESULT_FAILED;
          Serial.println("Takeoff rejected - system not armed");
        }
        break;
        
      case MAV_CMD_NAV_LAND:
        startLanding();
        result = MAV_RESULT_ACCEPTED;
        Serial.println("Landing initiated");
        break;
        
      case MAV_CMD_NAV_RETURN_TO_LAUNCH:
        startReturnToLaunch();
        result = MAV_RESULT_ACCEPTED;
        Serial.println("Return to Launch initiated");
        break;
        
      case MAV_CMD_MISSION_START:
        if (currentMission.size() > 0) {
          missionActive = true;
          currentWaypointIndex = 0;
          result = MAV_RESULT_ACCEPTED;
          Serial.println("Mission started");
        } else {
          result = MAV_RESULT_FAILED;
          Serial.println("No mission loaded");
        }
        break;
        
      case MAV_CMD_DO_SET_MODE:
        {
          uint8_t baseMode = (uint8_t)cmd.param1;
          uint32_t customMode = (uint32_t)cmd.param2;
          if (setFlightMode(baseMode, customMode)) {
            result = MAV_RESULT_ACCEPTED;
            Serial.printf("Flight mode changed to %d\n", customMode);
          } else {
            result = MAV_RESULT_FAILED;
            Serial.println("Mode change rejected");
          }
        }
        break;
        
      case MAV_CMD_PREFLIGHT_CALIBRATION:
        if (cmd.param1 == 1.0) {
          // Gyro calibration
          startGyroCalibration();
          result = MAV_RESULT_ACCEPTED;
          Serial.println("Gyro calibration started");
        } else if (cmd.param2 == 1.0) {
          // Magnetometer calibration
          startMagCalibration();
          result = MAV_RESULT_ACCEPTED;
          Serial.println("Magnetometer calibration started");
        } else if (cmd.param5 == 1.0) {
          // Accelerometer calibration
          startAccelCalibration();
          result = MAV_RESULT_ACCEPTED;
          Serial.println("Accelerometer calibration started");
        }
        break;
        
      default:
        result = MAV_RESULT_UNSUPPORTED;
        Serial.printf("Unsupported command: %d\n", cmd.command);
        break;
    }
    
    // Send command acknowledgment
    sendCommandAck(cmd.command, result);
  }
  
  void handleMissionCount(mavlink_message_t& msg) {
    mavlink_mission_count_t missionCount;
    mavlink_msg_mission_count_decode(&msg, &missionCount);
    
    Serial.printf("Receiving mission with %d waypoints\n", missionCount.count);
    
    currentMission.clear();
    currentMission.resize(missionCount.count);
    
    // Request first mission item
    if (missionCount.count > 0) {
      sendMissionRequest(0);
    }
  }
  
  void handleMissionItem(mavlink_message_t& msg) {
    mavlink_mission_item_t missionItem;
    mavlink_msg_mission_item_decode(&msg, &missionItem);
    
    if (missionItem.seq < currentMission.size()) {
      currentMission[missionItem.seq] = missionItem;
      
      Serial.printf("Received waypoint %d: %.6f, %.6f, %.1fm\n", 
        missionItem.seq, missionItem.x, missionItem.y, missionItem.z);
      
      // Request next item
      if (missionItem.seq + 1 < currentMission.size()) {
        sendMissionRequest(missionItem.seq + 1);
      } else {
        // Mission upload complete
        sendMissionAck(MAV_MISSION_ACCEPTED);
        Serial.println("Mission upload completed");
      }
    }
  }
  
  void sendParameter(const String& name, float value, uint8_t type, uint16_t index, uint16_t count) {
    mavlink_message_t msg;
    mavlink_param_value_t param;
    
    strncpy(param.param_id, name.c_str(), 16);
    param.param_value = value;
    param.param_type = type;
    param.param_count = count;
    param.param_index = index;
    
    mavlink_msg_param_value_encode(systemId, componentId, &msg, &param);
    sendMessage(msg);
  }
  
  void sendCommandAck(uint16_t command, uint8_t result) {
    mavlink_message_t msg;
    mavlink_command_ack_t ack;
    
    ack.command = command;
    ack.result = result;
    
    mavlink_msg_command_ack_encode(systemId, componentId, &msg, &ack);
    sendMessage(msg);
  }
  
  void sendMissionRequest(uint16_t seq) {
    mavlink_message_t msg;
    mavlink_mission_request_t request;
    
    request.target_system = targetSystem;
    request.target_component = targetComponent;
    request.seq = seq;
    
    mavlink_msg_mission_request_encode(systemId, componentId, &msg, &request);
    sendMessage(msg);
  }
  
  void sendMissionAck(uint8_t result) {
    mavlink_message_t msg;
    mavlink_mission_ack_t ack;
    
    ack.target_system = targetSystem;
    ack.target_component = targetComponent;
    ack.type = result;
    
    mavlink_msg_mission_ack_encode(systemId, componentId, &msg, &ack);
    sendMessage(msg);
  }
  
  // Функции-заглушки для интеграции с реальной системой
  float getCurrentRoll() { return 0.0; }
  float getCurrentPitch() { return 0.0; }
  float getCurrentYaw() { return 0.0; }
  float getCurrentRollRate() { return 0.0; }
  float getCurrentPitchRate() { return 0.0; }
  float getCurrentYawRate() { return 0.0; }
  double getCurrentLatitude() { return 0.0; }
  double getCurrentLongitude() { return 0.0; }
  float getCurrentAltitude() { return 0.0; }
  float getRelativeAltitude() { return 0.0; }
  float getVelocityX() { return 0.0; }
  float getVelocityY() { return 0.0; }
  float getVelocityZ() { return 0.0; }
  float getGroundSpeed() { return 0.0; }
  float getAirSpeed() { return 0.0; }
  float getClimbRate() { return 0.0; }
  uint8_t getGPSFixType() { return 3; }
  float getGPSHDOP() { return 1.0; }
  float getGPSVDOP() { return 1.0; }
  float getCourseOverGround() { return 0.0; }
  uint8_t getGPSSatellites() { return 8; }
  float getBatteryVoltage() { return 12.0; }
  float getBatteryCurrent() { return 5.0; }
  uint8_t getBatteryCellCount() { return 3; }
  float getCellVoltage(int cell) { return 4.0; }
  float getBatteryTemperature() { return 25.0; }
  float getRemainingCapacity() { return 0.8; }
  int getConsumedCapacity() { return 1000; }
  int getConsumedEnergy() { return 500; }
  uint16_t getRCChannel(int channel) { return 1500; }
  uint8_t getRadioRSSI() { return 200; }
  uint8_t getThrottlePercent() { return 50; }
  uint8_t getCurrentFlightMode() { return 2; }
  uint8_t getSystemStatus() { return MAV_STATE_ACTIVE; }
  float getCPUUsage() { return 0.5; }
  uint16_t getCommDropRate() { return 0; }
  uint16_t getCommErrors() { return 0; }
  uint16_t getSystemError1() { return 0; }
  uint16_t getSystemError2() { return 0; }
  uint16_t getSystemError3() { return 0; }
  uint16_t getSystemError4() { return 0; }
  bool isArmed() { return false; }
  bool hasManualInput() { return true; }
  bool isGuidedMode() { return false; }
  bool isAutoMode() { return false; }
  bool isIMUHealthy() { return true; }
  bool isMagHealthy() { return true; }
  bool isBaroHealthy() { return true; }
  bool isGPSHealthy() { return true; }
  bool areMotorsHealthy() { return true; }
  bool isRadioHealthy() { return true; }
  bool isBatteryHealthy() { return true; }
  bool isSystemArmable() { return true; }
  bool updateParameter(const String& name, float value) { return true; }
  bool setFlightMode(uint8_t baseMode, uint32_t customMode) { return true; }
  void armSystem() {}
  void disarmSystem() {}
  void startTakeoff(float altitude) {}
  void startLanding() {}
  void startReturnToLaunch() {}
  void startGyroCalibration() {}
  void startMagCalibration() {}
  void startAccelCalibration() {}
};

// Использование MAVLink системы
MAVLinkTelemetry mavlink(&Serial2);

void setup() {
  Serial.begin(115200);
  mavlink.begin();
  
  Serial.println("MAVLink Ground Control Station interface ready");
  Serial.println("Connect with Mission Planner or QGroundControl");
}

void loop() {
  mavlink.update();
  delay(10);
}

🎯 УРОК 4: ПРАКТИЧЕСКАЯ АТТЕСТАЦИЯ МОДУЛЯ (1.5 часа)

4.1 Комплексный экзамен “Системный инженер дронов”

ТЕОРЕТИЧЕСКАЯ ЧАСТЬ (40% оценки):

📋 СТРУКТУРА ЭКЗАМЕНА:

БЛОК 1: ТЕОРИЯ УПРАВЛЕНИЯ (25% теории)
Вопросы:
1. Объясните принцип работы каждой составляющей PID регулятора
2. Как влияет изменение параметров PID на поведение системы?
3. Почему используются каскадные системы управления?
4. Какие ограничения накладываются между уровнями управления?

БЛОК 2: РЕЖИМЫ ПОЛЕТА (35% теории)
Вопросы:
1. Перечислите условия для перехода между режимами полета
2. Как работает система Failsafe?
3. Объясните принцип навигации по waypoints
4. Какие данные необходимы для автономного полета?

БЛОК 3: ТЕЛЕМЕТРИЯ И ДИАГНОСТИКА (25% теории)
Вопросы:
1. Какие параметры критичны для безопасности полета?
2. Как определить проблемы в системе по телеметрии?
3. Объясните структуру MAVLink протокола
4. Как настроить Ground Control Station?

БЛОК 4: СИСТЕМНАЯ ИНТЕГРАЦИЯ (15% теории)
Вопросы:
1. Как все подсистемы взаимодействуют друг с другом?
2. Какие принципы используются для обеспечения отказоустойчивости?
3. Как проводится диагностика всей системы?

Формат: Письменный тест + устная защита решений
Время: 60 минут письменно + 30 минут устно
Проходной балл: 80%

ПРАКТИЧЕСКАЯ ЧАСТЬ (60% оценки):

Задание 1: “Настройка системы управления” (30% практики)

// ЗАДАЧА: Полная настройка автопилота с нуля

/* ТРЕБОВАНИЯ:
1. Настроить PID контроллеры для всех осей
2. Реализовать каскадную систему управления
3. Протестировать все режимы полета
4. Настроить системы безопасности
5. Провести полную диагностику системы

КРИТЕРИИ ОЦЕНКИ:
- Качество настройки PID (30%)
- Стабильность всех режимов полета (25%)
- Правильность работы Failsafe (20%)
- Качество диагностики (25%)

ВРЕМЯ ВЫПОЛНЕНИЯ: 90 минут
*/

class FlightControllerSetup {
public:
  void performFullSetup() {
    Serial.println("=== ПОЛНАЯ НАСТРОЙКА АВТОПИЛОТА ===");
    
    // 1. Калибровка датчиков
    calibrateAllSensors();
    
    // 2. Настройка PID контроллеров
    tunePIDControllers();
    
    // 3. Настройка режимов полета
    configureFlightModes();
    
    // 4. Настройка систем безопасности
    configureSafetySystems();
    
    // 5. Проведение тестов
    runSystemTests();
    
    // 6. Финальная проверка
    performFinalValidation();
  }
  
private:
  void calibrateAllSensors() {
    Serial.println("Калибровка датчиков...");
    
    // Калибровка IMU
    Serial.println("1. Калибровка гироскопа...");
    // Студент должен реализовать калибровку
    
    Serial.println("2. Калибровка акселерометра...");
    // 6-точечная калибровка
    
    Serial.println("3. Калибровка магнитометра...");
    // Калибровка компаса
    
    Serial.println("4. Калибровка барометра...");
    // Установка референсного давления
  }
  
  void tunePIDControllers() {
    Serial.println("Настройка PID контроллеров...");
    
    // Студент должен настроить все PID
    tunePIDForAxis("Roll", 1.0, 0.1, 0.05);
    tunePIDForAxis("Pitch", 1.0, 0.1, 0.05);
    tunePIDForAxis("Yaw", 2.0, 0.2, 0.0);
    tunePIDForAxis("Altitude", 0.5, 0.1, 0.2);
    
    // Проверка качества настройки
    validatePIDPerformance();
  }
  
  void configureFlightModes() {
    Serial.println("Настройка режимов полета...");
    
    // Студент должен настроить переходы между режимами
    setupModeTransitions();
    
    // Тестирование каждого режима
    testFlightMode("STABILIZE");
    testFlightMode("ALT_HOLD");
    testFlightMode("LOITER");
    testFlightMode("AUTO");
    testFlightMode("RTL");
  }
  
  void configureSafetySystems() {
    Serial.println("Настройка систем безопасности...");
    
    // Failsafe настройки
    configureRadioFailsafe();
    configureBatteryFailsafe();
    configureGPSFailsafe();
    
    // Геозоны
    configureGeofence();
    
    // Аварийные процедуры
    configureEmergencyProcedures();
  }
  
  void runSystemTests() {
    Serial.println("Проведение системных тестов...");
    
    // Тест производительности
    performanceTest();
    
    // Тест стабильности
    stabilityTest();
    
    // Тест безопасности
    safetyTest();
    
    // Тест нагрузки
    loadTest();
  }
  
  void performFinalValidation() {
    Serial.println("Финальная валидация системы...");
    
    bool allTestsPassed = true;
    
    // Проверка критических систем
    if (!validateCriticalSystems()) {
      allTestsPassed = false;
      Serial.println("❌ Критические системы не прошли валидацию");
    }
    
    // Проверка производительности
    if (!validatePerformance()) {
      allTestsPassed = false;
      Serial.println("❌ Производительность не соответствует требованиям");
    }
    
    // Проверка безопасности
    if (!validateSafety()) {
      allTestsPassed = false;
      Serial.println("❌ Системы безопасности работают некорректно");
    }
    
    if (allTestsPassed) {
      Serial.println("✅ ВСЕ ТЕСТЫ ПРОЙДЕНЫ! Система готова к полету.");
      generateSetupReport();
    } else {
      Serial.println("❌ ЕСТЬ ПРОБЛЕМЫ! Требуется дополнительная настройка.");
    }
  }
  
  // Вспомогательные функции (студент должен реализовать)
  void tunePIDForAxis(String axis, float kp, float ki, float kd) {
    Serial.printf("Настройка PID для %s: P=%.2f I=%.3f D=%.3f\n", axis.c_str(), kp, ki, kd);
    // Реализация настройки
  }
  
  bool validatePIDPerformance() {
    // Проверка качества PID настройки
    return true;
  }
  
  void setupModeTransitions() {
    // Настройка логики переходов между режимами
  }
  
  void testFlightMode(String mode) {
    Serial.printf("Тестирование режима %s...\n", mode.c_str());
    // Тестирование режима
  }
  
  void configureRadioFailsafe() {
    Serial.println("Настройка Radio Failsafe: потеря сигнала > 2 сек → RTL");
  }
  
  void configureBatteryFailsafe() {
    Serial.println("Настройка Battery Failsafe: напряжение < 10.5V → RTL");
  }
  
  void configureGPSFailsafe() {
    Serial.println("Настройка GPS Failsafe: потеря GPS → ALT_HOLD");
  }
  
  void configureGeofence() {
    Serial.println("Настройка Geofence: радиус 500м, высота 150м");
  }
  
  void configureEmergencyProcedures() {
    Serial.println("Настройка аварийных процедур");
  }
  
  bool performanceTest() {
    Serial.println("Тест производительности: проверка частоты циклов");
    return true;
  }
  
  bool stabilityTest() {
    Serial.println("Тест стабильности: проверка реакции на возмущения");
    return true;
  }
  
  bool safetyTest() {
    Serial.println("Тест безопасности: проверка Failsafe систем");
    return true;
  }
  
  bool loadTest() {
    Serial.println("Тест нагрузки: работа при максимальной нагрузке");
    return true;
  }
  
  bool validateCriticalSystems() {
    // Проверка критически важных систем
    return true;
  }
  
  bool validatePerformance() {
    // Проверка производительности
    return true;
  }
  
  bool validateSafety() {
    // Проверка безопасности
    return true;
  }
  
  void generateSetupReport() {
    Serial.println("\n=== ОТЧЕТ О НАСТРОЙКЕ СИСТЕМЫ ===");
    Serial.println("Дата: " + String(__DATE__) + " " + String(__TIME__));
    Serial.println("Настройщик: [ИМЯ СТУДЕНТА]");
    Serial.println("Все системы настроены и протестированы");
    Serial.println("Система готова к эксплуатации");
    Serial.println("Следующая проверка: через 50 часов полета");
  }
};

// Практическое задание для студента
FlightControllerSetup setupSystem;

void practicalExam() {
  setupSystem.performFullSetup();
}

Задание 2: “Диагностика проблем” (30% практики)

// ЗАДАЧА: Найти и устранить проблемы в системе

/* СЦЕНАРИИ НЕИСПРАВНОСТЕЙ:
1. Дрон дрейфует влево в режиме LOITER
2. Плохое качество стабилизации (колебания)
3. Неточная работа автономных режимов
4. Проблемы с телеметрией
5. Ложные срабатывания Failsafe

ТРЕБУЕТСЯ:
- Проанализировать логи полета
- Определить причину каждой проблемы
- Предложить решение
- Реализовать исправления
- Проверить результат

КРИТЕРИИ ОЦЕНКИ:
- Точность диагностики (40%)
- Правильность решения (35%)
- Качество исправления (25%)
*/

class SystemDiagnostics {
public:
  void diagnoseAndFix() {
    Serial.println("=== ДИАГНОСТИКА СИСТЕМЫ ===");
    
    // Сбор данных для анализа
    collectDiagnosticData();
    
    // Анализ каждой проблемы
    analyzeStabilizationIssues();
    analyzeNavigationIssues();
    analyzeTelemetryIssues();
    analyzeFailsafeIssues();
    
    // Генерация отчета
    generateDiagnosticReport();
  }
  
private:
  void collectDiagnosticData() {
    Serial.println("Сбор диагностических данных...");
    
    // Анализ логов
    analyzeLogs();
    
    // Проверка датчиков
    checkSensorHealth();
    
    // Проверка калибровки
    checkCalibration();
    
    // Проверка настроек
    checkConfiguration();
  }
  
  void analyzeStabilizationIssues() {
    Serial.println("\nАнализ проблем стабилизации:");
    
    // Симуляция проблем для студента
    Serial.println("ОБНАРУЖЕНО: Колебания по оси Roll");
    Serial.println("Возможные причины:");
    Serial.println("1. Слишком высокий P-коэффициент");
    Serial.println("2. Недостаточный D-коэффициент");
    Serial.println("3. Механические проблемы");
    Serial.println("4. Проблемы с IMU");
    
    // Студент должен выбрать правильную причину и исправить
  }
  
  void analyzeNavigationIssues() {
    Serial.println("\nАнализ проблем навигации:");
    
    Serial.println("ОБНАРУЖЕНО: Дрейф в режиме LOITER");
    Serial.println("Возможные причины:");
    Serial.println("1. Плохая точность GPS");
    Serial.println("2. Неправильная калибровка компаса");
    Serial.println("3. Неправильные PID настройки позиции");
    Serial.println("4. Влияние магнитных помех");
  }
  
  void analyzeTelemetryIssues() {
    Serial.println("\nАнализ проблем телеметрии:");
    
    Serial.println("ОБНАРУЖЕНО: Потеря пакетов телеметрии");
    Serial.println("Возможные причины:");
    Serial.println("1. Перегрузка канала связи");
    Serial.println("2. Проблемы с антенной");
    Serial.println("3. Слишком высокая частота передачи");
    Serial.println("4. Программные ошибки");
  }
  
  void analyzeFailsafeIssues() {
    Serial.println("\nАнализ проблем Failsafe:");
    
    Serial.println("ОБНАРУЖЕНО: Ложные срабатывания Battery Failsafe");
    Serial.println("Возможные причины:");
    Serial.println("1. Неправильные пороги напряжения");
    Serial.println("2. Плохие контакты в цепи питания");
    Serial.println("3. Шумы в измерении напряжения");
    Serial.println("4. Проблемы с калибровкой ADC");
  }
  
  void analyzeLogs() {
    Serial.println("Анализ логов полета...");
    // Анализ файлов логов
  }
  
  void checkSensorHealth() {
    Serial.println("Проверка здоровья датчиков...");
    // Проверка всех датчиков
  }
  
  void checkCalibration() {
    Serial.println("Проверка калибровки...");
    // Проверка калибровочных данных
  }
  
  void checkConfiguration() {
    Serial.println("Проверка конфигурации...");
    // Проверка настроек
  }
  
  void generateDiagnosticReport() {
    Serial.println("\n=== ДИАГНОСТИЧЕСКИЙ ОТЧЕТ ===");
    Serial.println("Найдено проблем: 4");
    Serial.println("Критических: 1");
    Serial.println("Предупреждений: 3");
    Serial.println("\nРекомендации:");
    Serial.println("1. Снизить P-коэффициент Roll PID");
    Serial.println("2. Перекалибровать компас");
    Serial.println("3. Снизить частоту телеметрии");
    Serial.println("4. Проверить цепи питания");
  }
};

4.2 Уровни сертификации:

🏆 SYSTEMS ARCHITECT (90-100 баллов):

  • Сертификат “Архитектор систем управления дронов”
  • Право на проектирование сложных автопилотов
  • Создание собственных алгоритмов управления
  • Техническое руководство проектами

⚡ CONTROL SYSTEMS ENGINEER (80-89 баллов):

  • Сертификат “Инженер систем управления”
  • Настройка и оптимизация автопилотов
  • Интеграция новых подсистем
  • Диагностика сложных проблем

🎛️ FLIGHT CONTROLLER SPECIALIST (70-79 баллов):

  • Сертификат “Специалист по контроллерам полета”
  • Настройка стандартных автопилотов
  • Базовая диагностика
  • Техническая поддержка

🎓 ЗАКЛЮЧЕНИЕ МОДУЛЯ 6

Философское завершение:

“Поздравляю! Вы достигли вершины технического мастерства в области дронов. Теперь вы не просто собираете и программируете дроны — вы создаете интеллектуальные системы, способные принимать решения, адаптироваться к ситуации и выполнять сложные задачи автономно.”

“Путь от простого пилота до системного архитектора завершен. Вы овладели искусством создания ‘думающих’ дронов — систем, которые понимают окружающий мир, планируют свои действия и обеспечивают безопасность полета.”

Достижения Модуля 6:

🎯 ОСВОЕННЫЕ НАВЫКИ:

ТЕХНИЧЕСКОЕ МАСТЕРСТВО:
✅ Проектирование многоуровневых систем управления
✅ Настройка сложных PID регуляторов
✅ Создание интеллектуальных режимов полета
✅ Реализация систем навигации и планирования миссий

СИСТЕМНАЯ ИНТЕГРАЦИЯ:
✅ Объединение всех подсистем в единое целое
✅ Создание отказоустойчивых архитектур
✅ Реализация систем мониторинга и диагностики
✅ Интеграция с наземными станциями управления

ПРАКТИЧЕСКИЕ КОМПЕТЕНЦИИ:
✅ Диагностика сложных системных проблем
✅ Оптимизация производительности
✅ Обеспечение безопасности полета
✅ Создание пользовательских интерфейсов

Переход к следующему блоку:

БЛОК C: ПРОФЕССИОНАЛЬНОЕ ПРИМЕНЕНИЕ ждет впереди!

  • Модуль 7: Коммерческие применения дронов
  • Модуль 8: Специализированные системы
  • Модуль 9: Проектный модуль

“Техническая база заложена. Теперь время применить ваши знания в реальном мире — от коммерческих проектов до создания собственных инновационных решений!”

🚀 Результат Модуля 6: Студент овладел проектированием и реализацией интеллектуальных систем управления дронами, может создавать сложные автопилоты, интегрировать множественные подсистемы и обеспечивать высокий уровень автономности и безопасности полета.

Урок 6: Специальные техники и “хаки”

# Продвинутые техники и трюки пилотирования

## 🎯 Точные маневры

### 1. Precision Hovering Techniques
- **Micro-corrections:** минимальные движения стиков
- **Pendulum dampening:** гашение колебаний
- **Wind compensation:** компенсация ветра вручную
- **Optical flow landing:** посадка по визуальным меткам

### 2. Emergency Procedures
- **Failsafe recovery:** восстановление после потери сигнала
- **Motor failure simulation:** полет на трех моторах
- **Inverted recovery:** восстановление из перевернутого состояния
- **Autorotation landing:** посадка с выключенными моторами

## 🚀 Трюки и акробатика

### 3. Advanced Aerobatics
- **Tornado roll:** штопор с набором высоты
- **Cuban eight:** восьмерка в вертикальной плоскости
- **Hammer head:** вертикальный подъем с разворотом
- **Knife edge circle:** круг на ребре винта

### 4. Freestyle элементы
- **Juicy flick:** резкий флип с остановкой
- **Matty flip:** сальто назад через препятствие
- **Wall ride:** полет вдоль вертикальной поверхности
- **Tree tap:** касание объекта винтом без повреждений