Отправка уведомлений из ESP8266 / ESP32 в Telegram

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

Для чего это нужно? В настоящее время все мои устройства, собранные на базе ESP8266, исправно публикуют текущие данные через MQTT брокер на контрольную панель (сделанную из древнего планшета).

Консоль управления

Консоль управления

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

Графики изменений

Графики изменений

Но, как всегда, хочется большего! Например, чтобы при наступлении какого-либо события приходило уведомление на телефон и компьютер. Можно конечно поставить “взрослый” и полноценный контроллер “умного дома”, на многих платформах такое наверняка есть. Можно попробовать написать свое приложение для android. Но можно сделать гораздо проще – просто отправить уведомление в какой-нибудь популярный мессенджер, сообщения которого можно с одинаковым успехом прочесть и на смартфоне, и на компьютере.

Почему именно telegram?

Для этого есть несколько вполне объективных причин:

  1. В telegram реализован самый простой механизм отправки сообщений через API ботов. Нигде более просто отправить сообщение в чат или канал “со стороны” не получится.
  2. Все сообщения, отправленные через бота, синхронно доставляются всем подписчикам бота и на смартфоны, и на компьютеры.
  3. При необходимости через тот же telegram можно не только отправлять уведомления от ESP к человеку, но и наоборот – отправлять команды от человека к устройству.

На момент написания данной статьи telegram заблокирован на территории РФ, хотя и продолжает более-менее успешно работать. Поэтому для доступа к API telegram потребуется какой-либо proxy-сервер. У меня данный сайт хостится на сервере в Германии, где запрета на доступ к telegram нет, поэтому в качестве прокси я буду использовать свой хостинг. Кроме этого, использование “своего” хостинга немного упрощает отправку сообщений “на стороне” ESP. 

Как альтернативный вариант можно написать точно такого же бота для нового “клона” telegram – мессенджер ТамТам. Только недавно там открыли, наконец-то, API для ботов. Судя по всему, в “там-там” реализован очень похожий механизм отправки сообщений, если не точно такой-же. И, по идее, никакие proxy-сервера не понадобятся, все можно сделать напрямую из ESP. Но… сам я не пробовал, поэтому на 100% не уверен. Пробуйте. Если будет необходимость – пишите, попробую сделать и бота для TamTam.

Регистрация бота

Для начала находим в поиске бота @BotFather:

Отец всех ботов

Отец всех ботов

Для того,чтобы получить команды бота, отправьте команду /help:

Команды бота

Справка по командам

Отправьте команду /newbot. @BotFather спросит имя бота, в ответ на запрос отправьте желаемое имя бота. Затем бот запросит имя пользователя для бота, я обычно отправляю тоже самое имя, но заканчивающиеся на “_bot”:

Создание нового бота

Создание нового бота

Все. Бот создан. Для работы с ботом  потребуется токен доступа к API – запишите его, он потом потребуется! 

По желанию к нему можно добавить описание, поменять картинку, добавить список поддерживаемых команд и т.д. Например:

Изменение описания

Изменение описания

Теперь Вы можете найти своего бота в поиске Telegram, и начать с ним работу. Но бот не ответит – ведь он пока еще ничего “не умеет”. Но как говорится – “не умеешь – научим, не хочешь – заставим”. 

В принципе, этого уже достаточно для отправки сообщений в telegram – нужно только знать Ваш идентификатор в telegram (как его узнать – будет рассказано далее). Но – не очень удобно. Когда пользователь бота только один – то нормально. Но для того, чтобы отправлять уведомления сразу нескольким пользователям, придется “прописывать” каждого на ESP и отправлять их каждому по очереди. Крайне неудобно. Но выход есть – создаем приватный канал, добавляем туда нужных пользователей (по ссылке-приглашению, например), а только что созданного бота добавляем администратором. Вуаля – бот отправляет сообщение в канал, а все пользователи одновременно получают сообщения из канала. При необходимости можно добавлять или удалять пользователей канала, не затрагивая при этом программную начинку ESP.

Создаем приватный канал. Для этого нажимаем “Меню” и выбираем команду “Создать канал”:

Создание нового канала

Создание нового канала

Указываем название и описание канала. Здесь же можно загрузить картинку для канала:

Название канала

Название канала

Указываем, что канал будет приватным:

Частный канал

Частный канал

Сразу же можно добавить участников канала:

Добавление участников канала

Добавление участников канала

Но можно сделать это и потом, через меню канала:

Меню канала

Меню канала

А можно просто отправить потенциальным читателям канала ссылку – приглашение. 

Теперь нужно добавить бота в канал. Добавить бота можно только к администраторам. Для этого открываем меню “Управление каналом”, и нажимаем на ссылку “Администраторы”:

Управление каналом

Управление каналом

Добавление бота в список администраторов

Добавление бота в список администраторов

Настроенный канал

Настроенный канал

Теперь нужно выяснить идентификатор канала, в который бот должен отправлять сообщения. Для этого достаточно отправить в канал любое сообщение, любой текст. Затем нужно отправить к API telegram запрос на получение новых сообщений от пользователей и выяснить номер чата канала. Проще всего это сделать через обычный браузер. Но вся сложность в том, что API telegram в РФ блокируется, поэтому придется воспользоваться любым прокси-сервером или анонимайзером. Подключаем прокси (или анонимайзером) и набираем в адресной строке ссылку:

https://api.telegram.org/bot567087417:FLBLAAUYkQ9H1-3G58DVFH_R-vkIjaIolc0/getUpdates

где 567087417:FLBLAAUYkQ9H1-3G58DVFH_R-vkIjaIolc0 – токен бота, который мы получили в BotFather ранее.

В ответ мы должны получить JSON следующего вида:

{"ok":true,"result":[{"update_id":509976507,
"channel_post":{"message_id":2,"chat":{"id":-9149928783242,"title":"test","type":"channel"},"date":1564339476,"text":"test"}}]}

Нам нужен “chat” > “id” из “channel_post”, то есть -9149928783242 (цифры здесь заменены на случайные). Записываем это число, оно также нам пригодится. Кстати, обратите внимание – идентификаторы пользователей всегда положительные, идентификаторы каналов – отрицательные. 

Отправка сообщений в telegram от имени бота

Для того, чтобы отправить сообщение в telegram, достаточно отправить POST-запрос (можно и GET) по адресу:

https://api.telegram.org/bot567087417:FLBLAAUYkQ9H1-3G58DVFH_R-vkIjaIolc0/sendMessage

API допускает передачу данных для отправки как в виде обычных POST-параметров (или GET), так и в JSON формате. Я использовал обычный POST-формат. Мне потребовались следующие параметры:

chat_id => $chatId, 
parse_mode => 'HTML', 
text => $tgMsg

Где:

  • $chatId – это идентификатор чата (то самое огромное отрицательное число),
  • parse_mode – тип форматирования сообщения (может быть ‘text’, ‘html’ и ‘markdown’ – я пользуюсь ‘html’)
  • $tgMsg – собственно текст сообщения

Подробнее об этом смотри здесь.

Сообщения при этом можно форматировать с помощью стандартной html-разметки. Но нужно помнить, что telegram не может обработать два типа разметки сразу – то есть можно отправить текст курсив или жирный, но нельзя жирный + курсив – в ответ будет ошибка.

В принципе, отправку уведомления несложно сделать и на самом ESP – есть уже готовые библиотеки. Но есть одна сложность – блокировка telegram. Нужен прокси. А прокси имеют одну гаденькую особенность – часто бесследно исчезать, особенно бесплатные. Переписывать же постоянно скетч никто не станет. Кстати, если использовать уведомления в ТамТам вместо Telegram, прокси не понадобятся.

Создаем php-скрипт для пересылки сообщений

Я воспользовался тем, что данный сайт физически расположен в Германии. И хостинг имеет прямой доступ к API, а я имею прямой доступ к своему сайту (когда я выбирал хостинг, я сознательно выбирал хостинги с серверами за границей, ибо создал далеко не один бот telegram). Пишем небольшой PHP-скрипт, который будет получать текст сообщения и отправлять его на API. Больше от него ничего не требуется.

<?php

// ************************************************************************
// Скрипт отправки сообщений в канал telegram от имени бота
// ************************************************************************

$tgChat	  = "укажите здесь chat id";
$tgToken  = "укажите здесь токен бота";
$tgKey    = "54E25F63-7532-4F7F-A7F1-CAFB402B2EB0";
$tgSend   = "https://api.telegram.org/bot".$tgToken."/sendMessage";
$tgMsg    = 'test message';

// Считываем сообщение из параметров вызова 

if (isset($_GET["msg"]))
  { $tgMsg = $_GET["msg"]; };
if (isset($_POST["msg"]))
  { $tgMsg = $_POST["msg"]; };

// Проверяем секретный ключ, чтобы сообщение не мог отправить любой посетитель сайта

$key = "";
if (isset($_GET["key"]))
  { $key = $_GET["key"]; };
if (isset($_POST["key"]))
  { $key = $_POST["key"]; };

if ($key != $tgKey) {
  exit("Invalid key!");
};

// Отправляем сообщение в telegram

$tg = curl_init();
curl_setopt($tg, CURLOPT_URL, $tgSend);
curl_setopt($tg, CURLOPT_SSL_VERIFYPEER, 0);
curl_setopt($tg, CURLOPT_SSL_VERIFYHOST, 0);
curl_setopt($tg, CURLOPT_RETURNTRANSFER, false);
curl_setopt($tg, CURLOPT_TIMEOUT, 60);
curl_setopt($tg, CURLOPT_POST, 1);
curl_setopt($tg, CURLOPT_POSTFIELDS, array('chat_id' => $tgChat, 'parse_mode' => 'HTML', 'text' => $tgMsg));
curl_exec($tg);
curl_close($tg);

?>

Сохраняем скрипт на сервере с любым именем. Имя лучше выбрать посекретнее, что-то вроде “A07BEFBF39F2.PHP” – нам не все равно, а случайный посетитель вряд-ли натолкнется на такую станицу на сервере. Разумеется имя скрипта и ключ здесь приведены случайные.

Отправка сообщения в этом случае становится еще проще и сводится к GET вызову скрипта на хостинге с параметрами:

https://site.ru/scripts/A07BEFBF39F2.PHP?key=54E25F63-7532-4F7F-A7F1-CAFB402B2EB0&msg=текст%20сообщения

Обратите внимание – при отправке сообщения все пробелы в тексте (и другие спецсимволы вроде CR, LF, TAB и т.д.) должны быть предварительно заменены на их цифровые коды.

Библиотека для отправки сообщений в telegram

Для отправки сообщений в telegram я написал свою небольшую библиотечку под Arduino IDE. Функция отправки уведомления не сложная:

bool tgSendEx(String device, String message, String timestamp) {
  bool result = false;
 
  if (WiFi.status() == WL_CONNECTED) {
    // Для отправки запроса на ThingSpeak используем локальный экземпляр WiFiClient
    #if defined(ESP32) 
      // -- ESP32 :: begin --------------------------------------------------
      WiFiClientSecure tgClient;
      // -- ESP32 :: end ----------------------------------------------------
    #else 
      // -- ESP8266 :: begin ------------------------------------------------
      axTLS::WiFiClientSecure tgClient;
      // -- ESP8266 :: end --------------------------------------------------
    #endif

    #if defined(rSerialDebug) 
    Serial.printf("TLG :: Send message \"%s\": ", message.c_str());
    #endif

    // Формируем сообщение
    device.replace(" ", "%20");
    message.replace(" ", "%20");
    timestamp.replace(" ", "%20");
    
    String request = String("<b>") + device + "</b>" + "%0D%0A%0D%0A" + message + "%0D%0A%0D%0A<code>" + timestamp + "</code>";
  
    // Отправляем HTTP-запрос
    if (tgClient.connect(tg_ApiServer, httpsPort)) {
      tgClient.printf(tg_ApiPost, request.c_str());
      tgClient.printf(tg_ApiHost, tg_ApiServer);
      tgClient.println(F("User-Agent: ESP8266 (nothans)/1.0"));
      tgClient.println(F("Connection: close"));
      tgClient.println();
    
      // Читаем результат (первую строку отправленного заголовка)
      #if defined(rSerialDebug) 
      Serial.println(tgClient.readStringUntil('\n'));
      #endif

      tgClient.stop();

      result = true;
    }
    else {
      #if defined(rSerialDebug) 
      Serial.print(F("#ERROR# Failed connection to "));
      Serial.println(tg_ApiServer);
      #endif
    };
  };

  return result;
}

При отправке уведомления кроме самого сообщения необходимо передать имя устройства и его текущее время. Время я получаю по NTP и периодически синхронизирую.

Кроме собственно функции отправки сообщений, в библиотеку встроена очередь отправляемых сообщений. Для чего это нужно? А нужно это для того, чтобы можно было поместить в очередь оповещения, когда нет подключения к WiFi; а уже после того как подключение будет автоматически восстановлено (как это сделать, будет рассказано в другой статье) – сообщения из очереди будут успешно отправлены. 

Очередь QueueArray.h я надыбал из какого-то примера, и давно успешно использую для собственных целей (автор этого модуля: Efstathios Chatzikyriakidis (contact@efxa.org)). Для хранения записи в очереди я создал класс pubTgMessage:

class pubTgMessage {
  public:
    pubTgMessage(String _device, String _message) {
      device = _device;
      message = _message;
      timestamp = timeDateTimeString();
    };
  String device;
  String message;
  String timestamp;
};

Для того, чтобы было понятно, когда фактически было сформировано уведомление, в него “встраивается” временная метка. Кроме того, все сообщение имеют заголовок с “именем” устройства, которое его отправило. Это позволяет легко ориентироваться в общем потоке уведомлений.

Помещаем сообщение в очередь:

bool tgSend(String device, String message) {
  // Добавляем в очередь на отправку
    if (_queueTg.push(new pubTgMessage(device, message))) {
      #if defined(rSerialDebug) 
      Serial.printf("TLG :: Message \"%s\" :: added to queue, total %d messages\n", message.c_str(), _queueTg.count());
      #endif
    }
    else {
      #if defined(rSerialDebug) 
      Serial.printf("TLG :: failed post message '%s' - queue is full!\n", message.c_str());
      #endif
    };
};

Обработка очереди (то есть реальная отправка сообщений) происходит в каждом цикле void loop():

bool tgProcessQueue()
{
  bool result = false;
  
  while ((WiFi.status() == WL_CONNECTED) && (!_queueTg.isEmpty())) 
  {
    pubTgMessage *nextPub = _queueTg.peek();
    if (tgSendEx(nextPub->device, nextPub->message, nextPub->timestamp)) {
      result = true;
      nextPub = _queueTg.pop();
      delete nextPub;
    };
    delay(100);
  };

  return result;
};

Необходимо добавить вызов tgProcessQueue() в тело loop(), если есть подключение к WiFi сети.

Результаты

Сообщения от всех контроллеров “сыпятся” в один и тот же чат, но их легко идентифицировать благодаря заголовкам с именем устройства:

Сообщения от нескольких разных контроллеров

Сообщения от нескольких разных контроллеров

Получилась очень удобная и гибкая система оповещения. Параметры оповещения можно настраивать средствами самого клиента Telegram. Аналогичное оповещение наверняка можно сделать и на примере других мессенджеров, но лично меня пока что устраивает этот вариант. Уведомления работают достаточно быстро, и самое главное – абсолютно бесплатно (в отличие от SMS). Единственный и существенный недостаток таких уведомлений – невозможность доставки при отсутствии интернета, поэтому я планирую сделать дублирующий канал оповещения через SIM800 для особо критичных случаев (охрана, пожарная сигнализация и т.д.)

Если дальше развивать этот проект, можно сделать удаленное управление устройствами посредством команд через тот же канал. Однако при этом придется продумать, как направить команду конкретному устройству. Впрочем, это уже технические моменты, ничего принципиально невозможного тут нет.

На сим пока, спасибо что дочитали до конца. Желаю удачи в реализации Ваших проектов.

7 комментариев для “Отправка уведомлений из ESP8266 / ESP32 в Telegram”

  1. Если у Вас нет хостинга за рубежом, а сделать такое оповещение хочется, я могу разместить прокси-скрипт на своем сайте – пишите в личку, за небольшую плату договоримся.

  2. А можете пожалуйста подробнее описать как добавить библиотеку в arduino IDE и какой код куда предназначен , без стандартных void setup() void loop() , я потерялся 🙁

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

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