ESP32 ve Arduino

ESP32-CAM MQTT: JPEG Görüntüler Yakalayın ve İkili Payload Olarak Yayınlayın

Bir AI-Thinker ESP32-CAM'i, istek üzerine veya zamanlayıcıyla JPEG görüntüler yayınlayan, broker mesaj sınırları içinde kalan ve her kareyi küçük bir Python subscriber'ı ile kaydeden bir MQTT kamerasına dönüştürün.

Bir esp32-cam mqtt projesinde bulut broker'a JPEG görüntü yayınlayan AI-Thinker ESP32-CAM kartı
Bunu kendi MQTT broker’ınızda çalıştırınFree planla özel, yönetilen bir MQTT broker alın ve hazır kod örnekleriyle başlayın.
Ücretsiz deneyin Kod örnekleri Kart gerekmez

Bu ESP32-CAM MQTT eğitimi, bir AI-Thinker ESP32-CAM üzerinde JPEG görüntülerin nasıl yakalanacağını ve her karenin ham ikili (binary) bir MQTT payload'ı olarak nasıl yayınlanacağını gösterir. Sonunda bir zamanlayıcıyla veya komutla fotoğraf gönderen bir kameraya, her görüntüyü diske kaydeden bir Python betiğine ve bir karenin ulaşıp ulaşmayacağını belirleyen mesaj boyutu sınırlarına dair net bir anlayışa sahip olacaksınız.

Kamera Görüntüleri Neden MQTT Üzerinden Gönderilir?

MQTT genellikle küçük sensör ölçümleriyle ilişkilendirilir, ancak protokolün kendisi bir payload'ın içinde ne olduğunu umursamaz. Bir JPEG yalnızca bir bayt dizisidir ve spesifikasyon, küçük bir kameranın üreteceği herhangi bir görüntüden çok daha büyük payload'lara izin verir. Görüntüleri telemetrinizle aynı broker üzerinden göndermenin gerçek avantajları vardır:

  • Tek bağlantı, tek güvenlik modeli. Kamera, diğer tüm cihazlarla aynı kimlik bilgilerini, TLS ayarlarını ve topic kurallarını kullanır.
  • Olay güdümlü yakalama. Bir hareket sensörü, bir kapı kontağı veya bir dashboard düğmesi bir komut yayınlayabilir ve kamera bir kareyle yanıt verir. Yoklama yok, cihazda HTTP sunucusu yok.
  • Ücretsiz dağıtım (fan-out). Birkaç subscriber (bir arşiv betiği, bir dashboard, bir uyarı akışı) kamera ekstra iş yapmadan aynı kareyi alabilir.

Yine de MQTT bir video protokolü değildir. Birkaç saniyede bir veya olaylarla alınan anlık görüntüler için uygundur, sürekli video akışı için ise uygun değildir. Canlı videoya ihtiyacınız varsa özel bir akış protokolü kullanın ve MQTT'yi kontrol ve meta veriler için saklayın.

Gereken Donanım ve Yazılım

Öğe Notlar
OV2640'lı AI-Thinker ESP32-CAM En yaygın modül; aşağıdaki pin haritası bu kart içindir
USB-seri adaptör (FTDI veya ESP32-CAM-MB) Yükleme sırasında GPIO0 GND'ye çekilmelidir
Kararlı 5 V besleme, en az 1 A önerilir Yakalama sırasındaki voltaj düşmeleri sıfırlamaların bir numaralı nedenidir
esp32 kart paketiyle Arduino IDE 2.x Kart: "AI Thinker ESP32-CAM"
Nick O'Leary'nin PubSubClient kütüphanesi Library Manager'dan kurulur
paho-mqtt 2.x ile Python 3 Alıcı betik için

Daha önce hiç bir ESP32'yi bir broker'a bağlamadıysanız ESP32 IoT eğitimi Wi-Fi, PubSubClient temelleri ve yeniden bağlanma mantığını daha ayrıntılı ele alır.

Asıl Kısıt Mesaj Boyutudur

Herhangi bir kod yazmadan önce bir çözünürlüğe karar verin; çünkü JPEG'in boyutu onu hangi broker'a gönderebileceğinizi ve ne kadar RAM'e ihtiyacınız olduğunu belirler.

Yayın yaptığınız yer Maksimum mesaj boyutu
broker.connectmq.com herkese açık test alanı 64 KB
Serverless (Free) plan 256 KB
Serverless (Paid) plan 1 MB

JPEG boyutu çözünürlüğe, jpeg_quality ayarına (düşük sayı daha yüksek kalite ve daha büyük dosya demektir) ve sahnenin kendisine bağlıdır. Hareketli, ayrıntılı bir sahne beyaz bir duvardan daha kötü sıkıştırılır. Pratik başlangıç noktaları:

  • FRAMESIZE_QVGA (320x240), 64 KB'ın altına kolayca sığan küçük kareler üretir; herkese açık MQTT test broker'ı üzerinde test için idealdir.
  • FRAMESIZE_VGA (640x480) izleme için iyi bir uzlaşmadır ve genellikle ücretsiz plana sığar.
  • FRAMESIZE_SVGA ve üzeri 64 KB'ı aşabilir; bunlara güvenmeden önce seri monitörde fb->len değerini kontrol edin.

Aşağıdaki sketch kare uzunluğunu bir sınırla karşılaştırır ve broker'ın bağlantıyı düşürmesine izin vermek yerine fazla büyük kareleri atlar.

PubSubClient ile Büyük Payload'ları Akış Halinde Göndermek

PubSubClient normalde topic'i ve payload'ı varsayılan olarak 256 bayt olan dahili bir tampona kopyalar. Bunu setBufferSize() ile artırabilirsiniz, ancak kamera kare tamponunun üzerine 50 KB'lık bir tampon ayırmak RAM israfıdır. Daha iyi yaklaşım akış API'sidir:

  1. beginPublish(topic, length, retained), MQTT başlığını nihai uzunlukla yazar.
  2. write(buffer, length), JPEG baytlarını doğrudan kamera kare tamponundan gönderir.
  3. endPublish() paketi tamamlar.

Payload hiçbir zaman kopyalanmaz; bu yüzden büyük kareler bile yalnızca küçük bir MQTT tamponuna ihtiyaç duyar. PubSubClient'ın QoS 0 ile yayın yaptığını unutmayın; bu anlık görüntüler için sorun değildir: biri kaybolursa bir sonraki birkaç saniye uzaklıktadır. Genel olarak tampon ayarı hakkında daha fazla bilgi için PubSubClient tampon boyutu yazısına bakın.

ESP32-CAM MQTT Sketch'i

Topic düzeni, karelerinizin herkese açık broker'daki başka kimseninkilerle çakışmaması için benzersiz bir önek kullanır:

Topic Yön Payload
connectmq/demo/cam-7f3a/snapshot kamera yayınlar ham JPEG baytları
connectmq/demo/cam-7f3a/status kamera yayınlar, retained online / offline (Last Will)
connectmq/demo/cam-7f3a/cmd kamera subscribe olur capture, flash_on, flash_off

cam-7f3a ifadesini kendi rastgele string'inizle değiştirin.

#include <WiFi.h>
#include <PubSubClient.h>
#include "esp_camera.h"

// ---------- Wi-Fi ve MQTT ayarları ----------
const char* WIFI_SSID     = "YOUR_WIFI";
const char* WIFI_PASSWORD = "YOUR_WIFI_PASSWORD";
const char* MQTT_HOST     = "broker.connectmq.com";
const uint16_t MQTT_PORT  = 1883;
const char* CLIENT_ID     = "cam-7f3a-esp32";           // boş olmamalı ve benzersiz olmalı
const char* TOPIC_SNAP    = "connectmq/demo/cam-7f3a/snapshot";
const char* TOPIC_STATUS  = "connectmq/demo/cam-7f3a/status";
const char* TOPIC_CMD     = "connectmq/demo/cam-7f3a/cmd";

const size_t   MAX_PAYLOAD      = 60 * 1024;   // herkese açık 64 KB sınırının altında kal
const uint32_t CAPTURE_INTERVAL = 30000;       // her 30 sn'de zamanlanmış görüntü
const int      FLASH_LED_PIN    = 4;

// ---------- AI-Thinker ESP32-CAM pin haritası ----------
#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

WiFiClient net;
PubSubClient mqtt(net);
uint32_t lastCapture = 0;
volatile bool captureRequested = false;

bool initCamera() {
  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;
  config.frame_size   = FRAMESIZE_VGA;   // kareler çok büyükse QVGA deneyin
  config.jpeg_quality = 12;              // 10-63, düşük = daha iyi kalite, daha büyük dosya
  config.fb_count     = 1;
  config.fb_location  = CAMERA_FB_IN_PSRAM;
  config.grab_mode    = CAMERA_GRAB_LATEST;

  esp_err_t err = esp_camera_init(&config);
  if (err != ESP_OK) {
    Serial.printf("Camera init failed: 0x%x\n", err);
    return false;
  }
  return true;
}

void onMessage(char* topic, byte* payload, unsigned int length) {
  String cmd;
  for (unsigned int i = 0; i < length; i++) cmd += (char)payload[i];
  cmd.trim();
  if (cmd == "capture")        captureRequested = true;
  else if (cmd == "flash_on")  digitalWrite(FLASH_LED_PIN, HIGH);
  else if (cmd == "flash_off") digitalWrite(FLASH_LED_PIN, LOW);
}

void connectWifi() {
  WiFi.mode(WIFI_STA);
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.printf("\nWi-Fi OK, IP %s\n", WiFi.localIP().toString().c_str());
}

void connectMqtt() {
  while (!mqtt.connected()) {
    Serial.print("Connecting to MQTT... ");
    // Last Will: kamera kaybolursa broker "offline" (retained) yayınlar
    if (mqtt.connect(CLIENT_ID, TOPIC_STATUS, 1, true, "offline")) {
      Serial.println("connected");
      mqtt.publish(TOPIC_STATUS, "online", true);
      mqtt.subscribe(TOPIC_CMD);
    } else {
      Serial.printf("failed, state=%d, retrying in 5 s\n", mqtt.state());
      delay(5000);
    }
  }
}

void publishSnapshot() {
  camera_fb_t* fb = esp_camera_fb_get();
  if (!fb) {
    Serial.println("Capture failed");
    return;
  }
  if (fb->len > MAX_PAYLOAD) {
    Serial.printf("Frame %u bytes exceeds limit, skipped\n", (unsigned)fb->len);
    esp_camera_fb_return(fb);
    return;
  }
  bool ok = mqtt.beginPublish(TOPIC_SNAP, fb->len, false);
  if (ok) {
    size_t sent = mqtt.write(fb->buf, fb->len);
    ok = mqtt.endPublish() && sent == fb->len;
  }
  Serial.printf("Snapshot %u bytes -> %s\n", (unsigned)fb->len, ok ? "sent" : "FAILED");
  esp_camera_fb_return(fb);
}

void setup() {
  Serial.begin(115200);
  pinMode(FLASH_LED_PIN, OUTPUT);
  digitalWrite(FLASH_LED_PIN, LOW);
  if (!initCamera()) {
    delay(5000);
    ESP.restart();
  }
  connectWifi();
  mqtt.setServer(MQTT_HOST, MQTT_PORT);
  mqtt.setCallback(onMessage);
  mqtt.setKeepAlive(30);
  mqtt.setSocketTimeout(10);   // büyük yazmalar varsayılandan fazlasını gerektirir
  connectMqtt();
}

void loop() {
  if (WiFi.status() != WL_CONNECTED) connectWifi();
  if (!mqtt.connected()) connectMqtt();
  mqtt.loop();

  uint32_t now = millis();
  if (captureRequested || now - lastCapture >= CAPTURE_INTERVAL) {
    captureRequested = false;
    lastCapture = now;
    publishSnapshot();
  }
}

Kartı GPIO0 GND'ye bağlıyken yükleyin, ardından jumper'ı çıkarın ve reset'e basın. Seri monitör IP adresini, başarılı bir MQTT bağlantısını ve her görüntü için bayt cinsinden boyutunu içeren bir satır göstermelidir.

Görüntüleri Python ile Almak ve Kaydetmek

Bilgisayarınızda bu betik snapshot topic'ine subscribe olur ve her kareyi zaman damgalı bir dosyaya yazar. Ayrıca istek üzerine nasıl görüntü isteneceğini de gösterir.

import time
from datetime import datetime
from pathlib import Path

import paho.mqtt.client as mqtt

PREFIX = "connectmq/demo/cam-7f3a"
OUT = Path("snapshots")
OUT.mkdir(exist_ok=True)


def on_connect(client, userdata, flags, reason_code, properties):
    print("Connected:", reason_code)
    client.subscribe(f"{PREFIX}/snapshot", qos=0)
    client.subscribe(f"{PREFIX}/status", qos=1)


def on_message(client, userdata, msg):
    if msg.topic.endswith("/status"):
        print("Camera status:", msg.payload.decode())
        return
    if not msg.payload.startswith(b"\xff\xd8"):
        print("Payload is not a JPEG, ignored")
        return
    name = OUT / f"{datetime.now():%Y%m%d-%H%M%S-%f}.jpg"
    name.write_bytes(msg.payload)
    print(f"Saved {name} ({len(msg.payload)} bytes)")


client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id="cam-7f3a-viewer")
client.on_connect = on_connect
client.on_message = on_message
client.connect("broker.connectmq.com", 1883, keepalive=60)
client.loop_start()

time.sleep(2)
client.publish(f"{PREFIX}/cmd", "capture", qos=1)   # hemen bir kare iste

try:
    while True:
        time.sleep(1)
except KeyboardInterrupt:
    client.loop_stop()
    client.disconnect()

FF D8 baytlarının (JPEG görüntü başlangıç işareti) kontrolü, başka biri topic'inize metin yayınlarsa betiğin anlamsız veri kaydetmesini önler. Komut satırından da görüntü tetikleyebilirsiniz:

mosquitto_pub -h broker.connectmq.com -p 1883 -i cam-7f3a-cli -t connectmq/demo/cam-7f3a/cmd -m capture

ESP32-CAM MQTT Kurulumunu Production'a Taşımak

Herkese açık alan herkes tarafından okunabilir; bu yüzden onu kullanırken kamerayı asla özel bir şeye doğrultmayın. Gerçek bir kurulum için bir yönetilen MQTT broker planında broker oluşturun ve sketch'i TLS'e geçirin:

  1. ConnectMQ'ya kaydolun, bir broker oluşturun ve otomatik oluşturulan kullanıcı adını, parolayı ve client ID'yi kopyalayın.
  2. WiFiClient yerine WiFiClientSecure kullanın, ISRG Root X1 sertifikasını setCACert() ile yükleyin ve portu 8883 yapın.
  3. mqtt.connect("YOUR_CLIENT_ID", "YOUR_USERNAME", "YOUR_PASSWORD", TOPIC_STATUS, 1, true, "offline") çağırın. ConnectMQ client ID'leri verilen kimlik bilgilerine bağlar; bu yüzden portaldakini kullanın.
  4. MAX_PAYLOAD değerini planınıza göre artırın; örneğin ücretsiz Serverless planında 256 KB'ın hemen altına.

TLS, kamera tamponlarının üzerine ek RAM kullanımı getirir. AI-Thinker modülünde kare tamponlarını dahili belleğin dışında tutan PSRAM bulunur, ancak bellek ayırma hataları görürseniz tek bir kare tamponuna ve daha küçük bir kare boyutuna inin. ESP32 MQTT SSL rehberi sertifika yüklemeyi adım adım anlatır.

Sorun Giderme Kontrol Listesi

  1. Açılışta veya yakalamada "Brownout detector was triggered". Sağlam bir 5 V besleme ve kısa kablolar kullanın; kamera ve Wi-Fi radyosu akım tepelerini birlikte çeker.
  2. Camera init failed: 0x105 veya benzeri. Şerit kablo gevşek veya pin haritası kart varyantınızla eşleşmiyor.
  3. Görüntü "FAILED" ama küçük mesajlar çalışıyor. Kare broker sınırını aşıyor veya soket yazma sırasında zaman aşımına uğradı. frame_size değerini düşürün, jpeg_quality sayısını artırın veya setSocketTimeout() değerini yükseltin.
  4. Kamera büyük bir yayından hemen sonra bağlantıyı kesiyor. Herkese açık broker'da 64 KB'tan büyük bir mesaj reddedilir. Seri logda yazdırılan boyutu kontrol edin.
  5. Görüntüler başta yeşil veya karanlık görünüyor. OV2640'ın otomatik pozlamayı oturtması için birkaç kareye ihtiyacı vardır; açılıştan sonra bir kare yakalayıp atın.
  6. Wi-Fi koptuktan sonra yeniden bağlanma döngüleri. Backoff ekleyin ve dashboard'ların kamerayı çevrimdışı göstermesi için Last Will'i koruyun; ESP32 MQTT yeniden bağlanma yazısına bakın.

Kamera Topic'leri İçin Tasarım İpuçları

İkili kareleri kendi topic'lerinde tutun ve meta verileri ayrı gönderin. Bir snapshot/meta topic'inde boyut, çözünürlük ve tetikleme nedenini içeren küçük bir JSON mesajı, dashboard'ların görüntüyü indirmeden tepki vermesini sağlar. "Son fotoğraf" davranışına ihtiyacınız yoksa paylaşımlı broker'larda görüntü mesajlarını retained yapmaktan kaçının; çünkü her yeni subscriber tam JPEG'i anında alır. Kareleri birden fazla servis tüketiyorsa bunu MQTT fan-out'u halleder; bir bulut MQTT broker da bu altyapıyı kendiniz çalıştırma ihtiyacını ortadan kaldırır.

ConnectMQ ile Başlayın

Kameranızı önce ücretsiz herkese açık alanda test edin, görüntüler önem kazandığında özel bir broker'a geçin. ConnectMQ size bir TLS uç noktası, otomatik oluşturulan kimlik bilgileri ve görüntü trafiğinin gelişini izlemek için canlı bir topic gezgini sunar. app.connectmq.com adresinde ücretsiz Serverless planında kredi kartı gerektirmeden bir broker oluşturun ve sketch'teki host ile kimlik bilgilerini değiştirin.

Sık Sorulan Sorular

Bir ESP32-CAM, MQTT üzerinden video akışı yapabilir mi?

Kareleri hızlı art arda gönderebilir, ancak MQTT video için tasarlanmamıştır ve paylaşımlı broker'larda hız sınırları geçerlidir. Anlık görüntüler ve olayla tetiklenen yakalamalar için MQTT'yi, canlı video için bir akış protokolünü kullanın.

Yayınlayabileceğim en büyük görüntü nedir?

Broker'a bağlıdır: herkese açık test alanında 64 KB, Serverless (Free) planında 256 KB ve Serverless (Paid) planında 1 MB. fb->len değerini kontrol edin ve sınırınızın altında kalan bir kare boyutu seçin.

Görüntüler için PubSubClient tamponunu artırmam gerekir mi?

beginPublish(), write() ve endPublish() kullanıyorsanız hayır. Bu çağrılar kareyi doğrudan akış halinde gönderir; bu yüzden başlık için varsayılan tampon yeterlidir.

Görüntü mesajları retained olmalı mı?

Genellikle hayır. Büyük bir ikili mesajı retained yapmak, her yeni subscriber'ın onu anında alması anlamına gelir; özellikle "en son görüntü" davranışı istemiyorsanız yalnızca küçük status topic'ini retained yapın.

İlgili yazılar

ESP32 NTP MQTT Zaman Damgası: Her Mesaja Doğru Zaman Ekleyin

ESP32 saatini NTP ile eşitleyin, ISO 8601 ve epoch milisaniye zaman damgalarını biçimlendirin ve bunları MQTT payload'larına ekleyin; böylece ölçümler geç yayınlansa bile gerçek alınma zamanlarını korur.

6 dk okuma

Tüm yazılar →

Özel MQTT broker’ınız birkaç tık uzağınızda

Hesabınızı oluşturun, broker kimlik bilgilerinizi alın ve ilk cihazınızı bağlayın — kurulacak, yamalanacak ya da ölçeklendirilecek sunucu yok.