Станция обслуживания дрона: управление и архитектура взаимодействия

от автора

Станция обслуживания дрона — это комплексный автоматизированный модуль, выполняющий функции замены аккумуляторов и обработки грузов (загрузка/выгрузка) в полностью автономном режиме. Управление ею реализовано с помощью микроконтроллера Arduino Uno, связанного с модулем ESP8266, который подключается к удалённому серверу по WebSocket и обменивается командами в формате JSON.

🔧 Архитектура управления станцией

Станция состоит из нескольких механических компонентов, управляемых шаговыми двигателями и отслеживаемых с помощью концевых датчиков. Управляющая логика разбита на два уровня:

1. Arduino Uno — логика управления станцией

Arduino отвечает за:

  • Поворотную платформу для грузов и аккумуляторов;

  • Подъём/опускание лифта;

  • Фиксацию/освобождение дрона;

  • Механизм загрузки/выгрузки груза;

  • Калибровку всех компонентов;

  • Отправку телеметрии на сервер;

  • Приём JSON-команд через UART от ESP8266 и их выполнение.

Каждый механизм снабжён шаговым двигателем, а его положение контролируется концевыми выключателями. Arduino обрабатывает команды вида:

{ "command": "move_lift", "value": 50, "operation_id": "abc123" } 

…и, выполнив их, возвращает результат, например:

{ "type": "operation_result", "success": true, "message": "Lift moved", "operation_id": "abc123" } 

Также отправляется телеметрия о текущем состоянии всех систем.

2. ESP8266 — шлюз между сервером и Arduino

ESP8266 подключается к Wi-Fi и устанавливает WebSocket-соединение с сервером. Его роль — посредник:

  • Принимает команды с сервера и передаёт их Arduino через UART.

  • Считывает ответы от Arduino и пересылает обратно на сервер.

  • Автоматически повторно подключается при потере связи.

  • Отвечает на PING-запросы сервера для поддержания heartbeat и передачи общего статуса работы.

3. Сервер управления

Сервер принимает WebSocket-соединения от устройств и предоставляет пользователю веб-интерфейс (временное решение для тестирования). Он:

  • Хранит список зарегистрированных устройств и пользователей.

  • Обеспечивает аутентификацию устройств.

  • Принимает и маршрутизирует команды, отправленные с веб-интерфейса.

  • Обрабатывает телеметрию и ошибки от устройств.

  • Показывает актуальное состояние системы через веб-интерфейс.

В дальнейшем планируется, что сервер управления будет лишён пользовательского интерфейса и будет действовать как промежуточное звено между системами высокого уровня и станцией, получая команды от внешнего управляющего сервера.

🔑 Принцип работы в реальном времени

  1. Пользователь отправляет команду через веб-интерфейс сервера.

  2. Сервер отправляет JSON-команду устройству esp02 (станция).

  3. ESP8266 передаёт JSON по UART на Arduino Uno.

  4. Arduino выполняет команду и возвращает JSON-результат.

  5. ESP8266 передаёт результат обратно на сервер.

  6. Сервер отображает результат пользователю.

📊 Пример телеметрии

{   "type": "telemetry",   "status": "работает",   "lift_position": 50,   "platform_position": 90,   "is_drone_locked": true,   "has_cargo": false,   "is_error": false,   "last_operation": "Drone locked",   "switches": [true, false, false, true, false, false, true, false] } 

🧩 Преимущества архитектуры

  • 🔌 Модульность: Arduino Uno отделяет механику от сетевой логики.

  • 🌐 Удалённое управление: весь обмен данными через WebSocket.

  • 🔁 Устойчивость: повторные подключения, heartbeat, обработка ошибок.

  • 🔄 Автоматизация: полностью автоматическое выполнение команд.

Код WIFI модуля

