Картинка Benzoix, Freepik

Многие знают и даже пробовали, что микроконтроллер esp32 позволяет управлять собой удалённо через интернет, используя протокол mqtt — что позволяет избавиться от необходимости выяснять IP адрес у esp32 и не заботиться о его постоянных изменениях, например, в случае перезагрузки микроконтроллера.

Однако в наше время было бы обидно пройти мимо гораздо более удобного способа, который, на мой взгляд, частенько многократно более удобен, и к тому же находится всегда под рукой — управление с помощью телеграм-бота…

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

Для тех, кто думает, что будет большой лаг: я тоже так думал, однако, практика показала, что этот лаг не превышает 100 миллисекунд (а реально даже и ещё меньше), что субъективно воспринимается как «мгновенно».

Тем не менее, у этого способа есть и свои неудобства (маленькие, но всё же):

  • Требуется внешний mqtt-брокер, который частенько отваливается (скажем, по причине тех же санкций — это то, с чем я сталкивался на практике) и не только.
  • У этих брокеров обычно имеются довольно жёсткие ограничения на количество запросов в секунду, чтобы не перегружать брокер (я сталкивался даже с ограничением в один запрос в секунду максимум), что может быть неудобным в деле управления в реальном времени робототехническими устройствами.
  • Это опять же надо регистрироваться на брокере, заводить аккаунт, да и вообще, разбираться, что собой представляет этот протокол и как работает (если вы раньше не имели с ним дела).
  • Необходимость поднимать свой брокер, если вы хотите убрать зависимость от чужого.
  • И т. д.

В общем, как говорил один из персонажей «Собачьего сердца» — «это какой-то позор» © :-))), так как в наше время всё то же самое можно делат�� с использованием того же самого telegram-бота.

Но на самом деле, приводя выше цитату из «Собачьего сердца» я здесь утрирую, так как просто нужно учитывать, что протокол mqtt появился гораздо раньше того же telegram-а, и с появлением последнего, многое упростилось, что мы и попробуем рассмотреть ниже.

Ну и опять же, гораздо удобнее управлять через мессенджер, который всегда под рукой, чем разбираться и использовать стороннюю систему…

Правда, тут есть нюансы: насколько мне известно, mqtt обеспечивает гораздо меньшую задержку, по сравнению с telegram-ботом (0,1 сек против 1-3 сек).

Эта задержка возникает из-за того, что в случае telegram-a, устройство (esp32) вынуждено опрашивать с определённой периодичностью (например, 0,2-3 сек) сервер — «нет ли новых сообщений?» Минусом такого подхода является излишняя нагрузка на сервер, за счёт генерации пустых запросов.

Минимально допустимый интервал опроса составляет от 0,2 сек и более. Если опрашивать сервер слишком часто (т. е. с интервалом менее 0,2 сек), то сервер временно заблокирует бота и будет возникать ошибка 429.

Такой способ отличается простотой реализацией и не требует постоянного соединения на основе протокола websocket (как у mqtt).

В противовес ему, в случае mqtt, мы постоянно подключены к серверу и подписаны на обновления в определённых топиках, за счёт чего мгновенно (менее 100 миллисекунд) получаем обновления, если появилось новое сообщение. Также, как мы видим, при этом способе не генерируются пустые запросы (как в случае с telegram-ботом).

Кстати говоря, в качестве интересной идеи на будущее: а что, если реализовать управление машинкой с использованием telegram-бота в качестве пульта управления? Это интересно как минимум с точки зрения проверки в реальных условиях временного лага, который возникает при этом случае.

Мои подозрения (по опыту, так как я ставил такой лаг принудительно, во время экспериментов с mqtt, при управлении роботами-хоккеистами): 200 миллисекунд — уже чувствуется, но вполне терпимо. И даже для роботов с управлением в реальном времени. Надо протестить на досуге...

Тем не менее, если у вас нет жёстких ограничений по времени отклика системы — например, мы управляем открытием окна для вентиляции теплицы, то этот способ вполне годится.

То есть, можно сказать, что вариант управления с помощью telegram-бота годится практически для 90% IoT-устройств, поэтому он и интересен для рассмотрения.

Итак, попробуем пошагово пройти все этапы создания telegram-бота и подключения его к esp32.

На первом этапе нам нужно запустить мессенджер telegram и забить в поиск: @BotFather

Или просто перейти вот по этой ссылке: BotFather — так мы попадём на адрес создания ботов, где нового бота создаём командой /newbot.

После чего нам выдают секретный ключ (HTTP API), которым мы будем пользоваться для подключения к этому боту:



Также, как можно видеть в самой нижней части картинки выше, нам сообщили полный адрес, по которому можно найти нашего бота — запоминаем его ( t.me/......_bot), он нам понадобится дальше.

Кроме того, как можно видеть по картинке выше, здесь есть ряд возможностей, которые стоит изучить, для увеличения возможностей взаимодействия с вашим новым ботом.

Любое управление становится гораздо нагляднее, если оно имеет обратный отклик, поэтому нам потребуется код для загрузки в esp32, который выполняет две функции:

  • Принимает входящие команды.
  • Отправляет telegram-боту отклик, о том, как команда понята.

Один из самых простых и наглядных способов протестировать работу системы — зажигать и гасить встроенный в esp32 светодиод.

Сделаем так, чтобы мы могли из бота отправлять слова on, off (без учёта регистра, то есть система должна реагировать, независимо от того, ��рислали ли мы слова, написанные заглавными буквами или маленькими), в ответ на которые esp32 будет, соответственно, зажигать или гасить встроенный светодиод. Сделано так исключительно из соображений удобства, чтобы не задумываться ещё и о регистре надписей (ссылки на библиотеки для этого кода можно найти в конце статьи):
Полный код для загрузки в esp32 — с обработкой команд on/off
//Разработано с применением DeepSeek
//Весь код ниже приведён справочно, без какой-либо гарантии его надлежащей работы.
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <UniversalTelegramBot.h>

// Настройки (ЗАМЕНИТЕ НА СВОИ!)
const char* WIFI_SSID = "вписать сюда"; // Название Wi-Fi-сети
const char* WIFI_PASS = "вписать сюда"; // Пароль Wi-Fi-сети
const char* BOT_TOKEN = "вписать сюда"; // Токен бота от @BotFather

WiFiClientSecure secureClient;
UniversalTelegramBot bot(BOT_TOKEN, secureClient);

const int LED_PIN = 2;  // Встроенный светодиод
String CHAT_ID = "";    // Храним Chat ID

void debugLog(String message) {
  Serial.println("[DEBUG] " + message);
}

void connectToWiFi() {
  debugLog("Подключаемся к WiFi: " + String(WIFI_SSID));
  
  WiFi.begin(WIFI_SSID, WIFI_PASS);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  
  debugLog("Успешно подключено! IP: " + WiFi.localIP().toString());
}

void handleCommand(String cmd, String chat_id) {
  cmd.toLowerCase(); // Нормализуем команду
  debugLog("Принята команда: '" + cmd + "' от chat_id: " + chat_id);

  if (cmd == "on" || cmd == "/on") {
    digitalWrite(LED_PIN, HIGH);
    String response = "💡 Светодиод включён!";
    if (bot.sendMessage(chat_id, response, "")) {
      debugLog("Отправлено: '" + response + "'");
    } else {
      debugLog("Ошибка отправки сообщения!");
    }
  } 
  else if (cmd == "off" || cmd == "/off") {
    digitalWrite(LED_PIN, LOW);
    String response = "🌑 Светодиод выключен!";
    if (bot.sendMessage(chat_id, response, "")) {
      debugLog("Отправлено: '" + response + "'");
    } else {
      debugLog("Ошибка отправки сообщения!");
    }
  }
  else {
    String response = "Неизвестная команда. Используйте /on или /off";
    bot.sendMessage(chat_id, response, "");
    debugLog("Отправлен ответ на неизвестную команду");
  }
}

void setup() {
  Serial.begin(115200);
  pinMode(LED_PIN, OUTPUT);
  digitalWrite(LED_PIN, LOW);
  
  connectToWiFi();
  secureClient.setInsecure();
  
  debugLog("Система инициализирована. Ожидаем команды...");
}

void loop() {
  int newMessages = bot.getUpdates(bot.last_message_received + 1);
  
  if (newMessages > 0) {
    debugLog("Обнаружено новых сообщений: " + String(newMessages));
  }

  for (int i = 0; i < newMessages; i++) {
    String chat_id = String(bot.messages[i].chat_id);
    String text = bot.messages[i].text;
    
    if (CHAT_ID == "") {
      CHAT_ID = chat_id;
      debugLog("Зарегистрирован новый Chat ID: " + CHAT_ID);
    }
    
    handleCommand(text, chat_id);
  }

  delay(500);
}


Работает это следующим образом: при первом получении сообщения от бота, микроконтроллер запоминает Chat ID и отвечает:«Бот-повелитель света готов к работе!».

Небольшая справка: наверняка на этом этапе у некоторых возникнет вопрос, а зачем вообще запоминать Chat ID, у нас ведь уже есть уникальный токен, не излишняя ли это процедура?

Дело тут в том, что токен бота и Chat ID это разные вещи, и запоминание Chat ID — необходимая процедура в целях безопасности: если этого не сделать, то любой пользователь сможет управлять вашим ботом, если узнает имя бота, а токен бота тут ни при чём, так как он только даёт доступ к API.

После чего, можно отправлять команды on, off. Можно и сразу отправить команду, без «предварительных раскланиваний» с микроконтроллером, — тогда он сначала вежливо ответит, а потом отпишется, что задание выполнено :-)

На дальнейшие отправки команд он будет отвечать только лаконично и по делу:


Казалось бы, всё работает и всё хорошо… Однако, telegram предоставляет чуть большие возможности, и грех было бы не воспользоваться ими!

А конкретнее: каждый раз набивать команды неудобно, что, если бы под эти команды сделать кнопки, нажимая на которые мы могли бы управлять устройством?

Запросто: telegram как раз предоставляет такой функционал.

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

[DEBUG] Подключаемся к WiFi: MyWiFi
.....
[DEBUG] Успешно подключено! IP: 192.168.1.100
[DEBUG] Система инициализирована. Ожидаем команды...
[DEBUG] Обнаружено новых сообщений: 1
[DEBUG] Зарегистрирован новый Chat ID: 123456789
[DEBUG] Принята команда: '/on' от chat_id: 123456789
[DEBUG] Отправлено: '💡 Светодиод включён!'

Теперь наш код принимает не только текстовые сообщения вида on/off, но и выполняет тот же самый функционал, только ещё и при нажатии на кнопки:
Полный код для загрузки в esp32 — с кнопками для telegram
//Разработано с применением DeepSeek
//Весь код ниже приведён справочно, без какой-либо гарантии его надлежащей работы.
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <UniversalTelegramBot.h>

const char* WIFI_SSID = "вписать сюда"; // Название Wi-Fi-сети
const char* WIFI_PASS = "вписать сюда"; // Пароль Wi-Fi-сети
const char* BOT_TOKEN = "вписать сюда"; // Токен бота от @BotFather

WiFiClientSecure secureClient;
UniversalTelegramBot bot(BOT_TOKEN, secureClient);

const int LED_PIN = 2;
String CHAT_ID = "";

// Создаём клавиатуру с кнопками
String makeKeyboard() {
  return "[["
    "{ \"text\":\"🔆 ВКЛЮЧИТЬ\", \"callback_data\":\"LED_ON\" },"
    "{ \"text\":\"🌑 ВЫКЛЮЧИТЬ\", \"callback_data\":\"LED_OFF\" }"
  "]]";
}

void connectToWiFi() {
  Serial.begin(115200);
  Serial.print("Подключаемся к WiFi...");
  WiFi.begin(WIFI_SSID, WIFI_PASS);
  
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("\nПодключено! IP: " + WiFi.localIP().toString());
}

void handleCommand(String cmd, String chat_id) {
  Serial.print("Получена команда: ");
  Serial.println(cmd);

  if (cmd == "LED_ON" || cmd == "/on" || cmd == "on" || cmd == "🔆 ВКЛЮЧИТЬ") {
    digitalWrite(LED_PIN, HIGH);
    bot.sendMessageWithReplyKeyboard(chat_id, "💡 Светодиод включён!", "", makeKeyboard());
    Serial.println("Светодиод включён");
  }
  else if (cmd == "LED_OFF" || cmd == "/off" || cmd == "off" || cmd == "🌑 ВЫКЛЮЧИТЬ") {
    digitalWrite(LED_PIN, LOW);
    bot.sendMessageWithReplyKeyboard(chat_id, "🌑 Светодиод выключен!", "", makeKeyboard());
    Serial.println("Светодиод выключен");
  }
  else {
    bot.sendMessageWithReplyKeyboard(chat_id, "Используйте кнопки ниже:", "", makeKeyboard());
    Serial.println("Неизвестная команда");
  }
}

void setup() {
  pinMode(LED_PIN, OUTPUT);
  digitalWrite(LED_PIN, LOW);
  
  connectToWiFi();
  secureClient.setInsecure();
  
  Serial.println("Бот запущен. Ожидаем сообщений...");
}

void loop() {
  int numNewMessages = bot.getUpdates(bot.last_message_received + 1);

  for (int i = 0; i < numNewMessages; i++) {
    String chat_id = String(bot.messages[i].chat_id);
    CHAT_ID = chat_id;
    
    // Обрабатываем текст сообщения или callback_data
    String message_text = bot.messages[i].text;
    if (message_text != "") {
      handleCommand(message_text, chat_id);
    }
  }

  // Отправляем клавиатуру при первом запуске
  static bool firstRun = true;
  if (firstRun && CHAT_ID != "") {
    bot.sendMessageWithReplyKeyboard(CHAT_ID, "Выберите действие:", "", makeKeyboard());
    firstRun = false;
  }

  delay(500);
}


Теперь попробуем загрузить этот код в esp32, после чего запустим нашего telegram-бота и обратимся к микроконтроллеру:


Как можно видеть по картинке выше, после отправки любой команды, даже некорректной, микроконтроллер отвечает, присылая кнопки — изначально их нет, и они появляются внизу экрана, после первого сообщения от микроконтроллера.

После чего можно управлять микроконтроллером, нажимая на эти кнопки (в данном случае мы управляем встроенным светодиодом, включая и выключая его).

Кнопки в коде описываются в формате json, и это позволяет создавать весьма сложные клавиатуры, разной степени вложенности:

String keyboard = "[["
  "{\"text\":\"🔆 ВКЛЮЧИТЬ\", \"callback_data\":\"ON\"},"
  "{\"text\":\"🌑 ВЫКЛЮЧИТЬ\", \"callback_data\":\"OFF\"}"
"]]";

В коде выше: text — это то, что наблюдает пользователь, а callback_data — команда для esp32 (например, «ON»).

Непосредственная отправка кнопок происходит с помощью прикрепления их к сообщению одной строкой:

bot.sendMessageWithReplyKeyboard(chat_id, "Выберите действие:", "", keyboard);

В свою очередь, микроконтроллер мониторит эти сообщения в цикле:

int updates = bot.getUpdates(bot.last_message_received + 1);
for (int i=0; i<updates; i++) {
  if (bot.messages[i].callback_data == "ON") {
    digitalWrite(LED_PIN, HIGH);
    bot.sendMessage(chat_id, "💡 Включено!");
  }
}

Таким образом, используя описанные выше подходы, можно управлять IoT-устройствами весьма простым способом, который в большинстве случаев будет намного проще, чем mqtt: теплицы, полив цветов, видеонаблюдение (разворот вебкамеры, например) и прочее, прочее, прочее (на что воображения хватит).

Тем не менее, не стоит списывать и mqtt со счетов, так как потенциальные задержки при прохождении команд в варианте управления с использованием telegram-бота могут быть неприемлемыми для особо чувствительных к этому устройств.

Ниже вы можете найти библиотеки, которые следует установить, чтобы приведённые выше прошивки микроконтроллера работали. К слову — тестировалась вся работа на микроконтроллере esp32-wroom-32:


© 2025 ООО «МТ ФИНАНС»

Telegram-канал со скидками, розыгрышами призов и новостями IT 💻