ESP32-CAM with Telegram: Take Photos, Control Outputs, Request Sensor Readings and Motion Notifications

In this project we’ll create a PCB shield for the ESP32-CAM AI-Thinker board with a PIR motion sensor, a BME280 temperature, humidity and pressure sensor and some additional exposed pins. We’ll create a Telegram bot for the ESP32-CAM that allows you to control your board from anywhere to request a photo, sensor readings or control the flash. Additionally, you’ll receive a notification with a new photo whenever motion is detected.

Control ESP32-CAM with Telegram Take Photos, Control Outputs, Request Sensor Readings and Motion Notifications

Alternatively, you can also follow this project by wiring the circuit on a breadboard.

Watch the Video Tutorial

This project is available in video format and in written format. You can watch the video below or you can scroll down for the written instructions.

Resources

You can find all the resources needed to build this project in the links below (or you can visit the GitHub project):

Project Overview

This project consists of three parts:

  1. Designing and Building the PCB shield
  2. Creating the Telegram Bot
  3. Programming the PCB shield using Arduino IDE

ESP32-CAM PCB Shield Features

The PCB shield is designed to be stacked to the ESP32-CAM. For this reason, if you want to use our PCB, you need the same ESP32-CAM board. We’re using the ESP32-CAM AI-Thinker Module.

ESP32-CAM AI Thinker Module Shield PCB Parts Components Mounted

We’re also using a camera module with a longer ribbon. So that when you mount the shield, the camera is on the same side of the PIR motion sensor.

Alternatively, you can also assemble the circuit on a breadboard.

ESP32-CAM Project Telegram Test Circuit Diagram Breadboard Wiring

The shield consists of:

  • BME280 temperature, humidity and pressure sensor (4 pins);
  • Mini PIR motion sensor (AM312);
  • Exposed 5V and GND pins to power up the shield and ESP32-CAM;
  • Other exposed GPIOs if you want to add additional features.

ESP32-CAM PCB Shield Pin Assignment

This is the pin assignment for the BME280 and PIR motion sensor on the PCB shield:

  • PIR Motion Sensor: GPIO 13
  • BME280: GPIO 14 (SDA), GPIO 15 (SCL)

ESP32-CAM Telegram Bot

To control the ESP32-CAM shield, we’ll create a Telegram bot, so that you can monitor your ESP32-CAM from anywhere (as long as you have internet access in your smartphone). You can use the following commands to interact with your bot:

  • /start: sends a welcome message with the valid commands to control the shield;
  • /flash: toggles the ESP32-CAM LED Flash;
  • /photo: takes a new photo and sends it to your Telegram account;
  • /readings: requests the latest BME280 sensor readings.
Control ESP32-CAM with Telegram Take Photos Control Outputs Request Sensor Readings and Motion Notifications Demonstration

Additionally, you’ll receive a notification with a photo whenever motion is detected. Finally, only you (or any other authorized user that you want) can control the ESP32-CAM using Telegram.

Testing the Circuit on a Breadboard

Before designing and building the PCB shield, it’s important to test the circuit on a breadboard. If you don’t want to make a PCB, you can still follow this project by assembling the circuit on a breadboard.

ESP32-CAM Project Telegram Test Circuit Diagram Breadboard Wiring

Parts Required

To assemble the circuit on a breadboard you need the following parts:

You can use the preceding links or go directly to MakerAdvisor.com/tools to find all the parts for your projects at the best price!

After gathering all the parts, assemble the circuit by following the next schematic diagram:

ESP32-CAM with BME280 and PIR Motion Sensor Wiring Schematic Diagram

Designing the PCB

To design the circuit and PCB, we used EasyEDA which is a browser based software to design PCBs. If you want to customize your PCB, you just need to upload the following files:

Designing the circuit works like in any other circuit software tool, you place some components and you wire them together. Then, you assign each component to a footprint.

ESP32-CAM PCB Shield Circuit Diagram Telegram Take Photo PIR BME280

Having the parts assigned, place each component. When you’re happy with the layout, make all the connections and route your PCB.

ESP32-CAM PCB Shield Telegram Tale Photo PIR BME280

Save your project and export the Gerber files.

Note: you can grab the project files and edit them to customize the shield for your own needs.

Ordering the PCBs at PCBWay

This project is sponsored by PCBWay. PCBWay is a full feature Printed Circuit Board manufacturing service.

Ordering the PCBs at PCBWay

Turn your DIY breadboard circuits into professional PCBs – get 10 boards for approximately $5 + shipping (which will vary depending on your country).

Once you have your Gerber files, you can order the PCB. Follow the next steps to download the file.

1. Download the Gerber files – click here to download the .zip file

Upload Gerber Files PCB folder

2. Go to PCBWay website and open the PCB Instant Quote page. 

PCBWay Order PCB open instant quote page

3. PCBWay can grab all the PCB details and automatically fill them for you. Use the “Quick-order PCB (Autofill parameters)”.

PCBWay Order PCB autofill parameters

4. Press the “+ Add Gerber file” button to upload the provided Gerber files.

PCBWay Order PCB add gerber file button

And that’s it. You can also use the OnlineGerberViewer to check if your PCB is looking as it should.

PCBWay Upload Gerbers Files and PCB Online preview

If you aren’t in a hurry, you can use the China Post shipping method to lower your cost significantly. In our opinion, we think they overestimate the China Post shipping time.

PCBWay Order PCB China post shipping method

You can increase your PCB order quantity and change the solder mask color. I’ve ordered the Blue color.

PCBWay Order PCB final step and save to cart

Once you’re ready, you can order the PCBs by clicking “Save to Cart” and complete your order.

Unboxing

After approximately one week using the DHL shipping method, I received the PCBs at my office.

PCBWay Unboxing

Everything comes well packed, and the PCBs are really high-quality. The letters on the silkscreen are really well-printed and easy to read. Additionally, the solder sticks easily to the pads.

ESP32-CAM Shield PCB for ESP32-CAM AI Thinker Module Unboxing

Besides the PCBs, I also received some gifts (celebration of their 6th anniversary): a badge, some stickers, a t-shirt, a pen and some rulers.

ESP32-CAM Shield PCBWay Unboxing PCBs and Gift bag

Soldering the Components

The next step is soldering the components to the PCB. You just need to solder female header pins. The PIR motion sensor and the BME280 will then connect to those pins.

Here’s a list of all the components needed to build the PCB shield:

ESP32-CAM AI Thinker Module Shield PCB Parts Components Required

Here’s the soldering tools I’ve used:

TS80 Soldering Iron Review Best Portable Soldering Iron

Read our review about the TS80 Soldering Iron: TS80 Soldering Iron Review – Best Portable Soldering Iron.

The soldering process is pretty simple as you just need to solder the headers pins. There are some exposed GPIOs in the middle of the shield. Solder pins to those GPIOs if you want to use them to connect any other peripherals.

ESP32-CAM AI Thinker Module Shield PCB Soldering Parts Components

Here’s how the ESP32-CAM PCB Shield looks like after assembling.

ESP32-CAM AI Thinker Module Shield PCB Final Assembled Demonstration

Creating a Telegram Bot

The ESP32-CAM will interact with a Telegram bot to receive and handle the messages, and send responses to your Telegram account (sensor readings and photos). Follow the next steps to create a Telegram bot.

Go to Google Play or App Store, download and install Telegram.

Install and Download Telegram

Open Telegram and follow the next steps to create a Telegram Bot. First, search for “botfather” and click the BotFather as shown below. Or open this link t.me/botfather in your smartphone.

botfather

The following window should open and you’ll be prompted to click the start button.

Telegram Start BotFather to Create a new Bot

Type /newbot and follow the instructions to create your bot. Give it a name and username.

Telegram BotFather Create a New Bot

If your bot is successfully created, you’ll receive a message with a link to access the bot and the bot token. Save the bot token because you’ll need it so that the ESP32/ESP8266 can interact with the bot.

Telegram BotFather Get Bot Token

Get Your Telegram User ID

Anyone that knows your bot username can interact with it. To make sure that we ignore messages that are not from our Telegram account (or any authorized users), you can get your Telegram User ID. Then, when your telegram bot receives a message, the ESP can check whether the sender ID corresponds to your User ID and handle the message or ignore it.

In your Telegram account, search for “IDBot” or open this link t.me/myidbot in your smartphone.

Telegram Get Chat ID with IDBot

Start a conversation with that bot and type /getid. You will get a reply back with your user ID. Save that user ID, because you’ll need it later in this tutorial.

Telegram Get Chat ID with IDBot getid

Preparing Arduino IDE

We’ll program the ESP32-CAM using Arduino IDE, so make sure you have the ESP32 add-on installed in your Arduino IDE.

Universal Telegram Bot Library

To interact with the Telegram bot, we’ll use the Universal Telegram Bot Library created by Brian Lough that provides an easy interface for the Telegram Bot API.

Follow the next steps to install the latest release of the library.

  1. Click here to download the Universal Arduino Telegram Bot library.
  2. Go to Sketch Include Library > Add .ZIP Library...
  3. Add the library you’ve just downloaded.

And that’s it. The library is installed.

Important: don’t install the library through the Arduino Library Manager because it might install a deprecated version.

For all the details about the library, take a look at the Universal Arduino Telegram Bot Library GitHub page.

ArduinoJson Library

You also have to install the ArduinoJson library. Follow the next steps to install the library.

  1. Go to Sketch Include Library > Manage Libraries.
  2. Search for “ArduinoJson”.
  3. Install the library.

We’re using ArduinoJson library version 6.15.2.

Install Arduino JSONLibrary

BME280 SparkFun Library

In most of our projects with the BME280 sensor, we use the Adafruit_BME280 library. However, it conflicts with some of the ESP32-CAM libraries. So, to avoid modifying the library files, we used the BME280 Sparkfun library instead that works well with the ESP32-CAM. Follow the next steps to install the BME280 Sparkfun library.

  1. Go to Sketch Include Library > Manage Libraries.
  2. Search for “Sparkfun BME280”.
  3. Install the library.
Install BME280 Sparkfun Library in Arduino IDE

Control ESP32-CAM with Telegram – Arduino Sketch

The following sketch allows you to control the ESP32-CAM using your Telegram account. You’ll also receive a notification with a photo when motion is detected.

Copy the following code to your Arduino IDE. To make it work for you, you need to insert your network credentials (SSID and password), your Telegram Bot Token and your 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_sscb_sda = SIOD_GPIO_NUM;
  config.pin_sscb_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");
    }
  }
}

View raw code

How the Code Works

Continue reading to learn how the code works, or skip to the next section.

Importing Libraries

Start by importing the required libraries.

#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"

Network Credentials

Insert your network credentials in the following variables.

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

Telegram User ID

Insert your Telegram chat ID on the chatId variable. The one you’ve got from the IDBot.

String chatId = "XXXXXXXXXX";

Telegram Bot Token

Insert your Telegram Bot token you’ve got from Botfather on the BOTtoken variable.

String BOTtoken = "XXXXXXXXXX:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";

The sendPhoto Boolean variable indicates whether it is time to send a new photo to your telegram account. By default, it is set to false.

bool sendPhoto = false;

Create a new WiFi client with WiFiClientSecure.

WiFiClientSecure clientTCP;

Create a bot with the token and client defined earlier.

UniversalTelegramBot bot(BOTtoken, clientTCP);

Camera Pins

Define the pins used by the 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

This is the pin definition for the AI-Thinker board, if you’re using another camera model, check the pinout for your board: ESP32-CAM Camera Boards: Pin and GPIOs Assignment Guide.

Flash LED

Create a variable to hold the flash LED pin (FLASH_LED_PIN). In the ESP32-CAM AI‑Thinker, the flash is connected to GPIO 4. By default, set it to LOW.

#define FLASH_LED_PIN 4
bool flashState = LOW;

Motion Sensor

The motionDetected variable indicates whether motion has been detected. It is set to false by default.

bool motionDetected = false;

BME280

Define the SDA and SCL pins to be used with the BME280.

#define I2C_SDA 14
#define I2C_SCL 15

Create a BME280 instance called bme.

BME280 bme;

Request Delay

The botRequestDelay and lasTimeBotRan variables are used to check for new Telegram messages every x number of seconds. In this case, the code will check for new messages every second (1000 milliseconds). You can change that delay time in the botRequestDelay variable.

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

handleNewMessages()

The handleNewMessages() function handles what happens when new messages arrive.

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

Get the chat ID for that particular message and store it in the chat_id variable. The chat ID identifies who sent the message.

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

If the chat_id is different from your chat ID (chatId), it means that someone (that is not you) has sent a message to your bot. If that’s the case, ignore the message and wait for the next message.

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

Otherwise, it means that the message was sent from a valid user, so we’ll save it in the text variable and check its content.

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

The from_name variable saves the name of the sender.

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

If it receives the /flash message, invert the flashState variable and update the flash led state. If it was previously LOW, set it to HIGH. If it was previously HIGH, set it to LOW.

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

If it receives the /photo message, set the sendPhoto variable to true. Then, in the loop(), we’ll check the value of the sendPhoto variable and proceed accordingly.

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

If it receives the /readings message, call the getReadings() function (we’ll take a look at that function later on) and send the readings to the bot.

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

Sending a message to the bot is very simple. You just need to use the sendMessage() method on the bot object and pass as arguments the recipient’s chat ID, the message, and the parse mode.

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

Finally, if it receives the /start message, we’ll send the valid commands to control the ESP. This is useful if you happen to forget what are the commands to control your board.

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()

The sendPhotoTelegram() function takes a photo with the 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";
}

Then, it makes an HTTP POST request to send the photo to your telegram bot.

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()

The getReadings() function requests temperature and humidity from the BME280 sensor.

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

The readings are concatenated in the message variable that is returned by the function.

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

detectsMovement()

The detectsMovement() is a callback function that is called when motion is detected. In this case, we set the motionDetected variable to true. Then, in the loop(), we’ll handle what happens when there’s motion (sends a photo).

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

setup()

In the setup(), initialize the Serial Monitor.

Serial.begin(115200);

Set the flash LED as an output and set it to its initial state.

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

Initialize the BME280 sensor:

// 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();

Connect your ESP32-CAM to your local network.

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());

Configure and initialize the camera.

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_sscb_sda = SIOD_GPIO_NUM;
config.pin_sscb_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

Setup an interrupt on 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()

In the loop(), check the state of the sendPhoto variable. If it is true, call the sendPhotoTelegram() function to take and send a photo to your telegram account.

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

When it’s done, set the sendPhoto variable to false.

sendPhoto = false; 

When motion is detected, send a notification to your Telegram account and call the senPhototoTelegram() function. Then, set the motionDetected variable to false.

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

Check for new Telegram messages every second.

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();
}

When a new message arrives, call the handleNewMessages() function.

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

That’s pretty much how the code works.

Upload Code to the ESP32-CAM

After making the necessary changes, upload the code to your ESP32-CAM (before connecting the shield). Follow the next steps to upload code or follow this tutorial: How to upload code to ESP32-CAM.

1) Wire the ESP32-CAM to the FTDI programmer as shown in the following diagram.

Note: the order of the FTDI pins on the diagram may not match yours. Make sure you check the silkscreen label next to each pin.

Important: GPIO 0 needs to be connected to GND so that you’re able to upload code.

2) Go to Tools Board and select AI-Thinker ESP32-CAM. You must have the ESP32 add-on installed. Otherwise, this board won’t show up on the Boards menu.

3) Go to Tools Port and select the COM port the ESP32-CAM is connected to.

4) Then, click the Upload button in your Arduino IDE.

Program ESP32-CAM with Arduino IDE

5) When you start to see some dots on the debugging window, press the ESP32-CAM on-board RST button.

Upload code to ESP32-CAM Connecting to Serial Port
ESP32-CAM Press RESET RST on-board button to restart

After a few seconds, the code should be successfully uploaded to your board.

6) When you see the “Done uploading” message, remove GPIO 0 from GND.

Open the Serial Monitor, press the on-board RST button, and check that the ESP32-CAM is connecting to your network without any problems.

Demonstration

With the code uploaded to your ESP32-CAM, attach the PCB shield and all the components.

ESP32-CAM Shield PCB Stack to AI Thinker Module

Apply power using the 5V and GND pins on the shield.

Then, press the ESP32-CAM RST button, so that it starts running the code.

Now, open your Telegram account and test your board. Send the following messages to your ESP32 Telegram bot to control your ESP32-CAM:

  • /start: sends a welcome message with the valid commands to control the shield;
  • /flash: toggles the ESP32-CAM LED Flash;
  • /photo: takes a new photo and sends it to your Telegram account;
  • /readings: requests the latest BME280 sensor readings.

Additionally, you’ll receive a notification with a photo whenever motion is detected.

Control ESP32-CAM with Telegram Take Photos Control Outputs Request Sensor Readings and Motion Notifications Demonstration

If you try to interact with your bot from another account, you’ll get the the “Unauthorized user” message.

Control ESP32 ESP8266 Outputs Telegram Unauthorized user

Wrapping Up

In this tutorial we’ve created a PCB shield for the ESP32-CAM with a PIR motion sensor and BME280. This creates a more permanent circuit in a small footprint that you can put inside a small enclosure or dummy camera.

ESP32-CAM shield PCB Demonstration Fake dummy surveillance camera

You also learned how to use your Telegram account to control your ESP32-CAM using a Telegram bot. This allows you to control and monitor your board from anywhere, as long as you have internet access in your smartphone.

You can also create your own code to do any other tasks with the shield.

We have other similar projects that include building and designing PCBs that you may like:

Learn more about the ESP32-CAM with our resources:

We’re giving away 5 bare PCBs to someone that posts a comment below (comments might take up to 24 hours to be approved)! Simply post a comment in this blog post about what you would like to do with the PCB and you’re entered for a chance to win one of these bare PCBs. We’re currently confirm the winners and we will announce them during this weekend (August 22). So, stay tuned! [Update] the giveaway ended and the winners are: Gerald Maurer, Jason Wilkins, Svein Utne, Domenico Carvetta, and João Paulo.



Learn how to build a home automation system and we’ll cover the following main subjects: Node-RED, Node-RED Dashboard, Raspberry Pi, ESP32, ESP8266, MQTT, and InfluxDB database DOWNLOAD »
Learn how to build a home automation system and we’ll cover the following main subjects: Node-RED, Node-RED Dashboard, Raspberry Pi, ESP32, ESP8266, MQTT, and InfluxDB database DOWNLOAD »

Recommended Resources

Build a Home Automation System from Scratch » With Raspberry Pi, ESP8266, Arduino, and Node-RED.

Home Automation using ESP8266 eBook and video course » Build IoT and home automation projects.

Arduino Step-by-Step Projects » Build 25 Arduino projects with our course, even with no prior experience!

What to Read Next…


Enjoyed this project? Stay updated by subscribing our newsletter!

165 thoughts on “ESP32-CAM with Telegram: Take Photos, Control Outputs, Request Sensor Readings and Motion Notifications”

  1. This tutorial offers a clear insight in the whole process off processing and forwarding data. So I sure will to analyse also this example. One thing that thrills me with this example: how can I adapt the software, so that it can run on batteries. Maybe by using deepsleep and build some low current circuit that awakes the software of the ESP32. Maybe by using an attiny85 that itself wakes up every 10 seconds and then very shortly tests movement.

    Reply
  2. Great looking project, if I had the PCB I would modify it to control a servo. As is, the code could be altered to include a deep sleep routine allowing extended battery life. I’m going to have a go at making this and modifying the software myself!

    Reply
  3. Dear Sara,
    why don’t use directly the instruction: uint32_t instead of uint16_t to cover the UXGA format? Am I wrong ?

    Thanks. Domenico

    Reply
  4. Molto interessante come progetto. Mi piacerebbe usarlo per assemblare, anche con l’aiuto di una stampante 3d, la mia prima ipCam completamente programmabile. Mi piacerebbe anche poter sfruttare i restanti PIN rimasti liberi per poter eccitare eventualmente dei relay all’occorrenza. Grazie a voi per questo tutorial.

    Very interesting as a project. I would like to use it to assemble, even with the help of a 3d printer, my first fully programmable ipCam. I would also like to be able to take advantage of the remaining PINs left free to eventually excite relays if necessary. Thanks to you for this tutorial.

    Reply
  5. I really appreciate the whole process including PCB. Been researching this as a part of other projects. So glad to see this included here! Kudos to you.

    Reply
  6. There are a few things wrong with this sketch.
    First off it’s missing the declaration of the clientTCP variable (WiFiClientSecure clientTCP;).
    Then there are these extraneous chatId variables (I replaced them with CHAT_ID).
    Finally you need to add String casts around CHAT_ID and BOTtoken wherever they are used.

    Once all that is done it compiles and works.

    And to Domenico’s point, for low res camera captures 16bit is OK but I changed the code to 32 bit so I can set my camera to a higher resolution.

    Reply
    • Hello Steve, thank you so much for testing our code and letting me know. While removing the crendetials, we’ve copied the wrong bot token and chat id definitions. We’ve also deleted the WiFiClientSecure declaration by mistake (while explaining how the code works).
      All those changes have been made and the code now compiles.

      Thanks again!

      Reply
  7. Thanks for this excellent tutorial (as all your tutorials, very clear and interesting).
    I would very much like to have this custom made PCB Shield to try an adaption of this wonderful project as a remote ringbell image notification (i’ll leave the suggestion here).
    The idea would be to use a dummy camera like in your suggestion, but instead of having a motion sensor, it would have an input from the main doorbell button, and an output for the conventional doorbell system, while allowing the Telegram Bot to transmit the photo of the person @ the door, taken the moment it presses the doorbell button 🙂
    What do you think about that? (maybe an idea for your next tutorial?)

    Reply
  8. Hey, this is so great, I am going to try that. But would it be possible to try the very same thing on cloudmqtt? Especially the camera picture to be posted over cloudmqtt?
    Thanks in advance 🙂

    Reply
  9. Translation courtesy of Google
    I have read several times correctly interpret the indications that this web https://arduinojson.org/v6/doc/upgrade/
    but I have to admit that, after several failed attempts,
    The solution of updating Brian Lough’s UniversalTelegramBot.cpp library version 1.1.0 (installed) to adapt it to version 6.x of ArduinoJson is beyond my possibilities. Sorry

    I would appreciate a copy of the UniversalTelegramBot.cpp file from whoever obtained it, or the indications of the commands that I must modify.

    Reply
  10. Translation courtesy of Google
    I got it !!, after much struggle, I have managed to get UniversalTelegramBot to understand with ArduinoJson V6.16.1. (Installed)
    I no longer have compilation errors. Thank you all !!

    Reply
  11. Dear Sara, about the PIR Motion Sensor mode INPUT_PULLUP, I found that sometime you use as follows:
    err = gpio_isr_handler_add(GPIO_NUM_13, &detectsMovement, (void *) 13);

    sometime also:
    esp_err_t err = gpio_isr_handler_add(GPIO_NUM_13, &detectsMovement, (void *) 13);

    I guess both works fine, but unknown the difference, thanks for letting us know.

    Reply
  12. Very good project.
    I have been waiting for something like this for several years, but now I got it.
    Today I put one ESP32-Cam in my sailboat, with an old phone that is providing the internet connection with an extra data sim card. This cost less than €3 per month. Now I plan to also put one or two in my cottage. I might want to use two at home two in the sailboat and two at the cottage.
    Then the question is: what do I need to change? The chatId or the BOTtoken or both?

    Reply
    • Yes, just be sure to change the text on the “bot.sendMessage” line of code on each ESP32-CAM so you can identify which sensor is sending the message!!!

      So, instead of “Motion detected!!”, you should put “Home entry Motion detected!!”, “Sailboat entry Motion detected!!” or whatever makes you identify the origin of motion 🙂

      Reply
      • Thanks a lot for this help. What if I want to send a message to one of the ESP32-CAM, then maybe I need different chatId for each?

        Reply
  13. Great project. I put it together and got it running with minimal effort. Your instructions were excellent.
    I added two commands; moff and mon – which disable and enable the checking of the motion sensor. I did this by adding a variable;
    bool motionDetectEnable = false;
    and modifying the check of motionDetected as follows;
    if(motionDetected && motionDetectEnable) {

    And of course adding code to process the commands;
    if (text == “/mon”) {
    motionDetectEnable = true;
    Serial.println(“New motion detect ON request”);
    }
    if (text == “/moff”) {
    motionDetectEnable = false;
    Serial.println(“New motion detect OFF request”);
    }

    I did this because I seem to get an abundance of motionDetected even when in a quiet room.
    I’ve also noticed that it often takes minutes to receive the commands. Have others encountered this?

    Thanks again for your excellent instructions!

    Reply
  14. Hola te saludo desde Colombia, buscando en Internet sobre IOT te encontré, es asombroso el trabajo que haces, te felicito y agradezco en nombre propio y de la comunidad el tiempo que debes dedicarle a esto. Muchas gracias y fuerza para seguir en la labor.

    Reply
  15. Dear Rui & Sara,

    thanks for another great project. I’m signed up to your RNTLAB for a few years now to support the work you do.

    I would like to move from using breadboards to PCB’s like you have in this one.

    I started using KiCad to draw my circuits but am struggling to then create the PCB.

    I have just loaded EasyEDA and it seems much more user friendly.

    One problem I am struggling with is that i want to use a different ESP kit

    I want to use ESP_Devkitc_v4 when i try and find that device for the footprint and to use in the circuit i cant find it. I also cannot find the one you have actually used, if i search for ESP32_DOIT_DEVKIT_V1_36_SHIELD that also is not listed.

    I am obliviously missing something, would you be so kind as to advise how you would select a device that matches the device you are using in the circuit and also how you select for the correct footprint on the PCB layout.

    Greatly appreciate your assistance
    Jase

    Reply
    • Hello Jason, Try to just search “ESP32”, then look for all the components and try to find the right footprint. If you type the exact board name in the search bar, you might not find it… I hope that helps!

      Reply
      • Many thanks,

        When I first searched for ESP32 after install only found about three. The next day after giving up I rebooted and then the same search term “ESP32” shows loads up and mine was listed.

        Really strange

        Many thanks

        Reply
  16. I run into an other problem. The HC-SR501 got a potmeter for adjusting the sensetivity, but from max to min there is very small change, so when the vind is blowing and the boat is moving, it starts to take pictures all the time even on the lowest sensetivity, so after about 20 pictures Telegram stop working for 3 hours.

    Reply
    • For a boat a movement sensor is probably not the best option. What are you trying to detect? If it’s someone on board you may want to use a pressure mat instead.

      Reply
  17. I do not have any pressure mat, but I got some RLWL-0516. When you put in a 1M ohm resistor its sensitivity changes from 9 meters till only 4 meters. I will try that tomorrow.

    Reply
  18. It looks like the RLWL-0516 will work, when I put it in a can, and let the opening point in the direction I want to take the picture when motion. The RLWL-0516 uses radar tecnology, and it works 360 degrees even true table tops and the hull of the boat, but with a metal can, it can be stoped. The problem now is that I took too many pictures during this testing, so now I am banned again from Telegram for some hours, so I cannot make the final test before tomorrow. What is the rule Telegram is following when banning somone?

    Reply
  19. hello, I tried to load the program but after compilation the esp can’t connect to the home network. I am sure I have entered the correct password and ssid because I have copied and pasted them from another program that work. can you tell me how to solve this problem?

    Reply
      • Mine connect to network and keep disconnecting, can I just take picture with it if I don’t have the bme 280 sensor yet. But I want to fix the sensor on it later

        Reply
    • I am having the same problem, it gets as far as showing IP address but then stops. If I run a basic sketch without the telgram side of things it connects to home server.?

      Reply
  20. Today I received some AM312 PIR. Thay are small and has only 3 meter range, but that was just what I needed, so now it looks to be working fine. We will know more in a day or two.

    Reply
  21. Useful project. Thanks for all the work you put into it. I built it and it works, it works too well, in that it constantly triggers the motion detector and sends me pictures…
    Any suggestions as to why it is so sensitive and what to do about it?

    Thanks,
    Jacob

    Reply
  22. Try using the AM312, og is lese sensitiv. Ny problem now is that Telegram stop working after some hours, and I have to test the ESP32 to make it tun again.

    Reply
  23. Thanks for the project, it’s great, I’ve already ordered some boards.
    2 questions about compatibility:

    1.- use the RCWL-0516 presence sensor to replace the AM312 (it works inside the fake camera)

    2.- use the DHT11 sensor to replace the BME280. (I have several )

    It’s just out of curiosity

    Reply
  24. Hi Rui and Sara, sadly my comment from yesterday disappeard. Was it deleted? I tried the project but unfortunatly the bot.getUpdates() always returns 0 even though there are new messages for the bot in the chat.
    I am sure the telegram bot integration works bebause I can post messages via bot.sendMessage(). So the Bot API-key, the chat_ID and my WiFi credentials are crearly okay.
    Do you have any idea how to solve this issue? The issue looks similar to this one: https://github.com/witnessmenow/Universal-Arduino-Telegram-Bot/issues/121. However on ESP32 there is no more WifiClientSecure.setInsecure() method as suggested there.
    Any help is much appreciated.

    Reply
    • Hi.
      I can’t find any previous comment from you.
      The Telegram examples always worked fine for us with both the ESP32 and ESP8266 without any issues.
      The link you’ve sent me is related with the ESP8266.
      I don’t know how to solve the issue for the ESP32.
      I see that you’ve opened an issue on their forum.
      If you find out something, please share. Other people might be having the same issue.
      Regards,
      Sara

      Reply
  25. Thank you guys very much for this project. It is very nicely presented and documented. I built it and it works well first time. Now, I added a small change whereby I can enable/disable the motion detector via an additional command –
    /trigg_en. That works fine however for some reason the /start command stopped working. I expected to see the “menu” every time I hit /start, but it stopped after the first time. Any ideas? I then noticed that removing the “Markdown” from bot.sendMessage(chatId, welcome, “Markdown”); fixed the problem. Any ideas?

    Reply
  26. Hey. Do I have to make my own ibot for each camera? or two respond to different calls for example / photo for cam 1 and / photo wp for second cam.
    Another problem is that both cam’s do not always start up when turning on, then I have to reset via the button too often. Often the cam cannot be called up, even at a short distance, and only works again after a reset or on / off of the power supply.
    How much power / amp the cam actually uses. That is not clear to me.
    Nice project that I enjoy working on.

    Reply
  27. Hello great tutorial

    i just have one little problem
    I have a pressure mat instead of the presence sensor. I connected this with a 10k PULLUP resistor.
    My problem is that I keep getting motion detections.
    am I doing something wrong?
    best regards
    Gerhard

    Reply
  28. Quote “If you try to interact with your bot from another account, you’ll get the the “Unauthorized user” message.”

    I do not want this feature, since i want all of the people living in a house to receive Photo
    how can i remove this authorization? or add multiple accounts to ‘authorized’ list?

    Reply
    • Hi.
      You can use a group for that, where several people inside that group can control and receive messages. We’ll post an article about that soon.

      Alternatively, you can delete the following lines in the code:
      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;
      }

      Or you can add all authorized chat IDs and check inside that if statement if it corresponds to one of the authorized IDs.

      I hope this helps.
      Regards,
      Sara

      Reply
      • Hey Sara,

        Yes i was able to figure it out,
        I tried using Group but it was difficult to get a chat_id for a group, the ID bot doesnt tell me, i was unable to get the chat id of a group.

        so i just addid another statement chat_id = chatId ; and itll assign the unknown ID to current id

        Reply
  29. Hey. Thank you for the article. Putting together the project – everything works fine. I don’t understand only one thing – why the camera settings are made
    config.frame_size = FRAMESIZE_UXGA;
    or
    config.frame_size = FRAMESIZE_SVGA;
    and the photos come from the bot with a resolution of only
    FRAMESIZE_CIF, // 400×296
    How can I fix this?
    Sorry for my English. This is google translation ..

    Reply
  30. Hallo Sara, Rui,
    Please help me out . . .
    What address should I use to access my ESP32-Cam ?
    When I enter /start nothing happens !
    Am I missing something ?
    Regards,
    Met vriendelijke groeten,
    Tom

    Reply
    • Hi.
      What do you mean?
      With this project, you control the ESP32-CAM from your Telegram account. It is not accessible from a browser.
      Regards,
      Sara

      Reply
  31. Hi!

    Thank you for the great content!

    I followed the schematics on a breadboard and the first thing I tried to do was to run the i2c scanner in order to find the address for BME sensor but I keep getting “No I2C devices found”. I was expecting the actual address.
    Am I missing something? Is it normal? Are GPIO14 (SDA) and GPIO15(SCL) the actual I2C pins? I tried with different I2C devices but got the same result.
    I’ve got pictures from my setup, but can’t upload them =)

    Thanks in advance!

    João

    Reply
    • Hi.
      Those are not the default ESP32 I2C pins, but we set them on the code.
      If you’re running an I2C scanner sketch without specifying those pins, it won’t find any sensor, because it will look up on the default pins.
      Usually, the I2C address of the BME280 sensor is 0x76.
      I hope this makes sense.
      Regards,
      Sara

      Reply
  32. Hello, I have this error in the compilation, so I understand the library in the IDE is missing. But I didn’t find it on the internet. Could you guide me?
    Arduino: 1.8.12 (Windows 7), Placa:”AI Thinker ESP32-CAM”

    Foram encontradas múltiplas bibliotecas para “WiFi.h”
    esp32-cam-shield-telegram:13:23: fatal error: soc / soc.h: No such file or directory

    Usado: C:\Users\Thalis Mazzarino\AppData\Local\Arduino15\packages\esp32\hardware\esp32\1.0.4\libraries\WiFi
    compilation terminated.

    Não usado: C:\Program Files (x86)\Arduino\libraries\WiFi
    exit status 1
    soc / soc.h: No such file or directory

    Reply
  33. Got it working after figuring out chatID is User ID.
    Changed Temperature to display ºF.

    Added:
    // Print the Signal Strength:
    long rssi = WiFi.RSSI() + 100;
    Serial.print(“Signal Strength = ” + String(rssi));
    if (rssi > 50) Serial.println(F(” (>50 – Good)”)); else Serial.println(F(” (Could be Better)”));

    and toggled The “Flash” so it only is on when taking a picture.

    if (sendPhoto) {
    if (flashState == true) digitalWrite(FLASH_LED_PIN, HIGH); // FLASH ON
    Serial.println(“Preparing photo”);
    sendPhotoTelegram();
    sendPhoto = false;
    digitalWrite(FLASH_LED_PIN, LOW); // FLASH OFF
    }

    Removed “/” in “command” mode, for ease of use.
    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”;
    if (flashState == true) welcome += “Flash : ON now\n”; else welcome += “Flash : OFF now\n”;
    welcome += “Readings : request sensor readings\n\n”;
    welcome += “You’ll receive a photo whenever motion is detected.\n”;
    bot.sendMessage(chatId, welcome, “Markdown”);
    }

    Reply
      • Here is code minus my network credentials and above:
        Hope this helps you.

        const char* ServerName = “esp32cam”; // Address to the server with http://esp32cam.local/

        String local_hwaddr; // WiFi local hardware Address
        String local_swaddr; // WiFi local software Address

        // Initialize Telegram BOT
        // My_ESP_camBot
        String chatId = “XXXXXXXX”; // User ID
        String BOTtoken = “1489139834:XXXXXXXXXXXXXXXXXXXXXXXXXXX”;

        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 SENSOR_LED 12 // SENSOR_LED PIN: GPIO 12
        #define FLASH_LED_PIN 4 // FLASH_LED PIN: GPIO 4
        // Motion Sensor AM312 PIR sensor or XYC-WB-DC radar sensor
        #define MOTION_SENSOR 13 // MOTION_SENSOR PIN: GPIO 13

        bool FlashState = false;
        bool MotionDetected = false;
        bool MotionState = false;

        // Define I2C Pins for BME280
        #define I2C_SDA 14
        #define I2C_SCL 15
        // Create a BME280 instance called bme
        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();

        float temperatureC;
        float temperatureF;
        float humidity;
        // Get BME280 sensor readings and return them as a String variable
        String getReadings() {
        float temperatureC, temperatureF, humidity;
        temperatureC = bme.readTempC();
        temperatureF = bme.readTempF();
        humidity = bme.readFloatHumidity();
        //String message = “Temperature: ” + String(temperatureC) + ” ºC\n”;
        String message = “Temperature: ” + String(temperatureF) + ” ºF\n”;
        message += “Humidity: ” + String (humidity) + ” % \n”;
        long rssi = WiFi.RSSI() + 100;
        message += “Signal Strength: ” + String(rssi) + “\n”;
        return message;
        }

        /*
        // Indicates when motion is detected
        static void IRAM_ATTR detectsMovement(void * arg) {
        MotionDetected = true;
        }
        */

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

        pinMode(MOTION_SENSOR, INPUT); // MOTION_SENSOR as INPUT

        pinMode(SENSOR_LED, OUTPUT); // SENSOR_LED as OUTPUT
        digitalWrite(SENSOR_LED, LOW); // Turn SENSOR LED Off

        pinMode(FLASH_LED_PIN, OUTPUT); // LED as FLASH
        digitalWrite(FLASH_LED_PIN, LOW); // Turn FLASH LED Off

        // 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();

        delay(500);
        Serial.println(“\nESP Cam using Telegram Bot”);
        String readings = getReadings();
        Serial.print(readings);

        WiFi.mode(WIFI_STA);
        Serial.println();
        Serial.print(“Connecting to “);
        Serial.println(ssid);
        WiFi.begin(ssid, password);

        // ADDED This Update
        clientTCP.setCACert(TELEGRAM_CERTIFICATE_ROOT); // Add root certificate for api.telegram.org

        while (WiFi.status() != WL_CONNECTED) {
        Serial.print(“.”);
        delay(500);
        }
        Serial.println(” >> CONNECTED”);

        Serial.print(“ESP32-CAM IP Address: “);
        Serial.println(WiFi.localIP());

        // Print the Signal Strength:
        long rssi = WiFi.RSSI() + 100;
        Serial.print(“Signal Strength = ” + String(rssi));
        if (rssi > 50) {
        Serial.println(F(” (>50 – Good)”));
        } else {
        Serial.println(F(” (Could be Better)”));
        }
        /*
        wifiMulti.addAP(ssid, password);
        if (MDNS.begin(ServerName)) { // The name that will identify your device on the network
        local_hwaddr = “http://” + WiFi.localIP().toString();
        Serial.println(“Enter This Url Address \t: ” + local_hwaddr);
        local_swaddr = “http://” + String(ServerName) + “.local/”;
        Serial.println(” Or This Url Address \t: ” + local_swaddr);
        }
        else {
        Serial.println(F(“ERROR setting up MDNS responder”));
        }
        */

        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_sscb_sda = SIOD_GPIO_NUM;
        config.pin_sscb_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();
        }
        Serial.printf(“Camera Initialized >> OK \r\n”);

        // 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);
        }
        */

        Serial.printf(“Type ‘Start’ in Telegram to start bot\r\n”);

        }

        void loop() {
        if (sendPhoto) {

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

        }

        // Read Motion Sensor

        MotionDetected = digitalRead(MOTION_SENSOR);
        if (MotionDetected) {
        digitalWrite(SENSOR_LED, HIGH); // Turn SENSOR LED On
        bot.sendMessage(chatId, “Motion detected”, “”);
        Serial.println(“Motion Detected”);
        digitalWrite(SENSOR_LED, LOW); // Turn SENSOR LED Off

        if (MotionState) {
        sendPhotoTelegram();
        MotionDetected = false;
        } else {
        delay(30000); // Delay Wait for SENSOR to reset For Stability
        }

        }

        if (millis() > lastTimeBotRan + botRequestDelay) {
        int numNewMessages = bot.getUpdates(bot.last_message_received + 1);
        while (numNewMessages) {
        Serial.print(“Message received : “);
        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;

        if (FlashState == true) digitalWrite(FLASH_LED_PIN, HIGH); // FLASH ON

        delay(10);

        fb = esp_camera_fb_get();

        digitalWrite(FLASH_LED_PIN, LOW); // FLASH OFF

        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 (c == '\n') {
        if (getAll.length() == 0) state = true;
        getAll = "";
        }
        else if (c != '\r') {
        getAll += String(c);
        }
        if (state == true) {
        getBody += String(c);
        }
        startTimer = millis();
        }
        if (getBody.length() > 0) break;
        }
        clientTCP.stop();

        // Print Information
        //Serial.println(getBody);
        Serial.println("Photo Sent");

        }
        else {
        getBody = “Connected to api.telegram.org failed.”;
        Serial.println(“Connected to api.telegram.org failed.”);
        }
        return getBody;
        }

        void handleNewMessages(int 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 message received
        String fromName = bot.messages[i].from_name;
        String text = bot.messages[i].text;

        Serial.println(numNewMessages + " From " + fromName + " >" + text + " request");

        if (text == "Flash") {
        FlashState = !FlashState;
        Serial.print("FlashState = ");
        if (FlashState == true) Serial.println("ON"); else Serial.println("OFF");
        // digitalWrite(FLASH_LED_PIN, FlashState);

        }
        if (text == "Motion") {
        MotionState = !MotionState;
        Serial.print("MotionState = ");
        if (MotionState == true) Serial.println("ON"); else Serial.println("OFF");
        // digitalWrite(FLASH_LED_PIN, MotionState);
        }

        if (text == "Photo") {
        sendPhoto = true;
        }
        if (text == "Readings") {
        String readings = getReadings();
        bot.sendMessage(chatId, readings, "");
        }
        if (text == "Start") {
        String welcome = "Welcome to the ESP32-CAM Telegram bot.\n";
        long rssi = WiFi.RSSI() + 100;
        welcome += "Signal Strength: " + String(rssi) + "\n";
        welcome += "Photo : takes a new photo\n";
        welcome += "Flash : toggle flash LED\n";
        if (FlashState == true) welcome += "Flash : ON now\n"; else welcome += "Flash : OFF now\n";
        welcome += "Motion : toggle Motion Sensor\n";
        if (MotionState == true) welcome += "Motion : ON now\n"; else welcome += "Motion : OFF now\n";
        welcome += "Readings : Temp & Humidity\n\n";
        welcome += "If Motion is ON, you'll receive a photo whenever motion is detected.\n";
        bot.sendMessage(chatId, welcome, "Markdown");
        }

        }
        }

        Reply
          • You may want to try this. To print file name in set up:
            Add these lines in set up:

            Serial.println(“Program ~ ” + Filename());
            Serial.println(“Date Compiled ~ ” + String(DATE));

            and this after setup:

            String Filename() {
            return String(FILE).substring(String(FILE).lastIndexOf(“\”) + 1);
            }

    • Hi John I appreciated your improvements and introductions to my project. except that I get this error due to the instructions entered for printing the WiFi signal strength.
      Could you help me. Thank you. Paul

      here is the error received during compilation :
      F:\ESP32_CAM_TELEGRAM_RCWL_RUI_MOD\Esp32_Cam_RCWL0516_Rui_3\Esp32_Cam_RCWL0516_Rui_3.ino:417: error: unterminated argument list invoking macro “F”
      }

      exit status 1

      Compilation error: unterminated argument list invoking macro “F”

      Reply
  34. So I order parts for the camera , motion sensor and am waiting for parts. I liked the dome idea but I didn’t find any links to the place to find the dome. Can you provide it, just wanted to make sure I get the correct one so it fits.Also , is it weather resistant? Thanks

    Reply
  35. Hi Rui/Sara,

    Thanks for another great project, has inspired me to get back to learning programming. I plan to build a couple, one as a game camera. From a hardware side, I have no issues, but coding presents its challenges! Is there any way to add servo control so that I am able to pan the camera? Waiting for parts to arrive now.
    Thanks

    Reply
  36. Hi Rui and Sara,

    thanks for that project first! I have a minor question: what are the middle 5 contacts for? I received the PCBs as you described and I find now the 5 holes and dont know how to use them. Pls could you tell me what they are for?
    Thanks a lot

    Greets
    Clemens

    Reply
    • Hi.
      Those are optional.
      You can solder header pins if you want to connect additional sensors or outputs and if you want to have an additional power source.
      Regards,
      Sara

      Reply
  37. Hi Sara and Rui,

    thank you for this great idea!

    I want to use it for looking after my expensive plants. For this I want to collect the humidity and temperature data for the whole time. Your webserver (Youtube for ESP32) is nice, but I think it doesn´t save the data for weeks.

    Can you tell me, how to use a ESP32-cam-module with a data-collecting-service like thingspeak? The code I wrote causes problems with receiving telegram-messages. Sometimes it freezes and new telegram-messages are not received.

    Also I tried to use your modified sketch with a DHT11 and a soil moisture sensor, but unfortunately it doesn´t work 🙁

    Thanks for your great job!

    Reply
  38. Thank you so much for this wonderful tutorial. I really love it!!!. 👌👌 All the steps are very clear. Again, thanks very much …..

    Reply
  39. Hello, can the hc-sr501 sensor be used instead of the one mentioned in the project? Well, I have some difficulties, I used this hc-sr501 and I have problems with the detection, normally what happens is that when I change the sensitivity and time adjustment I get the photo by telegram, but after that I move my hand in front of the sensor and I don’t get more pictures … ???

    Reply
  40. Greetings. I liked your project very much. Everything is working ! But there is one problem. I set the quality of the pictures for the camera to “high”, and through the telegram server, the photos with low quality come to the phone. I think this is due to the fact that the photo from ESP 32 is transmitted as “PHOTO” and not as a “file”. Therefore, the telegram server reduces its size for easier transmission in chat … Can I fix this somehow ??? So that the photos from the camera are transmitted without compression.

    Reply
  41. Hi Guys,
    Firstly can I say they I appreciate your work. I am learning a lot from your tutorials.
    This project however refuses to work for me and I think it is because I am not using an Ai Thinker esp32 cam board but a Wrover Module. Could this be my problem?
    If so how easy is it to change the code to work on the Wrover Module?
    Regards

    Reply
    • Hi Steve.
      But the pinout is the same as the AI.Thinker, right?
      So, the code should work for your camera. It must be other thing.
      What’s exactly the issue you have?
      Regards,
      Sara

      Reply
      • I did get this code to work last night and the camera started sending images to Telegram. My board setting on Arduino IDE was set wrong. Thanks

        Reply
  42. Hello please update the code, this code wont work unless you add “clientTCP.setCACert(TELEGRAM_CERTIFICATE_ROOT);”
    i had telegram connection failed error later i found this and most programs miss this essential line of code..may be it is a necessary in updated library

    Reply
    • Hi.
      Thanks for telling me about this issue.
      I’ve tested it, and you’re right. There must have been some update on the library or on the Telegram app.
      We’ll update all tutorials that use Telegram as soon as possible.
      Regards,
      Sara

      Reply
  43. Got the code working again !!
    I am trying out the XYC-WB-DC radar sensor. It works but I get some spurious readings, so I added an LED across the radar sensor and created an OUTPUT pin to drive another LED when motion is detected.
    I also have made a few changes to the code in “Start” to:
    A: Toggle ON or OFF the Flash.
    B: Toggle ON or OFF the MotionState to enable/disable the send Photo
    C: Added Signal Strength.
    Also my iPhone adds a Capital letter to the commands sent from Telegram, so I changed to code to accept this capital character, “start” is now “Start”.

    Motion Sensor Code Changed:

    /*
    // Read Motion Sensor
    if (MotionDetected) {
    digitalWrite(SENSOR_LED, HIGH); // Turn SENSOR LED On
    bot.sendMessage(chatId, “Motion detected!!”, “”);
    Serial.println(“Motion Detected”);
    digitalWrite(SENSOR_LED, LOW); // Turn SENSOR LED Off

    if (MotionState) {
    sendPhotoTelegram();
    MotionDetected = false;
    }
    }

    */

    // Alternative Read Motion Sensor
    int SensorVal = digitalRead(MOTION_SENSOR);
    if (SensorVal == 0) {
    MotionDetected = false;
    digitalWrite(SENSOR_LED, LOW); // Turn SENSOR LED Off
    } else {
    MotionDetected = true;
    digitalWrite(SENSOR_LED, HIGH); // Turn SENSOR LED On
    bot.sendMessage(chatId, “Motion detected”, “”);
    Serial.println(“Motion Detected”);
    digitalWrite(SENSOR_LED, LOW); // Turn SENSOR LED Off

    if (MotionState) {
    sendPhotoTelegram();
    MotionDetected = false;
    }

    }

    Question: Why use “static void IRAM_ATTR detectsMovement(void * arg)” when you can can define an INPUT on GPIO 13 for the motion sensor?

    Keep up the good work.

    Reply
    • Hi John, I also had the same problem why not define GPIO13 in order to do something clean. I wonder if you changed the instructions by removing use of .

      “static void IRAM_ATTR detectsMovement(void * arg)” ????

      If you have modified the program could you send me the correct changes. Thank you

      Reply
  44. Hi Sara,
    used the ESP32 CAM MB USB Programmer:

    Ok when reviewing the sketch.
    The following error message appears when compiling the sketch (excerpt)

    C:\Users\Uwe Dolata\AppData\Local\Arduino15\packages\esp32\tools\esptool_py\2.6.1/esptool.exe –chip esp32 –port COM4 –baud 460800 –before default_reset –after hard_reset write_flash -z –flash_mode dio –flash_freq 80m –flash_size detect 0xe000 C:\Users\Uwe Dolata\AppData\Local\Arduino15\packages\esp32\hardware\esp32\1.0.4/tools/partitions/boot_app0.bin 0x1000 C:\Users\Uwe Dolata\AppData\Local\Arduino15\packages\esp32\hardware\esp32\1.0.4/tools/sdk/bin/bootloader_qio_80m.bin 0x10000 C:\Users\UWEDOL~1\AppData\Local\Temp\arduino_build_435116/Control_ESP32-CAM_with_Telegram.ino.bin 0x8000 C:\Users\UWEDOL~1\AppData\Local\Temp\arduino_build_435116/Control_ESP32-CAM_with_Telegram.ino.partitions.bin
    esptool.py v2.6
    Serial port COM4
    Connecting…….._____….._____….._____….._____….._____….._____….._____

    A fatal error occurred: Failed to connect to ESP32: Timed out waiting for packet header

    I followed the instructions.
    For Telegram I use my already existing bot.

    Reply
    • Hi.
      Try to hold the programmer IO0 button and then press the ESP32-CAM on-board RST button when you start seeing the dots on the debugging window.
      Regards,
      Sara

      Reply
      • Hi Sara,
        Thanks for the quick answer, I could have found it myself in the error list.
        Supi worked !!

        A new problem:
        I can’t get in touch with my telegram bot.
        He doesn’t understand the news.
        Also, something seems to be wrong with the PCB shield.
        Both the motion detector and the BME seem to have a loose contact or my soldering points are not OK. When pulling out / inserting both, the message “Motion detected” plus photo appears on my mobile phone !?

        Reply
        • Hi.
          The PCB worked just fine for us. Maybe there is some bad solder joint on your PCB? It would be a good idea to check the connections with a multimeter.
          As for the Telegram bot, did it ever worked before?
          Usually, the problem with this kind of projects is that people don’t insert the right BotToken or the chat user ID. Can you double-check that?
          Regards,
          Sara

          Reply
          • Hi Sara,
            see log files of my Iobroker.
            Do I have to create a second, new bot?

            2021-05-11 19:21:43.997 – warn: telegram.0 (20757) polling_error: ETELEGRAM, ETELEGRAM: 409 Conflict: terminated by other getUpdates request; make sure that only one bot instance is running
            2021-05-11 19:21:44.046 – debug: telegram.0 (20757) getMe (reconnect): {“id”:1236242331,”is_bot”:true,”first_name”:”Waschmaschine”,”username”:”Boschwasch_bot”,”can_join_groups”:true,”can_read_all_group_messages”:false,”supports_inline_queries”:false}
            Best regards

  45. Hi guys this is really amazing and made it too for my project…but I want to ask what mode the PIR sensor is in….is it retriggering or non retriggering mode

    Reply
    • MIne too. So I have been playing with a time delay.
      // Read Motion Sensor
      // Delays are to avoid multiple triggers and messages
      MotionDetected = digitalRead(MOTION_SENSOR);
      if (MotionDetected) Serial.println(“Motion Detected”);
      delay(1000); // Wait 1 Second before detecting again

      if (MotionState) {
      if (MotionDetected) {
      digitalWrite(SENSOR_LED, HIGH); // Turn SENSOR LED On
      bot.sendMessage(chatId, “Motion detected”, “”);
      digitalWrite(SENSOR_LED, LOW); // Turn SENSOR LED Off

      sendPhotoTelegram();
      MotionDetected = false;
      Serial.print(" Please Wait ");
      delay(30000); // Wait 30 Seconds before detecting again
      Serial.println("> Ready to detect again");
      }

      }

      Reply
      • Hi John,
        Just to be clear you are using the PIR sensor in retriggered or repeat trigger mode? You added a delay so it will not trigger continuously. Why not set it in single trigger mode?
        Did you have inconsistent triggers with this mode?

        Previously you had mentioned you had used the XYC-WB-DC radar sensor. Were you satisfied with the results or is the PIR a better choice?

        Have you tried putting the ESP in deep sleep? Adding to my code does not work but it works as a stand alone code.

        Thanks,
        Joe

        Reply
  46. Hi,
    Thanks for great tutorial. However my ESP32-CAM doesn’t send any message to telegram bot.
    Or did I understand wrongly?
    Where shall I send those /start, /photo commands? In BotFather or IDBot?
    How can I determine that ESP32 is connected to telegram bot.
    I receive only “ESP32-CAM IP Address: 192.168.0.90”, that means, it is connecting to my home network but I dont know if it is connected to telegram bot. How can I find out?
    Thanks.

    Reply
    • Hi.
      In your Telegram contacts, you should search for the bot you have created and send those messages to the bot.
      Regards,
      Sara

      Reply
  47. Hi,
    i have three espcam divices running youre code. Works like a charm. I have found one isseu i can not resolve. If the wifi accespoint reboots the espcam will not reconnect to the wifi. I have to powercycle the espcam to reconnect to the wifi. Can you help resolve this? Is this an isseu with the wifi libary?

    Reply
  48. Hello
    Telegram App is blocked in my area and i am using MTProto Proxy on my mobile to have access Telegram. Question is , How is it possible my ESP32-Cam also use the MTProto Proxy to connect Telegram server and access Bot ?

    Reply
  49. Hi,
    i’m testing here with now 3 ESP32Cam’s and have everytime the same problem.
    All Cam’s are working with the orig. Software when it goes up with the own AP. i can connect to the cam and all works fine.
    when i upload your sketch and the cam connect’s to my wifi, there are always ping failures.
    the RSSI is always fine. My Cam and the Notebook are around 2meters of my AP away.
    But this issue with all 3 ESP32Cam-Modules.
    thanks
    pat

    Reply
  50. Hello Sara and Rui. Great project. I would like to add the deep sleep feature but I am having issues placing the code in the right section of your code. I have read on deep sleep from the ESP32 course I bought from you and it explains it very well. I have used the EXT 1 and masked the pins. These are the three lines of code I added. Can you explain to me where I should add the last two lines?

    #define BUTTON_PIN_BITMASK 0x2000 // 2^13 in hex

    esp_sleep_enable_ext1_wakeup(BUTTON_PIN_BITMASK,ESP_EXT1_WAKEUP_ANY_HIGH);
    esp_deep_sleep_start();

    Thanks very much,
    Joe

    Reply
    • Hi.
      You can put the following line anywhere in the setup as long as it goes before esp_deep_sleep_start():
      esp_sleep_enable_ext1_wakeup(BUTTON_PIN_BITMASK,ESP_EXT1_WAKEUP_ANY_HIGH);
      The other line should be the last thing in your code. When the ESP32 reaches that line of code, it goes into sleep mode and won’t run any other code after that.
      Let me know if you need further help.
      Regards,
      Sara

      Reply
      • Hi Sara, Thanks very much for your quick response. The deep sleep works when I run the deep sleep code and I ground the GPIO 13 with a resistor. The ESP32 draws about 2 mA. When I incorporate the deep sleep code to my telegram code and use the same resistor to ground GPIO 13 the current does not decrease and I observe a 0 every second on the serial monitor. This means the ESP is not going to sleep. I added the at the end to the program but it seems to be looping. If I add it outside the brackets the very last entry I get an error. Here is the code w/o SSID info.. Thanks for your support. Joe

        #include <WiFi.h>
        #include <WiFiClientSecure.h>
        #include “soc/soc.h”
        #include “soc/rtc_cntl_reg.h”
        #include “esp_camera.h”

        #define uS_TO_S_FACTOR 1000000 /* Conversion factor for micro seconds to seconds /
        #define TIME_TO_SLEEP 10 /
        Time ESP32 will go to sleep (in seconds) */

        //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 BUTTON_PIN_BITMASK 0x2000 // 2^13 in hex

        int gpioPIR = 13; //PIR Motion Sensor

        void setup()
        {
        esp_sleep_enable_ext1_wakeup(BUTTON_PIN_BITMASK,ESP_EXT1_WAKEUP_ANY_HIGH);
        WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0);

        Serial.begin(115200);
        delay(10);
        WiFi.mode(WIFI_STA);
        Serial.println(“”);
        Serial.print(“Connecting to “);
        Serial.println(ssid);
        WiFi.begin(ssid, password);
        long int StartTime=millis();
        while (WiFi.status() != WL_CONNECTED)
        {
        delay(500);
        if ((StartTime+10000) < millis()) break;
        }

        Serial.println(“”);
        Serial.println(“STAIP address: “);
        Serial.println(WiFi.localIP());
        Serial.println(“”);

        if (WiFi.status() != WL_CONNECTED) {
        Serial.println(“Reset”);

        ledcAttachPin(4, 3);
        ledcSetup(3, 5000, 8);
        ledcWrite(3,10);
        delay(200);
        ledcWrite(3,0);
        delay(200);
        ledcDetachPin(3);
        delay(1000);
        ESP.restart();

        }
        else
        {
        ledcAttachPin(4, 3);
        ledcSetup(3, 5000, 8);
        for (int i=0;i<5;i++) {
        ledcWrite(3,10);
        delay(200);
        ledcWrite(3,0);
        delay(200);
        }
        ledcDetachPin(3);
        }

        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_sscb_sda = SIOD_GPIO_NUM;
        config.pin_sscb_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;

        if(psramFound())
        {
        config.frame_size = FRAMESIZE_VGA;
        config.jpeg_quality = 10; //0-63 lower number means higher quality
        config.fb_count = 2;
        }
        else
        {
        config.frame_size = FRAMESIZE_QQVGA;
        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();
        }

        sensor_t * s = esp_camera_sensor_get();
        s->set_framesize(s, FRAMESIZE_XGA);

        }

        void loop()
        {

        pinMode(gpioPIR, INPUT_PULLUP);
        int v = digitalRead(13);
        Serial.println(v);
        if (v==1)
        {
        alerts2Telegram(token, chat_id);
        delay(10000);
        }
        delay(1000);

        }
        //esp_deep_sleep_start();
        String alerts2Telegram(String token, String chat_id)
        {
        const char* myDomain = “api.telegram.org”;
        String getAll=””, 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”;
        // esp_sleep_enable_timer_wakeup(TIME_TO_SLEEP * uS_TO_S_FACTOR);//deep sleep
        // esp_deep_sleep_start();

        }

        esp_deep_sleep_start();
        WiFiClientSecure client_tcp;

        if (client_tcp.connect(myDomain, 443))
        {
        Serial.println(“Connected to ” + String(myDomain));

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

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

        client_tcp.println("POST /bot"+token+"/sendPhoto HTTP/1.1");
        client_tcp.println("Host: " + String(myDomain));
        client_tcp.println("Content-Length: " + String(totalLen));
        client_tcp.println("Content-Type: multipart/form-data; boundary=India");
        client_tcp.println();
        client_tcp.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)

        {
        client_tcp.write(fbBuf, 1024);
        fbBuf += 1024;
        }
        else if (fbLen%1024>0)
        {
        size_t remainder = fbLen%1024;
        client_tcp.write(fbBuf, remainder);
        }
        }

        client_tcp.print(tail);

        esp_camera_fb_return(fb);

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

        while ((startTime + waitTime) > millis())
        {
        Serial.print(".");
        delay(100);
        while (client_tcp.available())
        {
        char c = client_tcp.read();
        if (c == '\n')
        {
        if (getAll.length()==0) state=true;
        getAll = "";
        }
        else if (c != '\r')
        getAll += String(c);
        if (state==true) getBody += String(c);
        startTime = millis();
        }
        if (getBody.length()>0) break;
        }
        client_tcp.stop();
        Serial.println(getBody);

        }
        else {
        getBody = “Connection to telegram failed.”;
        Serial.println(“Connection to telegram failed.”);
        }

        return getBody;
        //esp_deep_sleep_start();

        }
        //esp_deep_sleep_start();

        Reply
        • Hi.
          It is very difficult to read code like this. Please, post your code in Pastebin or GitHub and then, share the corresponding link.
          Regards,
          Sara

          Reply
  51. Hi again.

    When i take a photo with command by Telegram, i see the snapshot is not actual.
    so the snapshot is taken a couple of seconds before.
    is there a snapshot buffer or else?
    thanks
    pat

    Reply
  52. Hi and thanks for this great project.
    i would like to used a Normally open micro Switch to trigger the even of sending the photo.
    how can i use it instead of the motion sensor.
    should i connect the 2 leads from the microswitch to where the yellow and black connections are on the motion sensor.

    Reply
  53. one question about image resolution
    how can it be improve it, I am getting resolution of 400×296 on the pictures.
    try to change config.jpeg_quality but does not make any difference.
    read some where in this long thread to change from 16 to 32bit but don’t know where to do those changes.
    just got today this board and was just plug and play on windows 10
    https://www.amazon.com/gp/product/B09263L8DQ/
    any help would be really appreciated.
    best regards

    Reply
  54. Can we work this in a P2P set up ? where the phone with telegram connects to the ESP32 AP and then communicates the readings ?

    Reply
  55. Hello,
    I am building this project. the ESP32 cam connects to the network, also, it connects to the telegram bot. however it does not respond to any further command from the bot such as /flash or /photo. I checked the serial monitor as well, the last status I see is “Handle New messages: 1” /start. But any further command from bot is not registered to the esp32
    please help in getting it working .

    Reply
  56. Hi Sara,

    Thanks for wonderful website and so much of educational material!
    In terms of this project however I have some critique . As Andreas mentioned Esp32-cam seems to “store buffer” and display old photos so whole idea of motion triggering makes no sense.. Somebody will trigger camera but you will receive old photo without whoever triggered it… Assuming someone could disconnect camera at this point you will never know if this was false alarm or who triggered it. IT seems like major floor to this project..

    Unless this buffer problem is specific to certain esp32-cam models.. But all I tried behaved same way also is present on ESP32 web server example.

    I wasn’t able to overcome this I really hope you could check and fix it somehow. Many thanks

    ps sorry I posted it on wrong project initially

    Reply
  57. Can I have ESP32-CAM to do live web streaming and telegram take photo, control output and motion notification to be put together? Cause I try to combine the code for them both, it did not work.

    Reply
    • Hi.
      I’m not sure.
      I don’t think you can take pictures and live streaming at the same time without crashing the board, but I might be wrong…
      Regards,
      Sara

      Reply
  58. Hello Sara,
    Wonderful and meticulosly written instructions. I especially appreciate the section where you also explain the working of the code.

    One question – can you give any hint to integrate this with MQTT brokers – like sending notification to the broker, say using PubSubClient. Any more hints would be appreciated.

    Reply
  59. Hi, I have this project wired up to a project board, using a bmp 280, instead of the bme 280 and have double checked my wiring. Everything works great, with one exception. I’m only getting motion detected when I first plug the unit in, and when I request readings, it will return both values, as 0, and then immediately after, I get motion detected message. So somehow it’s not reading the sensor values, and triggering the motion upon that request…. Any ideas? I’m going to try a different esp32 ai thinker just to rule out the board, in the meantime.

    Reply
  60. HI. Could you please explain this portion of the sketch :

    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.”);

    Becaus eI’m having the message “Connected to api.telegram.org failed”

    Thanks in advance

    Reply
  61. Hi. I have 2 internet connections at home(so 2 Internet Service Providers). How it comes every thing is working flawlessly with one ISP and with the second one i’m getting the message : “connected to api.telegram.org failed” ?
    What could be the problem and how to solve this issue?
    Thanks

    Reply
  62. Hello thank for this beautiful tutorial;
    My esp 32 cam doesn’t take picture at real time
    When sendPhotoTelegram is called when i request /photo, the cam take photo few minute before it’s real position..
    How can i fix this problem
    Papis !

    Reply
  63. Good morning, I would kindly ask Rui or Sara for a little help. I tried to modify this project by replacing the PIR with RCWL-0516, but I can’t get it to work. Would you be kind enough to propose the solution perhaps with a new project. Thank you with all my heart. Paul

    Reply
  64. Thanks Eddy, now it works for me too, except that the RCWL-0516 module, even in the absence of movement or presence, always sends images. In practice, it always senses the presence of movement even if it is in a closed room where no one enters. does it do it to you too? thanks for your time. Greeting . Paul

    Reply
    • This is because your rcwl is close to WiFi signal. Try to put it as far as possible from WiFi signals to avoid interferences. You can also decrease its sensitivity (use Google for that).

      Reply
      • Hi Eddy, thank you for your willingness to help me, but I tried to upload a simple sketch like the one proposed by Rui and Sara’s site with a mega Arduino positioned in the same point where I had placed the project with the esp32 cam, and I have to tell you that the scwl-0516 sensor works very well without making non-existent readings continuously. I will do the test you suggest by placing the esp32 cam away from the WiFi router. Could you please suggest to me if it is worth installing a photoresistor? another thing Eddy, to mount an external antenna on the esp32 cam, do you have to cut a track on the printed circuit board? Thank you . Greetings from Bari Italy.

        Reply
        • I have never used a photo resistor on my rcwl.
          To install an external antenna you have to change the position of the small resistor located near the antenna connector or you can desolder this resistor and put a bridge between two solder pads (there is a lot of tutorials on how to do this in the net.).

          Reply
      • Hi Eddy, I did what you advised me to do, keep the esp32 cam away from the wifi router. Nothing to do continuously sends non-existent movements. Yet with Arduino Mega it works very well, do you have any ideas? furthermore the wifi range is really limited, by moving the esp to a room 6 meters away it no longer picks up wifi. ? Does it improve much with an antenna? I don’t want to get discouraged and abandon this project. If you can give me some other tips. Maybe if you can send me a photo of your wiring to my email address. Thank you

        Reply
  65. Help, I connected RCWL-0516 instead of the PIR, but after many attempts, esp32 cam continuously sends photos endlessly without having detected a movement. If anyone has solved the problem brilliantly, he could kindly help me. Thank you

    Reply
      • Hi Sara, as in my previous comments, the RCWL-0516 module with a simple Skizzo and mega Arduino works very well. This module connected with esp32 cam which has the Wifi module on board creates problems. Try it too and let me know. I’m opting for another sensor of the Radar HW-MS03 type, on the web someone who tried it wrote that he had no problems.

        Reply
    • i have the same issue that the esp32 cam continuously sends photos endlessly without having detected a movement i make a unit test for the PIR sensor and it works fine and i don’t know what is the problem

      Reply
      • Hi Ahmed, could you be more precise about the solution that works, I don’t understand what “pulling down the PIR sensor pin” means. Thank you

        Reply
  66. Can one use the same code for an ESP32-S or must it be the regular ESP32?

    Does Telegram need to be open at all times for the motion sensor picture capture to work?

    Reply

Leave a Reply to Joseph Drago Cancel reply

Download Our Free eBooks and Resources

Get instant access to our FREE eBooks, Resources, and Exclusive Electronics Projects by entering your email address below.