#include <ESP8266WiFi.h> #include <WebSocketsClient.h> #include <ArduinoJson.h>  const char* ssid = "ASUS"; const char* password = "123456789098";  WebSocketsClient webSocket;  const char* deviceName = "esp02"; const char* devicePassword = "5678";  const int LED_PIN = LED_BUILTIN;  unsigned long previousMillis = 0; unsigned long lastConnectionAttempt = 0; const long connectionInterval = 10000; // 10 секунд между попытками подключения  int ledState = LOW; int connectionState = 0; // 0 - нет подключения, 1 - WiFi подключен, 2 - WebSocket подключен  void updateLed() {   unsigned long currentMillis = millis();   unsigned long interval;      switch(connectionState) {     case 0: // Нет подключения - быстрое мигание (500ms)       interval = 500;       break;     case 1: // WiFi подключен - медленное мигание (1000ms)       interval = 1000;       break;     case 2: // WebSocket подключен - постоянно включен       digitalWrite(LED_PIN, LOW);       return;   }      if (currentMillis - previousMillis >= interval) {     previousMillis = currentMillis;     ledState = (ledState == LOW) ? HIGH : LOW;     digitalWrite(LED_PIN, ledState);   } }  void sendToArduino(const String& message) {   Serial.println(message); // Просто перенаправляем сообщение на Arduino }  void webSocketEvent(WStype_t type, uint8_t* payload, size_t length) {   switch (type) {     case WStype_CONNECTED:       //Serial.println("[WS] Connected");       connectionState = 2;  // WebSocket подключен       updateLed();        // Отправляем информацию об устройстве при подключении       {         StaticJsonDocument<256> doc;         doc["type"] = "auth";         doc["name"] = deviceName;         doc["password"] = devicePassword;         String json;         serializeJson(doc, json);         webSocket.sendTXT(json);       }       break;      case WStype_DISCONNECTED:       //Serial.println("[WS] Disconnected");       connectionState = (WiFi.status() == WL_CONNECTED) ? 1 : 0;       break;      case WStype_TEXT:       //Serial.printf("[WS] Received: %s\n", payload);        // Обработка PING       if (String((char*)payload) == "PING") {         StaticJsonDocument<128> doc;         doc["type"] = "telemetry";         doc["status"] = "работает";         String json;         serializeJson(doc, json);         webSocket.sendTXT(json);       }       // Обработка команды reboot       else if (String((char*)payload) == "reboot") {         //Serial.println("Rebooting...");         ESP.restart();       }       // Все остальные сообщения просто перенаправляем на Arduino       else {         //Serial.printf("Сообщения отправлено на arduino\n");         sendToArduino(String((char*)payload));       }       break;   } }  void checkUART() {   if (Serial.available() > 0) {     String received = Serial.readStringUntil('\n');     received.trim();          if (received.length() > 0) {      // Serial.printf("Received from Arduino: %s\n", received.c_str());              // Просто перенаправляем полученные данные на сервер       if (webSocket.isConnected()) {         webSocket.sendTXT(received);       }     }   } }  void setup() {   pinMode(LED_PIN, OUTPUT);   digitalWrite(LED_PIN, HIGH);      Serial.begin(115200); // Инициализация UART для связи с другим Arduino   WiFi.begin(ssid, password);      // Начальное состояние - нет подключения   connectionState = 0; }  void loop() {   unsigned long currentMillis = millis();      // Обновляем состояние подключения WiFi   if (WiFi.status() != WL_CONNECTED) {     connectionState = 0;     if (currentMillis - lastConnectionAttempt >= connectionInterval) {       //Serial.println("Connecting to WiFi...");       WiFi.begin(ssid, password);       lastConnectionAttempt = currentMillis;     }   }    else if (connectionState == 0) {     connectionState = 1;     //Serial.println("\nWiFi connected");     //Serial.print("IP address: ");     //Serial.println(WiFi.localIP());   }      updateLed();   webSocket.loop();      // Если WiFi подключен, но WebSocket еще нет - пытаемся подключиться   if (WiFi.status() == WL_CONNECTED && !webSocket.isConnected() &&        currentMillis - lastConnectionAttempt >= connectionInterval) {     connectionState = 1;     //Serial.println("Connecting to WebSocket server...");     webSocket.begin("178.66.182.97", 8765, "/");     webSocket.onEvent(webSocketEvent);     webSocket.setReconnectInterval(5000);     lastConnectionAttempt = currentMillis;   }      // Проверяем входящие сообщения от Arduino   checkUART();      delay(10); // Небольшая задержка для стабильности }


ссылка на оригинал статьи https://habr.com/ru/articles/923826/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *