Pull to refresh

Универсальный бокс для дрона

Level of difficultyHard
Reading time7 min
Views1.2K

Предисловие

Данная статья это рассказ об отдельной части моего проекта по созданию системы доставки дронами. Система совсем в ранней стадии разработки. Но в принципе эта часть готова и если кому интересно может пользоваться в своих проектов. Если будет заинтересованность могу продолжить публиковать чертежи, схемы и программный код.

Введение

С ростом использования дронов в логистике и техническом обслуживании растет потребность в универсальных модулях для перевозки грузов и сменных аккумуляторов. Для решения этой задачи был разработан интеллектуальный бокс, управляемый удалённо через контроллер на базе ESP8266, который способен:

Общая схема. Флашки фиксации будут с обоих сторон и сервопривод будет посередине. Так же не показанны концевики.
Общая схема. Флашки фиксации будут с обоих сторон и сервопривод будет посередине. Так же не показанны концевики.
  • 📦 открывать и закрывать отсек для груза;

  • 🔋 открывать и закрывать отсек для сменного аккумулятора;

  • 📡 передавать телеметрию о состоянии устройства;

  • ✅ принимать команды через WebSocket-сервер.

Аппаратная часть

Устройство построено на контроллере ESP8266 (например, ESP-01 или NodeMCU) и включает в себя:

  • 2 сервопривода:
    Servo1 — управление крышкой грузового отсека.
    Servo2 — управление крышкой отсека аккумулятора.

  • 2 концевых переключателя:
    Button1 — наличие груза в боксе.
    Button2 — наличие аккумулятора.

  • Светодиод: используется для индикации состояния соединения.

Сеть и связь

Контроллер подключается к заданной Wi-Fi-сети и устанавливает соединение с WebSocket-сервером. Это позволяет оператору дрона или логистической системе управлять боксом в режиме реального времени.

Состояния подключения визуализируются через встроенный светодиод:

  • ✴️ Быстро мигает — нет подключения к Wi-Fi.

  • ⏳ Медленно мигает — Wi-Fi подключен, WebSocket нет.

  • ✅ Постоянно горит — соединение WebSocket установлено.

Принцип работы

После включения устройство:

  1. Подключается к Wi-Fi.

  2. Периодически пытается установить WebSocket-соединение.

  3. После подключения отправляет данные об устройстве (идентификатор и пароль).

  4. Ожидает команды от сервера и реагирует на них.

Обработка команд

Поддерживаются следующие команды:

  • "servo1" — установить угол открытия грузового отсека.

  • "servo2" — установить угол открытия аккумуляторного отсека.

  • "reboot" — перезагрузка устройства.

Телеметрия

Устройство автоматически отправляет телеметрию по команде "PING":

  • Статус "работает",

  • Углы обоих сервоприводов,

  • Состояние обоих концевиков ("pressed" или "released").

Кнопки (концевики)

Концевики подключены с использованием программного класса Button. Система отслеживает их состояния и отправляет обновления при нажатии или по таймеру (раз в секунду), если WebSocket-соединение активно.

Программная архитектура

Ключевые технологии:

  • ESP8266WiFi — подключение к сети.

  • WebSocketsClient — двусторонняя связь с сервером.

  • ArduinoJson — обработка JSON-команд и телеметрии.

  • Servo — управление сервоприводами.

  • Кастомный класс Button — простая логика обработки состояний кнопок.

Применение

Такая система может использоваться в следующих случаях:

  • 📦 Доставка малых грузов: автоматически открыть отсек для выгрузки.

  • 🔋 Обслуживание дронов: быстрое извлечение и замена аккумуляторов.

  • 📡 Мониторинг состояния: оператор всегда знает, находится ли груз или аккумулятор в боксе.

Так выглядит подключение на практике

Куда что подключать можно понять из прошивки.

Код для прошивки.

#include <ESP8266WiFi.h>
#include <WebSocketsClient.h>
#include <ArduinoJson.h>
#include <Servo.h>
#include "Button.h" // Подключаем наш класс Button

const char* ssid = "----";
const char* password = "-----";

WebSocketsClient webSocket;

const char* deviceName = "esp01";
const char* devicePassword = "1234";

const int LED_PIN = LED_BUILTIN;
const int BUTTON1_PIN = 14; // GPIO14 (D5 на NodeMCU)
const int BUTTON2_PIN = 4; // GPIO4 (D2 на NodeMCU)
const int SERVO1_PIN = 12; // Пин первого сервопривода (D6)
const int SERVO2_PIN = 13; // Пин второго сервопривода (D7)
Button button1(BUTTON1_PIN);
Button button2(BUTTON2_PIN);

Servo servo1;
Servo servo2;

unsigned long previousMillis = 0;
unsigned long lastConnectionAttempt = 0;
const long connectionInterval = 10000; // 10 секунд между попытками подключения

