Ratgeber · 10 Min. Lesezeit

ESP32-CAM Telegram: Foto bei Bewegung aufs Handy senden

ESP32-CAM sendet Fotos per Telegram: PIR anschließen und freien Pin wählen, /foto-Befehl, Blitz steuern, Deep Sleep für Akkubetrieb, Nachtaufnahmen, Code.

Wenn du einen ESP32-CAM Telegram-Bot einrichtest, macht das Board in dem Moment ein Foto, in dem es eine Bewegung erkennt, und schickt es dir innerhalb weniger Sekunden aufs Handy. Du musst keine App installieren, keinen Server mieten und keinen Port im Router öffnen: Das Board lädt das Foto direkt auf die Telegram-Server hoch, und Telegram stellt es dir als Sofortbenachrichtigung zu. Über denselben Bot kannst du jederzeit /foto schreiben, um deine Haustür zu sehen, den Blitz ein- oder ausschalten und den Alarm deaktivieren.

In dieser Anleitung erfährst du Schritt für Schritt, an welchen der wenigen freien Pins der ESP32-CAM du einen PIR-Sensor anschließt, wie das Foto zu Telegram hochgeladen wird, wie der komplette Code mit Befehlen aussieht und wie du den Blitz richtig einsetzt. Danach betreibst du das Board mit Deep Sleep tagelang am Akku, machst Nachtaufnahmen mit IR-Beleuchtung und siehst, wo dieser Aufbau als Überwachungskamera an seine Grenzen stößt. Falls du das Board noch nicht eingerichtet und deinen ersten Sketch hochgeladen hast, lies zuerst ESP32-CAM verwenden.

ESP32-CAM-Kameramodul für ein Telegram-Fotosystem
ESP32-CAM: OV2640-Kamera, Blitz-LED und WLAN auf einem Board. Mit einem PIR-Sensor schickt sie bei Bewegung ein Foto an Telegram.

Wie funktioniert ein ESP32-CAM Telegram-Fotosystem?

Die Kette besteht aus vier Schritten. Der PIR-Sensor bemerkt eine Änderung der Wärmestrahlung eines menschlichen Körpers und zieht seinen OUT-Pin auf 3,3 V. Sieht der ESP32 diese steigende Flanke, fordert er von der OV2640-Kamera ein JPEG-Bild an, das im 4 MB großen PSRAM des Boards landet. Danach baut der ESP32 eine verschlüsselte HTTPS-Verbindung auf und schickt einen POST-Request im Format multipart/form-data an den Endpunkt sendPhoto der Telegram Bot API; das ist dasselbe Verfahren, das ein Browser beim Hochladen einer Datei nutzt. Telegram fügt das Foto in deinen Chat ein, und dein Handy zeigt eine Benachrichtigung. Befehle laufen in die andere Richtung: Jede Sekunde fragt der ESP32 mit getUpdates nach neuen Nachrichten, und wenn /foto angekommen ist, führt er denselben Upload für deine Anfrage aus.

BauteilEigenschaftHinweis
ESP32-CAM (AI-Thinker)OV2640, 4 MB PSRAMDie Version mit PSRAM schafft XGA-Fotos problemlos
ESP32-CAM-MB oder USB-TTL-AdapterCH340 / CP2102Zum Hochladen; das Board hat kein USB
PIR-SensorHC-SR501 (5 V) oder AM312 (3,3 V)Das OUT-Signal hat 3,3 V und geht direkt an den ESP32
Stromversorgung5 V, mindestens 2 AWLAN und Blitz ziehen gleichzeitig viel Strom
Elektrolytkondensator470–1000 µFZwischen 5V und GND, nah am Board
Optional18650-Akku + 5-V-Aufwärtswandler, 850-nm-IR-StrahlerFür Akkubetrieb und Nachtaufnahmen

PIR-Sensor anschließen und einen freien Pin wählen

Die ESP32-CAM hat 16 Pins an den Rändern, doch die Kamera allein belegt 15 GPIOs, und der microSD-Slot teilt sich sechs weitere. Die meisten übrigen Pins sind entweder Boot-Pins (Strapping-Pins) oder werden für die serielle Schnittstelle gebraucht. Da dieses Projekt keine SD-Karte nutzt, sind die SD-Leitungen frei, und der sicherste davon ist GPIO 13: Er beeinflusst den Bootvorgang nicht, hat einen internen Pull-down-Widerstand und kann als RTC-Pin das Board aus dem Deep Sleep wecken. Die folgende Tabelle vergleicht auch die anderen Kandidaten.

PinOhne SD-KarteWeckt aus dem Deep Sleep?Hinweis
GPIO 13FreiJa (ext0)Die beste Wahl für den PIR
GPIO 14FreiJaBeim Booten kann kurz ein PWM-Signal anliegen; als Eingang unproblematisch
GPIO 15FreiJaStrapping-Pin; LOW unterdrückt die Bootmeldungen
GPIO 2FreiJaStrapping-Pin; darf beim Hochladen nicht HIGH sein
GPIO 12FreiJaIst er beim Booten HIGH, startet das Board nicht; nicht verwenden
GPIO 4Blitz-LEDJaFür den Blitz reserviert; nicht anderweitig nutzen
GPIO 3 (U0R)Serielles RXNeinNur wenn du die serielle Schnittstelle nicht brauchst
GPIO 16PSRAMNeinNiemals verwenden
PIR-PinESP32-CAM-PinBeschreibung
VCC5VDer HC-SR501 braucht mindestens 4,5 V; beim AM312 3V3
OUTGPIO 133,3 V bei Bewegung, sonst 0 V
GNDGNDGemeinsame Masse
Der mit VCC beschriftete Pin des Boards ist kein Eingang, sondern der Ausgang des 3,3-V-Spannungsreglers; gib die 5 V für PIR und Board immer auf den 5V-Pin. Schließe den PIR niemals an GPIO 12 an: Liefert der Sensor beim Booten HIGH, versucht der ESP32 seinen Flash-Speicher mit der falschen Spannung zu betreiben, und das Board startet überhaupt nicht.

Dreh von den beiden Trimmern des HC-SR501 den Zeit-Trimmer (Tx) auf die kürzeste Stellung (ganz gegen den Uhrzeigersinn, etwa 3 Sekunden); die Wartezeit übernimmt der Code. Lass den Trigger-Jumper auf H (wiederholtes Auslösen) und stelle die Empfindlichkeit je nach Bedarf zwischen 3 und 7 Metern ein. Nach dem Einschalten braucht der Sensor etwa eine Minute, um sich an die Umgebung zu gewöhnen, und löst in dieser Zeit zufällig aus; deshalb ignoriert der Code die ersten 60 Sekunden. Wie der Sensor im Detail funktioniert, liest du in PIR-Bewegungsmelder.

Telegram-Bot vorbereiten: Token und Chat-ID

  1. Öffne in Telegram @BotFather, schreibe /newbot und gib deinem Bot einen Namen und einen Benutzernamen, der auf bot endet.
  2. Kopiere das Token im Format 1234567890:AAE..., das BotFather dir gibt; es kommt in die Zeile BOT_TOKEN im Code.
  3. Um deine eigene Benutzernummer zu erfahren, schreibe /start an @userinfobot; der zurückgegebene Wert Id ist deine CHAT_ID.
  4. Öffne deinen eigenen Bot und schreibe einmal /start; Telegram erlaubt Bots nicht, jemandem zu schreiben, mit dem sie nie Kontakt hatten.
  5. Installiere in der Bibliotheksverwaltung der Arduino IDE die Bibliotheken UniversalTelegramBot (Brian Lough) und ArduinoJson.
  6. Wähle unter Werkzeuge > Board das Board AI Thinker ESP32-CAM und prüfe, dass das Partition Scheme Huge APP (3MB No OTA/1MB SPIFFS) ist.
Das Token ist das Passwort deines Bots: Lösche diese Zeile unbedingt, bevor du deinen Code teilst oder irgendwo hochlädst. Wenn du glaubst, dass das Token bekannt geworden ist, hol dir mit dem Befehl /revoke bei BotFather ein neues. Bot-Erstellung, Chat-ID und Zertifikat erklären wir ausführlicher in ESP32 Telegram-Bot.

Foto an Telegram senden: Wie funktioniert sendPhotoByBinary?

Die Funktion sendPhotoByBinary() der Bibliothek UniversalTelegramBot lädt das Foto wie eine Datei hoch. Sie sendet zuerst den Request-Header und das Feld mit der Chat-ID und holt die Daten dann über drei kleine Funktionen, die du bereitstellst: veriVarMi() meldet, ob noch ein Stück zu senden ist, sonrakiTampon() liefert die Adresse der Daten im Speicher und tamponBoyu() ihre Größe in Byte. Da der Bildpuffer der Kamera bereits ein einziges JPEG ist, übergibt der Code das ganze Foto auf einmal und setzt das Flag zurück. Ein Foto mit 1024×768 bei Qualität 12 ist je nach Motiv 50–120 KB groß und ist bei gutem WLAN in 1–2 Sekunden hochgeladen. Telegram erlaubt Fotos bis 10 MB, an die Größengrenze kommst du also nie heran; die eigentliche Grenze ist die Upload-Zeit.

Kompletter Code: /foto-Befehl, Blitzsteuerung und Bewegungsalarm

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}
TeilWas er macht
kameraBaslat()Startet die Kamera mit PSRAM in XGA (1024×768) mit zwei Bildpuffern, ohne PSRAM in SVGA (800×600)
fotoGonder()Schaltet bei Bedarf den Blitz ein, verwirft das alte Bild, nimmt ein neues auf, schickt zuerst eine kurze Nachricht und dann das Foto
mesajlariIsle()Beantwortet nur die Befehle /start, /foto, /flas, /alarm und /durum von CHAT_ID
PIR-Teil von loop()Prüft steigende Flanke, 60 s Aufwärmzeit und 30 s Sperrzeit
Telegram-Teil von loop()Fragt einmal pro Sekunde nach neuen Nachrichten und verarbeitet aufgelaufene Nachrichten nacheinander

Der Code achtet auf die steigende Flanke des PIR, nicht auf seinen Pegel: Auch wenn der Sensor 3 Sekunden lang HIGH bleibt, wird nur ein Foto gemacht. Die Sperrzeit von 30 Sekunden verhindert, dass jemand, der im Raum herumläuft, dein Handy mit Dutzenden Fotos pro Minute flutet, und dass Telegram mehr als eine Nachricht pro Sekunde an denselben Chat drosselt. Weil jede Abfrage ein HTTPS-Request ist, kann der Aufruf von getUpdates die Schleife etwa eine halbe Sekunde aufhalten; da der Ausgang des HC-SR501 mindestens 3 Sekunden HIGH bleibt, geht in dieser Zeit keine Bewegung verloren. Ist das Board dauerhaft an, vergehen zwischen Bewegung und Foto auf dem Handy meist 2–4 Sekunden.

Blitzsteuerung: Wann solltest du ihn einschalten?

Die weiße LED vorn wird über einen Transistor an GPIO 4 angesteuert und zieht bei voller Helligkeit mehr als 100 mA aus der Versorgung. Der Code schaltet den Blitz nur im Moment der Aufnahme ein und wartet etwa 150 ms; da sich die Belichtungsautomatik der Kamera erst nach einigen Bildern an das neue Licht anpasst, wird auch das erste Bild im Puffer verworfen. Der Blitz wirkt bis etwa 1–2 Meter; eine weiter entfernte Tür oder der Garten wird nicht ausgeleuchtet. Fotografierst du durch eine Scheibe, schalte den Blitz aus, sonst siehst du auf dem Foto nur die helle Spiegelung im Glas. Für einen schwächeren Blitz kannst du GPIO 4 per PWM ansteuern; der Schieberegler LED Intensity im Beispiel CameraWebServer macht genau das. Denk daran, dass ein weißer Blitz in der Nacht auch verrät, wo die Kamera hängt; für unauffällige Überwachung eignet sich der IR-Abschnitt weiter unten besser.

Akku sparen mit Deep Sleep

Eine ESP32-CAM mit eingeschaltetem WLAN zieht ständig 150–200 mA und leert damit einen 18650-Akku mit 2600 mAh in einem halben Tag. Im Akkubetrieb muss das Board fast die ganze Zeit im Deep Sleep verbringen und nur aufwachen, wenn der PIR auslöst. Im folgenden Code ist GPIO 13 die Weckquelle ext0: Geht der PIR-Ausgang auf HIGH, startet das Board neu, macht zuerst das Foto, verbindet sich dann mit dem WLAN, schickt das Foto und schläft wieder ein. Die Reihenfolge ist wichtig: Der WLAN-Verbindungsaufbau dauert einige Sekunden, und wenn du das Foto vorher machst, erwischst du genau den Moment der Bewegung.

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}

Im Code stecken vier Details, die man leicht übersieht. Erstens wartet das Board vor dem Einschlafen, bis der PIR-Ausgang wieder LOW ist; schläft es ein, während das Signal noch HIGH ist, wacht es sofort wieder auf und hängt in einer Schleife fest. Zweitens hält rtc_gpio_hold_en() den Blitz-Pin während des Schlafs auf LOW; ohne diese Zeile ist GPIO 4 unbeschaltet, und der Blitz glimmt im Schlaf schwach, was den Akku leert und Aufmerksamkeit erregt. Drittens macht rtc_gpio_deinit() GPIO 13 nach dem Aufwachen wieder zu einem normalen Eingang, sonst kann digitalRead() falsch lesen. Viertens bleibt der mit RTC_DATA_ATTR markierte Zähler im Schlaf erhalten, sodass dir jede Nachricht sagt, die wievielte Bewegung es war. Alle Schlafmodi und Weckquellen findest du in ESP32 Deep Sleep.

SzenarioMittlerer Strom (Akkuseite)Mit einem 18650 mit 2600 mAh
Dauerhaft an, WLAN verbunden (erster Sketch)≈ 290 mA≈ 9 Stunden
Deep Sleep + 20 Bewegungen pro Tag≈ 11 mA≈ 10 Tage
Selbsthalteschaltung (Board komplett aus) + 20 Bewegungen pro Tag≈ 0,7 mA≈ 4–5 Monate (theoretisch)

Die Werte in der Tabelle gehen von einem 5-V-Aufwärtswandler mit 85 % Wirkungsgrad aus. Auch wenn der ESP32-Chip selbst im Deep Sleep nur etwa 10 µA braucht, zieht das ESP32-CAM-Board wegen Spannungsregler, PSRAM und Kamera auch im Schlaf rund 6 mA, und genau das bestimmt die Akkulaufzeit. Jedes Aufwachen dauert 6–10 Sekunden: Booten, Kamera, WLAN-Verbindung und zwei HTTPS-Requests. Willst du monatelange Laufzeit, brauchst du eine Selbsthalteschaltung (Power Latch), die die Versorgung des Boards per MOSFET komplett trennt, das Board beim Auslösen des PIR einschaltet und es sich nach getaner Arbeit selbst abschalten lässt. Dein eigenes Szenario rechnest du mit dem Akkulaufzeit-Rechner durch; bei häufigen Neustarts hilft ESP32 Brownout.

Nachtaufnahmen: Blitz, IR-LEDs und Kameraeinstellungen

