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

от автора

Предисловие

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

Введение

С ростом использования дронов в логистике и техническом обслуживании растет потребность в универсальных модулях для перевозки грузов и сменных аккумуляторов. Для решения этой задачи был разработан интеллектуальный бокс, управляемый удалённо через контроллер на базе 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-соединению обеспечивается высокая скорость отклика, а наличие телеметрии и визуальной индикации делает эксплуатацию безопасной и предсказуемой. Данный подход легко масштабируется и может быть адаптирован под различные типы дронов и задач.


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


Комментарии

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

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