Telegram: ESP32-CAM Take and Send Photo (Arduino IDE)

In this tutorial, you’ll create a Telegram bot to interact with the ESP32-CAM to request a new photo. You can request a new photo using your Telegram account from anywhere. You just need to have access to the internet in your smartphone.

ESP32-CAM: Take and Send Photo to Telegram App using Arduino IDE

Note: this project is compatible with any ESP32 Camera Board with the OV2640 camera. You just need to make sure you use the right pinout for the board you’re using.

Project Overview

ESP32-CAM Take and Send Photo to Telegram Project Overview

Here’s an overview of the project you’ll build:

  • You’ll create a Telegram bot for your ESP32-CAM;
  • You can start a conversation with the ESP32-CAM bot;
  • When you send the message /photo to the ESP32-CAM bot, the ESP32-CAM board receives the message, takes a new photo and responds with that photo;
  • You can send the message /flash to toggle the ESP32-CAM’s LED flash;
  • You can send the /start message to receive a welcome message with the commands to control the board;
  • The ESP32-CAM will only respond to messages coming from your Telegram account ID.

This is a simple project, but shows how you can use Telegram in your IoT and Home Automation projects. The idea is to apply the concepts learned in your own projects.

We also have a dedicated project for the ESP32-CAM with Telegram that covers more advanced features: Take Photos, Control Outputs, Request Sensor Readings and Motion Notifications.

Introducing Telegram

Telegram Messenger is a cloud-based instant messaging and voice over IP service. You can easily install it in your smartphone (Android and iPhone) or computer (PC, Mac and Linux). It’s free and without any ads. Telegram allows you to create bots that you can interact with.

“Bots are third-party applications that run inside Telegram. Users can interact with bots by sending them messages, commands and inline requests. You control your bots using HTTPS requests to Telegram Bot API”.

The ESP32-CAM will interact with the Telegram bot to receive and handle the messages, and send responses. In this tutorial, you’ll learn how to use Telegram to send messages to your bot to request a new photo taken with the ESP32-CAM. You can receive the photo wherever you are (you just need Telegram and access to the internet).

Creating 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 can interact with the bot.

Telegram BotFather Get Bot Token

Note: the bot token is a very long string. To make sure you get it right, you can go to the Telegram Web Interface and copy your bot token from there.

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 board using Arduino IDE, so make sure you have them 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.

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.5.12.

Install in Arduino IDE the ArduinoJSON library

Code

Copy the following code the Arduino IDE. To make this sketch work for you, you need to insert your network credentials (SSID and password), the Telegram Bot token and your Telegram user ID. Additionally, check the pin assignment for the camera board that you’re using.

/*
  Rui Santos
  Complete project details at https://RandomNerdTutorials.com/telegram-esp32-cam-photo-arduino/
  
  Permission is hereby granted, free of charge, to any person obtaining a copy
  of this software and associated documentation files.
  
  The above copyright notice and this permission notice shall be included in all
  copies or substantial portions of the Software.
*/

#include <Arduino.h>
#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>

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

// Initialize Telegram BOT
String BOTtoken = "XXXXXXXXXX:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";  // your Bot Token (Get from Botfather)

// 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 CHAT_ID = "XXXXXXXXXX";

bool sendPhoto = false;

WiFiClientSecure clientTCP;
UniversalTelegramBot bot(BOTtoken, clientTCP);

#define FLASH_LED_PIN 4
bool flashState = LOW;

//Checks for new messages every 1 second.
int botRequestDelay = 1000;
unsigned long lastTimeBotRan;

//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


