Guides · 12 min de lecture

ESP32-CAM Telegram : envoyer une photo dès qu'il y a un mouvement

Envoie des photos de l'ESP32-CAM sur Telegram : câblage du PIR et broche libre, commande /foto, flash, deep sleep sur batterie, photos de nuit et code complet.

Quand tu mets en place un bot ESP32-CAM Telegram, la carte prend une photo dès qu'elle détecte un mouvement et te l'envoie sur ton téléphone en quelques secondes. Pas besoin d'installer une application, de louer un serveur ni d'ouvrir un port sur ta box : la carte envoie la photo directement aux serveurs de Telegram, qui te la livre sous forme de notification instantanée. Avec le même bot, tu peux taper /foto pour voir ta porte d'entrée quand tu veux, allumer ou éteindre le flash et désactiver l'alarme.

Dans ce guide, tu verras pas à pas sur laquelle des rares broches libres de l'ESP32-CAM brancher un capteur PIR, comment la photo est envoyée à Telegram, le code complet piloté par commandes et la bonne façon d'utiliser le flash. Ensuite, tu feras tourner la carte plusieurs jours sur batterie grâce au deep sleep, tu prendras des photos de nuit avec un éclairage IR et tu verras où ce montage atteint ses limites comme caméra de surveillance. Si tu n'as pas encore configuré la carte ni téléversé ton premier programme, lis d'abord Utiliser l'ESP32-CAM.

Module caméra ESP32-CAM pour un système de photos Telegram
ESP32-CAM : caméra OV2640, LED de flash et Wi-Fi sur une seule carte. Avec un capteur PIR, elle envoie une photo sur Telegram quand un mouvement est détecté.

Comment fonctionne un système photo ESP32-CAM avec Telegram ?

La chaîne compte quatre étapes. Le capteur PIR remarque un changement de la chaleur émise par un corps humain et met sa broche OUT à 3,3 V. Quand l'ESP32 voit ce front montant, il demande une image JPEG à la caméra OV2640, qui est écrite dans la PSRAM de 4 Mo de la carte. L'ESP32 ouvre ensuite une connexion HTTPS chiffrée et envoie une requête POST au format multipart/form-data à la méthode sendPhoto de l'API des bots Telegram ; c'est la même méthode qu'un navigateur utilise pour envoyer un fichier. Telegram ajoute la photo à ta conversation et ton téléphone affiche une notification. Les commandes vont dans l'autre sens : chaque seconde, l'ESP32 demande avec getUpdates s'il y a de nouveaux messages et, si /foto est arrivé, il fait le même envoi pour ta demande.

ComposantCaractéristiqueRemarque
ESP32-CAM (AI-Thinker)OV2640, 4 Mo de PSRAMLa version avec PSRAM gère sans peine les photos XGA
ESP32-CAM-MB ou convertisseur USB-TTLCH340 / CP2102Pour téléverser le code ; la carte n'a pas d'USB
Capteur PIRHC-SR501 (5 V) ou AM312 (3,3 V)La sortie OUT est en 3,3 V et se branche directement sur l'ESP32
Alimentation5 V, au moins 2 ALe Wi-Fi et le flash consomment beaucoup en même temps
Condensateur électrolytique470–1000 µFEntre 5V et GND, près de la carte
En optionAccu 18650 + convertisseur élévateur 5 V, projecteur IR 850 nmPour le fonctionnement sur batterie et les photos de nuit

Câblage du capteur PIR et choix d'une broche libre

L'ESP32-CAM a 16 broches sur ses bords, mais la caméra utilise à elle seule 15 GPIO et le lecteur microSD en partage six autres. La plupart de celles qui restent sont des broches de démarrage (strapping) ou servent au port série. Comme ce projet n'utilise pas de carte SD, les lignes SD sont libres et la plus sûre est GPIO 13 : elle n'influence pas le démarrage, possède une résistance de pull-down interne et, comme c'est une broche RTC, elle peut réveiller la carte du deep sleep. Le tableau ci-dessous compare aussi les autres candidates.

