ESP32-CAM Telegram: inviare una foto quando rileva movimento
Invia foto dalla ESP32-CAM a Telegram: collegamento del PIR e pin libero, comando /foto, flash, deep sleep per la batteria, foto notturne e codice completo.
Quando configuri un bot ESP32-CAM Telegram, la scheda scatta una foto nell'istante in cui rileva un movimento e te la invia sul telefono in pochi secondi. Non devi installare app, noleggiare un server né aprire porte sul router: la scheda carica la foto direttamente sui server di Telegram, che te la consegna come notifica istantanea. Con lo stesso bot puoi scrivere /foto per vedere la porta di casa quando vuoi, accendere o spegnere il flash e disattivare l'allarme.
In questa guida vedrai passo passo a quale dei pochi pin liberi della ESP32-CAM collegare un sensore PIR, come la foto viene caricata su Telegram, il codice completo comandato da messaggi e come usare bene il flash. Poi farai funzionare la scheda a batteria per giorni con il deep sleep, scatterai foto notturne con l'illuminazione IR e scoprirai dove questo sistema non basta come telecamera di sicurezza. Se non hai ancora configurato la scheda e caricato il primo sketch, leggi prima Come usare la ESP32-CAM.

Come funziona un sistema di foto ESP32-CAM con Telegram?
La catena è fatta di quattro passi. Il sensore PIR nota una variazione del calore emesso da un corpo umano e porta il pin OUT a 3,3 V. Quando l'ESP32 vede questo fronte di salita, chiede alla fotocamera OV2640 un fotogramma JPEG, che viene scritto nei 4 MB di PSRAM della scheda. L'ESP32 apre quindi una connessione HTTPS cifrata e invia una richiesta POST in formato multipart/form-data al metodo sendPhoto della Bot API di Telegram; è lo stesso metodo che usa un browser per caricare un file. Telegram aggiunge la foto alla tua chat e il telefono mostra una notifica. I comandi viaggiano nella direzione opposta: ogni secondo l'ESP32 chiede con getUpdates se ci sono nuovi messaggi e, se è arrivato /foto, esegue lo stesso caricamento per la tua richiesta.
| Componente | Caratteristica | Nota |
|---|---|---|
| ESP32-CAM (AI-Thinker) | OV2640, 4 MB di PSRAM | La versione con PSRAM gestisce senza problemi le foto XGA |
| ESP32-CAM-MB o convertitore USB-TTL | CH340 / CP2102 | Per caricare il codice; la scheda non ha USB |
| Sensore PIR | HC-SR501 (5 V) o AM312 (3,3 V) | L'uscita OUT è a 3,3 V e si collega direttamente all'ESP32 |
| Alimentazione | 5 V, almeno 2 A | Wi-Fi e flash assorbono molta corrente nello stesso momento |
| Condensatore elettrolitico | 470–1000 µF | Tra 5V e GND, vicino alla scheda |
| Opzionale | Batteria 18650 + step-up a 5 V, illuminatore IR da 850 nm | Per il funzionamento a batteria e le foto notturne |
Collegamento del sensore PIR e scelta di un pin libero
La ESP32-CAM ha 16 pin sui bordi, ma la fotocamera da sola usa 15 GPIO e lo slot microSD ne condivide altri sei. Gran parte di quelli rimasti sono pin di avvio (strapping) oppure servono alla porta seriale. Dato che questo progetto non usa la scheda SD, le linee SD sono libere e la più sicura è GPIO 13: non influisce sull'avvio, ha una resistenza di pull-down interna e, essendo un pin RTC, può risvegliare la scheda dal deep sleep. La tabella qui sotto confronta anche gli altri candidati.
| Pin | Senza scheda SD | Risveglia dal deep sleep? | Nota |
|---|---|---|---|
| GPIO 13 | Libero | Sì (ext0) | La scelta migliore per il PIR |
| GPIO 14 | Libero | Sì | All'avvio può emettere un breve segnale PWM; nessun problema come ingresso |
| GPIO 15 | Libero | Sì | Pin di avvio; se tenuto LOW zittisce i messaggi di avvio |
| GPIO 2 | Libero | Sì | Pin di avvio; non deve essere HIGH durante il caricamento |
| GPIO 12 | Libero | Sì | Se è HIGH all'avvio la scheda non parte; non usarlo |
| GPIO 4 | LED flash | Sì | Riservato al flash; non usarlo per altro |
| GPIO 3 (U0R) | RX seriale | No | Solo se non ti serve la porta seriale |
| GPIO 16 | PSRAM | No | Non usarlo mai |
| Pin del PIR | Pin della ESP32-CAM | Descrizione |
|---|---|---|
| VCC | 5V | L'HC-SR501 richiede almeno 4,5 V; con un AM312 usa 3V3 |
| OUT | GPIO 13 | 3,3 V in presenza di movimento, 0 V altrimenti |
| GND | GND | Massa comune |
Dei due trimmer dell'HC-SR501, gira quello del tempo (Tx) sulla posizione più breve (tutto in senso antiorario, circa 3 secondi); l'attesa la gestirà il codice. Lascia il jumper di trigger su H (trigger ripetuto) e regola la sensibilità tra 3 e 7 metri secondo le tue esigenze. Dopo l'accensione il sensore impiega circa un minuto ad adattarsi all'ambiente e in quel tempo scatta a caso; per questo il codice ignora i primi 60 secondi. I dettagli sul funzionamento del sensore sono in Sensore di movimento PIR.
Preparare il bot Telegram: token e chat ID
- Apri @BotFather su Telegram, scrivi /newbot e dai al tuo bot un nome e un nome utente che finisca con bot.
- Copia il token nel formato 1234567890:AAE... che ti dà BotFather; andrà nella riga BOT_TOKEN del codice.
- Per conoscere il tuo numero utente scrivi /start a @userinfobot; il valore Id che restituisce è il tuo CHAT_ID.
- Apri il tuo bot e scrivi /start una volta; Telegram non permette ai bot di scrivere a chi non ha mai parlato con loro.
- Nel Gestore librerie dell'Arduino IDE installa UniversalTelegramBot (Brian Lough) e ArduinoJson.
- In Strumenti > Scheda scegli AI Thinker ESP32-CAM e verifica che il Partition Scheme sia Huge APP (3MB No OTA/1MB SPIFFS).
Inviare una foto a Telegram: come funziona sendPhotoByBinary?
La funzione sendPhotoByBinary() della libreria UniversalTelegramBot carica la foto proprio come un file. Prima invia l'intestazione della richiesta e il campo del chat ID, poi chiede i dati a tre piccole funzioni che fornisci tu: veriVarMi() dice se resta un pezzo da inviare, sonrakiTampon() dà l'indirizzo dei dati in memoria e tamponBoyu() quanti byte sono. Poiché il buffer della fotocamera è già un unico JPEG, il codice consegna l'intera foto in una volta e abbassa il flag. Una foto 1024×768 con qualità 12 pesa 50–120 KB a seconda della scena e si carica in 1–2 secondi con un buon Wi-Fi. Telegram accetta foto fino a 10 MB, quindi non ti avvicinerai mai al limite di dimensione; il vero limite è il tempo di caricamento.
Codice completo: comando /foto, controllo del flash e allarme di 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 | Cosa fa |
|---|---|
| kameraBaslat() | Avvia la fotocamera in XGA (1024×768) con due buffer se c'è la PSRAM, altrimenti in SVGA (800×600) |
| fotoGonder() | Accende il flash se richiesto, scarta il fotogramma vecchio e ne scatta uno nuovo, invia prima un breve messaggio e poi la foto |
| mesajlariIsle() | Risponde solo ai comandi /start, /foto, /flas, /alarm e /durum che arrivano da CHAT_ID |
| Parte PIR di loop() | Controlla il fronte di salita, i 60 s di riscaldamento e i 30 s di attesa |
| Parte Telegram di loop() | Chiede i nuovi messaggi una volta al secondo ed elabora in ordine quelli accumulati |
Il codice osserva il fronte di salita del PIR, non il suo livello: anche se il sensore resta HIGH per 3 secondi, viene scattata una sola foto. L'attesa di 30 secondi evita che qualcuno che gira per la stanza ti riempia il telefono di decine di foto al minuto e che Telegram rallenti più di un messaggio al secondo verso la stessa chat. Poiché ogni richiesta è una chiamata HTTPS, getUpdates può fermare il loop per circa mezzo secondo; dato che l'uscita dell'HC-SR501 resta HIGH per almeno 3 secondi, nel frattempo non si perde alcun movimento. Con la scheda sempre accesa, tra il movimento e l'arrivo della foto sul telefono passano di solito 2–4 secondi.
Controllo del flash: quando accenderlo?
Il LED bianco frontale è pilotato da un transistor collegato a GPIO 4 e, alla massima luminosità, assorbe più di 100 mA dall'alimentazione. Il codice accende il flash solo al momento dello scatto e aspetta circa 150 ms; dato che l'esposizione automatica della fotocamera si adatta alla nuova luce solo dopo alcuni fotogrammi, viene scartato anche il primo fotogramma nel buffer. Il flash è efficace fino a 1–2 metri; una porta o un giardino più lontani non vengono illuminati. Se fotografi attraverso un vetro, spegni il flash, altrimenti nella foto vedrai solo il riflesso luminoso sul vetro. Se vuoi un flash più tenue puoi applicare il PWM a GPIO 4; il cursore LED Intensity dell'esempio CameraWebServer fa esattamente questo. Ricorda che un flash bianco che scatta di notte rivela anche dove si trova la telecamera; per una sorveglianza discreta è più adatta la sezione IR più sotto.
Risparmiare batteria con il deep sleep
Una ESP32-CAM con il Wi-Fi acceso assorbe 150–200 mA in continuazione, cioè scarica una batteria 18650 da 2600 mAh in mezza giornata. A batteria, la scheda deve passare quasi tutto il tempo in deep sleep e svegliarsi solo quando scatta il PIR. Nel codice seguente GPIO 13 è la sorgente di risveglio ext0: quando l'uscita del PIR va HIGH, la scheda riparte da zero, scatta prima la foto, poi si collega al Wi-Fi, invia la foto e torna a dormire. L'ordine conta: la connessione Wi-Fi richiede qualche secondo, e scattare la foto prima di collegarsi cattura l'istante esatto del 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}Nel codice ci sono quattro dettagli che sfuggono facilmente. Primo, la scheda aspetta che l'uscita del PIR torni LOW prima di dormire; se dorme con il segnale ancora HIGH, si risveglia subito e resta in un ciclo continuo. Secondo, rtc_gpio_hold_en() blocca il pin del flash su LOW durante il sonno; senza questa riga GPIO 4 resta flottante e il flash si illumina debolmente mentre la scheda dorme, scaricando la batteria e attirando l'attenzione. Terzo, al risveglio rtc_gpio_deinit() riporta GPIO 13 a ingresso normale, altrimenti digitalRead() può leggere male. Quarto, il contatore marcato con RTC_DATA_ATTR si conserva durante il sonno, così ogni messaggio ti dice di quale movimento si tratta. Tutte le modalità di sonno e le sorgenti di risveglio sono in Deep sleep dell'ESP32.
| Scenario | Corrente media (lato batteria) | Con una 18650 da 2600 mAh |
|---|---|---|
| Sempre accesa, Wi-Fi connesso (primo sketch) | ≈ 290 mA | ≈ 9 ore |
| Deep sleep + 20 movimenti al giorno | ≈ 11 mA | ≈ 10 giorni |
| Circuito di autoritenuta (scheda del tutto spenta) + 20 movimenti al giorno | ≈ 0,7 mA | ≈ 4–5 mesi (teorico) |
I valori della tabella presuppongono uno step-up a 5 V con efficienza dell'85%. Anche se il chip ESP32 da solo consuma circa 10 µA in deep sleep, la scheda ESP32-CAM assorbe ancora circa 6 mA mentre dorme a causa di regolatore, PSRAM e fotocamera, ed è questo che determina davvero la durata della batteria. Ogni risveglio dura 6–10 secondi: avvio, fotocamera, connessione Wi-Fi e due richieste HTTPS. Se vuoi mesi di autonomia ti serve un circuito di autoritenuta (power latch) che tagli del tutto l'alimentazione della scheda con un MOSFET, la accenda quando scatta il PIR e lasci che la scheda si spenga da sola a lavoro finito. Puoi calcolare il tuo scenario con il Calcolatore di durata della batteria e, in caso di riavvii frequenti, consultare Brownout dell'ESP32.
Foto notturne: flash, LED IR e impostazioni della fotocamera
Gli obiettivi standard della ESP32-CAM contengono un filtro IR-cut fisso che blocca la luce infrarossa; per questo di giorno i colori appaiono naturali, ma una scena illuminata solo a infrarossi resta buia. Per vedere al buio ci sono due strade: il flash bianco oppure l'illuminazione IR con un obiettivo senza filtro. I moduli OV2640 venduti come night vision o 850 nm non hanno questo filtro; di giorno i colori virano al rosa, ma una scena illuminata con LED IR a 850 nm si vede nitida in bianco e nero. I LED a 940 nm sono del tutto invisibili, ma l'OV2640 è molto meno sensibile a quella lunghezza d'onda e l'immagine risulta decisamente più scura. Alimenta l'illuminatore IR da una sorgente separata a 5 V o 12 V; i moduli già pronti con sensore di luce si accendono da soli quando fa buio. Se vuoi comandarlo da un GPIO usa un MOSFET e non collegare mai i LED direttamente al pin.
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() {}Queste impostazioni alzano il tetto di guadagno e il tempo di esposizione del sensore. Il prezzo è che al buio cala la frequenza dei fotogrammi, una persona in movimento risulta mossa e il guadagno elevato aggiunge grana all'immagine. Il modo più efficace per migliorare una foto notturna resta la luce; anche un illuminatore IR debole dà un risultato più nitido che alzare il guadagno. Puoi aggiungere il codice subito dopo la chiamata a kameraBaslat() e, se vuoi, misurare la luce ambiente con una LDR per passare in automatico tra impostazioni diurne e notturne.
I suoi limiti come telecamera di sicurezza
- Non registra video: Questo sistema invia solo foto. Per la registrazione continua o per rivedere cosa è successo serve una vera telecamera di sicurezza.
- C'è un ritardo: 2–4 secondi con la scheda accesa, 6–10 secondi al risveglio dal deep sleep; chi passa velocemente potrebbe essere già uscito dall'inquadratura.
- Il PIR non vede attraverso il vetro: Il vetro non lascia passare la radiazione termica infrarossa. Un sensore dietro una finestra non rileva il movimento all'esterno, ma il sole e i termosifoni possono comunque farlo scattare per errore.
- Dipende da internet: Se cadono il Wi-Fi o internet, la foto non parte. Per un uso critico, salvare la foto anche su una scheda SD fa da copia di riserva.
- Nessuna protezione fisica: La scheda si stacca o si spegne facilmente e all'aperto soffre umidità e sole; se la usi fuori ti servono una scatola stagna e un'alimentazione che regga la temperatura.
- Qualità d'immagine limitata: L'OV2640 da 2 MP non basta per riconoscere volti o leggere targhe a qualche metro; gli obiettivi grandangolari allargano l'inquadratura ma riducono i dettagli.
- Regole sulla privacy: Punta la telecamera verso la tua proprietà; prima di riprendere il giardino del vicino, le aree comuni o la strada, verifica le norme di legge del luogo in cui vivi.
Idee di progetto con ESP32-CAM e Telegram
- Videocitofono fai da te: Collega il pulsante del campanello a GPIO 13 così che una pressione fotografi l'ingresso e te lo invii; per il collegamento vedi come collegare un pulsante.
- Avviso cassetta della posta: Monta un interruttore reed sullo sportello, così aprendolo la scheda si sveglia e invia una foto dell'interno.
- Telecamera per nido o animale domestico: Scatta una foto quando c'è movimento e controlla durante il giorno con /foto.
- Occhi per un antifurto: Fai in modo che un antifurto con Arduino avvisi la ESP32-CAM quando scatta, così fotografa la scena.
- Monitoraggio di piante e serra: Risveglio a timer ogni ora per inviare una foto, e misure inviate a una dashboard con MQTT e Home Assistant.
- Gruppo di famiglia: Aggiungi il bot a un gruppo Telegram e usa l'ID del gruppo, così la foto arriva a tutti nello stesso momento.
Errori comuni e soluzioni
| Sintomo | Causa probabile | Soluzione |
|---|---|---|
| Arriva il messaggio ma non la foto | Fotogramma troppo grande, PSRAM disattivata o caricamento interrotto | Scegli la scheda AI Thinker ESP32-CAM, scendi a SVGA, migliora il segnale Wi-Fi |
| Il bot non risponde a nessun comando | Token o CHAT_ID sbagliati, oppure non hai mai scritto /start al bot | Ricopia il token, controlla il tuo ID con @userinfobot, scrivi /start al tuo bot |
| Arrivano foto di movimento senza motivo | Il PIR risente del segnale dell'antenna Wi-Fi o di una fonte di calore | Allontana il PIR di 10–20 cm dalla scheda, metti 100 nF tra VCC e GND, non puntare il sensore verso il sole o i termosifoni |
| La scheda si risveglia subito dopo essersi addormentata | L'uscita del PIR è ancora HIGH quando va a dormire | Aspetta che il pin torni LOW prima di dormire, porta al minimo il trimmer del tempo dell'HC-SR501 |
| Il flash si illumina debolmente durante il sonno | GPIO 4 resta flottante | Scrivi LOW e chiama rtc_gpio_hold_en(GPIO_NUM_4) prima di dormire |
| La foto è scura o verdastra | I primi fotogrammi subito dopo l'avvio della fotocamera | Scatta e scarta 2–3 fotogrammi prima della foto vera |
| La scheda si riavvia mentre invia una foto | Brownout: l'alimentazione non regge la corrente di Wi-Fi e flash | Usa un alimentatore da 5 V / 2 A, cavi corti e un condensatore da 470–1000 µF tra 5V e GND |
Insieme a Telegram, la ESP32-CAM trasforma una scheda economica in un occhio intelligente che invia foto al tuo telefono. Costruisci prima il sistema con lo sketch sempre acceso e regola il PIR, poi passa alla versione con deep sleep se ti serve. Nell'app BlueGrays trovi l'allarme di movimento con Telegram, PIR, buzzer e LED, il deep sleep dell'ESP32 e l'illuminazione automatica con PIR, con schemi di collegamento e codice pronto passo passo.
Domande frequenti
Come si invia una foto dalla ESP32-CAM a Telegram?
Crei un bot con BotFather per ottenere un token, installi la libreria UniversalTelegramBot e carichi il buffer della fotocamera sul metodo sendPhoto di Telegram con la funzione sendPhotoByBinary(). La libreria invia la foto via HTTPS in formato multipart, come un caricamento di file nel browser. Nel codice devi cambiare solo i dati del Wi-Fi, il token e il tuo chat ID.
A quale pin della ESP32-CAM si collega un sensore PIR?
Se non usi la scheda SD, GPIO 13 è la scelta più sicura: non influisce sull'avvio e, essendo un pin RTC, può risvegliare la scheda dal deep sleep. Collega VCC del PIR al pin 5V, GND a GND e OUT a GPIO 13. Evita GPIO 12; se è HIGH all'avvio la scheda non parte.
Quanto dura una ESP32-CAM a batteria?
Con il Wi-Fi sempre acceso, una 18650 da 2600 mAh dura circa 9 ore. Con il deep sleep e il risveglio solo al movimento si arriva a circa 10 giorni; anche quando dorme la scheda assorbe circa 6 mA per via di regolatore e PSRAM. Per mesi di autonomia serve un circuito di autoritenuta che tagli completamente l'alimentazione.
La ESP32-CAM vede di notte?
Il filtro IR-cut dei moduli standard blocca la luce infrarossa, quindi al buio ottieni un'immagine solo fino a 1–2 metri con il flash bianco. Con un modulo OV2640 senza filtro (night vision) e un illuminatore IR a 850 nm puoi scattare foto nitide in bianco e nero al buio. Alzare il tetto di guadagno nelle impostazioni aiuta, ma aggiunge grana.
La ESP32-CAM può inviare video su Telegram?
In pratica no. La ESP32-CAM produce singoli fotogrammi JPEG e non ha potenza né memoria per trasformarli in un video MP4. Inviare più foto di fila o guardare lo streaming nella rete di casa con CameraWebServer sono soluzioni più realistiche.
Si può inviare la foto a più persone?
Sì. Se aggiungi il bot a un gruppo Telegram e usi come CHAT_ID l'ID del gruppo, che inizia con un segno meno, la foto arriva a tutti i membri contemporaneamente. In alternativa metti più chat ID in un array nel codice e invia la foto a ciascuno a turno; ogni invio è un caricamento separato, quindi ci vuole più tempo.
Progetti da realizzare dopo questo articolo
Vedi tutti i progetti
Sistema di Allarme Movimento ESP32 (PIR + Buzzer + LED + Telegram)
Un sistema di allarme di sicurezza intelligente che attiva buzzer e LED quando il sensore PIR rileva movimento, inviando contemporaneamente una notifica istantanea al proprietario tramite Telegram. Dispone di un tempo di attesa di 30 secondi per limitare la frequenza degli allarmi ripetuti; questo meccanismo non verifica la validità dei rilevamenti del sensore PIR né elimina le false attivazioni. Adatto a sicurezza domestica, monitoraggio di garage, magazzini o studi. Con l'ESP32-CAM è possibile

Illuminazione automatica con sensore di movimento PIR ESP32
In questo progetto, utilizzeremo il sensore di movimento PIR HC-SR501 per rilevare la presenza di una persona che entra nella stanza e accendere automaticamente il LED. Monitoreremo il tempo di accensione del LED con millis(); ogni lettura HIGH del sensore aggiornerà l'istante dell'ultimo movimento e il LED si spegnerà quando saranno trascorsi almeno 10 secondi dall'ultima lettura HIGH. Il codice attuale attende anche 50 ms a ogni iterazione con delay(50), quindi il ciclo dell'applicazione n

ESP32 Deep Sleep — Risparmio della batteria (OLED + contatore in RTC RAM)
La modalità Deep Sleep dell'ESP32 riduce il consumo di corrente a circa 10 µA spegnendo la CPU e la maggior parte dei dispositivi periferici ed è di fondamentale importanza per i dispositivi IoT alimentati a batteria. In questo progetto utilizziamo tre diverse fonti di risveglio: il timer (15 secondi), un pin esterno (EXT0 — pulsante GPIO 33) e il sensore tattile (T0/GPIO 4). Le variabili contrassegnate con RTC_DATA_ATTR vengono preservate durante la modalità Deep Sleep: il contatore di avvio,

Illuminazione Intelligente ESP32 (PIR + LDR + Relè + OLED)
Trasforma il tuo corridoio, bagno o dispensa in illuminazione intelligente con ESP32! Utilizzando un sensore di movimento PIR, un sensore di luce LDR e un modulo relè, costruiamo lo scenario classico "la luce si accende quando viene rilevato movimento al buio" — se l'ambiente è già luminoso, il movimento NON attiva la luce, risparmiando energia durante il giorno. La luce rimane accesa per 30 secondi; se il movimento continua, il timer si azzera e la luce rimane accesa più a lungo. Il display OLE

