ESP32-CAM с Telegram: съёмка фото, управление выходами, показания датчиков и уведомления о движении
В этом проекте мы создадим плату расширения (PCB shield) для модуля ESP32-CAM AI-Thinker с PIR-датчиком движения, датчиком температуры, влажности и давления BME280, а также несколькими дополнительными выведенными пинами. Мы создадим Telegram-бота для ESP32-CAM, который позволит вам управлять платой из любой точки мира: запрашивать фото, показания датчиков или управлять вспышкой. Кроме того, при обнаружении движения вы будете получать уведомление с новым фото.
Также вы можете повторить этот проект, собрав схему на макетной плате.
Смотрите видеоурок
Этот проект доступен в видеоформате и в текстовом формате. Вы можете посмотреть видео ниже или прокрутить страницу для текстовых инструкций.
Ресурсы
Все необходимые ресурсы для этого проекта вы можете найти по ссылкам ниже (или посетить проект на GitHub):
`Скачать все файлы <https://github.com/RuiSantosdotme/ESP32-CAM-Shield-Telegram/archive/master.zip>`_
Обзор проекта
Этот проект состоит из трёх частей:
Проектирование и сборка платы расширения (PCB shield)
Создание Telegram-бота
Программирование платы расширения с помощью Arduino IDE
Особенности PCB-платы расширения для ESP32-CAM
Плата расширения спроектирована для установки поверх ESP32-CAM. По этой причине, если вы хотите использовать нашу плату, вам нужен точно такой же модуль ESP32-CAM. Мы используем модуль ESP32-CAM AI-Thinker.
Мы также используем модуль камеры с более длинным шлейфом. Благодаря этому при установке платы расширения камера оказывается на одной стороне с PIR-датчиком движения.
Также вы можете собрать схему на макетной плате.
Плата расширения состоит из:
Датчика 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.
Тестирование схемы на макетной плате
Перед проектированием и сборкой платы расширения важно протестировать схему на макетной плате. Если вы не хотите делать PCB, вы всё равно можете выполнить этот проект, собрав схему на макетной плате.
Необходимые компоненты
Для сборки схемы на макетной плате вам понадобятся следующие компоненты:
BME280 (4 пина)
Собрав все компоненты, соберите схему по следующей принципиальной схеме:
Проектирование PCB
Для проектирования схемы и PCB мы использовали EasyEDA — браузерное приложение для проектирования печатных плат. Если вы хотите настроить PCB под себя, просто загрузите следующие файлы:
Проектирование схемы работает так же, как и в любом другом ПО для создания схем: вы размещаете компоненты и соединяете их проводами. Затем каждому компоненту назначается посадочное место (footprint).
Назначив компоненты, разместите каждый из них. Когда расположение вас устроит, выполните все соединения и разведите вашу PCB.
Сохраните проект и экспортируйте Gerber-файлы.
Примечание
Вы можете скачать файлы проекта и отредактировать их, чтобы настроить плату расширения под свои нужды.
`Скачать Gerber .zip файл <https://github.com/RuiSantosdotme/ESP32-CAM-Shield-Telegram/raw/master/Gerber_PCB_2020-07-07_10-25-47_2020-07-07_11-49-04.zip>`_
Заказ PCB
Когда у вас есть Gerber-файлы, вы можете заказать PCB. Выполните следующие шаги.
Скачайте Gerber-файлы — нажмите здесь, чтобы скачать .zip файл
Распаковка
Примерно через неделю (при доставке DHL) платы были получены.
Всё хорошо упаковано, и платы очень высокого качества. Надписи на шелкографии отлично напечатаны и легко читаются. Кроме того, припой легко ложится на контактные площадки.
Пайка компонентов
Следующий шаг — пайка компонентов на PCB. Вам нужно припаять только гнёзда (female header pins). PIR-датчик движения и BME280 затем подключаются к этим разъёмам.
Список всех компонентов, необходимых для сборки платы расширения:
Вот инструменты для пайки, которые мы использовали:
Припой 60/40, диаметр 0.5 мм
Процесс пайки довольно простой, так как нужно припаять только штыревые разъёмы. В середине платы расширения есть несколько выведенных GPIO. Припаяйте к ним разъёмы, если хотите подключать дополнительную периферию.
Вот как выглядит плата расширения ESP32-CAM PCB Shield после сборки.
Создание Telegram-бота
ESP32-CAM будет взаимодействовать с Telegram-ботом для получения и обработки сообщений, а также для отправки ответов в ваш аккаунт Telegram (показания датчиков и фото). Выполните следующие шаги, чтобы создать Telegram-бота.
Перейдите в Google Play или App Store, скачайте и установите Telegram.
Откройте Telegram и выполните следующие шаги для создания Telegram-бота. Сначала найдите «botfather» и нажмите на BotFather, как показано ниже. Или откройте эту ссылку t.me/botfather на своём смартфоне.
Должно открыться следующее окно, и вам будет предложено нажать кнопку start.
Введите /newbot и следуйте инструкциям для создания бота. Задайте ему имя и username.
Если ваш бот успешно создан, вы получите сообщение со ссылкой для доступа к боту и токен бота. Сохраните токен бота, так как он понадобится для взаимодействия ESP32/ESP8266 с ботом.
Получение вашего Telegram User ID
Любой, кто знает username вашего бота, может с ним взаимодействовать. Чтобы игнорировать сообщения не из вашего аккаунта Telegram (или от авторизованных пользователей), вы можете получить ваш Telegram User ID. Тогда, когда ваш Telegram-бот получает сообщение, ESP может проверить, соответствует ли ID отправителя вашему User ID, и обработать или проигнорировать сообщение.
В вашем аккаунте Telegram найдите «IDBot» или откройте эту ссылку t.me/myidbot на своём смартфоне.
Начните диалог с этим ботом и введите /getid. Вы получите ответ с вашим user ID. Сохраните этот user ID, так как он понадобится позже в этом уроке.
Подготовка Arduino IDE
Мы будем программировать ESP32-CAM с помощью Arduino IDE, поэтому убедитесь, что у вас установлен аддон ESP32 в Arduino IDE.
Библиотека Universal Telegram Bot
Для взаимодействия с Telegram-ботом мы используем библиотеку Universal Telegram Bot, созданную Brian Lough, которая предоставляет удобный интерфейс для Telegram Bot API.
Выполните следующие шаги для установки последней версии библиотеки.
Нажмите здесь, чтобы скачать библиотеку Universal Arduino Telegram Bot.
Перейдите в Sketch > Include Library > Add .ZIP Library….
Добавьте библиотеку, которую вы только что скачали.
Вот и всё. Библиотека установлена.
Важно
Не устанавливайте библиотеку через Arduino Library Manager, так как он может установить устаревшую версию.
Подробнее о библиотеке читайте на странице GitHub библиотеки Universal Arduino Telegram Bot.
Библиотека ArduinoJson
Также вам нужно установить библиотеку ArduinoJson. Выполните следующие шаги для установки.
Перейдите в Sketch > Include Library > Manage Libraries.
Найдите «ArduinoJson».
Установите библиотеку.
Мы используем библиотеку ArduinoJson версии 6.15.2.
Библиотека BME280 SparkFun
В большинстве наших проектов с датчиком BME280 мы используем библиотеку Adafruit_BME280. Однако она конфликтует с некоторыми библиотеками ESP32-CAM. Поэтому, чтобы не модифицировать файлы библиотеки, мы использовали библиотеку BME280 SparkFun, которая хорошо работает с ESP32-CAM. Выполните следующие шаги для установки библиотеки BME280 SparkFun.
Перейдите в Sketch > Include Library > Manage Libraries.
Найдите «Sparkfun BME280».
Установите библиотеку.
Управление 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-программатору, как показано на следующей схеме.
Примечание
Порядок пинов 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.
Через несколько секунд код должен быть успешно загружен на вашу плату.
6) Когда вы увидите сообщение «Done uploading», отключите GPIO 0 от GND.
Откройте Serial Monitor, нажмите кнопку RST на плате и убедитесь, что ESP32-CAM подключается к вашей сети без проблем.
Демонстрация
После загрузки кода в ESP32-CAM установите плату расширения со всеми компонентами.
Подайте питание через пины 5V и GND на плате расширения.
Затем нажмите кнопку RST на ESP32-CAM, чтобы запустить выполнение кода.
Теперь откройте ваш аккаунт Telegram и протестируйте плату. Отправьте следующие сообщения вашему Telegram-боту ESP32 для управления ESP32-CAM:
/start: отправляет приветственное сообщение с допустимыми командами управления платой;
/flash: включает/выключает вспышку ESP32-CAM;
/photo: делает новое фото и отправляет его в ваш аккаунт Telegram;
/readings: запрашивает последние показания датчика BME280.
Кроме того, вы будете получать уведомление с фото при каждом обнаружении движения.
Если кто-то попытается взаимодействовать с вашим ботом с другого аккаунта, он получит сообщение «Unauthorized user».
Заключение
В этом уроке мы создали PCB-плату расширения для ESP32-CAM с PIR-датчиком движения и BME280. Это создаёт более постоянную схему в компактном форм-факторе, которую можно поместить в небольшой корпус или муляж камеры наблюдения.
Вы также узнали, как использовать ваш аккаунт Telegram для управления ESP32-CAM с помощью Telegram-бота. Это позволяет вам контролировать и отслеживать работу платы из любой точки мира, при наличии интернета на смартфоне.
Вы также можете создать собственный код для выполнения любых других задач с платой расширения.
У нас есть другие похожие проекты с проектированием и созданием PCB, которые могут вам понравиться: