СИСТЕМЫ УПРАВЛЕНИЯ
“От хаоса к порядку: создаем интеллектуальные системы управления полетом”
🔄 ПУТЬ ТЕХНИЧЕСКОГО МАСТЕРСТВА:
МОДУЛЬ 4: "МЕХАНИК"
- Понимание физики полета
- Знание характеристик компонентов
- Сборка и балансировка
МОДУЛЬ 5: "ПРОГРАММИСТ"
- Программирование микроконтроллеров
- Работа с датчиками и протоколами
- Создание базовых алгоритмов
МОДУЛЬ 6: "СИСТЕМНЫЙ ИНЖЕНЕР"
- Проектирование поведения системы
- Настройка сложных алгоритмов управления
- Создание интеллектуальных автопилотов
- Диагностика и оптимизация производительности
🤖 ОТ РЕАКТИВНОГО К ИНТЕЛЛЕКТУАЛЬНОМУ:
УРОВЕНЬ 1: РЕАКТИВНОЕ УПРАВЛЕНИЕ
- Простая реакция на команды пилота
- Базовая стабилизация
- Фиксированные алгоритмы
УРОВЕНЬ 2: АДАПТИВНОЕ УПРАВЛЕНИЕ
- Автоматическая подстройка параметров
- Компенсация внешних возмущений
- Обучение на основе опыта
УРОВЕНЬ 3: ПРЕДИКТИВНОЕ УПРАВЛЕНИЕ
- Предвидение развития ситуации
- Упреждающие корректировки
- Оптимизация траекторий
УРОВЕНЬ 4: ИНТЕЛЛЕКТУАЛЬНОЕ УПРАВЛЕНИЕ
- Принятие решений высокого уровня
- Планирование миссий
- Взаимодействие с другими системами
ИНТУИТИВНОЕ ПОНИМАНИЕ 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: УПРАВЛЕНИЕ ПОЛОЖЕНИЕМ
┌─────────────────────────────────────┐
│ 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);
}
АРХИТЕКТУРА РЕЖИМОВ:
🎯 КЛАССИФИКАЦИЯ РЕЖИМОВ ПО СЛОЖНОСТИ:
БАЗОВЫЕ РЕЖИМЫ (Ручное управление):
├── 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);
}
}
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);
}
}
АРХИТЕКТУРА ТЕЛЕМЕТРИИ:
📊 МНОГОУРОВНЕВАЯ СИСТЕМА МОНИТОРИНГА:
УРОВЕНЬ 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 основной цикл
}
СИСТЕМА МОНИТОРИНГА ЗДОРОВЬЯ:
// =====================================
// ДИАГНОСТИКА И МОНИТОРИНГ ЗДОРОВЬЯ СИСТЕМЫ
// =====================================
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, ¶mSet);
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, ¶m);
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);
}
ТЕОРЕТИЧЕСКАЯ ЧАСТЬ (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. Проверить цепи питания");
}
};
🏆 SYSTEMS ARCHITECT (90-100 баллов):
- Сертификат “Архитектор систем управления дронов”
- Право на проектирование сложных автопилотов
- Создание собственных алгоритмов управления
- Техническое руководство проектами
⚡ CONTROL SYSTEMS ENGINEER (80-89 баллов):
- Сертификат “Инженер систем управления”
- Настройка и оптимизация автопилотов
- Интеграция новых подсистем
- Диагностика сложных проблем
🎛️ FLIGHT CONTROLLER SPECIALIST (70-79 баллов):
- Сертификат “Специалист по контроллерам полета”
- Настройка стандартных автопилотов
- Базовая диагностика
- Техническая поддержка
“Поздравляю! Вы достигли вершины технического мастерства в области дронов. Теперь вы не просто собираете и программируете дроны — вы создаете интеллектуальные системы, способные принимать решения, адаптироваться к ситуации и выполнять сложные задачи автономно.”
“Путь от простого пилота до системного архитектора завершен. Вы овладели искусством создания ‘думающих’ дронов — систем, которые понимают окружающий мир, планируют свои действия и обеспечивают безопасность полета.”
🎯 ОСВОЕННЫЕ НАВЫКИ:
ТЕХНИЧЕСКОЕ МАСТЕРСТВО:
✅ Проектирование многоуровневых систем управления
✅ Настройка сложных PID регуляторов
✅ Создание интеллектуальных режимов полета
✅ Реализация систем навигации и планирования миссий
СИСТЕМНАЯ ИНТЕГРАЦИЯ:
✅ Объединение всех подсистем в единое целое
✅ Создание отказоустойчивых архитектур
✅ Реализация систем мониторинга и диагностики
✅ Интеграция с наземными станциями управления
ПРАКТИЧЕСКИЕ КОМПЕТЕНЦИИ:
✅ Диагностика сложных системных проблем
✅ Оптимизация производительности
✅ Обеспечение безопасности полета
✅ Создание пользовательских интерфейсов
БЛОК C: ПРОФЕССИОНАЛЬНОЕ ПРИМЕНЕНИЕ ждет впереди!
- Модуль 7: Коммерческие применения дронов
- Модуль 8: Специализированные системы
- Модуль 9: Проектный модуль
“Техническая база заложена. Теперь время применить ваши знания в реальном мире — от коммерческих проектов до создания собственных инновационных решений!”
🚀 Результат Модуля 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:** касание объекта винтом без повреждений