Предисловие
Данная статья это рассказ об отдельной части моего проекта по созданию системы доставки дронами. Система совсем в ранней стадии разработки. Но в принципе эта часть готова и если кому интересно может пользоваться в своих проектов. Если будет заинтересованность могу продолжить публиковать чертежи, схемы и программный код.
Введение
С ростом использования дронов в логистике и техническом обслуживании растет потребность в универсальных модулях для перевозки грузов и сменных аккумуляторов. Для решения этой задачи был разработан интеллектуальный бокс, управляемый удалённо через контроллер на базе ESP8266, который способен:
-
📦 открывать и закрывать отсек для груза;
-
🔋 открывать и закрывать отсек для сменного аккумулятора;
-
📡 передавать телеметрию о состоянии устройства;
-
✅ принимать команды через WebSocket-сервер.
Аппаратная часть
Устройство построено на контроллере ESP8266 (например, ESP-01 или NodeMCU) и включает в себя:
-
2 сервопривода:
Servo1 — управление крышкой грузового отсека.
Servo2 — управление крышкой отсека аккумулятора. -
2 концевых переключателя:
Button1 — наличие груза в боксе.
Button2 — наличие аккумулятора. -
Светодиод: используется для индикации состояния соединения.
Сеть и связь
Контроллер подключается к заданной Wi-Fi-сети и устанавливает соединение с WebSocket-сервером. Это позволяет оператору дрона или логистической системе управлять боксом в режиме реального времени.
Состояния подключения визуализируются через встроенный светодиод:
-
✴️ Быстро мигает — нет подключения к Wi-Fi.
-
⏳ Медленно мигает — Wi-Fi подключен, WebSocket нет.
-
✅ Постоянно горит — соединение WebSocket установлено.
Принцип работы
После включения устройство:
-
Подключается к Wi-Fi.
-
Периодически пытается установить WebSocket-соединение.
-
После подключения отправляет данные об устройстве (идентификатор и пароль).
-
Ожидает команды от сервера и реагирует на них.
Обработка команд
Поддерживаются следующие команды:
-
«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/
Добавить комментарий