Guias · 12 min de leitura

ESP32-CAM e Telegram: enviar foto ao detectar movimento

Envie fotos do ESP32-CAM para o Telegram: ligação do PIR e pino livre, comando /photo, controle do flash, deep sleep para bateria, fotos noturnas e código completo.

Quando você monta um bot ESP32-CAM Telegram, a placa tira uma foto no momento em que detecta movimento e a envia para o seu celular em poucos segundos. Não é preciso instalar app, alugar servidor nem abrir porta no roteador: a placa envia a foto direto para os servidores do Telegram, e o Telegram entrega a você como notificação instantânea. Pelo mesmo bot, você pode digitar /foto para ver a frente da sua porta quando quiser, ligar ou desligar o flash e desativar o alarme.

Neste guia você vai ver, passo a passo, qual dos poucos pinos livres do ESP32-CAM usar para o sensor PIR, como a foto é enviada ao Telegram, o código completo controlado por comandos e como usar o flash do jeito certo. Depois, vai fazer a placa funcionar com bateria por dias usando deep sleep, tirar fotos noturnas com iluminação IR e entender onde esta montagem deixa a desejar como câmera de segurança. Se você ainda não configurou a placa nem carregou seu primeiro sketch, leia antes Como usar o ESP32-CAM.

Módulo de câmera ESP32-CAM para um sistema de fotos com Telegram
ESP32-CAM: câmera OV2640, LED de flash e Wi-Fi em uma só placa. Com um sensor PIR, ela pode enviar uma foto ao Telegram quando detecta movimento.

Como funciona um sistema de fotos ESP32-CAM com Telegram?

A cadeia tem quatro passos. O sensor PIR percebe uma mudança no calor emitido por um corpo humano e leva o pino OUT a 3,3 V. Quando o ESP32 vê essa borda de subida, pede à câmera OV2640 um quadro JPEG, que é gravado nos 4 MB de PSRAM da placa. Em seguida, o ESP32 abre uma conexão HTTPS criptografada e envia uma requisição POST no formato multipart/form-data para o endpoint sendPhoto da Bot API do Telegram; é o mesmo método que o navegador usa quando você envia um arquivo. O Telegram adiciona a foto à sua conversa e o celular mostra uma notificação. Os comandos fazem o caminho inverso: a cada segundo, o ESP32 chama getUpdates para perguntar se há mensagens novas e, se chegou um /foto, faz o mesmo envio a seu pedido.

ComponenteEspecificaçãoObservação
ESP32-CAM (AI-Thinker)OV2640, 4 MB PSRAMA versão com PSRAM lida tranquilamente com fotos XGA
ESP32-CAM-MB ou conversor USB-TTLCH340 / CP2102Para carregar o código; a placa não tem USB
Sensor PIRHC-SR501 (5 V) ou AM312 (3,3 V)O sinal OUT é de 3,3 V e vai direto ao ESP32
Fonte de alimentação5 V, no mínimo 2 AO Wi-Fi e o flash puxam corrente alta ao mesmo tempo
Capacitor eletrolítico470–1000 µFEntre 5V e GND, perto da placa
OpcionalCélula 18650 + conversor elevador de 5 V, iluminador IR de 850 nmPara funcionar com bateria e tirar fotos noturnas

Ligação do sensor PIR e escolha de um pino livre

O ESP32-CAM tem 16 pinos nas bordas, mas só a câmera usa 15 GPIOs e o slot microSD compartilha mais seis. A maioria dos que sobram são pinos de boot (strapping) ou são necessários para a porta serial. Como este projeto não usa cartão SD, as linhas do SD ficam livres, e a mais segura delas é o GPIO 13: ele não afeta a inicialização, tem resistor de pull-down interno e, por ser um pino RTC, consegue acordar a placa do deep sleep. A tabela abaixo compara também os outros candidatos.

PinoSem cartão SDAcorda do deep sleep?Observação
GPIO 13LivreSim (ext0)A melhor escolha para o PIR
GPIO 14LivreSimPode mostrar um breve sinal PWM no boot; funciona bem como entrada
GPIO 15LivreSimPino de strapping; mantê-lo em LOW silencia as mensagens de boot
GPIO 2LivreSimPino de strapping; não pode estar em HIGH durante o carregamento
GPIO 12LivreSimSe estiver em HIGH no boot, a placa não inicia; não use
GPIO 4LED do flashSimReservado para o flash; não use para outra coisa
GPIO 3 (U0R)RX serialNãoSó se você não precisar da porta serial
GPIO 16PSRAMNãoNunca use
Pino do PIRPino do ESP32-CAMDescrição
VCC5VO HC-SR501 precisa de pelo menos 4,5 V; use 3V3 para o AM312
OUTGPIO 133,3 V quando há movimento, 0 V quando não há
GNDGNDGND comum
O pino da placa marcado como VCC não é uma entrada, e sim a saída do regulador de 3,3 V; para alimentar o PIR ou a placa, ligue sempre os 5 V no pino 5V. Nunca ligue o PIR ao GPIO 12: se o sensor estiver em HIGH no boot, o ESP32 tenta operar a memória flash com a tensão errada e a placa simplesmente não inicia.

Dos dois trimpots do HC-SR501, gire o de tempo (Tx) para o ajuste mais curto (todo no sentido anti-horário, cerca de 3 segundos); o código vai cuidar do tempo de espera. Deixe o jumper de disparo na posição H (disparo repetido) e ajuste a sensibilidade entre 3 e 7 metros, conforme sua necessidade. Depois de ligado, o sensor passa cerca de um minuto se acostumando com o ambiente e dispara aleatoriamente nesse período, por isso o código ignora os primeiros 60 segundos. Os detalhes de como o sensor funciona estão em sensor de movimento PIR.

Preparando o bot do Telegram: token e chat ID

  1. Abra o @BotFather no Telegram, digite /newbot e dê ao seu bot um nome e um nome de usuário que termine em bot.
  2. Copie o token que o BotFather fornece, parecido com 1234567890:AAE...; ele vai na linha BOT_TOKEN do código.
  3. Para descobrir seu número de usuário, envie /start ao @userinfobot; o valor Id que ele retorna é o seu CHAT_ID.
  4. Abra o seu próprio bot e envie /start uma vez; o Telegram não deixa bots mandarem mensagem para quem nunca conversou com eles.
  5. No Gerenciador de Bibliotecas do Arduino IDE, instale as bibliotecas UniversalTelegramBot (Brian Lough) e ArduinoJson.
  6. Escolha AI Thinker ESP32-CAM em Ferramentas > Placa e confira se o Partition Scheme está em Huge APP (3MB No OTA/1MB SPIFFS).
O token é a senha do seu bot: sempre apague essa linha antes de compartilhar o código ou publicá-lo em algum lugar. Se achar que o token vazou, gere um novo com o comando /revoke no BotFather. Explicamos com mais detalhes a criação do bot, o chat ID e o certificado em bot do Telegram com ESP32.

Enviando foto ao Telegram: como funciona o sendPhotoByBinary?

A função sendPhotoByBinary() da biblioteca UniversalTelegramBot envia a foto como se fosse um arquivo. Primeiro, ela manda o cabeçalho da requisição e o campo do chat ID; depois, pede os dados a três pequenas funções que você fornece: veriVarMi() diz se ainda há um pedaço a enviar, sonrakiTampon() informa o endereço dos dados na memória e tamponBoyu() diz quantos bytes eles têm. Como o buffer de quadro da câmera já é um JPEG inteiro, o código entrega a foto toda de uma vez e baixa a flag. Uma foto de 1024×768 com qualidade 12 tem de 50 a 120 KB, dependendo da cena, e sobe em 1–2 segundos numa boa conexão Wi-Fi. O Telegram aceita fotos de até 10 MB, então você nunca vai chegar perto do limite de tamanho; o limite real é o tempo de envio.

Código completo: comando /foto, controle do flash e alarme de movimento

C++ (Arduino IDE)
1#include <WiFi.h>
2#include <WiFiClientSecure.h>
3#include <UniversalTelegramBot.h>
4#include <ArduinoJson.h>
5#include <time.h>
6#include "esp_camera.h"
7
8const char* WIFI_SSID = "AgAdi";          // Wi-Fi adın (yalnız 2,4 GHz) / your Wi-Fi name (2.4 GHz only)
9const char* WIFI_PASS = "Sifre123";       // Wi-Fi şifren / your Wi-Fi password
10#define BOT_TOKEN "1234567890:AAExxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"   // BotFather'ın verdiği token / token from BotFather
11#define CHAT_ID   "123456789"             // yalnız bu ID'ye yanıt verilir / only this ID gets answers
12
13// AI-Thinker ESP32-CAM kamera pinleri / AI-Thinker ESP32-CAM camera pins
14#define PWDN_GPIO_NUM     32
15#define RESET_GPIO_NUM    -1
16#define XCLK_GPIO_NUM      0
17#define SIOD_GPIO_NUM     26
18#define SIOC_GPIO_NUM     27
19#define Y9_GPIO_NUM       35
20#define Y8_GPIO_NUM       34
21#define Y7_GPIO_NUM       39
22#define Y6_GPIO_NUM       36
23#define Y5_GPIO_NUM       21
24#define Y4_GPIO_NUM       19
25#define Y3_GPIO_NUM       18
26#define Y2_GPIO_NUM        5
27#define VSYNC_GPIO_NUM    25
28#define HREF_GPIO_NUM     23
29#define PCLK_GPIO_NUM     22
30
31#define FLAS_LED   4                          // beyaz flaş LED'i / white flash LED
32#define PIR_PIN   13                          // PIR OUT (SD kart kullanılmıyor) / PIR OUT (no SD card)
33
34const unsigned long PIR_ISINMA = 60000;       // PIR ilk 60 sn dengelenir / the PIR settles in the first 60 s
35const unsigned long BEKLEME = 30000;          // iki hareket fotoğrafı arası en az 30 sn / at least 30 s between motion photos
36const unsigned long KONTROL_ARALIGI = 1000;   // Telegram'a saniyede bir sor / poll Telegram once per second
37
38WiFiClientSecure client;
39UniversalTelegramBot bot(BOT_TOKEN, client);
40
41camera_fb_t *fb = nullptr;                    // gönderilecek kare / the frame being sent
42bool veriBekliyor = false;                    // kütüphanenin geri çağırma bayrağı / flag for the library callbacks
43bool alarmKurulu = true;                      // hareket alarmı açık mı / is the motion alarm armed
44bool flasAcik = false;                        // fotoğrafta flaş kullanılsın mı / use the flash for photos
45bool pirOnceki = false;
46unsigned long sonFoto = 0;
47unsigned long sonKontrol = 0;
48
49bool kameraBaslat() {
50  camera_config_t config = {};
51  config.ledc_channel = LEDC_CHANNEL_0;
52  config.ledc_timer   = LEDC_TIMER_0;
53  config.pin_d0 = Y2_GPIO_NUM;
54  config.pin_d1 = Y3_GPIO_NUM;
55  config.pin_d2 = Y4_GPIO_NUM;
56  config.pin_d3 = Y5_GPIO_NUM;
57  config.pin_d4 = Y6_GPIO_NUM;
58  config.pin_d5 = Y7_GPIO_NUM;
59  config.pin_d6 = Y8_GPIO_NUM;
60  config.pin_d7 = Y9_GPIO_NUM;
61  config.pin_xclk     = XCLK_GPIO_NUM;
62  config.pin_pclk     = PCLK_GPIO_NUM;
63  config.pin_vsync    = VSYNC_GPIO_NUM;
64  config.pin_href     = HREF_GPIO_NUM;
65  config.pin_sccb_sda = SIOD_GPIO_NUM;
66  config.pin_sccb_scl = SIOC_GPIO_NUM;
67  config.pin_pwdn     = PWDN_GPIO_NUM;
68  config.pin_reset    = RESET_GPIO_NUM;
69  config.xclk_freq_hz = 20000000;             // 20 MHz kamera saati / 20 MHz camera clock
70  config.pixel_format = PIXFORMAT_JPEG;
71
72  if (psramFound()) {
73    config.frame_size   = FRAMESIZE_XGA;      // 1024x768: net ve hızlı gönderilir / sharp and quick to send
74    config.jpeg_quality = 12;                 // küçük = daha kaliteli / lower = better quality
75    config.fb_count     = 2;
76    config.fb_location  = CAMERA_FB_IN_PSRAM;
77    config.grab_mode    = CAMERA_GRAB_LATEST;
78  } else {
79    config.frame_size   = FRAMESIZE_SVGA;     // PSRAM yoksa 800x600 / 800x600 without PSRAM
80    config.jpeg_quality = 12;
81    config.fb_count     = 1;
82    config.fb_location  = CAMERA_FB_IN_DRAM;
83    config.grab_mode    = CAMERA_GRAB_WHEN_EMPTY;
84  }
85  return esp_camera_init(&config) == ESP_OK;
86}
87
88// sendPhotoByBinary için üç geri çağırma: kareyi tek parça verir
89// Three callbacks for sendPhotoByBinary: they hand over the frame in one piece
90bool veriVarMi() {
91  if (veriBekliyor) {
92    veriBekliyor = false;
93    return true;
94  }
95  return false;
96}
97byte *sonrakiTampon() { return fb ? fb->buf : nullptr; }
98int tamponBoyu() { return fb ? fb->len : 0; }
99
100String saat() {                               // "14:05:31" biçiminde saat / time as "14:05:31"
101  struct tm t;
102  if (!getLocalTime(&t, 100)) return "--:--:--";
103  char buf[9];
104  strftime(buf, sizeof(buf), "%H:%M:%S", &t);
105  return String(buf);
106}
107
108bool fotoGonder(const String& chatId, const String& yazi) {
109  if (flasAcik) {
110    digitalWrite(FLAS_LED, HIGH);             // flaşı yak / flash on
111    delay(150);                               // pozlama yeni ışığa uysun / let exposure adapt
112  }
113  fb = esp_camera_fb_get();                   // tampondaki eski kareyi at / discard the stale frame
114  if (fb) esp_camera_fb_return(fb);
115  fb = esp_camera_fb_get();                   // asıl fotoğraf / the real photo
116  digitalWrite(FLAS_LED, LOW);                // flaşı söndür / flash off
117
118  if (!fb) {
119    bot.sendMessage(chatId, "Kamera hatası / camera error");
120    return false;
121  }
122  bot.sendMessage(chatId, yazi);              // önce kısa bildirim / a quick notification first
123  veriBekliyor = true;
124  String cevap = bot.sendPhotoByBinary(chatId, "image/jpeg", fb->len,
125                                       veriVarMi, nullptr, sonrakiTampon, tamponBoyu);
126  Serial.printf("Foto / photo: %u bayt / bytes\n", fb->len);
127  esp_camera_fb_return(fb);                   // kareyi sürücüye geri ver / give the frame back
128  fb = nullptr;
129  return cevap.indexOf("\"ok\":true") >= 0;  // Telegram başarıyı böyle bildirir / how Telegram reports success
130}
131
132void mesajlariIsle(int adet) {
133  for (int i = 0; i < adet; i++) {
134    String chatId = bot.messages[i].chat_id;
135    String metin  = bot.messages[i].text;
136    if (chatId != CHAT_ID) continue;          // yabancılara yanıt yok / never answer strangers
137
138    int at = metin.indexOf('@');              // gruptaki "/foto@bot_adi" -> "/foto" / "/foto@bot_name" -> "/foto"
139    if (at > 0) metin = metin.substring(0, at);
140
141    if (metin == "/start") {
142      String yardim = "/foto - fotoğraf çek / take a photo\n";
143      yardim += "/flas - flaşı aç ya da kapat / toggle the flash\n";
144      yardim += "/alarm - hareket alarmını aç ya da kapat / arm or disarm\n";
145      yardim += "/durum - son durum / status";
146      bot.sendMessage(chatId, yardim);
147    } else if (metin == "/foto") {
148      if (!fotoGonder(chatId, "Fotoğraf / photo " + saat())) {
149        bot.sendMessage(chatId, "Gönderilemedi / send failed");
150      }
151    } else if (metin == "/flas") {
152      flasAcik = !flasAcik;
153      bot.sendMessage(chatId, flasAcik ? "Flaş AÇIK / flash ON" : "Flaş KAPALI / flash OFF");
154    } else if (metin == "/alarm") {
155      alarmKurulu = !alarmKurulu;
156      bot.sendMessage(chatId, alarmKurulu ? "Alarm KURULU / ARMED" : "Alarm KAPALI / DISARMED");
157    } else if (metin == "/durum") {
158      String s = "Alarm: " + String(alarmKurulu ? "KURULU / ARMED" : "KAPALI / DISARMED");
159      s += "\nFlaş / flash: " + String(flasAcik ? "AÇIK / ON" : "KAPALI / OFF");
160      s += "\nWi-Fi: " + String(WiFi.RSSI()) + " dBm";
161      bot.sendMessage(chatId, s);
162    }
163  }
164}
165
166void setup() {
167  Serial.begin(115200);
168  pinMode(FLAS_LED, OUTPUT);
169  digitalWrite(FLAS_LED, LOW);
170  pinMode(PIR_PIN, INPUT_PULLDOWN);           // PIR bağlı değilken pin boşta kalmasın / keep the pin defined without a PIR
171
172  if (!kameraBaslat()) {
173    Serial.println("Kamera baslatilamadi / camera init failed");
174    delay(3000);
175    ESP.restart();
176  }
177
178  WiFi.mode(WIFI_STA);
179  WiFi.begin(WIFI_SSID, WIFI_PASS);
180  while (WiFi.status() != WL_CONNECTED) delay(500);
181  Serial.println(WiFi.localIP());
182
183  configTzTime("<+03>-3", "pool.ntp.org");    // Türkiye saati (UTC+3) / Turkey time (UTC+3)
184  client.setCACert(TELEGRAM_CERTIFICATE_ROOT); // api.telegram.org kök sertifikası / root certificate
185  bot.sendMessage(CHAT_ID, "Kamera hazır, /start yaz / camera ready, send /start");
186}
187
188void loop() {
189  bool pir = digitalRead(PIR_PIN);
190  // Yalnız yükselen kenarda, ısınmadan sonra ve bekleme süresi dolunca
191  // Only on a rising edge, after warm-up and once the cooldown has passed
192  if (pir && !pirOnceki && alarmKurulu && millis() > PIR_ISINMA && millis() - sonFoto > BEKLEME) {
193    sonFoto = millis();
194    fotoGonder(CHAT_ID, "Hareket algılandı! / Motion detected! " + saat());
195  }
196  pirOnceki = pir;
197
198  if (millis() - sonKontrol > KONTROL_ARALIGI) {
199    int adet = bot.getUpdates(bot.last_message_received + 1);
200    while (adet) {
201      mesajlariIsle(adet);
202      adet = bot.getUpdates(bot.last_message_received + 1);
203    }
204    sonKontrol = millis();
205  }
206}
ParteO que faz
kameraBaslat() (inicia a câmera)Inicia a câmera em XGA (1024×768) com dois buffers de quadro se houver PSRAM; caso contrário, em SVGA (800×600)
fotoGonder() (envia a foto)Liga o flash se for pedido, descarta o quadro antigo e captura um novo, envia primeiro uma mensagem curta e depois a foto
mesajlariIsle() (processa as mensagens)Responde apenas aos comandos /start, /foto, /flas, /alarm e /durum vindos do CHAT_ID
Parte do PIR no loop()Verifica a borda de subida, o aquecimento de 60 s e o tempo de espera de 30 s
Parte do Telegram no loop()Pergunta por mensagens novas uma vez por segundo e processa em sequência as que estiverem acumuladas

O código observa a borda de subida do PIR, não o nível: mesmo que o sensor fique em HIGH por 3 segundos, só uma foto é tirada. O tempo de espera de 30 segundos impede que alguém andando pelo cômodo encha seu celular com dezenas de fotos por minuto e evita que o Telegram limite mais de uma mensagem por segundo para a mesma conversa. Como cada consulta é uma requisição HTTPS, a chamada getUpdates pode segurar o loop por cerca de meio segundo; como a saída do HC-SR501 fica em HIGH por no mínimo 3 segundos, nenhum movimento se perde nesse meio-tempo. Com a placa sempre ligada, costumam se passar 2–4 segundos entre o movimento e a chegada da foto no celular.

Controle do flash: quando usar?

O LED branco da frente é acionado por um transistor ligado ao GPIO 4 e puxa mais de 100 mA da alimentação no brilho máximo. O código acende o flash só no momento da foto e espera cerca de 150 ms; como a exposição automática da câmera precisa de alguns quadros para se adaptar à nova luz, o primeiro quadro do buffer também é descartado. O flash é eficaz até 1–2 metros; uma porta ou um jardim mais distante não fica iluminado. Se for fotografar através de um vidro, desligue o flash, senão a foto vai mostrar só o reflexo forte no vidro. Se quiser um flash mais fraco, aplique PWM ao GPIO 4; o controle deslizante LED Intensity do exemplo CameraWebServer faz exatamente isso. Lembre-se de que um flash branco disparando à noite também revela onde está a câmera; para um monitoramento discreto, a seção de IR abaixo é mais adequada.

Economizando bateria com deep sleep

Um ESP32-CAM com Wi-Fi ligado puxa 150–200 mA o tempo todo, o que esgota uma célula 18650 de 2600 mAh em meio dia. Se for usar bateria, a placa precisa passar quase todo o tempo em deep sleep e acordar só quando o PIR disparar. No código abaixo, o GPIO 13 é a fonte de despertar ext0: quando a saída do PIR vai para HIGH, a placa inicia do zero, tira a foto primeiro, depois se conecta ao Wi-Fi, envia a foto e volta a dormir. A ordem importa: a conexão Wi-Fi leva alguns segundos, e tirar a foto antes de conectar captura o momento exato do movimento.

