Guide · 11 min di lettura

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.

Modulo fotocamera ESP32-CAM per un sistema di foto su Telegram
ESP32-CAM: fotocamera OV2640, LED flash e Wi-Fi su un'unica scheda. Con un sensore PIR può inviare una foto a Telegram quando rileva un movimento.

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.

ComponenteCaratteristicaNota
ESP32-CAM (AI-Thinker)OV2640, 4 MB di PSRAMLa versione con PSRAM gestisce senza problemi le foto XGA
ESP32-CAM-MB o convertitore USB-TTLCH340 / CP2102Per caricare il codice; la scheda non ha USB
Sensore PIRHC-SR501 (5 V) o AM312 (3,3 V)L'uscita OUT è a 3,3 V e si collega direttamente all'ESP32
Alimentazione5 V, almeno 2 AWi-Fi e flash assorbono molta corrente nello stesso momento
Condensatore elettrolitico470–1000 µFTra 5V e GND, vicino alla scheda
OpzionaleBatteria 18650 + step-up a 5 V, illuminatore IR da 850 nmPer 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.

PinSenza scheda SDRisveglia dal deep sleep?Nota
GPIO 13LiberoSì (ext0)La scelta migliore per il PIR
GPIO 14LiberoSìAll'avvio può emettere un breve segnale PWM; nessun problema come ingresso
GPIO 15LiberoSìPin di avvio; se tenuto LOW zittisce i messaggi di avvio
GPIO 2LiberoSìPin di avvio; non deve essere HIGH durante il caricamento
GPIO 12LiberoSìSe è HIGH all'avvio la scheda non parte; non usarlo
GPIO 4LED flashSìRiservato al flash; non usarlo per altro
GPIO 3 (U0R)RX serialeNoSolo se non ti serve la porta seriale
GPIO 16PSRAMNoNon usarlo mai
Pin del PIRPin della ESP32-CAMDescrizione
VCC5VL'HC-SR501 richiede almeno 4,5 V; con un AM312 usa 3V3
OUTGPIO 133,3 V in presenza di movimento, 0 V altrimenti
GNDGNDMassa comune
Il pin della scheda marcato VCC non è un ingresso, ma l'uscita del regolatore a 3,3 V; per alimentare il PIR o la scheda porta sempre i 5 V al pin 5V. Non collegare mai il PIR a GPIO 12: se il sensore dà HIGH all'avvio, l'ESP32 prova a far funzionare la memoria flash alla tensione sbagliata e la scheda non si avvia affatto.

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

  1. Apri @BotFather su Telegram, scrivi /newbot e dai al tuo bot un nome e un nome utente che finisca con bot.
  2. Copia il token nel formato 1234567890:AAE... che ti dà BotFather; andrà nella riga BOT_TOKEN del codice.
  3. Per conoscere il tuo numero utente scrivi /start a @userinfobot; il valore Id che restituisce è il tuo CHAT_ID.
  4. Apri il tuo bot e scrivi /start una volta; Telegram non permette ai bot di scrivere a chi non ha mai parlato con loro.
  5. Nel Gestore librerie dell'Arduino IDE installa UniversalTelegramBot (Brian Lough) e ArduinoJson.
  6. In Strumenti > Scheda scegli AI Thinker ESP32-CAM e verifica che il Partition Scheme sia Huge APP (3MB No OTA/1MB SPIFFS).
Il token è la password del tuo bot: cancella sempre quella riga prima di condividere il codice o caricarlo da qualche parte. Se pensi che sia trapelato, ottienine uno nuovo con il comando /revoke in BotFather. Creazione del bot, chat ID e certificato sono spiegati più a fondo in Bot Telegram con ESP32.

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

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}
ParteCosa 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.

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}

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.

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

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

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

SintomoCausa probabileSoluzione
Arriva il messaggio ma non la fotoFotogramma troppo grande, PSRAM disattivata o caricamento interrottoScegli la scheda AI Thinker ESP32-CAM, scendi a SVGA, migliora il segnale Wi-Fi
Il bot non risponde a nessun comandoToken o CHAT_ID sbagliati, oppure non hai mai scritto /start al botRicopia il token, controlla il tuo ID con @userinfobot, scrivi /start al tuo bot
Arrivano foto di movimento senza motivoIl PIR risente del segnale dell'antenna Wi-Fi o di una fonte di caloreAllontana 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 addormentataL'uscita del PIR è ancora HIGH quando va a dormireAspetta 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 sonnoGPIO 4 resta flottanteScrivi LOW e chiama rtc_gpio_hold_en(GPIO_NUM_4) prima di dormire
La foto è scura o verdastraI primi fotogrammi subito dopo l'avvio della fotocameraScatta e scarta 2–3 fotogrammi prima della foto vera
La scheda si riavvia mentre invia una fotoBrownout: l'alimentazione non regge la corrente di Wi-Fi e flashUsa un alimentatore da 5 V / 2 A, cavi corti e un condensatore da 470–1000 µF tra 5V e GND
Quando invii a un gruppo, l'ID del gruppo inizia con un segno meno (ad es. -1001234567890); scrivilo con il segno nella riga CHAT_ID. Perché il bot possa leggere i messaggi del gruppo potresti dover disattivare /setprivacy in BotFather oppure inviare i comandi come /foto@nome_bot; il codice rimuove già questi suffissi.

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.

Vedi tutti i progetti
Sistema di Allarme Movimento ESP32 (PIR + Buzzer + LED + Telegram)
#145Avanzato
Premium

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

0.0(0)
9
Illuminazione automatica con sensore di movimento PIR ESP32
#121Intermedio
Premium

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

4.4(24)
38
ESP32 Deep Sleep — Risparmio della batteria (OLED + contatore in RTC RAM)
#135Avanzato
Premium

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,

4.3(10)
16
Illuminazione Intelligente ESP32 (PIR + LDR + Relè + OLED)
#141Avanzato
Premium

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

4.3(6)
11
Connessione Wi-Fi STA con ESP32 (con display LCD)
#127Avanzato

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.

4.4(21)
34
ESP32 mDNS: accesso al dispositivo tramite bluegrays-esp.local (con schermo LCD)
#131Avanzato
Premium

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

4.8(14)
23
Rilevatore di movimento con sensore di movimento
#24Principiante
Premium

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

4.6(47)
67