void configInitCamera(){
  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
}

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

  for (int i = 0; i < numNewMessages; i++) {
    String chat_id = String(bot.messages[i].chat_id);
    if (chat_id != CHAT_ID){
      bot.sendMessage(chat_id, "Unauthorized user", "");
      continue;
    }
    
    // Print the received message
    String text = bot.messages[i].text;
    Serial.println(text);
    
    String from_name = bot.messages[i].from_name;
    if (text == "/start") {
      String welcome = "Welcome , " + from_name + "\n";
      welcome += "Use the following commands to interact with the ESP32-CAM \n";
      welcome += "/photo : takes a new photo\n";
      welcome += "/flash : toggles flash LED \n";
      bot.sendMessage(CHAT_ID, welcome, "");
    }
    if (text == "/flash") {
      flashState = !flashState;
      digitalWrite(FLASH_LED_PIN, flashState);
      Serial.println("Change flash LED state");
    }
    if (text == "/photo") {
      sendPhoto = true;
      Serial.println("New photo request");
    }
  }
}

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" + CHAT_ID + "\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 setup(){
  WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); 
  // Init Serial Monitor
  Serial.begin(115200);

  // Set LED Flash as output
  pinMode(FLASH_LED_PIN, OUTPUT);
  digitalWrite(FLASH_LED_PIN, flashState);

  // Config and init the camera
  configInitCamera();

  // Connect to Wi-Fi
  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()); 
}

void loop() {
  if (sendPhoto) {
    Serial.println("Preparing photo");
    sendPhotoTelegram(); 
    sendPhoto = 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();
  }
}

View raw code

How the Code Works

This section explains how the code works. Continue reading to learn how the code works or skip to the Demonstration section.

Importing Libraries

Start by importing the required libraries.

#include <Arduino.h>
#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>

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 chat ID. The one you’ve got from the IDBot.

String CHAT_ID = "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);

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;

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;
unsigned long lastTimeBotRan;

ESP32-CAM Initialization

The following lines assign the ESP32-CAM AI-Thinker pins. If you’re using a different ESP32 camera model, don’t forget to change the pinout (read ESP32-CAM Camera Boards: Pin and GPIOs Assignment Guide).

//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

The configInitCamera() function initializes the ESP32 camera.

void configInitCamera(){
  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
}

handleNewMessages()

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

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

It checks the available messages:

for (int i = 0; i < numNewMessages; i++) {

Get the chat ID for a 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 (CHAT_ID), 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 != CHAT_ID){
  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 from_name = bot.messages[i].from_name;

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 , " + from_name + "\n";
  welcome += "Use the following commands to interact with the ESP32-CAM \n";
  welcome += "/photo : takes a new photo\n";
  welcome += "/flash : toggles flash LED \n";
  bot.sendMessage(CHAT_ID, welcome, "");
}

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 = "");

In our example, we’ll send the message to the ID stored on the CHAT_ID variable (that corresponds to your personal chat id) and send the message saved on the welcome variable.

bot.sendMessage(CHAT_ID, welcome, "");

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);
  Serial.println("Change flash LED state");
}

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

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

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

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

Call the configInitCamera() function to configure and initialize the camera.

configInitCamera();

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

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

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

sendPhoto = false;

In the loop(), you also check for new 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.

Demonstration

Upload the code to your ESP32-CAM board. Don’t forget to go to Tools > Board and select the board you’re using. Go to Tools > Port and select the COM port your board is connected to.

After uploading the code, press the ESP32-CAM on-board RST button so that it starts running the code. Then, you can open the Serial Monitor to check what’s happening in the background.

Go to your Telegram account and open a conversation with your bot. Send the following commands and see the bot responding:

  • /start shows the welcome message with the valid commands;
  • /flash inverts the state of the LED flash;
  • /photo takes a new photo and sends it to your Telegram account.
ESP32-CAM Take and Send Photo to Telegram Demonstration

At the same time, on the Serial Monitor, you should see that the ESP32-CAM is receiving the messages.

ESP32-CAM Send Photo to Telegram Serial Monitor

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

Wrapping Up

In this tutorial you’ve learned how to send a photo from the ESP32-CAM to your Telegram account. As long as you have access to the internet in your smartphone, you can request a new photo no matter where you are. This is great to monitor your ESP32-CAM from anywhere in the world.

We have other tutorials using Telegram that you might be interested in:

Learn more about the ESP32-CAM with our resources:

Thanks for reading.



Build Web Server projects with the ESP32 and ESP8266 boards to control outputs and monitor sensors remotely. Learn HTML, CSS, JavaScript and client-server communication protocols DOWNLOAD »

Build Web Server projects with the ESP32 and ESP8266 boards to control outputs and monitor sensors remotely. Learn HTML, CSS, JavaScript and client-server communication protocols DOWNLOAD »


Enjoyed this project? Stay updated by subscribing our newsletter!

60 thoughts on “Telegram: ESP32-CAM Take and Send Photo (Arduino IDE)”

  1. Hi Sara,
    very interesting article about the ESP32 and the OV2640 (and alike) camera !
    Do you intend to publish some articles about the Sipeed Maixduino AI processor Kit having also an ESP32 with a OV2640 camera on board and beeing also programmed with the Arduini IDE ?
    I believe that many your follower folks would like you, Sara or Ruis having published on this modern topic on AI and alike !
    thanks a lot, Bruno

    Reply
  2. Really impressed. I have been struggling for ages with another (well-known) IoT platform via a Raspberry as a gateway, but could never make it stable. This took me an hour tops, including reading the techo bits, and its up and running with no problems.

    Reply
    • Except I am having a problem with this. I was getting an image, but then I realised it is minimum resolution, and the file size is only about 5K even though it is set for UXGA.
      What I found is that this piece of code is keeping it in a low res mode, although I see the point in that for timelapse:
      s->set_framesize(s, FRAMESIZE_CIF); // UXGA|SXGA|XGA|SVGA|VGA|CIF|QVGA|HQVGA|QQVGA

      And so I changed the framesize to _SVGA, and also changed the camera config to set it to SVGA mode even if psram is found. I now get a larger file size generated (as confirmed by a println statement showing me fbLen).

      However it now seems to time out before the image is uploaded to Telegraph. In the…
      while ((startTimer + waitTime) > millis())
      … loop the “.” is printed continuously until the timeout occurs. I have tried lengthening waitTime but it still times out.
      I am running the ESP-CAM with Arduino rev 1.8.13. This must be a newer version CAM, as it has a micro-USB connector and signal converter incorporated. It works perfectly with the Camera Web Server example, and I can change resolutions up to the max.
      Thanks, Derek

      Reply
        • Hi Chong – no resolution. Been a while since I was playing with it, but I did not get anywhere. It seems to time out from the Telegraph end of things with larger file sizes.

          I have just come back to this project and about to try other options, probably having to email the photo as an attachment. I have been told that smtp2go.com will do this from an app. Also my private email provider (a paid service) has recently introduced an app specific password system which would get over my reluctance to embed my master email passwords into a device.

          Good luck, Derek

          Reply
        • One more thing that just came to me from memory. Even on mundane text only messages, there would sometimes be long delays (like10 or more minutes) before the reply would come back. So most likely it is just each package being sent not getting an acknowledgement before the ESP32 times out.

          Reply
          • Hi,
            i just tested SVGA and for me it is working (esp32-Cam AI Thinker)
            ” s->set_framesize(s, FRAMESIZE_SVGA); ”
            but no response for SXGA or XGA

          • Thanks Sara. That sorts out the Telegraph timeout side of things, and images are being received reliably and in good time.

            However there is a deeper issue, which looks to be in the esp_camera library. It does not seem to like transferring larger amounts of image data from the camera.

            It works okup to SXGA (quality=8) as long as frame_size is still set yo UXGA. At that I get an image that seems like 1280×1024 (although it is chunky on lines, so not sure if it really is. Also file size is only around 75kB.

            In UXGA I get an image 1280×960 and around 65kB file size.

  3. Sara and Rui, Many thanks for your esp 32 projects, i am trying esp 32 with telegram app taking photo and sending via telegram app, during the uploading i am facing this error message
    Error COMPILING FOR BOARD ESP 32 WROVER Module.
    pls help to fix this problem.
    S.Yogaraja
    TN- INDIA

    Reply
  4. Hi there,

    I receive a error message when compiling:
    #include “soc/soc.h” No such file or directory
    #include “soc/rtc_cntl_reg.h” No such file or directory

    Which library is this a part of?

    Regards,
    Leon

    Reply
  5. Hello,
    I am still not receiving photos on the Telegram bot. Program seems to work (start/flash), but pictures do not come true. I have tried changing the uint16 to 32 and used all different resolutions. Any ideas? ESP32-AI thinker board.

    Thx

    Reply
  6. Hi Sara:
    There is a bug in the code. Please modify it. I am truly grateful to you for what you have done.

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

    Reply
  7. If this line of code was on an incorrect line, what was the fault that it generated ?
    My esp32-cam worked good i think from the start in September 2020.

    Reply
  8. Hi,
    I am from Iran and Telegram has been filtered in Iran and I could not do this project because the Iranian Internet is filtered for Telegram. You can help

    Reply
  9. Greetings to authors, Sara and Rui! You are doing greatest work for hobbyist embedded programmers teaching.
    I did some experimental work to port this ESP32-CAM, trying to get connected with telegram bot on GSM network, by stand alone SIM800L module, the same, as on TTGO T-Call
    The sketch is almost the same compared with original version, beside of connection to Internet. All WiFi oriented initialization and and commands are removed and were written beneath lines:
    #include <TinyGsmClient.h>
    #define SerialMon Serial
    #define SerialAT Serial1

    // Your GPRS credentials (leave empty, if not needed)
    const char apn[] = “xxxxxxxxxxxxxx”; // APN use https://wiki.apnchanger.org
    const char gprsUser[] = “xxxxxxx”; // GPRS User
    const char gprsPass[] = “xxxxxxx”; // GPRS Password

    // SIM card PIN (leave empty, if not defined)
    const char simPIN[] = “****”;

    // Configure TinyGSM library
    #define TINY_GSM_MODEM_SIM800 // Modem is SIM800
    #define TINY_GSM_RX_BUFFER 1024 // Set RX buffer to 1Kb
    #define TINY_GSM_USE_GPRS true
    #define USE_SSL
    #define DUMP_AT_COMMANDS
    // Add a reception delay – may be needed for a fast processor at a slow baud rate
    #define TINY_GSM_YIELD() { delay(2); }
    #define TINY_GSM_DEBUG SerialMon

    // SIM800L to ESP32-CAM pins
    #define MODEM_RST 2
    #define MODEM_TX 14
    #define MODEM_RX 15

    // TinyGSM Client for Internet connection
    #ifdef USE_SSL
    TinyGsmClientSecure client(modem);
    const int port = 443;
    #else
    TinyGsmClientSecure client(modem);
    const int port = 80;
    #endif

    The following is the dump of terminal:
    10:15:23.890 -> AT
    10:15:24.195 -> AT
    10:15:24.229 ->
    10:15:24.229 -> OK
    10:15:24.229 -> ATE0
    10:15:24.263 ->
    10:15:24.263 -> OK
    10:15:24.263 -> AT+CMEE=2
    10:15:24.263 ->
    10:15:24.304 -> OK
    10:15:24.304 -> AT+GMM
    10:15:24.304 ->
    10:15:24.304 -> SIMCOM_SIM800L
    10:15:24.338 ->
    10:15:24.338 -> OK
    10:15:24.338 -> [11525] ### Modem: SIMCOM SIM800L
    10:15:24.372 -> [11525] ### Modem: SIMCOM SIM800L
    10:15:24.372 -> AT+CLTS=1
    10:15:24.372 ->
    10:15:24.372 -> OK
    10:15:24.372 -> AT+CBATCHK=1
    10:15:24.406 ->
    10:15:24.406 -> OK
    10:15:24.406 -> AT+CPIN?
    10:15:24.440 ->
    10:15:24.440 -> +CME ERROR: CFUN state is 0 or 4
    10:15:25.457 -> AT+CPIN?
    10:15:25.491 ->
    10:15:25.491 -> RDY
    10:15:25.491 ->
    10:15:25.491 -> +CFUN: 1
    10:15:25.525 ->
    10:15:25.525 -> +CPIN: READY
    10:15:25.559 ->
    10:15:25.559 -> +CPIN: READY
    10:15:25.593 ->
    10:15:25.593 -> OK
    10:15:35.600 ->
    10:15:35.600 -> AT+CPIN?
    10:15:35.634 ->
    10:15:35.634 -> Call Ready
    10:15:35.634 ->
    10:15:35.634 -> SMS Ready
    10:15:35.669 ->
    10:15:35.669 -> *PSUTTZ: 2021,3,11,6,15,31,”+16″,0
    10:15:35.702 -> [22863] ### Network time and time zone updated.
    10:15:35.702 ->
    10:15:35.702 -> DST: 0
    10:15:35.702 -> [22875] ### Daylight savings time state updated.
    10:15:35.702 ->
    10:15:35.702 -> +CPIN: READY
    10:15:35.736 ->
    10:15:35.736 -> OK
    10:15:35.736 -> AT+CPIN=”8722″
    10:15:35.770 ->
    10:15:35.770 -> +CME ERROR: operation not allowed
    10:15:35.804 -> Connecting to APN: internet.beeline.am
    10:15:35.804 -> AT+CIPSHUT
    10:15:35.838 ->
    10:15:35.838 -> SHUT OK
    10:15:35.875 -> AT+CGATT=0
    10:15:36.590 ->
    10:15:36.590 -> OK
    10:15:36.590 -> AT+SAPBR=3,1,”Contype”,”GPRS”
    10:15:36.624 ->
    10:15:36.658 -> OK
    10:15:36.658 -> AT+SAPBR=3,1,”APN”,”internet.beeline.am”
    10:15:36.692 ->
    10:15:36.692 -> OK
    10:15:36.726 -> AT+SAPBR=3,1,”USER”,”internet”
    10:15:36.760 ->
    10:15:36.760 -> OK
    10:15:36.760 -> AT+SAPBR=3,1,”PWD”,”internet”
    10:15:36.794 ->
    10:15:36.828 -> OK
    10:15:36.828 -> AT+CGDCONT=1,”IP”,”internet.beeline.am”
    10:15:36.862 ->
    10:15:36.862 -> OK
    10:15:36.896 -> AT+CGACT=1,1
    10:15:39.377 ->
    10:15:39.377 -> *PSUTTZ: 2021,3,11,6,15,37,”+16″,0
    10:15:39.411 -> [26576] ### Network time and time zone updated.
    10:15:39.411 ->
    10:15:39.411 -> DST: 0
    10:15:39.411 -> [26590] ### Daylight savings time state updated.
    10:15:45.529 ->
    10:15:45.529 -> *PSUTTZ: 2021,3,11,6,15,43,”+16″,0
    10:15:45.563 -> [32746] ### Network time and time zone updated.
    10:15:45.563 ->
    10:15:45.563 -> DST: 0
    10:15:45.597 -> [32758] ### Daylight savings time state updated.
    10:15:45.631 ->
    10:15:45.631 -> *PSUTTZ: 2021,3,11,6,15,43,”+16″,0
    10:15:45.665 -> [32850] ### Network time and time zone updated.
    10:15:45.665 ->
    10:15:45.699 -> DST: 0
    10:15:45.699 -> [32862] ### Daylight savings time state updated.
    10:15:45.699 ->
    10:15:45.699 -> +CIEV: 10,”28301″,”Beeline AM”,”Beeline AM”, 0, 0
    10:15:45.835 ->
    10:15:45.835 -> OK
    10:15:45.835 -> AT+SAPBR=1,1
    10:15:46.073 ->
    10:15:46.073 -> OK
    10:15:46.107 -> AT+SAPBR=2,1
    10:15:46.107 ->
    10:15:46.107 -> +SAPBR: 1,1,”10.111.24.84″
    10:15:46.175 ->
    10:15:46.175 -> OK
    10:15:46.175 -> AT+CGATT=1
    10:15:46.209 ->
    10:15:46.209 -> OK
    10:15:46.209 -> AT+CIPMUX=1
    10:15:46.243 ->
    10:15:46.243 -> OK
    10:15:46.243 -> AT+CIPQSEND=1
    10:15:46.277 ->
    10:15:46.277 -> OK
    10:15:46.277 -> AT+CIPRXGET=1
    10:15:46.311 ->
    10:15:46.311 -> OK
    10:15:46.311 -> AT+CSTT=”internet.beeline.am”,”internet”,”internet”
    10:15:46.379 ->
    10:15:46.379 -> OK
    10:15:46.413 -> AT+CIICR
    10:15:46.413 ->
    10:15:46.413 -> OK
    10:15:46.447 -> AT+CIFSR;E0
    10:15:46.447 ->
    10:15:46.447 -> 10.111.24.84
    10:15:46.481 ->
    10:15:46.481 -> OK
    10:15:46.515 -> AT+CDNSCFG=”8.8.8.8″,”8.8.4.4″
    10:15:46.549 ->
    10:15:46.549 -> OK
    10:15:46.549 -> Waiting for network…AT+CREG?
    10:15:46.583 ->
    10:15:46.583 -> +CREG: 0,1
    10:15:46.583 ->
    10:15:46.583 -> OK
    10:15:46.617 -> success
    10:15:46.617 -> AT+CREG?
    10:15:46.617 ->
    10:15:46.617 -> +CREG: 0,1
    10:15:46.651 ->
    10:15:46.651 -> OK
    10:15:46.651 -> Network connected
    10:15:46.651 -> AT+CIFSR;E0
    10:15:46.685 ->
    10:15:46.685 -> 10.111.24.84
    10:15:46.719 ->
    10:15:46.719 -> OK
    10:15:46.719 -> Local IP:10.111.24.84
    10:15:46.719 -> AT+CSQ
    10:15:46.719 ->
    10:15:46.719 -> +CSQ: 28,0
    10:15:46.753 ->
    10:15:46.753 -> OK
    10:15:46.753 -> Signal quality:28
    10:15:47.400 -> Camera init Ok
    10:15:47.434 -> AT+CIPRXGET=4,0
    10:15:47.468 ->
    10:15:47.468 -> +CIPRXGET: 4,0,0
    10:15:47.502 ->
    10:15:47.502 -> OK
    10:15:47.502 -> AT+CIPSTATUS=0
    10:15:47.536 ->
    10:15:47.536 -> +CIPSTATUS: 0,,””,””,””,”INITIAL”
    10:15:47.604 ->
    10:15:47.604 -> OK
    10:15:47.638 -> AT+CIPCLOSE=0,1
    10:15:47.638 ->
    10:15:47.638 -> +CME ERROR: operation not allowed
    10:15:47.707 -> AT+CIPSSL=1
    10:15:47.707 ->
    10:15:47.707 -> OK
    10:15:47.707 -> AT+CIPSTART=0,”TCP”,”api.telegram.org”,443
    10:15:47.780 ->
    10:15:47.780 -> OK
    10:15:48.969 ->
    10:15:48.969 -> 0, CLOSE OK
    10:15:49.003 -> AT+CIPRXGET=4,0
    10:15:49.003 ->
    10:15:49.003 -> +CIPRXGET: 4,0,0
    10:15:49.038 ->
    10:15:49.038 -> OK
    10:15:49.038 -> AT+CIPSTATUS=0
    10:15:49.076 ->
    10:15:49.076 -> +CIPSTATUS: 0,0,”TCP”,”149.154.167.220″,”443″,”CLOSED”
    10:15:49.212 ->

    10:15:49.212 ->
    10:15:49.212 -> OK
    10:15:49.212 -> See what bot updates 0
    10:15:50.230 -> AT+CIPRXGET=4,0
    10:15:50.230 ->
    10:15:50.230 -> +CIPRXGET: 4,0,0
    10:15:50.264 ->
    10:15:50.264 -> OK
    10:15:50.297 -> AT+CIPSTATUS=0
    10:15:50.297 ->
    10:15:50.297 -> +CIPSTATUS: 0,0,”TCP”,”149.154.167.220″,”443″,”CLOSED”

    Upon hitting /start on telegram bot nothing useful happens, just bot standard invitation appears.
    What could be the problem?

    Reply
  10. By the way, I did a try to get worked the original code with WiFi, but again none of performance. The photo by email sending sketch has been worked, thus it’s absolutely could be stated that my board is ok.

    Reply
  11. Hi,
    I have tried out the code on one unit of ESP32_CAM, it work well. I can received picture on Telegram. When I setup another ESP32-CAM and switched on two ESP32-CAM at the same time. I have problem, only one ESP32-CAM is working, the second one is not responding to telegram command /photo.
    Can we run multiple ESP32-CAMs with the same TelegramBot account ?

    When I switched off the ESP32-CAMs. Restart only one unit of ESP32-CAM again, it is not working anymore. It did not response to Telegram command /start, photo or /flash.

    I couldn’t find anything that has gone wrong with the two units of ESP32-CAMs that fail to response to Telegram commands now.

    Please help.

    Reply
      • The two units of ESP32-CAMs not able to accept TelegramBot commands /start, /flash and /photo.
        Serial monitor show WiFi is OK.

        01:20:59.717 -> x⸮x⸮x⸮x⸮⸮xxxx⸮x⸮⸮⸮⸮⸮⸮⸮⸮x⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮x⸮⸮⸮⸮⸮x⸮xx⸮xets Jun 8 2016 00:22:57
        01:20:59.717 ->
        01:20:59.717 -> rst:0x1 (POWERON_RESET),boot:0x12 (SPI_FAST_FLASH_BOOT)
        01:20:59.717 -> configsip: 0, SPIWP:0xee
        01:20:59.717 -> clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
        01:20:59.717 -> mode:DIO, clock div:1
        01:20:59.717 -> load:0x3fff0018,len:4
        01:20:59.717 -> load:0x3fff001c,len:1216
        01:20:59.717 -> ho 0 tail 12 room 4
        01:20:59.717 -> load:0x40078000,len:10944
        01:20:59.717 -> load:0x40080400,len:6388
        01:20:59.815 -> entry 0x400806b4
        01:21:01.677 ->
        01:21:01.677 -> Connecting to ShiHan
        01:21:01.677 -> .
        01:21:02.136 -> ESP32-CAM IP Address: 192.168.86.26

        I program the CAM with Video Streaming Web Server. No issue. It is working fine.

        Reply
  12. Successfully programmed the ESP Cam, works with the Webserver example. Says it connected successfully and then no further interaction (tried /start, /flash, /photo inside the bot). Bot Token an my ID are fine. Used the TeleframBot Library as ZIP as described.

    Maybe: The ArduinoJSON in the latest version (6 upwards) causes a lot of debug errors indicating that that’s version 5 code, so I changed back to the latest version 5 of ArduinoJSON and it compiles without error.

    Stretch goal: Can I ommit the CHAT_ID if I want anybody to access the cam?

    Any hints?

    Reply
    • Hi Martin.
      Yes, you can omit the chat id.
      Make sure that you are interacting with the right bot in your Telegram account.
      Regards,
      Sara

      Reply
      • Hello Sara,

        I found the reason why it did not work (found it in the echo bot example):

        I had to add the middle line with the setCACert

        WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
        clientTCP.setCACert(TELEGRAM_CERTIFICATE_ROOT); // Add root certificate for api.telegram.org
        while (WiFi.status() != WL_CONNECTED)

        Now this part works. Got a new one now: After requesting a photo an error message is displayed in the monitor:
        {“ok”:false,”error_code”:400,”description”:”Bad Request: group chat was upgraded to a supergroup chat”,”parameters”:{“migrate_to_chat_id”:-1001395804118}

        Thank you very much!

        Reply
        • And I answer it myself: When you change your group (e.g. add admin users) the ID changes sp just update the Group ID in the code and it will be fine!

          Reply
  13. Error when i compile :

    Line 245: client.setCACert(TELEGRAM_CERTIFICATE_ROOT); // Add root certificate for api.telegram.org

    But it’s “clientTCP.” , maybe you can update the code 😉

    Reply
      • Hello Sara,
        I keep getting an error that states that “TELEGRAM_CERTIFICATE_ROOT” was not declared in the scope, in the latest download. Have I missed out anything or do I need to declare it somewhere ?
        Thanks.

        Reply
  14. Hello Rui and Sara!
    Finally I got worked this project on GSM connection, having used SIM800C module. If you are interested in publishing my version of code, based on your original project, I’m ready to give the source code.

    Reply
  15. Hi Sarah/Rui,
    Do you have a tutorial on ESP32CAM with motion detector and sending pic to telegram when motion is detected? Ones found on youtube are all useless and I do not seem to see one on your tutorials.

    Regards
    Firdosh

    Reply
  16. Hi Sara and Rui,
    First of all, thank you so much for the great job you have done since the beginning of RNT. The technical knowledge is one thing, but you know furthermore how to share it, and you do it with a great pedagogy.
    I used this tutorial, it worked on the first try, as usual with your production :-). I was wondering one thing, most of all due to my mis-knwoledge of the telegramm platform and the bot privacy. Is it guarantee that only the user identify by CHAT_ID will receive the photo? What happen if the bot has been added to a group ? If instead of sending the picture on a command from an identified user with an CHAt_ID we decide to send it to the bot when an event occurs (PIR sensor, for example), am I sure that only me will see it ? An unauthorised user can try to talk to the bot, it’s command will be rejected, but what happen when the ESP32 send messages or pictures to the bot by itself, with no command preceding ?
    Hope my question make sense.
    Best regards

    Reply
    • Hi.
      In the code, we set to send the message to the authorized user ID. So, if someone that is not authorized tries to get something from the bot, it will get the “Unauthorized user” message.
      As shown in the following lines:
      if (chat_id != CHAT_ID){
      bot.sendMessage(chat_id, “Unauthorized user”, “”);
      continue;
      }

      AS for the other question, the bot only sends messages to the user that you define.
      Take a look at the sendPhotoTelegram() Telegram function.
      You’ll see that the request needs to include the chat id of the recipient that you set on the code.

      If the bot is added to a group, you would need to add the chat id of the group to your code. Only that way would the group receive the photos.

      I hope this is clear.
      Regards,
      Sara

      Reply
  17. Hi, this is a great project……I have a question to upload the program we just need TTL connect to ESP32 and upload the codes right????

    Reply
  18. Hi, where can I find libraries for these
    #include “soc/soc.h”
    #include “soc/rtc_cntl_reg.h”
    #include “esp_camera.h”

    Reply
    • Hi.
      Those libraries are included by default when you install the ESP32 boards add-on.
      Just make sure you have an ESP32 selected when compiling the code.
      Regards,
      Sara

      Reply
  19. Hallo, I have the following questions:
    1. if I chose UXGA the Transmission to telegram Time-out, the only way to ensure image will be transmitted is to chose SVGA or VGA. But if I held the pcb in my hand sometimes the UXGA image will transmitted !? Doesend make any sense to me.
    I applied condensator to the 3V3 and 5V so stabilize voltage. different cables and power supplys.
    2. the transmitted image is old. so I have to request 3 images to get the recent picture.
    Any ideas?
    Peter

    Reply
      • Hallo Sara,
        thank you for your support. No, I’m using the PCB antenna and the selector for the antenna is at the correct position.
        Do you think it is a issues with the connection? I’m near to the router at all 1 meter.
        Best regards Peter

        Reply
        • Hi Peter,
          I observed the exact same effect before changing the uint16_t to uint32_t variables. Since then I can send the SXGA format. For me, the ESP32-CAM had to be facing up. I was able to send smaller formats vertically, but it was difficult or impossible to send the larger ones. A very strange effect … and it is not due to reception, because it was also close to the router.
          Gerd

          Reply

Leave a Reply to Sara Santos 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.