Güvenlik konusuna şu yazıda değinilmişti. Kurum İçi Güvenlik Kontrol Cihazı

Burada yine odak noktamız güvenlik ve bilgi olacak. “Burada bir şeyler olmuş acaba kim veya ne ?” sorusuna cevap bulmak için, özellikle kırsalda ve kapalı mekanlarda, bu yapıyı / cihazı kullanabiliriz. Şahsen davetsiz misafirlerden haberdar olduğum için bu sistem işe yaramıştır ve işlevseldir.

Bu yazıda yapılmak istenen okuyucuya fikir vermektir, bu nedenle çok detaya girilmeyecektir. İlgili elemanları (yada benzerlerini), kodları vs… ve benzer üretimleri internette bulabilir ve hatta kodları bir ai modeline yazdırabilirsiniz.

NOT: Resim @gemini_ai ile üretilmiştir, temsilidir

Bir hareket olduğunda, bir kamera ile harekete sebep olan şeyin fotoğrafını çekmek isterseniz bunun için “Nasıl yapılır ?“ı yine DIY/kendin yap felsefesi ile sizlere kısaca anlatmak istiyorum.

Yine elektrik / elektronikte yaptığımız gibi akım/gerilim ( yani sinyaller) ve algoritmaları kullanacağız.

Temel malzemeler şunlardır:

  • PIR / hareket sensörü: üzerindeki kızılötesi sensör ile ortamdaki sıcaklık değişimlerini algılayıp sinyal üreten pasif devre elemanıdır. Canlılar vucut ısısına sahip oldukları için görüş alanına girdiklerinde PIR/hareket sensörü ısı farklılığını algılayıp bir sinyal üretir. (burada şunu belirtelim, ortamda ısı kaynağı ve ısı akışları mevcutsa sensör çok kararsız çalışır. Güneş, soba, ocak vb… )

  • Servo veya step motor: Çok detaya boğulmadan şöyle açıklayabiliriz; dar bir açıda ve belirli açılarda hareket ettirebildiğimiz motorlardır. Örneğin 10, 50, 100, 130 derece açılarda sıra ile hareket et gibi senaryolarda kullanılır. (Önemli: tam bağlantıları yapmadan motor açılarını kontrol etmenizi öneririm bu sayede beklenmedik sıkışmalardan ve arızalardan korunabiliriz.)

  • ESP CAM: Popüler ESP8266 ve ESP32 mikro denetleyici serilerinin geliştiricisi ve üreticisi “Espressif Systems” olan kameralı bir mikro denetleyicidir. Sistemin beynidir.

  • Güç kaynağı: Bu kullanılan elemanlara göre 3.3v ve 5v gerilim ve güç üreten bir adaptör veya batarya olabilir. Burada belirtmek istediğim şu var; ESP CAM ile servo yada step motoru ayrı güç kaynaklarıyla beslenmesi bu motorlar kalkınma anında aşırı akım çekebildikleri için gerilim dalgalanmalarına sebep olabilir buda mikrodenetleyicinin çalışmasını etkileyebilir.

(Önemli bir nokta; güç kaynaklarını tamamen kullandığınız elemanların ihtiyacına göre belirlemeniz gerekir, bu sistemi bir “Arduino” ile de yapabilirsiniz, motor seçiminizi daha güçlü bir motordan yana kullanabilirsiniz. Lütfen elamanın / cihazın ‘datasheet’ dediğimiz üretici tarafından hazırlanmış dokümanını inceleyiniz. )

  • İhtiyacınıza, zevkinize, tercihinize göre ek lamba, led, buzzer gibi çeşitli elemanları da ekleyebilirsiniz. Ben hedeflemeyi net görebilmek için bir lazer modülü ekledim.

Ayrıca ben gövde/şase için plastik bir dondurma kutusu kullandım o kısım artık sizin, bütçeniz ve yaratıcılığınız ile ilgili.

Bunun yanı sıra ESP çipleri wifi özelliklidir minik bir sunucu gibi kullanıp canlı yayın (stream) ve @telegram_bot kullanarak çekilen fotoğrafları telefonunuza anlık gönderebilirsiniz yada bir bilgisayarı sunucu gibi kullanarak çekilen fotoğrafları bilgisayara aktarabilir ve bir ai modeli ile analiz edebilirsiniz. Tamamen ihtiyaç, zevk ve hayal gücü…

