ESP32-CAM с Telegram: съёмка фото, управление выходами, показания датчиков и уведомления о движении

В этом проекте мы создадим плату расширения (PCB shield) для модуля ESP32-CAM AI-Thinker с PIR-датчиком движения, датчиком температуры, влажности и давления BME280, а также несколькими дополнительными выведенными пинами. Мы создадим Telegram-бота для ESP32-CAM, который позволит вам управлять платой из любой точки мира: запрашивать фото, показания датчиков или управлять вспышкой. Кроме того, при обнаружении движения вы будете получать уведомление с новым фото.

Управление ESP32-CAM через Telegram: съёмка фото, управление выходами, показания датчиков и уведомления о движении

Также вы можете повторить этот проект, собрав схему на макетной плате.

Смотрите видеоурок

Этот проект доступен в видеоформате и в текстовом формате. Вы можете посмотреть видео ниже или прокрутить страницу для текстовых инструкций.

Ресурсы

Все необходимые ресурсы для этого проекта вы можете найти по ссылкам ниже (или посетить проект на GitHub):

Обзор проекта

Этот проект состоит из трёх частей:

  1. Проектирование и сборка платы расширения (PCB shield)

  2. Создание Telegram-бота

  3. Программирование платы расширения с помощью Arduino IDE

Особенности PCB-платы расширения для ESP32-CAM

Плата расширения спроектирована для установки поверх ESP32-CAM. По этой причине, если вы хотите использовать нашу плату, вам нужен точно такой же модуль ESP32-CAM. Мы используем модуль ESP32-CAM AI-Thinker.

Модуль ESP32-CAM AI-Thinker с установленной платой расширения и компонентами

Мы также используем модуль камеры с более длинным шлейфом. Благодаря этому при установке платы расширения камера оказывается на одной стороне с PIR-датчиком движения.

Также вы можете собрать схему на макетной плате.

Тестовая схема проекта ESP32-CAM Telegram на макетной плате

Плата расширения состоит из:

  • Датчика BME280 температуры, влажности и давления (4 пина);

  • Миниатюрного PIR-датчика движения (AM312);

  • Выведенных пинов 5V и GND для питания платы расширения и ESP32-CAM;

  • Других выведенных GPIO на случай, если вы захотите добавить дополнительные функции.

Назначение пинов на PCB-плате расширения для ESP32-CAM

Назначение пинов для BME280 и PIR-датчика движения на плате расширения:

  • PIR-датчик движения: GPIO 13

  • BME280: GPIO 14 (SDA), GPIO 15 (SCL)

Telegram-бот для ESP32-CAM

Для управления платой расширения ESP32-CAM мы создадим Telegram-бота, чтобы вы могли контролировать свой ESP32-CAM из любой точки мира (при наличии интернета на смартфоне). Вы можете использовать следующие команды для взаимодействия с ботом:

  • /start: отправляет приветственное сообщение с допустимыми командами управления платой;

  • /flash: включает/выключает вспышку ESP32-CAM;

  • /photo: делает новое фото и отправляет его в ваш аккаунт Telegram;

  • /readings: запрашивает последние показания датчика BME280.

Управление ESP32-CAM через Telegram: демонстрация фото, управления выходами, показаний датчиков и уведомлений о движении

Кроме того, вы будете получать уведомление с фото при каждом обнаружении движения. Наконец, только вы (или любой другой авторизованный пользователь) сможете управлять ESP32-CAM через Telegram.

Тестирование схемы на макетной плате

Перед проектированием и сборкой платы расширения важно протестировать схему на макетной плате. Если вы не хотите делать PCB, вы всё равно можете выполнить этот проект, собрав схему на макетной плате.

Тестовая схема проекта ESP32-CAM Telegram на макетной плате

Необходимые компоненты

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

Собрав все компоненты, соберите схему по следующей принципиальной схеме:

Принципиальная схема подключения ESP32-CAM с BME280 и PIR-датчиком движения

Проектирование PCB

Для проектирования схемы и PCB мы использовали EasyEDA — браузерное приложение для проектирования печатных плат. Если вы хотите настроить PCB под себя, просто загрузите следующие файлы:

Проектирование схемы работает так же, как и в любом другом ПО для создания схем: вы размещаете компоненты и соединяете их проводами. Затем каждому компоненту назначается посадочное место (footprint).

Принципиальная схема PCB-платы расширения ESP32-CAM для Telegram с PIR и BME280

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

Разводка PCB-платы расширения ESP32-CAM Telegram с PIR и BME280

Сохраните проект и экспортируйте Gerber-файлы.

Примечание

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

Заказ PCB

Когда у вас есть Gerber-файлы, вы можете заказать PCB. Выполните следующие шаги.

  1. Скачайте Gerber-файлы — нажмите здесь, чтобы скачать .zip файл

Загрузка Gerber-файлов PCB

Распаковка

Примерно через неделю (при доставке DHL) платы были получены.

Распаковка PCB

Всё хорошо упаковано, и платы очень высокого качества. Надписи на шелкографии отлично напечатаны и легко читаются. Кроме того, припой легко ложится на контактные площадки.

Плата расширения для ESP32-CAM AI Thinker — распаковка Распаковка PCB и подарки

Пайка компонентов

Следующий шаг — пайка компонентов на PCB. Вам нужно припаять только гнёзда (female header pins). PIR-датчик движения и BME280 затем подключаются к этим разъёмам.

Список всех компонентов, необходимых для сборки платы расширения:

Компоненты для сборки платы расширения ESP32-CAM AI-Thinker

Вот инструменты для пайки, которые мы использовали:

Паяльник TS80 — лучший портативный паяльник

Процесс пайки довольно простой, так как нужно припаять только штыревые разъёмы. В середине платы расширения есть несколько выведенных GPIO. Припаяйте к ним разъёмы, если хотите подключать дополнительную периферию.

Пайка компонентов на плату расширения ESP32-CAM AI-Thinker

Вот как выглядит плата расширения ESP32-CAM PCB Shield после сборки.

Собранная плата расширения ESP32-CAM AI-Thinker — демонстрация

Создание Telegram-бота

ESP32-CAM будет взаимодействовать с Telegram-ботом для получения и обработки сообщений, а также для отправки ответов в ваш аккаунт Telegram (показания датчиков и фото). Выполните следующие шаги, чтобы создать Telegram-бота.

Перейдите в Google Play или App Store, скачайте и установите Telegram.

Установка и скачивание Telegram

Откройте Telegram и выполните следующие шаги для создания Telegram-бота. Сначала найдите «botfather» и нажмите на BotFather, как показано ниже. Или откройте эту ссылку t.me/botfather на своём смартфоне.

BotFather в Telegram

Должно открыться следующее окно, и вам будет предложено нажать кнопку start.

Запуск BotFather в Telegram для создания нового бота

Введите /newbot и следуйте инструкциям для создания бота. Задайте ему имя и username.

Создание нового бота в Telegram через BotFather

Если ваш бот успешно создан, вы получите сообщение со ссылкой для доступа к боту и токен бота. Сохраните токен бота, так как он понадобится для взаимодействия ESP32/ESP8266 с ботом.

Получение токена бота через BotFather в Telegram

Получение вашего Telegram User ID

Любой, кто знает username вашего бота, может с ним взаимодействовать. Чтобы игнорировать сообщения не из вашего аккаунта Telegram (или от авторизованных пользователей), вы можете получить ваш Telegram User ID. Тогда, когда ваш Telegram-бот получает сообщение, ESP может проверить, соответствует ли ID отправителя вашему User ID, и обработать или проигнорировать сообщение.

В вашем аккаунте Telegram найдите «IDBot» или откройте эту ссылку t.me/myidbot на своём смартфоне.

Получение Chat ID с помощью IDBot в Telegram

Начните диалог с этим ботом и введите /getid. Вы получите ответ с вашим user ID. Сохраните этот user ID, так как он понадобится позже в этом уроке.

Получение Chat ID через IDBot командой getid

Подготовка Arduino IDE

Мы будем программировать ESP32-CAM с помощью Arduino IDE, поэтому убедитесь, что у вас установлен аддон ESP32 в Arduino IDE.

Библиотека Universal Telegram Bot

Для взаимодействия с Telegram-ботом мы используем библиотеку Universal Telegram Bot, созданную Brian Lough, которая предоставляет удобный интерфейс для Telegram Bot API.

Выполните следующие шаги для установки последней версии библиотеки.

  1. Нажмите здесь, чтобы скачать библиотеку Universal Arduino Telegram Bot.

  2. Перейдите в Sketch > Include Library > Add .ZIP Library….

  3. Добавьте библиотеку, которую вы только что скачали.

Вот и всё. Библиотека установлена.

Важно

Не устанавливайте библиотеку через Arduino Library Manager, так как он может установить устаревшую версию.

Подробнее о библиотеке читайте на странице GitHub библиотеки Universal Arduino Telegram Bot.

Библиотека ArduinoJson

Также вам нужно установить библиотеку ArduinoJson. Выполните следующие шаги для установки.

  1. Перейдите в Sketch > Include Library > Manage Libraries.

  2. Найдите «ArduinoJson».

  3. Установите библиотеку.

Мы используем библиотеку ArduinoJson версии 6.15.2.

Установка библиотеки ArduinoJson

Библиотека BME280 SparkFun

В большинстве наших проектов с датчиком BME280 мы используем библиотеку Adafruit_BME280. Однако она конфликтует с некоторыми библиотеками ESP32-CAM. Поэтому, чтобы не модифицировать файлы библиотеки, мы использовали библиотеку BME280 SparkFun, которая хорошо работает с ESP32-CAM. Выполните следующие шаги для установки библиотеки BME280 SparkFun.

  1. Перейдите в Sketch > Include Library > Manage Libraries.

  2. Найдите «Sparkfun BME280».

  3. Установите библиотеку.

Установка библиотеки BME280 SparkFun в Arduino IDE

Управление ESP32-CAM через Telegram — скетч Arduino

Следующий скетч позволяет управлять ESP32-CAM через ваш аккаунт Telegram. Вы также будете получать уведомление с фото при обнаружении движения.

Скопируйте следующий код в Arduino IDE. Чтобы он заработал, вам нужно вставить свои сетевые учётные данные (SSID и пароль), токен Telegram-бота и Telegram User ID.

/*
  Rui Santos
  Complete project details at https://RandomNerdTutorials.com/esp32-cam-shield-pcb-telegram/

  Project created using Brian Lough's Universal Telegram Bot Library: https://github.com/witnessmenow/Universal-Arduino-Telegram-Bot

  The above copyright notice and this permission notice shall be included in all
  copies or substantial portions of the Software.
*/

#include <WiFi.h>
#include <WiFiClientSecure.h>
#include "soc/soc.h"
#include "soc/rtc_cntl_reg.h"
#include "esp_camera.h"
#include <UniversalTelegramBot.h>
#include <ArduinoJson.h>
#include <Wire.h>
#include "SparkFunBME280.h"

// Replace with your network credentials
const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";

// Use @myidbot to find out the chat ID of an individual or a group
// Also note that you need to click "start" on a bot before it can
// message you
String chatId = "XXXXXXXXXX";

// Initialize Telegram BOT
String BOTtoken = "XXXXXXXXXX:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";

bool sendPhoto = false;

WiFiClientSecure clientTCP;

UniversalTelegramBot bot(BOTtoken, clientTCP);

//CAMERA_MODEL_AI_THINKER
#define PWDN_GPIO_NUM     32
#define RESET_GPIO_NUM    -1
#define XCLK_GPIO_NUM      0
#define SIOD_GPIO_NUM     26
#define SIOC_GPIO_NUM     27

#define Y9_GPIO_NUM       35
#define Y8_GPIO_NUM       34
#define Y7_GPIO_NUM       39
#define Y6_GPIO_NUM       36
#define Y5_GPIO_NUM       21
#define Y4_GPIO_NUM       19
#define Y3_GPIO_NUM       18
#define Y2_GPIO_NUM        5
#define VSYNC_GPIO_NUM    25
#define HREF_GPIO_NUM     23
#define PCLK_GPIO_NUM     22

#define FLASH_LED_PIN 4
bool flashState = LOW;

// Motion Sensor
bool motionDetected = false;

// Define I2C Pins for BME280
#define I2C_SDA 14
#define I2C_SCL 15

BME280 bme;

int botRequestDelay = 1000;   // mean time between scan messages
long lastTimeBotRan;     // last time messages' scan has been done

void handleNewMessages(int numNewMessages);
String sendPhotoTelegram();

// Get BME280 sensor readings and return them as a String variable
String getReadings(){
  float temperature, humidity;
  temperature = bme.readTempC();
  //temperature = bme.readTempF();
  humidity = bme.readFloatHumidity();
  String message = "Temperature: " + String(temperature) + " ºC \n";
  message += "Humidity: " + String (humidity) + " % \n";
  return message;
}

// Indicates when motion is detected
static void IRAM_ATTR detectsMovement(void * arg){
  //Serial.println("MOTION DETECTED!!!");
  motionDetected = true;
}