In den Standardobjektiven der ESP32-CAM sitzt ein fester IR-Sperrfilter, der Infrarotlicht blockiert; deshalb wirken die Farben tagsüber natürlich, aber eine nur mit IR beleuchtete Szene bleibt dunkel. Im Dunkeln sehen kannst du auf zwei Wegen: mit dem weißen Blitz oder mit IR-Beleuchtung und einem Objektiv ohne Filter. OV2640-Module, die als Night Vision oder 850 nm verkauft werden, haben diesen Filter nicht; tagsüber werden die Farben rosastichig, aber eine mit 850-nm-IR-LEDs beleuchtete Szene siehst du klar in Schwarz-Weiß. 940-nm-LEDs sind völlig unsichtbar, doch die OV2640 ist bei dieser Wellenlänge viel unempfindlicher, sodass das Bild deutlich dunkler wird. Versorge den IR-Strahler aus einer eigenen 5-V- oder 12-V-Quelle; fertige Platinen mit Lichtsensor schalten sich bei Dunkelheit selbst ein. Willst du ihn über einen GPIO steuern, nimm einen MOSFET und schließe die LEDs nie direkt an den Pin an.

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

Diese Einstellungen erhöhen die Verstärkungsobergrenze und die Belichtungszeit des Sensors. Der Preis: Im Dunkeln sinkt die Bildrate, eine sich bewegende Person wird unscharf, und die hohe Verstärkung erzeugt Bildrauschen. Am wirksamsten verbesserst du ein Nachtfoto immer noch mit Licht; schon ein schwacher IR-Strahler bringt ein klareres Ergebnis als mehr Verstärkung. Den Code kannst du direkt nach dem Aufruf von kameraBaslat() einfügen und, wenn du willst, mit einem LDR das Umgebungslicht messen und automatisch zwischen Tag- und Nachteinstellungen wechseln.

Grenzen als Überwachungskamera

  • Keine Videoaufzeichnung: Dieses System schickt nur Einzelfotos. Für Daueraufzeichnung oder das Zurückspulen brauchst du eine echte Überwachungskamera.
  • Es gibt eine Verzögerung: 2–4 Sekunden bei laufendem Board, 6–10 Sekunden nach dem Aufwachen aus dem Deep Sleep; wer schnell vorbeigeht, ist auf dem Foto womöglich schon aus dem Bild.
  • Ein PIR sieht nicht durch Glas: Glas lässt die infrarote Wärmestrahlung nicht durch. Ein Sensor hinter dem Fenster erkennt draußen keine Bewegung, kann aber von Sonne und Heizkörpern trotzdem fehlausgelöst werden.
  • Abhängig vom Internet: Fallen WLAN oder Internet aus, wird kein Foto verschickt. Für kritische Anwendungen ist eine zusätzliche Speicherung auf SD-Karte ein Backup.
  • Kein physischer Schutz: Das Board lässt sich leicht abmontieren oder ausschalten und leidet draußen unter Feuchtigkeit und Sonne; im Außenbereich brauchst du ein dichtes Gehäuse und eine temperaturfeste Stromversorgung.
  • Begrenzte Bildqualität: Die 2-MP-OV2640 reicht nicht, um aus einigen Metern Gesichter zu erkennen oder Kennzeichen zu lesen; Weitwinkelobjektive vergrößern den Bildausschnitt, verringern aber die Details.
  • Datenschutzregeln: Richte die Kamera auf dein eigenes Grundstück; bevor du den Garten des Nachbarn, Gemeinschaftsflächen oder die Straße aufnimmst, prüfe die rechtlichen Regeln an deinem Wohnort.

ESP32-CAM Telegram: Projektideen

  • Türklingel-Kamera: Schließe den Klingeltaster an GPIO 13 an, damit ein Druck den Eingangsbereich fotografiert und dir schickt; die Verdrahtung zeigt Taster anschließen.
  • Briefkasten-Melder: Bring einen Reedkontakt an der Klappe an, damit das Board beim Öffnen aufwacht und ein Foto vom Inhalt schickt.
  • Nistkasten- oder Haustierkamera: Bei Bewegung ein Foto machen und tagsüber mit /foto nachsehen.
  • Augen für die Alarmanlage: Lass eine Arduino-Alarmanlage beim Auslösen der ESP32-CAM ein Signal geben, damit sie den Ort fotografiert.
  • Pflanzen- und Gewächshausüberwachung: Per Timer stündlich aufwachen und ein Foto schicken, Messwerte zusätzlich per MQTT an Home Assistant übertragen.
  • Familiengruppe: Füge den Bot einer Telegram-Gruppe hinzu und nutze die Gruppen-ID, damit das Foto alle gleichzeitig erreicht.

Häufige Fehler und Lösungen

SymptomMögliche UrsacheLösung
Die Nachricht kommt, das Foto nichtBild zu groß, PSRAM aus oder Upload abgebrochenBoard AI Thinker ESP32-CAM wählen, Auflösung auf SVGA senken, WLAN-Empfang verbessern
Der Bot reagiert auf keinen BefehlToken oder CHAT_ID falsch oder nie /start an den Bot geschriebenToken neu kopieren, ID mit @userinfobot prüfen, /start an deinen Bot schreiben
Bewegungsfotos kommen ohne GrundDer PIR wird vom Signal der WLAN-Antenne oder einer Wärmequelle gestörtPIR 10–20 cm vom Board entfernen, 100 nF zwischen VCC und GND, Sensor nicht auf Sonne oder Heizkörper richten
Das Board wacht direkt nach dem Einschlafen wieder aufDer PIR-Ausgang ist beim Einschlafen noch HIGHVor dem Schlafen warten, bis der Pin LOW ist, Zeit-Trimmer des HC-SR501 auf Minimum
Der Blitz glimmt im SchlafGPIO 4 ist unbeschaltetVor dem Schlafen LOW schreiben und rtc_gpio_hold_en(GPIO_NUM_4) aufrufen
Das Foto ist dunkel oder grünlichDie ersten Bilder direkt nach dem KamerastartVor dem eigentlichen Foto 2–3 Bilder aufnehmen und verwerfen
Das Board startet beim Senden neuBrownout: Die Versorgung schafft den Strom von WLAN und Blitz nicht5-V-/2-A-Netzteil, kurze Kabel und 470–1000 µF zwischen 5V und GND verwenden
Beim Senden an eine Gruppe beginnt die Gruppen-ID mit einem Minuszeichen (z. B. -1001234567890); trag sie mitsamt Vorzeichen in die Zeile CHAT_ID ein. Damit der Bot Gruppennachrichten lesen kann, musst du eventuell bei BotFather /setprivacy ausschalten oder Befehle als /foto@bot_name senden; der Code entfernt diese Zusätze bereits.

Zusammen mit Telegram macht die ESP32-CAM aus einem günstigen Board ein smartes Auge, das Fotos an dein Handy schickt. Bau das System zuerst mit dem Dauerbetrieb-Sketch auf und stelle den PIR ein, dann wechsle bei Bedarf zur Deep-Sleep-Version. In der BlueGrays-App findest du den Telegram-Bewegungsalarm mit PIR, Summer und LED, ESP32 Deep Sleep und automatische Beleuchtung mit PIR, jeweils mit Schaltplänen und fertigem Code Schritt für Schritt.

Häufige Fragen

Wie sende ich mit der ESP32-CAM ein Foto an Telegram?

Du erstellst bei BotFather einen Bot und bekommst ein Token, installierst die Bibliothek UniversalTelegramBot und lädst den Bildpuffer der Kamera mit der Funktion sendPhotoByBinary() an den Telegram-Endpunkt sendPhoto hoch. Die Bibliothek schickt das Foto über HTTPS im Multipart-Format, wie ein Datei-Upload im Browser. Im Code musst du nur WLAN-Daten, Token und deine eigene Chat-ID anpassen.

An welchen Pin der ESP32-CAM kommt ein PIR-Sensor?

Ohne SD-Karte ist GPIO 13 die sicherste Wahl: Er beeinflusst den Bootvorgang nicht und kann als RTC-Pin das Board aus dem Deep Sleep wecken. Verbinde VCC des PIR mit dem 5V-Pin, GND mit GND und OUT mit GPIO 13. Meide GPIO 12; ist er beim Booten HIGH, startet das Board nicht.

Wie lange läuft eine ESP32-CAM mit Akku?

Mit dauerhaft eingeschaltetem WLAN hält ein 18650-Akku mit 2600 mAh etwa 9 Stunden. Mit Deep Sleep und Aufwachen nur bei Bewegung sind es rund 10 Tage; selbst im Schlaf zieht das Board wegen Regler und PSRAM etwa 6 mA. Für monatelangen Betrieb brauchst du eine Selbsthalteschaltung, die die Versorgung komplett trennt.

Kann die ESP32-CAM nachts sehen?

Der IR-Sperrfilter der Standardmodule blockiert Infrarotlicht, im Dunkeln bekommst du also nur mit dem weißen Blitz bis etwa 1–2 Meter ein Bild. Mit einem OV2640-Modul ohne Filter (Night Vision) und einem 850-nm-IR-Strahler gelingen klare Schwarz-Weiß-Fotos im Dunkeln. Eine höhere Verstärkungsobergrenze in den Kameraeinstellungen hilft ebenfalls, erzeugt aber Bildrauschen.

Kann die ESP32-CAM Videos an Telegram schicken?

Praktisch nein. Die ESP32-CAM erzeugt einzelne JPEG-Bilder und hat weder die Rechenleistung noch den Speicher, um daraus ein MP4-Video zu machen. Mehrere Fotos hintereinander zu schicken oder den Livestream im Heimnetz mit CameraWebServer anzusehen, ist realistischer.

Kann das Foto an mehrere Personen gehen?

Ja. Fügst du den Bot einer Telegram-Gruppe hinzu und verwendest die mit Minus beginnende Gruppen-ID als CHAT_ID, erreicht das Foto alle Gruppenmitglieder gleichzeitig. Alternativ legst du mehrere Chat-IDs in einem Array ab und schickst das Foto nacheinander an jede; jeder Versand ist ein eigener Upload und dauert entsprechend länger.

Alle Projekte ansehen
ESP32 Bewegungsalarmsystem (PIR + Buzzer + LED + Telegram)
#145Fortgeschritten
Premium

ESP32 Bewegungsalarmsystem (PIR + Buzzer + LED + Telegram)

Ein intelligentes Sicherheitsalarmsystem, das Buzzer und LED aktiviert, sobald der PIR-Sensor Bewegung erkennt, und gleichzeitig eine sofortige Benachrichtigung per Telegram an den Besitzer sendet. Ein 30-sekündiger Cooldown begrenzt die Häufigkeit wiederholter Alarme; dieser Mechanismus überprüft weder die Gültigkeit der PIR-Erkennungen noch verhindert er Fehlauslösungen. Geeignet für Heimsicherheit, Garagenüberwachung, Lagerräume oder Arbeitszimmer. Mit ESP32-CAM sind Foto-Benachrichtigungen m

0.0(0)
9
Automatische Beleuchtung mit dem ESP32-PIR-Bewegungssensor
#121Mittel
Premium

Automatische Beleuchtung mit dem ESP32-PIR-Bewegungssensor

In diesem Projekt werden wir mithilfe des PIR-Bewegungssensors HC-SR501 Personen erkennen, die den Raum betreten, und die LED automatisch einschalten. Wir überwachen die Leuchtdauer der LED mit millis(); bei jedem HIGH-Messwert des Sensors wird der Zeitpunkt der letzten Bewegung aktualisiert, und die LED erlischt frühestens 10 Sekunden nach dem letzten HIGH-Messwert. Der aktuelle Code wartet außerdem in jedem Schleifendurchlauf mit delay(50) für 50 ms, sodass die Anwendungsschleife nicht vol

4.4(24)
38
ESP32 Deep Sleep – Batteriesparmodus (OLED + RTC-RAM-Zähler)
#135Fortgeschritten
Premium

ESP32 Deep Sleep – Batteriesparmodus (OLED + RTC-RAM-Zähler)

Der Deep-Sleep-Modus des ESP32 senkt den Stromverbrauch auf etwa 10 µA, indem er die CPU und die meisten Peripheriegeräte abschaltet, und ist für batteriebetriebene IoT-Geräte von entscheidender Bedeutung. In diesem Projekt verwenden wir drei verschiedene Weckquellen: den Timer (15 s), einen externen Pin (EXT0 – Taste GPIO 33) und die Berührungserkennung (T0/GPIO 4). Mit RTC_DATA_ATTR markierte Variablen bleiben während des Deep-Sleep-Modus erhalten – der Boot-Zähler, der Timer-Wake-Zähler und

4.3(10)
16
ESP32 Intelligente Beleuchtung (PIR + LDR + Relais + OLED)
#141Fortgeschritten
Premium

ESP32 Intelligente Beleuchtung (PIR + LDR + Relais + OLED)

Verwandle deinen Flur, dein Badezimmer oder deine Speisekammer mit dem ESP32 in intelligente Beleuchtung! Mit einem PIR-Bewegungssensor, einem LDR-Lichtsensor und einem Relaismodul realisieren wir das klassische Szenario "Licht geht an, wenn im Dunkeln Bewegung erkannt wird" — bei Tageslicht bleibt das Licht trotz Bewegung aus, was tagsüber Energie spart. Das Licht bleibt 30 Sekunden lang an; bei anhaltender Bewegung wird der Timer zurückgesetzt und das Licht bleibt länger an. Das SSD1306 OLED-D

4.3(6)
11
ESP32-WLAN-STA-Verbindung (mit LCD-Display)
#127Fortgeschritten

ESP32-WLAN-STA-Verbindung (mit LCD-Display)

In diesem Projekt verbinden wir den ESP32 im Station-Modus (STA) sicher und fehlertolerant mit dem heimischen WLAN. Wir lagern SSID und Passwort in eine separate secrets.h-Datei aus, um Leaks zu Git zu verhindern, richten einen Auto-Reconnect-Mechanismus ein und zeigen RSSI-Signalpegel und IP-Adresse auf einem I2C-LCD-Display an. Die interne LED dient als Statusanzeige. Ein Verbindungsmanager auf Produktionsniveau.

4.4(21)
34
ESP32 mDNS: Zugriff auf das Gerät über „bluegrays-esp.local“ (mit LCD-Display)
#131Fortgeschritten
Premium

ESP32 mDNS: Zugriff auf das Gerät über „bluegrays-esp.local“ (mit LCD-Display)

In diesem Projekt fügen wir dem ESP32 einen mDNS-Dienst (Multicast DNS) hinzu, um den Zugriff nicht über eine IP-Adresse, sondern über einen lesbaren Hostnamen wie bluegrays-esp.local zu ermöglichen. Selbst wenn sich die vom DHCP zugewiesene IP-Adresse ändert, können Sie das Gerät immer unter demselben Namen erreichen. Indem wir zusätzlich einen kleinen Webserver darauf betreiben, senden wir über den Browser unter der Adresse http://bluegrays-esp.local/ Befehle zur LED-Steuerung. Auf dem I2C

4.8(14)
23
Bewegungsmelder mit Bewegungssensor
#24Anfänger
Premium

Bewegungsmelder mit Bewegungssensor

Ein einfacher Wächter, der Bescheid gibt, wenn jemand den Raum betritt. Der Bewegungsmelder spürt die Wärme, die der menschliche Körper abgibt; der Arduino lässt die LED leuchten, solange sich etwas b

4.6(47)
67