BrocheSans carte SDRéveille du deep sleep ?Remarque
GPIO 13LibreOui (ext0)Le meilleur choix pour le PIR
GPIO 14LibreOuiPeut sortir un bref signal PWM au démarrage ; aucun souci en entrée
GPIO 15LibreOuiBroche de démarrage ; à LOW, elle coupe les messages de démarrage
GPIO 2LibreOuiBroche de démarrage ; ne doit pas être à HIGH pendant le téléversement
GPIO 12LibreOuiSi elle est à HIGH au démarrage, la carte ne démarre pas ; à éviter
GPIO 4LED de flashOuiRéservée au flash ; ne l'utilise pas pour autre chose
GPIO 3 (U0R)RX sérieNonSeulement si tu n'as pas besoin du port série
GPIO 16PSRAMNonNe jamais l'utiliser
Broche du PIRBroche de l'ESP32-CAMDescription
VCC5VLe HC-SR501 a besoin d'au moins 4,5 V ; avec un AM312, utilise 3V3
OUTGPIO 133,3 V en cas de mouvement, 0 V sinon
GNDGNDMasse commune
La broche marquée VCC sur la carte n'est pas une entrée, mais la sortie du régulateur 3,3 V ; pour alimenter le PIR ou la carte, envoie toujours le 5 V sur la broche 5V. Ne branche jamais le PIR sur GPIO 12 : si le capteur donne HIGH au démarrage, l'ESP32 essaie de faire fonctionner sa mémoire flash à la mauvaise tension et la carte ne démarre pas du tout.

Des deux potentiomètres du HC-SR501, tourne celui de la durée (Tx) sur la position la plus courte (à fond dans le sens antihoraire, environ 3 secondes) ; c'est le code qui gérera l'attente. Laisse le cavalier de déclenchement sur H (redéclenchement) et règle la sensibilité entre 3 et 7 mètres selon ton besoin. Après la mise sous tension, le capteur met environ une minute à s'habituer à son environnement et se déclenche au hasard pendant ce temps ; c'est pourquoi le code ignore les 60 premières secondes. Le fonctionnement détaillé du capteur est expliqué dans Capteur de mouvement PIR.

Préparer le bot Telegram : token et chat ID

  1. Ouvre @BotFather dans Telegram, tape /newbot et donne à ton bot un nom et un nom d'utilisateur qui se termine par bot.
  2. Copie le token au format 1234567890:AAE... que BotFather te donne ; il ira dans la ligne BOT_TOKEN du code.
  3. Pour connaître ton numéro d'utilisateur, envoie /start à @userinfobot ; la valeur Id renvoyée est ton CHAT_ID.
  4. Ouvre ton propre bot et envoie /start une fois ; Telegram ne permet pas à un bot d'écrire à quelqu'un avec qui il n'a jamais échangé.
  5. Dans le gestionnaire de bibliothèques de l'Arduino IDE, installe UniversalTelegramBot (Brian Lough) et ArduinoJson.
  6. Dans Outils > Type de carte, choisis AI Thinker ESP32-CAM et vérifie que le Partition Scheme est Huge APP (3MB No OTA/1MB SPIFFS).
Le token est le mot de passe de ton bot : supprime toujours cette ligne avant de partager ton code ou de le mettre en ligne. Si tu penses qu'il a fuité, obtiens-en un nouveau avec la commande /revoke dans BotFather. La création du bot, le chat ID et le certificat sont détaillés dans Bot Telegram avec l'ESP32.

Envoyer une photo sur Telegram : comment fonctionne sendPhotoByBinary ?

La fonction sendPhotoByBinary() de la bibliothèque UniversalTelegramBot envoie la photo comme un fichier. Elle transmet d'abord l'en-tête de la requête et le champ du chat ID, puis demande les données à trois petites fonctions que tu fournis : veriVarMi() indique s'il reste un morceau à envoyer, sonrakiTampon() donne l'adresse des données en mémoire et tamponBoyu() leur taille en octets. Comme le tampon d'image de la caméra est déjà un seul JPEG, le code transmet la photo entière d'un coup et baisse le drapeau. Une photo de 1024×768 en qualité 12 pèse entre 50 et 120 Ko selon la scène et part en 1 à 2 secondes avec un bon Wi-Fi. Telegram accepte les photos jusqu'à 10 Mo, tu n'approcheras donc jamais la limite de taille ; la vraie limite, c'est la durée d'envoi.

Code complet : commande /foto, contrôle du flash et alarme de mouvement

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}
PartieCe qu'elle fait
kameraBaslat()Démarre la caméra en XGA (1024×768) avec deux tampons s'il y a de la PSRAM, sinon en SVGA (800×600)
fotoGonder()Allume le flash si demandé, jette l'image périmée, en capture une nouvelle, envoie d'abord un court message puis la photo
mesajlariIsle()Ne répond qu'aux commandes /start, /foto, /flas, /alarm et /durum venant de CHAT_ID
Partie PIR de loop()Vérifie le front montant, les 60 s de chauffe et les 30 s d'attente
Partie Telegram de loop()Demande les nouveaux messages une fois par seconde et traite dans l'ordre ceux qui se sont accumulés

Le code surveille le front montant du PIR et non son niveau : même si le capteur reste à HIGH pendant 3 secondes, une seule photo est prise. L'attente de 30 secondes évite qu'une personne qui circule dans la pièce inonde ton téléphone de dizaines de photos par minute et que Telegram ralentisse plus d'un message par seconde vers la même conversation. Comme chaque interrogation est une requête HTTPS, l'appel à getUpdates peut bloquer la boucle environ une demi-seconde ; la sortie du HC-SR501 restant à HIGH au moins 3 secondes, aucun mouvement n'est manqué entre-temps. Carte toujours allumée, il s'écoule en général 2 à 4 secondes entre le mouvement et l'arrivée de la photo sur ton téléphone.

Contrôle du flash : quand l'allumer ?

La LED blanche à l'avant est commandée par un transistor relié à GPIO 4 et tire plus de 100 mA sur l'alimentation à pleine luminosité. Le code n'allume le flash qu'au moment de la photo et attend environ 150 ms ; comme l'exposition automatique de la caméra a besoin de quelques images pour s'adapter à la nouvelle lumière, la première image du tampon est elle aussi jetée. Le flash porte jusqu'à 1 à 2 mètres ; une porte ou un jardin plus loin ne sera pas éclairé. Si tu photographies à travers une vitre, éteins le flash, sinon tu ne verras que son reflet éclatant sur le verre. Pour un flash plus doux, tu peux appliquer une PWM sur GPIO 4 ; le curseur LED Intensity de l'exemple CameraWebServer fait exactement cela. N'oublie pas qu'un flash blanc qui se déclenche la nuit révèle aussi l'emplacement de la caméra ; pour une surveillance discrète, la partie IR plus bas convient mieux.

Économiser la batterie avec le deep sleep

Une ESP32-CAM avec le Wi-Fi actif consomme 150 à 200 mA en permanence, ce qui vide un accu 18650 de 2600 mAh en une demi-journée. Sur batterie, la carte doit passer presque tout son temps en deep sleep et ne se réveiller que lorsque le PIR se déclenche. Dans le code ci-dessous, GPIO 13 est la source de réveil ext0 : quand la sortie du PIR passe à HIGH, la carte redémarre de zéro, prend d'abord la photo, puis se connecte au Wi-Fi, envoie la photo et se rendort. L'ordre compte : la connexion Wi-Fi prend quelques secondes, et prendre la photo avant de se connecter capture l'instant exact du mouvement.

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}

Quatre détails du code passent facilement inaperçus. Premièrement, la carte attend que la sortie du PIR revienne à LOW avant de dormir ; si elle s'endort alors que le signal est encore à HIGH, elle se réveille aussitôt et tourne en boucle. Deuxièmement, rtc_gpio_hold_en() bloque la broche du flash à LOW pendant le sommeil ; sans cette ligne, GPIO 4 reste flottante et le flash luit faiblement pendant le sommeil, ce qui vide la batterie et attire l'attention. Troisièmement, au réveil, rtc_gpio_deinit() remet GPIO 13 en entrée normale, sinon digitalRead() risque de mal la lire. Quatrièmement, le compteur marqué RTC_DATA_ATTR est conservé pendant le sommeil, si bien que chaque message t'indique le numéro du mouvement. Tous les modes de sommeil et sources de réveil sont présentés dans Deep sleep de l'ESP32.

ScénarioCourant moyen (côté batterie)Avec un 18650 de 2600 mAh
Toujours allumée, Wi-Fi connecté (premier code)≈ 290 mA≈ 9 heures
Deep sleep + 20 mouvements par jour≈ 11 mA≈ 10 jours
Circuit d'auto-maintien (carte totalement éteinte) + 20 mouvements par jour≈ 0,7 mA≈ 4 à 5 mois (théorique)

Les valeurs du tableau supposent un convertisseur élévateur 5 V avec un rendement de 85 %. Même si la puce ESP32 elle-même ne consomme qu'environ 10 µA en deep sleep, la carte ESP32-CAM tire encore environ 6 mA en sommeil à cause de son régulateur, de la PSRAM et de la caméra, et c'est cela qui fixe vraiment l'autonomie. Chaque réveil dure 6 à 10 secondes : démarrage, caméra, connexion Wi-Fi et deux requêtes HTTPS. Pour des mois d'autonomie, il faut un circuit d'auto-maintien (power latch) qui coupe totalement l'alimentation de la carte avec un MOSFET, l'allume quand le PIR se déclenche et laisse la carte s'éteindre elle-même une fois le travail fini. Tu peux calculer ton propre scénario avec le Calculateur d'autonomie de batterie et, en cas de redémarrages fréquents, consulter Brownout de l'ESP32.

Photos de nuit : flash, LED IR et réglages de la caméra

Les objectifs standard de l'ESP32-CAM contiennent un filtre IR-cut fixe qui bloque la lumière infrarouge ; c'est pourquoi les couleurs paraissent naturelles de jour, mais une scène éclairée uniquement en IR reste sombre. Il y a deux façons de voir dans le noir : le flash blanc, ou un éclairage IR avec un objectif sans filtre. Les modules OV2640 vendus comme night vision ou 850 nm n'ont pas ce filtre ; de jour, les couleurs virent au rose, mais une scène éclairée par des LED IR de 850 nm apparaît nettement en noir et blanc. Les LED de 940 nm sont totalement invisibles, mais l'OV2640 est beaucoup moins sensible à cette longueur d'onde et l'image devient nettement plus sombre. Alimente le projecteur IR avec une source séparée de 5 V ou 12 V ; les modules tout faits équipés d'un capteur de lumière s'allument seuls à la tombée de la nuit. Pour le commander depuis un GPIO, utilise un MOSFET et ne branche jamais les LED directement sur la broche.

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

Ces réglages relèvent le plafond de gain et le temps d'exposition du capteur. En contrepartie, la cadence d'images baisse dans le noir, une personne en mouvement apparaît floue et le gain élevé ajoute du grain. La lumière reste le moyen le plus efficace d'améliorer une photo de nuit ; même un petit projecteur IR donne un résultat plus net que d'augmenter le gain. Tu peux ajouter ce code juste après l'appel à kameraBaslat() et, si tu veux, mesurer la lumière ambiante avec une LDR pour passer automatiquement des réglages de jour à ceux de nuit.

Ses limites comme caméra de surveillance

  • Pas d'enregistrement vidéo : Ce système n'envoie que des photos. Pour un enregistrement continu ou revoir ce qui s'est passé, il faut une vraie caméra de surveillance.
  • Il y a un délai : 2 à 4 secondes carte allumée, 6 à 10 secondes au réveil du deep sleep ; une personne qui passe vite peut déjà être sortie du cadre.
  • Le PIR ne voit pas à travers une vitre : Le verre ne laisse pas passer le rayonnement thermique infrarouge. Un capteur placé derrière une fenêtre ne détecte pas les mouvements dehors, mais le soleil et les radiateurs peuvent quand même le déclencher à tort.
  • Il dépend d'internet : Si le Wi-Fi ou internet tombe, la photo ne part pas. Pour un usage critique, enregistrer aussi la photo sur une carte SD sert de sauvegarde.
  • Aucune protection physique : La carte s'arrache ou s'éteint facilement et, dehors, elle souffre de l'humidité et du soleil ; en extérieur, il te faut un boîtier étanche et une alimentation qui supporte la température.
  • Qualité d'image limitée : L'OV2640 de 2 Mpx ne suffit pas pour reconnaître un visage ou lire une plaque à quelques mètres ; les objectifs grand-angle élargissent le cadre mais réduisent les détails.
  • Règles de confidentialité : Oriente la caméra vers ton propre espace ; avant de filmer le jardin du voisin, les parties communes ou la rue, vérifie les règles légales de ton lieu de résidence.

Idées de projets ESP32-CAM avec Telegram

  • Caméra de sonnette : Branche le bouton de sonnette sur GPIO 13 pour qu'un appui photographie l'entrée et te l'envoie ; vois brancher un bouton poussoir pour le câblage.
  • Alerte boîte aux lettres : Place un interrupteur reed sur le clapet pour qu'en l'ouvrant la carte se réveille et envoie une photo de l'intérieur.
  • Caméra de nichoir ou d'animal : Prendre une photo dès qu'il y a du mouvement et vérifier dans la journée avec /foto.
  • Des yeux pour une alarme : Fais en sorte qu'un système d'alarme Arduino prévienne l'ESP32-CAM quand il se déclenche pour qu'elle photographie les lieux.
  • Suivi des plantes et de la serre : Réveil par minuterie toutes les heures pour envoyer une photo, et envoi des mesures vers un tableau de bord avec MQTT et Home Assistant.
  • Groupe familial : Ajoute le bot à un groupe Telegram et utilise l'ID du groupe pour que la photo arrive chez tout le monde en même temps.

Erreurs fréquentes et solutions

SymptômeCause probableSolution
Le message arrive, mais pas la photoImage trop lourde, PSRAM désactivée ou envoi interrompuChoisis la carte AI Thinker ESP32-CAM, descends la résolution en SVGA, améliore le signal Wi-Fi
Le bot ne répond à aucune commandeToken ou CHAT_ID erroné, ou jamais envoyé /start au botRecopie le token, vérifie ton ID avec @userinfobot, envoie /start à ton bot
Des photos de mouvement arrivent sans raisonLe PIR capte le signal de l'antenne Wi-Fi ou une source de chaleurÉloigne le PIR de 10 à 20 cm de la carte, ajoute 100 nF entre VCC et GND, ne vise ni le soleil ni les radiateurs
La carte se réveille juste après s'être endormieLa sortie du PIR est encore à HIGH au moment du sommeilAttends que la broche repasse à LOW avant de dormir, règle le potentiomètre de durée du HC-SR501 au minimum
Le flash luit faiblement pendant le sommeilGPIO 4 reste flottanteÉcris LOW et appelle rtc_gpio_hold_en(GPIO_NUM_4) avant de dormir
La photo est sombre ou verdâtreLes premières images juste après le démarrage de la caméraCapture et jette 2 à 3 images avant la vraie photo
La carte redémarre pendant l'envoi d'une photoBrownout : l'alimentation ne suit pas le courant du Wi-Fi et du flashUtilise un adaptateur 5 V / 2 A, des fils courts et un condensateur de 470 à 1000 µF entre 5V et GND
Pour envoyer à un groupe, l'ID du groupe commence par un signe moins (par ex. -1001234567890) ; écris-le avec son signe dans la ligne CHAT_ID. Pour que le bot puisse lire les messages du groupe, il faudra peut-être désactiver /setprivacy dans BotFather ou envoyer les commandes sous la forme /foto@nom_du_bot ; le code supprime déjà ces suffixes.

Associée à Telegram, l'ESP32-CAM transforme une carte bon marché en un œil intelligent qui envoie des photos sur ton téléphone. Monte d'abord le système avec le code toujours allumé et règle le PIR, puis passe à la version deep sleep si tu en as besoin. Dans l'application BlueGrays, tu trouveras l'alarme de mouvement Telegram avec PIR, buzzer et LED, le deep sleep de l'ESP32 et l'éclairage automatique avec PIR, avec schémas de câblage et code prêt à l'emploi, étape par étape.

Questions fréquentes

Comment envoyer une photo de l'ESP32-CAM sur Telegram ?

Tu crées un bot avec BotFather pour obtenir un token, tu installes la bibliothèque UniversalTelegramBot et tu envoies le tampon d'image de la caméra à la méthode sendPhoto de Telegram avec la fonction sendPhotoByBinary(). La bibliothèque transmet la photo en HTTPS au format multipart, comme un envoi de fichier dans un navigateur. Dans le code, il suffit de changer les identifiants Wi-Fi, le token et ton chat ID.

Sur quelle broche de l'ESP32-CAM brancher un capteur PIR ?

Sans carte SD, GPIO 13 est le choix le plus sûr : elle n'influence pas le démarrage et, en tant que broche RTC, elle peut réveiller la carte du deep sleep. Relie le VCC du PIR à la broche 5V, le GND à GND et OUT à GPIO 13. Évite GPIO 12 ; si elle est à HIGH au démarrage, la carte ne démarre pas.

Combien de temps une ESP32-CAM tient-elle sur batterie ?

Avec le Wi-Fi allumé en permanence, un accu 18650 de 2600 mAh tient environ 9 heures. Avec le deep sleep et un réveil uniquement sur mouvement, on monte à une dizaine de jours ; même en sommeil, la carte consomme environ 6 mA à cause du régulateur et de la PSRAM. Pour des mois d'autonomie, il faut un circuit d'auto-maintien qui coupe complètement l'alimentation.

L'ESP32-CAM peut-elle voir la nuit ?

Le filtre IR-cut des modules standard bloque la lumière infrarouge, donc dans le noir tu n'obtiens une image qu'à 1 ou 2 mètres avec le flash blanc. Avec un module OV2640 sans filtre (night vision) et un projecteur IR de 850 nm, tu peux prendre des photos nettes en noir et blanc dans l'obscurité. Relever le plafond de gain dans les réglages aide aussi, mais ajoute du grain.

L'ESP32-CAM peut-elle envoyer une vidéo sur Telegram ?

En pratique, non. L'ESP32-CAM produit des images JPEG séparées et n'a ni la puissance ni la mémoire pour en faire une vidéo MP4. Envoyer plusieurs photos à la suite ou regarder le flux en direct sur ton réseau avec CameraWebServer sont des solutions plus réalistes.

Peut-on envoyer la photo à plusieurs personnes ?

Oui. Si tu ajoutes le bot à un groupe Telegram et que tu utilises comme CHAT_ID l'ID du groupe, qui commence par un signe moins, la photo arrive chez tous les membres en même temps. Tu peux aussi mettre plusieurs chat ID dans un tableau et envoyer la photo à chacun tour à tour ; chaque envoi est un téléversement séparé, donc c'est plus long.

Voir tous les projets
Système d'Alarme de Mouvement ESP32 (PIR + Buzzer + LED + Telegram)
#145Avancé
Premium

Système d'Alarme de Mouvement ESP32 (PIR + Buzzer + LED + Telegram)

Un système d'alarme de sécurité intelligent qui active le buzzer et la LED lorsque le capteur PIR détecte un mouvement, tout en envoyant une notification instantanée au propriétaire via Telegram. Il intègre un délai d'attente de 30 secondes pour limiter la fréquence des alarmes répétées ; ce mécanisme ne vérifie pas la validité des détections du capteur PIR et n'élimine pas les faux déclenchements. Idéal pour la sécurité domestique, la surveillance de garage, d'entrepôts ou de bureaux. Avec l'ES

0.0(0)
9
Éclairage automatique avec le capteur de mouvement PIR ESP32
#121Intermédiaire
Premium

Éclairage automatique avec le capteur de mouvement PIR ESP32

Dans ce projet, nous allons détecter toute personne entrant dans la pièce à l'aide du capteur de mouvement PIR HC-SR501 et allumer automatiquement la LED. Nous suivrons la durée d'allumage de la LED avec millis() ; chaque lecture HIGH du capteur actualisera l'instant du dernier mouvement, et la LED s'éteindra au moins 10 secondes après la dernière lecture HIGH. Le code actuel attend également 50 ms à chaque itération avec delay(50) ; la boucle de l'application n'est donc pas entièrement non

4.4(24)
38
Mode veille profonde de l'ESP32 — Économie de batterie (OLED + compteur RAM RTC)
#135Avancé
Premium

Mode veille profonde de l'ESP32 — Économie de batterie (OLED + compteur RAM RTC)

Le mode « Deep Sleep » de l'ESP32 réduit la consommation de courant à environ 10 µA en désactivant le processeur et la plupart des périphériques, ce qui revêt une importance cruciale pour les appareils IoT fonctionnant sur batterie. Dans ce projet, nous utilisons trois sources de réveil différentes : une minuterie (timer, 15 s), une broche externe (EXT0 — bouton GPIO 33) et un capteur tactile (T0/GPIO 4). Les variables marquées avec RTC_DATA_ATTR sont conservées pendant le mode Deep Sleep — le

4.3(10)
16
Éclairage Intelligent ESP32 (PIR + LDR + Relais + OLED)
#141Avancé
Premium

Éclairage Intelligent ESP32 (PIR + LDR + Relais + OLED)

Transformez votre couloir, votre salle de bain ou votre garde-manger en éclairage intelligent avec l'ESP32 ! En utilisant un capteur de mouvement PIR, un capteur de lumière LDR et un module relais, nous construisons le scénario classique "la lumière s'allume lorsqu'un mouvement est détecté dans l'obscurité" — si l'environnement est déjà lumineux, le mouvement N'ACTIVE PAS la lumière, économisant ainsi de l'énergie pendant la journée. La lumière reste allumée pendant 30 secondes ; si le mouvement

4.3(6)
11
Connexion Wi-Fi STA ESP32 (avec écran LCD)
#127Avancé

Connexion Wi-Fi STA ESP32 (avec écran LCD)

Dans ce projet, nous connectons l'ESP32 au réseau Wi-Fi domestique en mode Station (STA) de manière sécurisée et tolérante aux pannes. Nous isolons SSID/mot de passe dans un fichier secrets.h pour empêcher leur fuite sur Git, mettons en place une reconnexion automatique, et affichons le niveau RSSI et l'adresse IP sur un écran LCD I2C. La LED intégrée sert d'indicateur d'état.

4.4(21)
34
ESP32 mDNS : accès à l'appareil via bluegrays-esp.local (avec écran LCD)
#131Avancé
Premium

ESP32 mDNS : accès à l'appareil via bluegrays-esp.local (avec écran LCD)

Dans ce projet, en ajoutant un service mDNS (Multicast DNS) à l'ESP32, nous permettons d'accéder à l'appareil via un nom d'hôte lisible, tel que bluegrays-esp.local, plutôt qu'à l'aide d'une adresse IP. Même si l'adresse IP attribuée par le DHCP venait à changer, vous pourrez toujours accéder à l'appareil sous le même nom. En exécutant également un petit serveur web sur l'appareil, nous envoyons des commandes de contrôle des LED depuis un navigateur via l'adresse http://bluegrays-esp.local/.

4.8(14)
23
Détecteur de mouvement avec capteur de mouvement
#24Débutant
Premium

Détecteur de mouvement avec capteur de mouvement

Un gardien tout simple qui te prévient quand quelqu'un entre dans la pièce. Le détecteur de mouvement repère la chaleur dégagée par le corps humain ; l'Arduino garde la LED allumée tant qu'il y a du m

4.6(47)
67