C++ (Arduino IDE)
1#include <WiFi.h>
2#include <WiFiClientSecure.h>
3#include <UniversalTelegramBot.h>
4#include <ArduinoJson.h>
5#include "esp_camera.h"
6#include "driver/rtc_io.h"
7
8const char* WIFI_SSID = "AgAdi";
9const char* WIFI_PASS = "Sifre123";
10#define BOT_TOKEN "1234567890:AAExxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
11#define CHAT_ID   "123456789"
12
13// AI-Thinker ESP32-CAM kamera pinleri / AI-Thinker ESP32-CAM camera pins
14#define PWDN_GPIO_NUM     32
15#define RESET_GPIO_NUM    -1
16#define XCLK_GPIO_NUM      0
17#define SIOD_GPIO_NUM     26
18#define SIOC_GPIO_NUM     27
19#define Y9_GPIO_NUM       35
20#define Y8_GPIO_NUM       34
21#define Y7_GPIO_NUM       39
22#define Y6_GPIO_NUM       36
23#define Y5_GPIO_NUM       21
24#define Y4_GPIO_NUM       19
25#define Y3_GPIO_NUM       18
26#define Y2_GPIO_NUM        5
27#define VSYNC_GPIO_NUM    25
28#define HREF_GPIO_NUM     23
29#define PCLK_GPIO_NUM     22
30
31#define FLAS_LED   4                          // flaş LED'i (RTC pini) / flash LED (RTC pin)
32#define PIR_PIN   13                          // PIR OUT, ext0 ile uyandırır / PIR OUT, wakes via ext0
33
34RTC_DATA_ATTR int hareketSayisi = 0;          // uykuda korunur / survives deep sleep
35
36WiFiClientSecure client;
37UniversalTelegramBot bot(BOT_TOKEN, client);
38camera_fb_t *fb = nullptr;
39bool veriBekliyor = false;
40
41bool veriVarMi() {
42  if (veriBekliyor) {
43    veriBekliyor = false;
44    return true;
45  }
46  return false;
47}
48byte *sonrakiTampon() { return fb ? fb->buf : nullptr; }
49int tamponBoyu() { return fb ? fb->len : 0; }
50
51bool kameraBaslat() {
52  camera_config_t config = {};
53  config.ledc_channel = LEDC_CHANNEL_0;
54  config.ledc_timer   = LEDC_TIMER_0;
55  config.pin_d0 = Y2_GPIO_NUM;
56  config.pin_d1 = Y3_GPIO_NUM;
57  config.pin_d2 = Y4_GPIO_NUM;
58  config.pin_d3 = Y5_GPIO_NUM;
59  config.pin_d4 = Y6_GPIO_NUM;
60  config.pin_d5 = Y7_GPIO_NUM;
61  config.pin_d6 = Y8_GPIO_NUM;
62  config.pin_d7 = Y9_GPIO_NUM;
63  config.pin_xclk     = XCLK_GPIO_NUM;
64  config.pin_pclk     = PCLK_GPIO_NUM;
65  config.pin_vsync    = VSYNC_GPIO_NUM;
66  config.pin_href     = HREF_GPIO_NUM;
67  config.pin_sccb_sda = SIOD_GPIO_NUM;
68  config.pin_sccb_scl = SIOC_GPIO_NUM;
69  config.pin_pwdn     = PWDN_GPIO_NUM;
70  config.pin_reset    = RESET_GPIO_NUM;
71  config.xclk_freq_hz = 20000000;
72  config.pixel_format = PIXFORMAT_JPEG;
73  config.frame_size   = psramFound() ? FRAMESIZE_XGA : FRAMESIZE_SVGA;
74  config.jpeg_quality = 12;
75  config.fb_count     = 1;                    // tek kare yeter / one frame is enough
76  config.fb_location  = psramFound() ? CAMERA_FB_IN_PSRAM : CAMERA_FB_IN_DRAM;
77  config.grab_mode    = CAMERA_GRAB_WHEN_EMPTY;
78  return esp_camera_init(&config) == ESP_OK;
79}
80
81void uyu() {
82  // PIR çıkışı LOW'a dönmeden uyursan kart hemen yeniden uyanır
83  // If you sleep while the PIR output is still HIGH, the board wakes straight away
84  unsigned long t = millis();
85  while (digitalRead(PIR_PIN) == HIGH && millis() - t < 30000) delay(100);
86
87  digitalWrite(FLAS_LED, LOW);
88  rtc_gpio_hold_en(GPIO_NUM_4);               // flaş uykuda sönük kalsın / keep the flash off while asleep
89  esp_sleep_enable_ext0_wakeup(GPIO_NUM_13, 1);   // GPIO 13 HIGH olunca uyan / wake when GPIO 13 goes HIGH
90  Serial.println("Uyku / sleep");
91  Serial.flush();
92  esp_deep_sleep_start();
93}
94
95void setup() {
96  Serial.begin(115200);
97  rtc_gpio_hold_dis(GPIO_NUM_4);              // flaş pinini serbest bırak / release the flash pin
98  rtc_gpio_deinit(GPIO_NUM_13);               // pini yeniden normal GPIO yap / make the pin a normal GPIO again
99  pinMode(FLAS_LED, OUTPUT);
100  digitalWrite(FLAS_LED, LOW);
101  pinMode(PIR_PIN, INPUT);
102
103  if (esp_sleep_get_wakeup_cause() != ESP_SLEEP_WAKEUP_EXT0) {
104    Serial.println("Ilk acilis: PIR 60 sn isiniyor / first boot: PIR warming up 60 s");
105    delay(60000);
106    uyu();
107  }
108  hareketSayisi++;
109
110  // 1) Fotoğrafı hemen çek, Wi-Fi'yi sonra aç: an kaçmasın
111  // 1) Take the photo at once and start Wi-Fi later, so the moment is not lost
112  if (kameraBaslat()) {
113    for (int i = 0; i < 3; i++) {             // ilk kareler karanlık ya da yeşil olur / first frames are dark or green
114      fb = esp_camera_fb_get();
115      if (fb) esp_camera_fb_return(fb);
116    }
117    fb = esp_camera_fb_get();
118  }
119
120  // 2) Wi-Fi'ye bağlan, en fazla 15 sn bekle / connect to Wi-Fi, wait at most 15 s
121  WiFi.mode(WIFI_STA);
122  WiFi.begin(WIFI_SSID, WIFI_PASS);
123  unsigned long t0 = millis();
124  while (WiFi.status() != WL_CONNECTED && millis() - t0 < 15000) delay(100);
125
126  // 3) Gönder ve uyu / send and go back to sleep
127  if (WiFi.status() == WL_CONNECTED) {
128    client.setCACert(TELEGRAM_CERTIFICATE_ROOT);
129    bot.sendMessage(CHAT_ID, "Hareket / motion #" + String(hareketSayisi));
130    if (fb) {
131      veriBekliyor = true;
132      bot.sendPhotoByBinary(CHAT_ID, "image/jpeg", fb->len,
133                            veriVarMi, nullptr, sonrakiTampon, tamponBoyu);
134    } else {
135      bot.sendMessage(CHAT_ID, "Kamera hatası / camera error");
136    }
137  }
138  if (fb) esp_camera_fb_return(fb);
139  Serial.printf("Uyanik kalma / awake: %lu ms\n", millis());
140  uyu();
141}
142
143void loop() {
144}

Há quatro detalhes no código que passam despercebidos com facilidade. Primeiro, a placa espera a saída do PIR voltar a LOW antes de dormir; se ela dormir com o sinal ainda em HIGH, acorda na hora e fica presa num ciclo. Segundo, rtc_gpio_hold_en() trava o pino do flash em LOW durante o sono; sem essa linha, o GPIO 4 fica flutuando e o flash brilha fraquinho enquanto a placa dorme, gastando bateria e chamando atenção. Terceiro, depois de acordar, rtc_gpio_deinit() transforma o GPIO 13 de novo em uma entrada normal; caso contrário, o digitalRead() pode ler errado. Quarto, o contador marcado com RTC_DATA_ATTR sobrevive ao sono, então cada mensagem informa qual evento de movimento é. Todos os modos de sono e fontes de despertar estão em deep sleep no ESP32.

CenárioCorrente média (lado da bateria)Com uma 18650 de 2600 mAh
Sempre ligada, Wi-Fi conectado (primeiro sketch)≈ 290 mA≈ 9 horas
Deep sleep + 20 eventos de movimento por dia≈ 11 mA≈ 10 dias
Circuito de trava de alimentação (placa totalmente desligada) + 20 eventos de movimento por dia≈ 0,7 mA≈ 4–5 meses (teórico)

Os valores da tabela consideram que o conversor elevador de 5 V trabalha com 85% de eficiência. Embora o chip ESP32 em si consuma só uns 10 µA em deep sleep, a placa ESP32-CAM ainda puxa cerca de 6 mA dormindo por causa do regulador, da PSRAM e da câmera, e é isso que de fato define a autonomia. Cada despertar leva 6–10 segundos: boot, câmera, conexão Wi-Fi e duas requisições HTTPS. Se você quer meses de autonomia, precisa de um circuito de trava de alimentação (power latch), que corta totalmente a alimentação da placa com um MOSFET, liga a placa quando o PIR dispara e deixa a própria placa se desligar ao terminar. Você pode calcular o seu cenário com a ferramenta Autonomia da bateria e, para reinicializações frequentes, veja brownout no ESP32.

Fotos noturnas: flash, LEDs IR e ajustes da câmera

As lentes padrão do ESP32-CAM têm um filtro IR-cut fixo que bloqueia a luz infravermelha; por isso as cores parecem naturais de dia, mas uma cena iluminada só por IR fica escura. Há duas formas de enxergar no escuro: o flash branco ou iluminação IR com uma lente sem filtro. Os módulos OV2640 vendidos como night vision ou 850 nm não têm esse filtro; de dia as cores ficam rosadas, mas uma cena iluminada por LEDs IR de 850 nm aparece nítida em preto e branco. LEDs de 940 nm são totalmente invisíveis, porém o OV2640 é bem menos sensível nesse comprimento de onda, e a imagem fica visivelmente mais escura. Alimente o iluminador IR com uma fonte separada de 5 V ou 12 V; módulos prontos com sensor de luz acendem sozinhos quando escurece. Se quiser controlá-lo por um GPIO, use um MOSFET e nunca ligue os LEDs direto no pino.

C++ (Arduino IDE)
1#include "esp_camera.h"
2void setup() {
3// esp_camera_init() başarılı olduktan sonra / after esp_camera_init() succeeds
4sensor_t *s = esp_camera_sensor_get();
5s->set_gainceiling(s, GAINCEILING_32X);   // karanlıkta daha yüksek kazanç / more gain in the dark
6s->set_aec2(s, 1);                        // DSP pozlaması: gece modu / DSP exposure: night mode
7s->set_ae_level(s, 2);                    // pozlamayı artır (-2..2) / raise exposure (-2..2)
8s->set_whitebal(s, 1);                    // otomatik beyaz dengesi / auto white balance
9s->set_special_effect(s, 2);              // 2 = siyah-beyaz: IR'de renk zaten bozulur / 2 = grayscale: IR spoils colour anyway
10}
11void loop() {}

Esses ajustes elevam o teto de ganho do sensor e o tempo de exposição. O preço é que a taxa de quadros cai no escuro, uma pessoa em movimento sai borrada e o ganho alto deixa a imagem granulada. A luz continua sendo a forma mais eficaz de melhorar uma foto noturna; até um iluminador IR fraco dá um resultado mais nítido do que aumentar o ganho. Você pode adicionar o código logo após a chamada kameraBaslat() e, se quiser, medir a luz ambiente com um LDR para alternar automaticamente entre os ajustes de dia e de noite.

Limites como câmera de segurança

  • Não grava vídeo: Este sistema só envia fotos. Para gravação contínua ou para rever imagens passadas, você precisa de uma câmera de segurança de verdade.
  • Há atraso: 2–4 segundos com a placa ligada, 6–10 segundos quando ela acorda do deep sleep; alguém que passe rápido pode já ter saído do enquadramento.
  • O PIR não enxerga através de vidro: O vidro não deixa passar a radiação térmica infravermelha. Um sensor atrás de uma janela não detecta movimento do lado de fora, mas o sol e aquecedores ainda podem causar disparos falsos.
  • Depende da internet: Se o Wi-Fi ou a internet caírem, a foto não é enviada. Para uso crítico, salvar a foto também num cartão SD garante um backup.
  • Não tem proteção física: A placa é fácil de remover ou desligar e, ao ar livre, sofre com umidade e sol; se for usar do lado de fora, você precisa de uma caixa vedada e de uma alimentação que aguente a temperatura.
  • A qualidade da imagem é limitada: O OV2640 de 2 MP não basta para reconhecer rostos ou ler placas de carro a poucos metros; lentes grande-angulares ampliam o enquadramento, mas reduzem os detalhes.
  • Regras de privacidade: Aponte a câmera para a sua própria propriedade; antes de gravar o quintal do vizinho, áreas comuns ou a rua, confira as regras legais do lugar onde você mora.

Ideias de projetos com ESP32-CAM e Telegram

  • Câmera de campainha: Ligue o botão da campainha ao GPIO 13 para que, ao ser pressionado, a placa fotografe a sua porta e envie a foto para você; veja como ligar um botão para a ligação.
  • Aviso na caixa de correio: Coloque uma chave reed na tampa para que, ao abri-la, a placa acorde e envie uma foto do que tem dentro.
  • Câmera de ninho de passarinho ou de pet: Tire uma foto quando houver movimento e espie durante o dia com /foto.
  • Olhos para um sistema de alarme: Faça um sistema de alarme com Arduino avisar o ESP32-CAM quando disparar, para que ele fotografe a cena.
  • Monitoramento de plantas e estufa: Acorde por temporizador uma vez por hora para enviar uma foto e mande suas medições para um painel com MQTT e Home Assistant.
  • Grupo da família: Adicione o bot a um grupo do Telegram e use o ID do grupo, assim a foto chega para todos ao mesmo tempo.

Problemas comuns e soluções

SintomaCausa provávelSolução
A mensagem chega, mas a foto nãoO quadro é grande demais, a PSRAM está desativada ou o envio foi interrompidoSelecione a placa AI Thinker ESP32-CAM, reduza a resolução para SVGA, melhore o sinal do Wi-Fi
O bot não responde a nenhum comandoToken ou CHAT_ID errado, ou você nunca enviou /start ao botCopie o token de novo, confira seu ID com o @userinfobot, envie /start ao seu bot
Chegam fotos de movimento sem motivoO PIR está captando o sinal da antena Wi-Fi ou uma fonte de calorAfaste o PIR 10–20 cm da placa, coloque 100 nF entre VCC-GND, não aponte o sensor para o sol nem para aquecedores
A placa acorda de novo logo depois de dormirA saída do PIR ainda está em HIGH quando ela dormeEspere o pino voltar a LOW antes de dormir, ajuste o trimpot de tempo do HC-SR501 no mínimo
O flash brilha fraquinho durante o sonoO GPIO 4 fica flutuandoEscreva LOW e chame rtc_gpio_hold_en(GPIO_NUM_4) antes de dormir
A foto sai escura ou esverdeadaSão os primeiros quadros logo após a câmera iniciarCapture e descarte 2–3 quadros antes da foto de verdade
A placa reinicia ao enviar uma fotoBrownout: a alimentação não dá conta da corrente do Wi-Fi e do flashUse um adaptador de 5 V / 2 A, fios curtos e um capacitor de 470–1000 µF entre 5V-GND
Ao enviar para um grupo, o ID do grupo começa com sinal de menos (ex.: -1001234567890); escreva-o na linha CHAT_ID junto com o sinal. Para o bot ler as mensagens do grupo, talvez seja preciso desativar o /setprivacy no BotFather ou enviar os comandos como /foto@nome_do_bot; o código já remove esses sufixos.

Combinado com o Telegram, o ESP32-CAM transforma uma placa barata em um olho inteligente que envia fotos para o seu celular. Monte o sistema primeiro com o sketch sempre ligado e ajuste o PIR; depois, se precisar, passe para a versão com deep sleep. No app BlueGrays você encontra os projetos de alarme de movimento com Telegram usando PIR, buzzer e LED, deep sleep no ESP32 e iluminação automática com PIR, cada um com esquema do circuito passo a passo e código pronto.

Perguntas frequentes

Como enviar uma foto do ESP32-CAM para o Telegram?

Você cria um bot com o BotFather para obter um token, instala a biblioteca UniversalTelegramBot e envia o buffer de quadro da câmera para o endpoint sendPhoto do Telegram com a função sendPhotoByBinary(). A biblioteca manda a foto via HTTPS no formato multipart, como o envio de arquivo num navegador. No código, basta alterar os dados do Wi-Fi, o token e o seu próprio chat ID.

Em qual pino do ESP32-CAM eu ligo o sensor PIR?

Se você não usa cartão SD, o GPIO 13 é a escolha mais segura: não afeta a inicialização e, por ser um pino RTC, consegue acordar a placa do deep sleep. Ligue o VCC do PIR ao pino 5V, o GND ao GND e o OUT ao GPIO 13. Evite o GPIO 12; se ele estiver em HIGH no boot, a placa não inicia.

Quanto tempo o ESP32-CAM dura com bateria?

Com o Wi-Fi ligado o tempo todo, uma célula 18650 de 2600 mAh dura cerca de 9 horas. Se você usar deep sleep e acordar só com movimento, isso sobe para uns 10 dias; mesmo dormindo, a placa puxa cerca de 6 mA por causa do regulador e da PSRAM. Para meses de autonomia, você precisa de um circuito de trava de alimentação que corte totalmente a alimentação da placa.

