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.

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.
| Componente | Especificação | Observação |
|---|---|---|
| ESP32-CAM (AI-Thinker) | OV2640, 4 MB PSRAM | A versão com PSRAM lida tranquilamente com fotos XGA |
| ESP32-CAM-MB ou conversor USB-TTL | CH340 / CP2102 | Para carregar o código; a placa não tem USB |
| Sensor PIR | HC-SR501 (5 V) ou AM312 (3,3 V) | O sinal OUT é de 3,3 V e vai direto ao ESP32 |
| Fonte de alimentação | 5 V, no mínimo 2 A | O Wi-Fi e o flash puxam corrente alta ao mesmo tempo |
| Capacitor eletrolítico | 470–1000 µF | Entre 5V e GND, perto da placa |
| Opcional | Célula 18650 + conversor elevador de 5 V, iluminador IR de 850 nm | Para 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.
| Pino | Sem cartão SD | Acorda do deep sleep? | Observação |
|---|---|---|---|
| GPIO 13 | Livre | Sim (ext0) | A melhor escolha para o PIR |
| GPIO 14 | Livre | Sim | Pode mostrar um breve sinal PWM no boot; funciona bem como entrada |
| GPIO 15 | Livre | Sim | Pino de strapping; mantê-lo em LOW silencia as mensagens de boot |
| GPIO 2 | Livre | Sim | Pino de strapping; não pode estar em HIGH durante o carregamento |
| GPIO 12 | Livre | Sim | Se estiver em HIGH no boot, a placa não inicia; não use |
| GPIO 4 | LED do flash | Sim | Reservado para o flash; não use para outra coisa |
| GPIO 3 (U0R) | RX serial | Não | Só se você não precisar da porta serial |
| GPIO 16 | PSRAM | Não | Nunca use |
| Pino do PIR | Pino do ESP32-CAM | Descrição |
|---|---|---|
| VCC | 5V | O HC-SR501 precisa de pelo menos 4,5 V; use 3V3 para o AM312 |
| OUT | GPIO 13 | 3,3 V quando há movimento, 0 V quando não há |
| GND | GND | GND comum |
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
- Abra o @BotFather no Telegram, digite /newbot e dê ao seu bot um nome e um nome de usuário que termine em bot.
- Copie o token que o BotFather fornece, parecido com 1234567890:AAE...; ele vai na linha BOT_TOKEN do código.
- Para descobrir seu número de usuário, envie /start ao @userinfobot; o valor Id que ele retorna é o seu CHAT_ID.
- 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.
- No Gerenciador de Bibliotecas do Arduino IDE, instale as bibliotecas UniversalTelegramBot (Brian Lough) e ArduinoJson.
- Escolha AI Thinker ESP32-CAM em Ferramentas > Placa e confira se o Partition Scheme está em Huge APP (3MB No OTA/1MB SPIFFS).
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
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}| Parte | O 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.
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ário | Corrente 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.
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
| Sintoma | Causa provável | Solução |
|---|---|---|
| A mensagem chega, mas a foto não | O quadro é grande demais, a PSRAM está desativada ou o envio foi interrompido | Selecione 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 comando | Token ou CHAT_ID errado, ou você nunca enviou /start ao bot | Copie o token de novo, confira seu ID com o @userinfobot, envie /start ao seu bot |
| Chegam fotos de movimento sem motivo | O PIR está captando o sinal da antena Wi-Fi ou uma fonte de calor | Afaste 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 dormir | A saída do PIR ainda está em HIGH quando ela dorme | Espere 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 sono | O GPIO 4 fica flutuando | Escreva LOW e chame rtc_gpio_hold_en(GPIO_NUM_4) antes de dormir |
| A foto sai escura ou esverdeada | São os primeiros quadros logo após a câmera iniciar | Capture e descarte 2–3 quadros antes da foto de verdade |
| A placa reinicia ao enviar uma foto | Brownout: a alimentação não dá conta da corrente do Wi-Fi e do flash | Use um adaptador de 5 V / 2 A, fios curtos e um capacitor de 470–1000 µF entre 5V-GND |
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.
Projetos para montar depois desta leitura
Ver todos os projetos
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.

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.

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.

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.

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.

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

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
Mais artigos
- Tutorial do ESP32-CAM: configuração, programação, transmissão ao vivo e cartão SD
- Bot do Telegram com ESP32: alertas, controle de LED e relé
- Sensor de movimento PIR (HC-SR501): detecção de movimento com Arduino, ESP32 e Pico
- ESP32 deep sleep: fontes de despertar, consumo de corrente e autonomia da bateria
App BlueGrays
Monte este circuito passo a passo pelo celular
Esquema do circuito, código e lista de componentes em uma só tela. Mais de 200 projetos com Arduino, ESP32 e Pico prontos para montar.
Escaneie com seu celular
Aponte a câmera para o código para abrir a loja de aplicativos do seu celular.
ou abrir a loja