int ledState = LOW;
int connectionState = 0; // 0 - нет подключения, 1 - WiFi подключен, 2 - WebSocket подключен
int servo1Pos = 90; // Текущее положение сервопривода 1 (0-180)
int servo2Pos = 90; // Текущее положение сервопривода 2 (0-180)

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 handleCommand(const char* cmd, const char* value) {
Serial.printf("Received command: %s, value: %s\n", cmd, value);

if (strcmp(cmd, "servo1") == 0) {
int pos = atoi(value);
if (pos >= 0 && pos <= 360) {
servo1Pos = pos;
servo1.write(servo1Pos);
Serial.printf("Servo1 set to %d\n", servo1Pos);
}
}
else if (strcmp(cmd, "servo2") == 0) {
int pos = atoi(value);
if (pos >= 0 && pos <= 360) {
servo2Pos = pos;
servo2.write(servo2Pos);
Serial.printf("Servo2 set to %d\n", servo2Pos);
}
}
else if (strcmp(cmd, "reboot") == 0) {
Serial.println("Rebooting...");
ESP.restart();
}
}

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);
//Serial.printf("-%s-\n", payload);

// Обработка PING
//if (strstr((char*)payload, "\"command\":\"PING\"") != NULL) {
if (strstr((char*)payload, "\"command\": \"PING\"") != NULL) {

Serial.println("Processing PING command");
StaticJsonDocument<512> doc;
doc["type"] = "telemetry";
doc["status"] = "работает";
doc["servo1"] = servo1Pos;
doc["servo2"] = servo2Pos;
doc["button1"] = button1.isPressed() ? "pressed" : "released";
doc["button2"] = button2.isPressed() ? "pressed" : "released";
String json;
serializeJson(doc, json);
webSocket.sendTXT(json);
Serial.println("Телеметрия отправлена");
}
// Обработка команд
else {
StaticJsonDocument<256> doc;
DeserializationError error = deserializeJson(doc, payload);

Serial.print("Deserialization error: ");
Serial.println(error.c_str()); // Выведет "Ok" если ошибок нет

// Выводим сырой payload для проверки
Serial.print("Raw payload: ");
Serial.println((char*)payload);

// Выводим содержимое doc в Serial
Serial.println("Parsed JSON content:");
serializeJsonPretty(doc, Serial); // Красивый вывод с отступами
Serial.println("\n---");

if (!error && doc.containsKey("command") && doc.containsKey("value")) {
const char* cmd = doc["command"];
const char* value = doc["value"];
handleCommand(cmd, value);

// Отправляем подтверждение
doc["type"] = "command_ack";
doc["status"] = "ok";
String json;
serializeJson(doc, json);
webSocket.sendTXT(json);
Serial.println("");
}
}
break;
}
}

void setup() {
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, HIGH);

pinMode(BUTTON1_PIN, INPUT_PULLUP);
pinMode(BUTTON2_PIN, INPUT_PULLUP);

servo1.attach(SERVO1_PIN);
servo2.attach(SERVO2_PIN);
servo1.write(servo1Pos);
servo2.write(servo2Pos);

Serial.begin(115200);
WiFi.begin(ssid, password);

// Начальное состояние - нет подключения
connectionState = 0;
}

void handleButtons() {
static unsigned long lastSend = 0;

// Проверяем нажатия
if (button1.click() button2.click() millis() - lastSend > 1000) {
if (webSocket.isConnected()) {
sendButtonStates();
lastSend = millis();
}
}
}
void sendButtonStates() {
StaticJsonDocument<200> doc;
doc["type"] = "button_state";
doc["button1"] = button1.isPressed() ? "pressed" : "released";
doc["button2"] = button2.isPressed() ? "pressed" : "released";

String json;
serializeJson(doc, json);
webSocket.sendTXT(json);
}

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();
handleButtons();
webSocket.loop();

// Если WiFi подключен, но WebSocket еще нет - пытаемся подключиться
if (WiFi.status() == WL_CONNECTED && !webSocket.isConnected() &&
currentMillis - lastConnectionAttempt >= connectionInterval) {
connectionState = 1;
Serial.println("Connecting to WebSocket server...");
webSocket.begin("192.168.1.10", 8765, "/");
webSocket.onEvent(webSocketEvent);
webSocket.setReconnectInterval(5000);
lastConnectionAttempt = currentMillis;
}

delay(10); // Небольшая задержка для стабильности

Библиотека button.h

#ifndef Button_h
#define Button_h
#include
class Button {
public:
Button(byte pin) : pin(pin), lastState(HIGH), lastDebounceTime(0), flag(false) {
pinMode(_pin, INPUT_PULLUP);
}
bool click() {
bool currentState = digitalRead(_pin);
bool result = false;
if (currentState != lastState) {
lastDebounceTime = millis();
}
if ((millis() - lastDebounceTime) > DEBOUNCEDELAY) {
if (!currentState && !_flag) {
flag = true;
result = true;
}
if (currentState &&
flag) {
flag = false;
}
}
lastState = currentState;
return result;
}
bool isPressed() {
return (digitalRead(_pin) == LOW);
}
private:
static const uint32_t DEBOUNCE_DELAY = 50;
byte pin;
bool
lastState;
uint32_t lastDebounceTime;
bool
flag;
};

Код сервера который будет управлять данным устройством я приведу в следующей статье если будет кому интересно.

Заключение

Разработанный программный модуль представляет собой надёжную и гибкую платформу для удаленного управления дроновым оборудованием. Благодаря WebSocket-соединению обеспечивается высокая скорость отклика, а наличие телеметрии и визуальной индикации делает эксплуатацию безопасной и предсказуемой. Данный подход легко масштабируется и может быть адаптирован под различные типы дронов и задач.

Tags:
Hubs:
+1
Comments8

Articles