void setup(){
  WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0);
  Serial.begin(115200);

  pinMode(FLASH_LED_PIN, OUTPUT);
  digitalWrite(FLASH_LED_PIN, flashState);

  // Init BME280 sensor
  Wire.begin(I2C_SDA, I2C_SCL);
  bme.settings.commInterface = I2C_MODE;
  bme.settings.I2CAddress = 0x76;
  bme.settings.runMode = 3;
  bme.settings.tStandby = 0;
  bme.settings.filter = 0;
  bme.settings.tempOverSample = 1;
  bme.settings.pressOverSample = 1;
  bme.settings.humidOverSample = 1;
  bme.begin();

  WiFi.mode(WIFI_STA);
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);
  WiFi.begin(ssid, password);
  clientTCP.setCACert(TELEGRAM_CERTIFICATE_ROOT); // Add root certificate for api.telegram.org
  while (WiFi.status() != WL_CONNECTED) {
    Serial.print(".");
    delay(500);
  }
  Serial.println();
  Serial.print("ESP32-CAM IP Address: ");
  Serial.println(WiFi.localIP());

  camera_config_t config;
  config.ledc_channel = LEDC_CHANNEL_0;
  config.ledc_timer = LEDC_TIMER_0;
  config.pin_d0 = Y2_GPIO_NUM;
  config.pin_d1 = Y3_GPIO_NUM;
  config.pin_d2 = Y4_GPIO_NUM;
  config.pin_d3 = Y5_GPIO_NUM;
  config.pin_d4 = Y6_GPIO_NUM;
  config.pin_d5 = Y7_GPIO_NUM;
  config.pin_d6 = Y8_GPIO_NUM;
  config.pin_d7 = Y9_GPIO_NUM;
  config.pin_xclk = XCLK_GPIO_NUM;
  config.pin_pclk = PCLK_GPIO_NUM;
  config.pin_vsync = VSYNC_GPIO_NUM;
  config.pin_href = HREF_GPIO_NUM;
  config.pin_sccb_sda = SIOD_GPIO_NUM;
  config.pin_sccb_scl = SIOC_GPIO_NUM;
  config.pin_pwdn = PWDN_GPIO_NUM;
  config.pin_reset = RESET_GPIO_NUM;
  config.xclk_freq_hz = 20000000;
  config.pixel_format = PIXFORMAT_JPEG;

  //init with high specs to pre-allocate larger buffers
  if(psramFound()){
    config.frame_size = FRAMESIZE_UXGA;
    config.jpeg_quality = 10;  //0-63 lower number means higher quality
    config.fb_count = 2;
  } else {
    config.frame_size = FRAMESIZE_SVGA;
    config.jpeg_quality = 12;  //0-63 lower number means higher quality
    config.fb_count = 1;
  }

  // camera init
  esp_err_t err = esp_camera_init(&config);
  if (err != ESP_OK) {
    Serial.printf("Camera init failed with error 0x%x", err);
    delay(1000);
    ESP.restart();
  }

  // Drop down frame size for higher initial frame rate
  sensor_t * s = esp_camera_sensor_get();
  s->set_framesize(s, FRAMESIZE_CIF);  // UXGA|SXGA|XGA|SVGA|VGA|CIF|QVGA|HQVGA|QQVGA

  // PIR Motion Sensor mode INPUT_PULLUP
  //err = gpio_install_isr_service(0);
  err = gpio_isr_handler_add(GPIO_NUM_13, &detectsMovement, (void *) 13);
  if (err != ESP_OK){
    Serial.printf("handler add failed with error 0x%x \r\n", err);
  }
  err = gpio_set_intr_type(GPIO_NUM_13, GPIO_INTR_POSEDGE);
  if (err != ESP_OK){
    Serial.printf("set intr type failed with error 0x%x \r\n", err);
  }
}

void loop(){
  if (sendPhoto){
    Serial.println("Preparing photo");
    sendPhotoTelegram();
    sendPhoto = false;
  }

  if(motionDetected){
    bot.sendMessage(chatId, "Motion detected!!", "");
    Serial.println("Motion Detected");
    sendPhotoTelegram();
    motionDetected = false;
  }

  if (millis() > lastTimeBotRan + botRequestDelay){
    int numNewMessages = bot.getUpdates(bot.last_message_received + 1);
    while (numNewMessages){
      Serial.println("got response");
      handleNewMessages(numNewMessages);
      numNewMessages = bot.getUpdates(bot.last_message_received + 1);
    }
    lastTimeBotRan = millis();
  }
}

String sendPhotoTelegram(){
  const char* myDomain = "api.telegram.org";
  String getAll = "";
  String getBody = "";

  camera_fb_t * fb = NULL;
  fb = esp_camera_fb_get();
  if(!fb) {
    Serial.println("Camera capture failed");
    delay(1000);
    ESP.restart();
    return "Camera capture failed";
  }

  Serial.println("Connect to " + String(myDomain));

  if (clientTCP.connect(myDomain, 443)) {
    Serial.println("Connection successful");

    String head = "--RandomNerdTutorials\r\nContent-Disposition: form-data; name=\"chat_id\"; \r\n\r\n" + chatId + "\r\n--RandomNerdTutorials\r\nContent-Disposition: form-data; name=\"photo\"; filename=\"esp32-cam.jpg\"\r\nContent-Type: image/jpeg\r\n\r\n";
    String tail = "\r\n--RandomNerdTutorials--\r\n";

    uint16_t imageLen = fb->len;
    uint16_t extraLen = head.length() + tail.length();
    uint16_t totalLen = imageLen + extraLen;

    clientTCP.println("POST /bot"+BOTtoken+"/sendPhoto HTTP/1.1");
    clientTCP.println("Host: " + String(myDomain));
    clientTCP.println("Content-Length: " + String(totalLen));
    clientTCP.println("Content-Type: multipart/form-data; boundary=RandomNerdTutorials");
    clientTCP.println();
    clientTCP.print(head);

    uint8_t *fbBuf = fb->buf;
    size_t fbLen = fb->len;
    for (size_t n=0;n<fbLen;n=n+1024) {
      if (n+1024<fbLen) {
        clientTCP.write(fbBuf, 1024);
        fbBuf += 1024;
      }
      else if (fbLen%1024>0) {
        size_t remainder = fbLen%1024;
        clientTCP.write(fbBuf, remainder);
      }
    }

    clientTCP.print(tail);

    esp_camera_fb_return(fb);

    int waitTime = 10000;   // timeout 10 seconds
    long startTimer = millis();
    boolean state = false;

    while ((startTimer + waitTime) > millis()){
      Serial.print(".");
      delay(100);
      while (clientTCP.available()) {
        char c = clientTCP.read();
        if (state==true) getBody += String(c);
        if (c == '\n') {
          if (getAll.length()==0) state=true;
          getAll = "";
        }
        else if (c != '\r')
          getAll += String(c);
        startTimer = millis();
      }
      if (getBody.length()>0) break;
    }
    clientTCP.stop();
    Serial.println(getBody);
  }
  else {
    getBody="Connected to api.telegram.org failed.";
    Serial.println("Connected to api.telegram.org failed.");
  }
  return getBody;
}

void handleNewMessages(int numNewMessages){
  Serial.print("Handle New Messages: ");
  Serial.println(numNewMessages);

  for (int i = 0; i < numNewMessages; i++){
    // Chat id of the requester
    String chat_id = String(bot.messages[i].chat_id);
    if (chat_id != chatId){
      bot.sendMessage(chat_id, "Unauthorized user", "");
      continue;
    }

    // Print the received message
    String text = bot.messages[i].text;
    Serial.println(text);

    String fromName = bot.messages[i].from_name;

    if (text == "/flash") {
      flashState = !flashState;
      digitalWrite(FLASH_LED_PIN, flashState);
    }
    if (text == "/photo") {
      sendPhoto = true;
      Serial.println("New photo  request");
    }
    if (text == "/readings"){
      String readings = getReadings();
      bot.sendMessage(chatId, readings, "");
    }
    if (text == "/start"){
      String welcome = "Welcome to the ESP32-CAM Telegram bot.\n";
      welcome += "/photo : takes a new photo\n";
      welcome += "/flash : toggle flash LED\n";
      welcome += "/readings : request sensor readings\n\n";
      welcome += "You'll receive a photo whenever motion is detected.\n";
      bot.sendMessage(chatId, welcome, "Markdown");
    }
  }
}

Посмотреть исходный код

Как работает код

Продолжайте чтение, чтобы узнать, как работает код, или перейдите к следующему разделу.

Импорт библиотек

Начните с импорта необходимых библиотек.

#include <WiFi.h>
#include <WiFiClientSecure.h>
#include "soc/soc.h"
#include "soc/rtc_cntl_reg.h"
#include "esp_camera.h"
#include <UniversalTelegramBot.h>
#include <ArduinoJson.h>
#include <Wire.h>
#include "SparkFunBME280.h"

Сетевые учётные данные

Вставьте свои сетевые учётные данные в следующие переменные.

const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";

Telegram User ID

Вставьте ваш Telegram chat ID в переменную chatId. Тот, который вы получили от IDBot.

String chatId = "XXXXXXXXXX";

Токен Telegram-бота

Вставьте токен вашего Telegram-бота, полученный от BotFather, в переменную BOTtoken.

String BOTtoken = "XXXXXXXXXX:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";

Булева переменная sendPhoto указывает, пора ли отправить новое фото в ваш аккаунт Telegram. По умолчанию она установлена в false.

bool sendPhoto = false;

Создайте нового клиента WiFi с помощью WiFiClientSecure.

WiFiClientSecure clientTCP;

Создайте бота с ранее определёнными токеном и клиентом.

UniversalTelegramBot bot(BOTtoken, clientTCP);

Пины камеры

Определите пины, используемые ESP32-CAM:

//CAMERA_MODEL_AI_THINKER
#define PWDN_GPIO_NUM     32
#define RESET_GPIO_NUM    -1
#define XCLK_GPIO_NUM      0
#define SIOD_GPIO_NUM     26
#define SIOC_GPIO_NUM     27