O ESP32-CAM enxerga à noite?

O filtro IR-cut dos módulos padrão bloqueia a luz infravermelha, então no escuro você só consegue imagem até 1–2 metros com o flash branco. Com um módulo OV2640 sem filtro (night vision) e um iluminador IR de 850 nm, dá para tirar fotos nítidas em preto e branco no escuro. Aumentar o teto de ganho nos ajustes da câmera também ajuda, mas deixa a imagem granulada.

O ESP32-CAM consegue enviar vídeo para o Telegram?

Na prática, não. O ESP32-CAM gera quadros JPEG individuais e não tem poder de processamento nem memória para transformá-los num vídeo MP4. Enviar várias fotos em sequência ou assistir à imagem ao vivo na rede de casa com o CameraWebServer são opções mais realistas.

Dá para enviar a foto para mais de uma pessoa?

Sim. Se você adicionar o bot a um grupo do Telegram e usar como CHAT_ID o ID do grupo, que começa com sinal de menos, a foto chega para todos do grupo ao mesmo tempo. Outra opção é colocar vários chat IDs num array no código e enviar a foto para cada um em sequência; cada envio é um upload separado, então demora mais.

Ver todos os projetos
Alarme de movimento com ESP32 (PIR + buzzer + LED + Telegram)
#145Avançado
Premium

Alarme de movimento com ESP32 (PIR + buzzer + LED + Telegram)

Um sistema de alarme de segurança inteligente que aciona o buzzer e o LED quando o sensor PIR detecta movimento e, ao mesmo tempo, envia uma notificação instantânea ao proprietário pelo Telegram. Inclui um intervalo de espera de 30 segundos para limitar a frequência dos alarmes repetidos; esse mecanismo não valida as detecções do PIR nem elimina disparos falsos. Pode ser usado na segurança de casas, no monitoramento de garagens, depósitos ou salas de trabalho.

0.0(0)
9
Iluminação automática com ESP32 e sensor de movimento PIR
#121Intermediário
Premium

Iluminação automática com ESP32 e sensor de movimento PIR

Neste projeto, usaremos o sensor de movimento PIR HC-SR501 para detectar quando alguém entra no cômodo e acender o LED automaticamente. Acompanharemos o tempo em que o LED fica aceso com millis(). Cada leitura HIGH do sensor atualizará o instante do último movimento, e o LED apagará pelo menos 10 segundos após a última leitura HIGH. O código atual também espera 50 ms em cada ciclo com delay(50), portanto o laço da aplicação não é totalmente livre de bloqueios.

4.4(24)
38
ESP32 em sono profundo — Economia de bateria (OLED + contador na RAM RTC)
#135Avançado
Premium

ESP32 em sono profundo — Economia de bateria (OLED + contador na RAM RTC)

O modo de sono profundo do ESP32 reduz o consumo de corrente para ~10 µA ao desligar a CPU e a maioria dos periféricos, sendo essencial para dispositivos IoT alimentados por bateria. Neste projeto, usamos três fontes de despertar: um temporizador (15 segundos), um pino externo (EXT0 — botão no GPIO 33) e toque (T0/GPIO 4). As variáveis marcadas com RTC_DATA_ATTR são preservadas durante o sono profundo, mantendo os contadores de inicializações e de despertares por temporizador e botão.

4.3(10)
16
Iluminação inteligente com ESP32 (PIR + LDR + relé + OLED)
#141Avançado
Premium

Iluminação inteligente com ESP32 (PIR + LDR + relé + OLED)

Transforme a iluminação do corredor, banheiro ou despensa em iluminação inteligente com ESP32! Com um sensor de movimento PIR, um sensor de luz LDR e um módulo relé, montamos o cenário clássico de "acender a luz ao detectar movimento no escuro" — se o ambiente já estiver claro, o movimento NÃO aciona a luz, economizando energia durante o dia. A luz fica acesa por 30 segundos; se o movimento continuar, o temporizador reinicia e a luz permanece acesa por mais tempo.

4.3(6)
11
Conexão Wi-Fi STA com ESP32 e display LCD
#127Avançado

Conexão Wi-Fi STA com ESP32 e display LCD

Neste projeto, conectamos o ESP32 à rede Wi-Fi de casa no modo estação (STA), de forma segura e tolerante a falhas. Guardamos o SSID e a senha em um arquivo secrets.h separado para evitar que sejam expostos no Git, configuramos um mecanismo de reconexão automática e exibimos o nível de sinal RSSI e o endereço IP em um display LCD I2C. O LED integrado funciona como indicador de status. É um gerenciador de conexão pronto para uso em produção.

4.4(21)
34
ESP32 mDNS: acesso via bluegrays-esp.local (com display LCD)
#131Avançado
Premium

ESP32 mDNS: acesso via bluegrays-esp.local (com display LCD)

Neste projeto, adicionamos o serviço mDNS (Multicast DNS) ao ESP32 para acessar o dispositivo por um nome de host fácil de ler, como bluegrays-esp.local, em vez de um endereço IP. Mesmo que o IP atribuído pelo DHCP mude, você pode acessar o dispositivo pelo mesmo nome. Também executamos um pequeno servidor web no dispositivo para enviar comandos de controle do LED pelo navegador no endereço http://bluegrays-esp.local/.

4.8(14)
23
Detector de movimento com sensor de movimento
#24Iniciante
Premium

Detector de movimento com sensor de movimento

Um vigia simples que avisa quando alguém entra no cômodo. O sensor de movimento detecta o calor emitido pelo corpo humano, e o Arduino mantém o LED aceso enquanto há movimento e o apaga quando ele par

4.6(47)
67