Çalışma Prensibi

Hareket sensörü bir nesne algıladığında bir elektrik sinyali üretir (akım/gerilim) bu üretilen sinyal ESP CAM’e (bağlantı yapılan pin ile) bir hareket olduğunu söyler. Servo/step motora bağlı olan ESP CAM içerisindeki algoritma (kod) bu şekilde yazıldığı için kayda başlaması gerektiği anlar ve senaryoya göre bir kaç açıdan fotoğraf alır kaydeder/iletir.

NOT:Bir motor kullanmadan da sadece tek açıdan görüntü alabilirsiniz ve hatta fotoğraflama davranışını süreye bağlayarak, bir hareket sensörü olmadan da devreyi kurabiliriz. Ancak her özellik genelde bir şeyler eklenmesiyle elde edilir, bu bazen yazılımsal bazende donanımsal olur.


Benim kullandığım kod aşağıdaki gibidir. Bunu kopyalayıp bir ai modeline inceletebilirsiniz. CPP formatındadır.

#include "esp_camera.h"
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <ESP32Servo.h>
#include <ArduinoOTA.h>
#include <WebServer.h>

const char* ssid = "~~wifi adı~~";
const char* password = "~~wifi şifresi~~";
String botToken = "~~telegram_botu_token~~";
String chatId = "~~bot_için_kullanıcı_id~~";

#define PWDN_GPIO_NUM     32
#define RESET_GPIO_NUM    -1
#define XCLK_GPIO_NUM      0
#define SIOD_GPIO_NUM     26
#define SIOC_GPIO_NUM     27
#define Y9_GPIO_NUM       35
#define Y8_GPIO_NUM       34
#define Y7_GPIO_NUM       39
#define Y6_GPIO_NUM       36
#define Y5_GPIO_NUM       21
#define Y4_GPIO_NUM       19
#define Y3_GPIO_NUM       18
#define Y2_GPIO_NUM        5
#define VSYNC_GPIO_NUM    25
#define HREF_GPIO_NUM     23
#define PCLK_GPIO_NUM     22

#define PIR_PIN 13       
#define FLASH_LED_PIN 4  
#define SERVO_PIN 14              
#define LAZER_PIN 12 

Servo myservo;
WebServer server(80);

int cekim_acilari[3] = {40, 85, 130};
bool tetiklemeBekliyor = false; 
unsigned long lastWiFiCheck = 0; 
unsigned long lastLocalPirTrigger = 0; 

bool lazerDurumu = false; 
bool yerelGuvenlikAcik = true; // YENİ: Başlangıçta kameranın kendi PIR sensörü açık

String webLog = "";

void addLog(String msg) {
  Serial.println(msg);
  webLog = "<b>" + String(millis()) + "ms:</b> " + msg + "<br>" + webLog;
  if (webLog.length() > 3000) webLog = webLog.substring(0, 3000); 
}

void sendPhoto(uint8_t * image_data, size_t length, int aci);

void handleRoot() {
  String html = "<html><head><meta charset='utf-8'><title>ESP-CAM Kontrol</title></head><body style='font-family:Arial; padding:20px; background-color:#f4f4f9;'>";
  html += "<h2 style='color:#333;'>ESP32-CAM GUVENLIK PANELI</h2>";
  html += "<div style='float:left; margin-right:30px;'>";
  html += "<h3 style='color:#555;'>Canli Yayin</h3>";
  html += "<img id='stream_img' src='' style='border:3px solid #333; border-radius:5px; width:400px; height:296px; background-color:#000; display:block; margin-bottom:10px;'>";
  html += "<button id='stream_btn' onclick='toggleStream()' style='padding:10px; background:#0275d8; color:white; font-weight:bold; cursor:pointer; border:none; border-radius:4px; width:100%; margin-bottom:10px;'>YAYINI BASLAT</button>";
  
  html += "<button onclick=\"sendCommand('/lazer')\" style='padding:10px; background:#5bc0de; color:white; font-weight:bold; cursor:pointer; border:none; border-radius:4px; width:100%; margin-bottom:10px;'>LAZERI AC / KAPAT</button>";
  
  // YENİ: Yerel PIR sensörünü (ESP-CAM üzerindekini) Açıp Kapatma Butonu
  html += "<button onclick=\"sendCommand('/guvenlik')\" style='padding:10px; background:#f0ad4e; color:white; font-weight:bold; cursor:pointer; border:none; border-radius:4px; width:100%; margin-bottom:20px;'>YEREL PIR (KAMERA SENSORU) AC / KAPAT</button>";
  
  html += "<button onclick=\"sendCommand('/trigger')\" style='padding:12px; background:#d9534f; color:white; font-weight:bold; cursor:pointer; border:none; border-radius:4px; width:100%; margin-bottom:20px;'>TEST TETIKLEMESI YAP (FOTO CEK)</button>";
  html += "<div style='background:#e9ecef; padding:10px; border-radius:5px;'>";
  html += "<b>Manuel Servo Yonetimi: </b><br><br>";
  html += "<button onclick=\"sendCommand('/angle?val=40')\" style='padding:8px 15px; cursor:pointer; margin-right:5px;'>Sol (40)</button>";
  html += "<button onclick=\"sendCommand('/angle?val=85')\" style='padding:8px 15px; cursor:pointer; margin-right:5px;'>Orta (85)</button>";
  html += "<button onclick=\"sendCommand('/angle?val=130')\" style='padding:8px 15px; cursor:pointer;'>Sag (130)</button>";
  html += "</div></div>";
  
  html += "<div style='float:left;'>";
  html += "<h3 style='color:#555;'>Sistem Loglari</h3>";
  html += "<div id='logbox' style='width:500px; height:450px; border:2px solid #333; background:#1e1e1e; color:#4af626; overflow-y:auto; padding:15px; font-family:Courier New, monospace; border-radius:5px;'>";
  html += webLog;
  html += "</div></div>";
  
  html += "<script>";
  html += "var streamActive = false;";
  html += "function toggleStream() {";
  html += "  var img = document.getElementById('stream_img');";
  html += "  var btn = document.getElementById('stream_btn');";
  html += "  if(streamActive) { img.src = ''; btn.innerHTML = 'YAYINI BASLAT'; btn.style.background = '#0275d8'; streamActive = false; }";
  html += "  else { img.src = '/stream'; btn.innerHTML = 'YAYINI DURDUR'; btn.style.background = '#0275d8'; streamActive = true; }";
  html += "}";
  html += "function sendCommand(url) {";
  html += "  var wasStr = streamActive;";
  html += "  if (wasStr) toggleStream();"; 
  html += "  setTimeout(function() {";
  html += "    fetch(url).then(r => r.text()).then(t => { if (wasStr) setTimeout(toggleStream, 1000); })";
  html += "    .catch(e => { if (wasStr) setTimeout(toggleStream, 1000); });";
  html += "  }, 300);";
  html += "}";
  html += "setInterval(function() { fetch('/log').then(response => response.text()).then(text => { document.getElementById('logbox').innerHTML = text; }); }, 2000);";
  html += "</script>";
  html += "</body></html>";
  server.send(200, "text/html", html);
}

void handleLogRequest() { server.send(200, "text/html", webLog); }

void handleLazer() {
  lazerDurumu = !lazerDurumu;
  digitalWrite(LAZER_PIN, lazerDurumu ? HIGH : LOW);
  addLog(lazerDurumu ? "Lazer manuel ACILDI" : "Lazer manuel KAPATILDI");
  server.send(200, "text/plain", lazerDurumu ? "ACIK" : "KAPALI");
}

// YENİ: Yerel Güvenlik Aç/Kapa Fonksiyonu
void handleGuvenlik() {
  yerelGuvenlikAcik = !yerelGuvenlikAcik;
  addLog(yerelGuvenlikAcik ? "Kamera uzerindeki yerel PIR: AKTIF" : "Kamera uzerindeki yerel PIR: PASIF (Kor mod)");
  server.send(200, "text/plain", yerelGuvenlikAcik ? "AKTIF" : "PASIF");
}

void taramaVeCekim() {
  addLog("--- Tarama ve Gonderim Baslatildi ---");
  digitalWrite(LAZER_PIN, HIGH); 
  
  for (int i = 0; i < 3; i++) {
    int hedef_aci = cekim_acilari[i];
    myservo.write(hedef_aci);
    delay(1200); 

    camera_fb_t * dummy = esp_camera_fb_get();
    if(dummy) esp_camera_fb_return(dummy);
    delay(100); 

    digitalWrite(FLASH_LED_PIN, HIGH); delay(1200); 
    camera_fb_t * fb = esp_camera_fb_get();
    digitalWrite(FLASH_LED_PIN, LOW);
    
    if (fb) {
      addLog("Fotograf: Aci " + String(hedef_aci));
      sendPhoto(fb->buf, fb->len, hedef_aci);
      esp_camera_fb_return(fb);
    } else {
      addLog("<span style='color:red;'>HATA: Fotograf alinamadi!</span>");
    }
    delay(300);
  }
  myservo.write(cekim_acilari[0]);
  
  if (!lazerDurumu) {
    digitalWrite(LAZER_PIN, LOW); 
  }
  
  addLog("Tarama tamamlandi.");
}

void handleStream() {
  WiFiClient client = server.client();
  String response = "HTTP/1.1 200 OK\r\nContent-Type: multipart/x-mixed-replace; boundary=frame\r\n\r\n";
  client.print(response);
  while (client.connected()) {
    if (tetiklemeBekliyor) { 
      tetiklemeBekliyor = false;
      taramaVeCekim();
    }
    camera_fb_t * fb = esp_camera_fb_get();
    if (!fb) { delay(50); continue; }
    client.print("--frame\r\nContent-Type: image/jpeg\r\nContent-Length: " + String(fb->len) + "\r\n\r\n");
    client.write(fb->buf, fb->len);
    client.print("\r\n");
    esp_camera_fb_return(fb);
    delay(80); 
  }
}

void handleAngle() {
  if (server.hasArg("val")) {
    int gelen_aci = server.arg("val").toInt();
    if (gelen_aci < 40) gelen_aci = 40;
    if (gelen_aci > 130) gelen_aci = 130;
    myservo.write(gelen_aci);
    addLog("Servo acisi degistirildi: " + String(gelen_aci));
    server.send(200, "text/plain", "Aci: " + String(gelen_aci));
  }
}

void handleTrigger() {
  server.send(200, "text/plain", "Ana merkezden tarama siraya alindi...");
  tetiklemeBekliyor = true; 
}

void setup() {
  Serial.begin(115200);
  
  pinMode(LAZER_PIN, OUTPUT); digitalWrite(LAZER_PIN, LOW); 
  pinMode(FLASH_LED_PIN, OUTPUT); digitalWrite(FLASH_LED_PIN, LOW);
  pinMode(PIR_PIN, INPUT);

  camera_config_t config;
  config.ledc_channel = LEDC_CHANNEL_0; config.ledc_timer = LEDC_TIMER_0;     
  config.pin_d0 = Y2_GPIO_NUM; config.pin_d1 = Y3_GPIO_NUM; config.pin_d2 = Y4_GPIO_NUM;
  config.pin_d3 = Y5_GPIO_NUM; config.pin_d4 = Y6_GPIO_NUM; config.pin_d5 = Y7_GPIO_NUM;
  config.pin_d6 = Y8_GPIO_NUM; config.pin_d7 = Y9_GPIO_NUM; config.pin_xclk = XCLK_GPIO_NUM;
  config.pin_pclk = PCLK_GPIO_NUM; config.pin_vsync = VSYNC_GPIO_NUM; config.pin_href = HREF_GPIO_NUM;
  config.pin_sccb_sda = SIOD_GPIO_NUM; config.pin_sccb_scl = SIOC_GPIO_NUM; config.pin_pwdn = PWDN_GPIO_NUM;
  config.pin_reset = RESET_GPIO_NUM; config.xclk_freq_hz = 20000000; config.pixel_format = PIXFORMAT_JPEG;

  if(psramFound()){ config.frame_size = FRAMESIZE_XGA; config.jpeg_quality = 10; config.fb_count = 1; } 
  else { config.frame_size = FRAMESIZE_VGA; config.jpeg_quality = 12; config.fb_count = 1; }

  esp_err_t err = esp_camera_init(&config);
  if (err != ESP_OK) { addLog("<span style='color:red;'>Kamera Hatasi!</span>"); } 
  else { addLog("Kamera baslatildi."); }

  myservo.setPeriodHertz(50);
  myservo.attach(SERVO_PIN, 500, 2400);
  myservo.write(cekim_acilari[0]);

  WiFi.begin(ssid, password);
  WiFi.setSleep(false);
  
  int wait_count = 0;
  while (WiFi.status() != WL_CONNECTED && wait_count < 15) { delay(500); wait_count++; }

  ArduinoOTA.setHostname("esp32cam-bahce");
  ArduinoOTA.begin();

  server.on("/", handleRoot);
  server.on("/log", handleLogRequest);
  server.on("/stream", handleStream); 
  server.on("/trigger", handleTrigger);
  server.on("/angle", handleAngle); 
  server.on("/lazer", handleLazer); 
  
  // YENİ: Yerel Güvenlik için HTTP Yönlendirmesi
  server.on("/guvenlik", handleGuvenlik); 
  
  server.begin();
}

void loop() {
  unsigned long currentMillis = millis();

  // YENİ: Kamera ancak "yerelGuvenlikAcik" true ise kendi PIR'ını umursar.
  if (yerelGuvenlikAcik && digitalRead(PIR_PIN) == HIGH && !tetiklemeBekliyor) {
    if (currentMillis - lastLocalPirTrigger >= 30000) {
      lastLocalPirTrigger = currentMillis;
      addLog("⚠️ YEREL PIR HAREKET ALGILADI! Otonom cekim basliyor.");
      tetiklemeBekliyor = true;
    }
  }

  if (WiFi.status() != WL_CONNECTED) {
    if (currentMillis - lastWiFiCheck >= 15000) {
      WiFi.disconnect();
      WiFi.begin(ssid, password);
      lastWiFiCheck = currentMillis;
    }
  } else {
    server.handleClient();
    ArduinoOTA.handle();
  }
  
  // Eğer dışarıdan (Ana ESP32'den) tetikleme geldiyse, yerel güvenlik kapalı olsa bile umursamaz, doğrudan çalışır.
  if (tetiklemeBekliyor) {
    tetiklemeBekliyor = false; 
    taramaVeCekim();
  }
  delay(10);
}

void sendPhoto(uint8_t * image_data, size_t length, int aci) {
  if (WiFi.status() != WL_CONNECTED) {
    addLog("<span style='color:red;'>HATA: İnternet yok!</span>");
    return;
  }

  WiFiClientSecure client;
  client.setInsecure(); 
  addLog("Telegram'a gonderiliyor...");
  
  if (client.connect("api.telegram.org", 443)) {
    String boundary = "----WebKitFormBoundary7MA4YWxkTrZu0gW";
    String head = "--" + boundary + "\r\nContent-Disposition: form-data; name=\"chat_id\"\r\n\r\n" + chatId + "\r\n--" + boundary + "\r\nContent-Disposition: form-data; name=\"photo\"; filename=\"cam_" + String(aci) + ".jpg\"\r\nContent-Type: image/jpeg\r\n\r\n";
    String tail = "\r\n--" + boundary + "--\r\n";
    size_t totalLen = head.length() + length + tail.length();

    client.println("POST /bot" + botToken + "/sendPhoto HTTP/1.1");
    client.println("Host: api.telegram.org");
    client.println("Content-Type: multipart/form-data; boundary=" + boundary);
    client.print("Content-Length: "); client.println(totalLen); client.println();
    client.print(head);

    size_t bufferSize = 1024;
    for (size_t i = 0; i < length; i += bufferSize) {
      size_t chunk = (length - i < bufferSize) ? (length - i) : bufferSize;
      client.write((const uint8_t *)image_data + i, chunk);
    }
    client.print(tail);
    
    long waitTime = millis();
    bool responseReceived = false;
    String telegramResponse = "";
    while (millis() - waitTime < 10000) { 
      while (client.available()) { char c = client.read(); telegramResponse += c; responseReceived = true; }
      if (responseReceived) break; 
    }
    client.stop();

    if(telegramResponse.length() > 0) {
       int firstLineEnd = telegramResponse.indexOf('\n');
       if(firstLineEnd > 0) addLog("<b>YANIT:</b> " + telegramResponse.substring(0, firstLineEnd));
    } else addLog("<span style='color:red;'>HATA: Yanit yok.</span>");
  } else addLog("<span style='color:red;'>HATA: Telegram ulasilamadi!</span>");
}

Bazı fotoğraf örnekleri;

  • Gece çekimi iyi değil, ek ışık kaynağı gerekiyor.
  • Güneş ışığından dolayı gereksiz algılamalar meydana gelmiştir
  • Uzaktan (yine telegram bot ile) açıp kapatma fonksiyonu eklenmiştir
  • Canlı yayın modu da ayrıca ağ üzerinden açıp kapatılabiliyor


"SON"