#define Y9_GPIO_NUM       35
#define Y8_GPIO_NUM       34
#define Y7_GPIO_NUM       39
#define Y6_GPIO_NUM       36
#define Y5_GPIO_NUM       21
#define Y4_GPIO_NUM       19
#define Y3_GPIO_NUM       18
#define Y2_GPIO_NUM        5
#define VSYNC_GPIO_NUM    25
#define HREF_GPIO_NUM     23
#define PCLK_GPIO_NUM     22

Это определение пинов для платы AI-Thinker. Если вы используете другую модель камеры, проверьте распиновку вашей платы: ESP32-CAM Camera Boards: Pin and GPIOs Assignment Guide.

Светодиод вспышки

Создайте переменную для хранения пина светодиода вспышки (FLASH_LED_PIN). В ESP32-CAM AI-Thinker вспышка подключена к GPIO 4. По умолчанию установлено значение LOW.

#define FLASH_LED_PIN 4
bool flashState = LOW;

Датчик движения

Переменная motionDetected указывает, было ли обнаружено движение. По умолчанию установлена в false.

bool motionDetected = false;

BME280

Определите пины SDA и SCL для BME280.

#define I2C_SDA 14
#define I2C_SCL 15

Создайте экземпляр BME280 с именем bme.

BME280 bme;

Задержка запросов

Переменные botRequestDelay и lastTimeBotRan используются для проверки новых сообщений Telegram каждые x секунд. В данном случае код будет проверять новые сообщения каждую секунду (1000 миллисекунд). Вы можете изменить это время задержки в переменной botRequestDelay.

int botRequestDelay = 1000;   // mean time between scan messages
long lastTimeBotRan;     // last time messages' scan has been done

handleNewMessages()

Функция handleNewMessages() обрабатывает то, что происходит при получении новых сообщений.

void handleNewMessages(int numNewMessages){
  Serial.print("Handle New Messages: ");
  Serial.println(numNewMessages);

Получаем chat ID для конкретного сообщения и сохраняем его в переменной chat_id. Chat ID идентифицирует, кто отправил сообщение.

String chat_id = String(bot.messages[i].chat_id);

Если chat_id отличается от вашего chat ID (chatId), это означает, что кто-то (не вы) отправил сообщение вашему боту. В этом случае игнорируем сообщение и ждём следующего.

if (chat_id != chatId){
  bot.sendMessage(chat_id, "Unauthorized user", "");
  continue;
}

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

String text = bot.messages[i].text;
Serial.println(text);

Переменная fromName сохраняет имя отправителя.

String fromName = bot.messages[i].from_name;

При получении сообщения /flash инвертируем переменную flashState и обновляем состояние светодиода вспышки. Если ранее было LOW, устанавливаем HIGH. Если было HIGH, устанавливаем LOW.

if (text == "/flash") {
  flashState = !flashState;
  digitalWrite(FLASH_LED_PIN, flashState);
}

При получении сообщения /photo устанавливаем переменную sendPhoto в true. Затем в loop() мы проверим значение переменной sendPhoto и выполним соответствующие действия.

if (text == "/photo") {
  sendPhoto = true;
  Serial.println("New photo request");
}

При получении сообщения /readings вызываем функцию getReadings() (мы рассмотрим её позже) и отправляем показания боту.

if (text == "/readings"){
  String readings = getReadings();
  bot.sendMessage(chatId, readings, "");
}

Отправка сообщения боту очень проста. Нужно использовать метод sendMessage() объекта bot и передать в качестве аргументов chat ID получателя, сообщение и режим разбора.

bool sendMessage(String chat_id, String text, String parse_mode = "")

Наконец, при получении сообщения /start мы отправляем допустимые команды управления ESP. Это полезно, если вы забыли команды для управления платой.

if (text == "/start"){
  String welcome = "Welcome to the ESP32-CAM Telegram bot.\n";
  welcome += "/photo : takes a new photo\n";
  welcome += "/flash : toggle flash LED\n";
  welcome += "/readings : request sensor readings\n\n";
  welcome += "You'll receive a photo whenever motion is detected.\n";
  bot.sendMessage(chatId, welcome, "Markdown");
}

sendPhotoTelegram()

Функция sendPhotoTelegram() делает фото с помощью ESP32-CAM.

camera_fb_t * fb = NULL;
fb = esp_camera_fb_get();
if(!fb) {
  Serial.println("Camera capture failed");
  delay(1000);
  ESP.restart();
  return "Camera capture failed";
}

Затем она выполняет HTTP POST-запрос для отправки фото вашему Telegram-боту.

clientTCP.println("POST /bot"+BOTtoken+"/sendPhoto HTTP/1.1");
clientTCP.println("Host: " + String(myDomain));
clientTCP.println("Content-Length: " + String(totalLen));
clientTCP.println("Content-Type: multipart/form-data; boundary=RandomNerdTutorials");
clientTCP.println();
clientTCP.print(head);

getReadings()

Функция getReadings() запрашивает температуру и влажность от датчика BME280.

String getReadings(){
  float temperature, humidity;
  temperature = bme.readTempC();
  //temperature = bme.readTempF();
  humidity = bme.readFloatHumidity();

Показания объединяются в переменной message, которая возвращается функцией.

String message = "Temperature: " + String(temperature) + " ºC \n";
message += "Humidity: " + String (humidity) + " % \n";
return message;

detectsMovement()

Функция detectsMovement() — это callback-функция, которая вызывается при обнаружении движения. В данном случае мы устанавливаем переменную motionDetected в true. Затем в loop() мы обрабатываем, что происходит при обнаружении движения (отправка фото).

static void IRAM_ATTR detectsMovement(void * arg){
  //Serial.println("MOTION DETECTED!!!");
  motionDetected = true;
}

setup()

В setup() инициализируем Serial Monitor.

Serial.begin(115200);

Устанавливаем светодиод вспышки как выход и задаём начальное состояние.

pinMode(FLASH_LED_PIN, OUTPUT);
digitalWrite(FLASH_LED_PIN, flashState);

Инициализируем датчик BME280:

// Init BME280 sensor
Wire.begin(I2C_SDA, I2C_SCL);
bme.settings.commInterface = I2C_MODE;
bme.settings.I2CAddress = 0x76;
bme.settings.runMode = 3;
bme.settings.tStandby = 0;
bme.settings.filter = 0;
bme.settings.tempOverSample = 1;
bme.settings.pressOverSample = 1;
bme.settings.humidOverSample = 1;
bme.begin();

Подключаем ESP32-CAM к локальной сети.

WiFi.mode(WIFI_STA);
Serial.println();
Serial.print("Connecting to ");
Serial.println(ssid);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
  Serial.print(".");
  delay(500);
}
Serial.println();
Serial.print("ESP32-CAM IP Address: ");
Serial.println(WiFi.localIP());

Настраиваем и инициализируем камеру.

camera_config_t config;
config.ledc_channel = LEDC_CHANNEL_0;
config.ledc_timer = LEDC_TIMER_0;
config.pin_d0 = Y2_GPIO_NUM;
config.pin_d1 = Y3_GPIO_NUM;
config.pin_d2 = Y4_GPIO_NUM;
config.pin_d3 = Y5_GPIO_NUM;
config.pin_d4 = Y6_GPIO_NUM;
config.pin_d5 = Y7_GPIO_NUM;
config.pin_d6 = Y8_GPIO_NUM;
config.pin_d7 = Y9_GPIO_NUM;
config.pin_xclk = XCLK_GPIO_NUM;
config.pin_pclk = PCLK_GPIO_NUM;
config.pin_vsync = VSYNC_GPIO_NUM;
config.pin_href = HREF_GPIO_NUM;
config.pin_sccb_sda = SIOD_GPIO_NUM;
config.pin_sccb_scl = SIOC_GPIO_NUM;
config.pin_pwdn = PWDN_GPIO_NUM;
config.pin_reset = RESET_GPIO_NUM;
config.xclk_freq_hz = 20000000;
config.pixel_format = PIXFORMAT_JPEG;

//init with high specs to pre-allocate larger buffers
if(psramFound()){
  config.frame_size = FRAMESIZE_UXGA;
  config.jpeg_quality = 10;  //0-63 lower number means higher quality
  config.fb_count = 2;
} else {
  config.frame_size = FRAMESIZE_SVGA;
  config.jpeg_quality = 12;  //0-63 lower number means higher quality
  config.fb_count = 1;
}

// camera init
esp_err_t err = esp_camera_init(&config);
if (err != ESP_OK) {
  Serial.printf("Camera init failed with error 0x%x", err);
  delay(1000);
  ESP.restart();
}

// Drop down frame size for higher initial frame rate
sensor_t * s = esp_camera_sensor_get();
s->set_framesize(s, FRAMESIZE_CIF);  // UXGA|SXGA|XGA|SVGA|VGA|CIF|QVGA|HQVGA|QQVGA

Настраиваем прерывание на GPIO 13:

err = gpio_isr_handler_add(GPIO_NUM_13, &detectsMovement, (void *) 13);
if (err != ESP_OK){
  Serial.printf("handler add failed with error 0x%x \r\n", err);
}
err = gpio_set_intr_type(GPIO_NUM_13, GPIO_INTR_POSEDGE);
if (err != ESP_OK){
  Serial.printf("set intr type failed with error 0x%x \r\n", err);
}

loop()

В loop() проверяем состояние переменной sendPhoto. Если она true, вызываем функцию sendPhotoTelegram() для съёмки и отправки фото в ваш аккаунт Telegram.

if (sendPhoto){
  Serial.println("Preparing photo");
  sendPhotoTelegram();
  sendPhoto = false;
}

Когда готово, устанавливаем переменную sendPhoto в false.

sendPhoto = false;

При обнаружении движения отправляем уведомление в ваш аккаунт Telegram и вызываем функцию sendPhotoTelegram(). Затем устанавливаем переменную motionDetected в false.

if(motionDetected){
  bot.sendMessage(chatId, "Motion detected!!", "");
  Serial.println("Motion Detected");
  sendPhotoTelegram();
  motionDetected = false;
}

Проверяем новые сообщения Telegram каждую секунду.

if (millis() > lastTimeBotRan + botRequestDelay){
  int numNewMessages = bot.getUpdates(bot.last_message_received + 1);
  while (numNewMessages){
    Serial.println("got response");
    handleNewMessages(numNewMessages);
    numNewMessages = bot.getUpdates(bot.last_message_received + 1);
  }
  lastTimeBotRan = millis();
}

При получении нового сообщения вызываем функцию handleNewMessages().

while (numNewMessages){
  Serial.println("got response");
  handleNewMessages(numNewMessages);
  numNewMessages = bot.getUpdates(bot.last_message_received + 1);
}

Вот так в целом работает код.

Загрузка кода в ESP32-CAM

После внесения необходимых изменений загрузите код в ESP32-CAM (до подключения платы расширения). Выполните следующие шаги для загрузки кода или следуйте этому руководству.

1) Подключите ESP32-CAM к FTDI-программатору, как показано на следующей схеме.