Connessione Wi-Fi STA con ESP32 (con display LCD)
In questo progetto colleghiamo l'ESP32 alla rete Wi-Fi domestica in modalità Station (STA) in modo sicuro e tollerante agli errori. Separiamo SSID e password in un file secrets.h per impedire fughe su Git, impostiamo un meccanismo di riconnessione automatica e visualizziamo il livello del segnale RSSI e l'indirizzo IP su un display LCD I2C. Il LED interno funge da indicatore di stato.

ESP32 mDNS: accesso al dispositivo tramite bluegrays-esp.local (con schermo LCD)
In questo progetto, aggiungendo il servizio mDNS (Multicast DNS) all'ESP32, consentiamo l'accesso tramite un nome host leggibile, come bluegrays-esp.local, anziché tramite l'indirizzo IP. Anche se l'IP assegnato dal DHCP dovesse cambiare, potrete sempre raggiungere il dispositivo con lo stesso nome. Eseguendo anche un piccolo server web, inviamo comandi di controllo dei LED dal browser all’indirizzo http://bluegrays-esp.local/. Sul display LCD I2C vengono visualizzati il nome host, l’IP e lo

Rilevatore di movimento con sensore di movimento
Un semplice guardiano che ti avvisa quando qualcuno entra nella stanza. Il sensore di movimento percepisce il calore emesso dal corpo umano, e l'Arduino tiene acceso il LED finché c'è movimento e lo s
Altri articoli
App BlueGrays
Costruisci questo circuito passo passo sul telefono
Schema, codice ed elenco componenti in un’unica schermata. Oltre 200 progetti Arduino, ESP32 e Pico da realizzare.
Inquadra con il telefono
Punta la fotocamera sul codice: si apre lo store del tuo telefono.
oppure apri lo store