ESP32-CAM подключён к FTDI-программатору для загрузки программы через Arduino IDE

Примечание

Порядок пинов FTDI на схеме может не совпадать с вашим. Убедитесь, что вы проверяете маркировку рядом с каждым пином.

Важно

GPIO 0 должен быть подключён к GND, чтобы вы могли загружать код.

2) Перейдите в Tools > Board и выберите AI-Thinker ESP32-CAM. У вас должен быть установлен аддон ESP32. В противном случае эта плата не появится в меню Boards.

3) Перейдите в Tools > Port и выберите COM-порт, к которому подключена ESP32-CAM.

4) Затем нажмите кнопку Upload в Arduino IDE.

5) Когда в окне отладки начнут появляться точки, нажмите кнопку RST на плате ESP32-CAM.

Загрузка кода в ESP32-CAM — подключение к Serial Port ESP32-CAM — нажмите кнопку RESET RST на плате для перезагрузки

Через несколько секунд код должен быть успешно загружен на вашу плату.

6) Когда вы увидите сообщение «Done uploading», отключите GPIO 0 от GND.

Откройте Serial Monitor, нажмите кнопку RST на плате и убедитесь, что ESP32-CAM подключается к вашей сети без проблем.

Демонстрация

После загрузки кода в ESP32-CAM установите плату расширения со всеми компонентами.

Установка PCB-платы расширения на модуль ESP32-CAM AI-Thinker

Подайте питание через пины 5V и GND на плате расширения.

Затем нажмите кнопку RST на ESP32-CAM, чтобы запустить выполнение кода.

Теперь откройте ваш аккаунт Telegram и протестируйте плату. Отправьте следующие сообщения вашему Telegram-боту ESP32 для управления ESP32-CAM:

  • /start: отправляет приветственное сообщение с допустимыми командами управления платой;

  • /flash: включает/выключает вспышку ESP32-CAM;

  • /photo: делает новое фото и отправляет его в ваш аккаунт Telegram;

  • /readings: запрашивает последние показания датчика BME280.

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

Управление ESP32-CAM через Telegram: демонстрация с фото, управлением выходами, показаниями датчиков и уведомлениями о движении

Если кто-то попытается взаимодействовать с вашим ботом с другого аккаунта, он получит сообщение «Unauthorized user».

Управление ESP32 ESP8266 через Telegram — неавторизованный пользователь

Заключение

В этом уроке мы создали PCB-плату расширения для ESP32-CAM с PIR-датчиком движения и BME280. Это создаёт более постоянную схему в компактном форм-факторе, которую можно поместить в небольшой корпус или муляж камеры наблюдения.

Демонстрация PCB-платы расширения ESP32-CAM в муляже камеры наблюдения

Вы также узнали, как использовать ваш аккаунт Telegram для управления ESP32-CAM с помощью Telegram-бота. Это позволяет вам контролировать и отслеживать работу платы из любой точки мира, при наличии интернета на смартфоне.

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

У нас есть другие похожие проекты с проектированием и созданием PCB, которые могут вам понравиться: