ESP32 ve Arduino

ESP32 BLE MQTT Gateway: Bluetooth Sensörlerini Tarayın ve Advertisement'ları İletin

Bir ESP32'yi, yakındaki sensör ve beacon'lardan advertisement toplayan, bunları filtreleyip tekilleştiren ve cihaz başına MQTT topic'lerine yayınlayan bir Bluetooth Low Energy tarayıcısı olarak kullanın.

Yakındaki sensörlerden Bluetooth advertisement'ları alan ve bunları Wi-Fi üzerinden ileten ESP32 ble mqtt gateway
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

Bir ESP32 BLE MQTT gateway, çevrenizdeki sayısız ucuz Bluetooth Low Energy sensörünü (termometreler, kapı sensörleri, beacon'lar, bitki izleyiciler) Wi-Fi üzerinden bir MQTT broker'ına bağlar. Bu eğitimde ESP32 üzerinde pasif bir BLE tarayıcı oluşturacak, Bluetooth görevinden gelen advertisement'ları güvenli şekilde toplayacak, tekilleştirecek ve her cihazın en son verisini kendi MQTT topic'ine yayınlayacaksınız.

BLE-MQTT Gateway Nasıl Çalışır?

Pille çalışan BLE sensörlerinin çoğu hiçbir zaman bağlantı kabul etmez. Yalnızca advertisement yayınlarlar: bir adres, isteğe bağlı bir ad ve üreticiye özel veri ya da servis verisi gibi veri alanları içeren, en fazla 31 baytlık kısa paketler. Sıcaklık bildiren bir sensör genellikle ölçümü birkaç saniyede bir bu alanlardan birine koyar.

Gateway bu paketleri dinler ve iletir. Bütün sır budur ve iki büyük avantajı vardır:

  • Sensörler düşük güçlü yayın modunda kalır, bu yüzden piller uzun süre dayanır.
  • Tek bir ESP32, bağlantı yönetmeden aynı anda birçok cihazı duyabilir.
Yaklaşım Ne zaman kullanılır Ödünleşim
Advertisement'ların pasif taranması Verisini yayınlayan sensörler Basit, iyi ölçeklenir, salt okunur
Aktif tarama (scan request) Scan response'a, çoğunlukla cihaz adına ihtiyacınız var Sensörde biraz daha fazla radyo trafiği ve güç tüketimi
GATT bağlantıları Cihaz veriyi yalnızca characteristic'ler üzerinden sunuyor veya yazma yapmanız gerekiyor Slot başına aynı anda tek bağlantı, daha fazla kod, daha fazla güç

Bu eğitim, sensör kullanım senaryolarının büyük çoğunluğunu karşılayan pasif taramayı kullanır.

Neye İhtiyacınız Var?

  • Bluetooth'lu bir ESP32 kartı (klasik ESP32, ESP32-S3 ve ESP32-C3 BLE'yi destekler; ESP32-S2 desteklemez).
  • Burada kullanılan BLE kütüphanesini içeren esp32 kart paketiyle Arduino IDE 2.x.
  • PubSubClient kütüphanesi.
  • Bir veya daha fazla BLE sensörü ya da test advertisement'ı yayınlayabilen bir telefon uygulaması.

Wi-Fi, Bluetooth ve MQTT bir arada büyük bir firmware oluşturur. Derleme "Sketch too big" hatasıyla başarısız olursa Tools > Partition Scheme menüsünden daha büyük bir uygulama bölümü, örneğin "Huge APP" seçin.

Wi-Fi ve BLE Tek Bir Radyoyu Paylaşır

ESP32'de Wi-Fi ile Bluetooth arasında zaman paylaşımlı kullanılan tek bir 2,4 GHz radyo vardır. Birlikte çalışma (coexistence) zamanlayıcısı bunu otomatik olarak yönetir, ancak bunun sonuçları vardır:

  • Yüzde 100'lük bir tarama penceresi Wi-Fi'ı aç bırakır. Tarama penceresini aralıktan biraz kısa ayarlayarak boşluk bırakın.
  • Bloklayan bir tarama sürerken MQTT döngünüz çalışmaz. Her taramayı MQTT keep alive aralığının epey altında tutun.
  • Bazı advertisement'ları kaçırmayı bekleyin. Sensörler yayınlarını tekrarladığı için bu normalde zararsızdır.

Espressif, birlikte çalışma davranışını ESP-IDF RF coexistence rehberinde belgeliyor.

Topic Tasarımı

Topic Payload
connectmq/demo/ble-a81f/devices/<mac> {"rssi":-71,"name":"LYWSD03","mfg":"...","svc":"..."}
connectmq/demo/ble-a81f/gateway/status online / offline, retained (Last Will)
connectmq/demo/ble-a81f/gateway/stats {"seen":12,"published":9,"dropped":0}

MQTT topic'lerinde iki nokta üst üste sorun değildir, ancak kod topic'leri düzenli ve terminalde kolay yazılabilir tutmak için bunları MAC adresinden çıkarır. ble-a81f ifadesini kendi rastgele tanımlayıcınızla değiştirin.

ESP32 BLE MQTT Gateway Sketch'i

BLE kütüphanesi tarama callback'ini kendi görevinden (task) çağırır. Bu callback'in içinden yayın yapmak, ağ client'ına aynı anda iki görevden dokunmak anlamına gelir ve bu da rastgele çökmelere yol açar. Bunun yerine callback her advertisement'ı bir FreeRTOS kuyruğuna kopyalar; loop() ise kuyruğu boşaltıp yayın yapar.

#include <WiFi.h>
#include <PubSubClient.h>
#include <BLEDevice.h>
#include <BLEScan.h>
#include <BLEAdvertisedDevice.h>

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     = "ble-a81f-gateway";
const char* PREFIX        = "connectmq/demo/ble-a81f";

const int   SCAN_SECONDS     = 5;
const int   MIN_RSSI         = -90;    // çok zayıf cihazları yok say
const uint32_t MIN_REPUBLISH_MS = 10000; // cihaz başına hız sınırı

struct Adv {
  char addr[18];
  char name[24];
  int  rssi;
  uint8_t mfg[31];
  uint8_t mfgLen;
};

QueueHandle_t advQueue;
WiFiClient net;
PubSubClient mqtt(net);
BLEScan* scanner;
uint32_t seen = 0, published = 0, dropped = 0;

// Hız sınırlama için basit cihaz başına bellek
struct Recent { char addr[18]; uint32_t lastMs; };
Recent recent[32];

class ScanCallbacks : public BLEAdvertisedDeviceCallbacks {
  void onResult(BLEAdvertisedDevice dev) override {
    if (dev.getRSSI() < MIN_RSSI) return;
    Adv a = {};
    auto addr = dev.getAddress().toString();
    strncpy(a.addr, addr.c_str(), sizeof(a.addr) - 1);
    if (dev.haveName()) {
      auto n = dev.getName();
      strncpy(a.name, n.c_str(), sizeof(a.name) - 1);
    }
    a.rssi = dev.getRSSI();
    if (dev.haveManufacturerData()) {
      auto md = dev.getManufacturerData();
      a.mfgLen = min((size_t)md.length(), sizeof(a.mfg));
      memcpy(a.mfg, md.c_str(), a.mfgLen);
    }
    seen++;
    if (xQueueSend(advQueue, &a, 0) != pdTRUE) dropped++;
  }
};

bool shouldPublish(const char* addr) {
  uint32_t now = millis();
  int freeSlot = -1, oldest = 0;
  for (int i = 0; i < 32; i++) {
    if (strcmp(recent[i].addr, addr) == 0) {
      if (now - recent[i].lastMs < MIN_REPUBLISH_MS) return false;
      recent[i].lastMs = now;
      return true;
    }
    if (recent[i].addr[0] == 0 && freeSlot < 0) freeSlot = i;
    if (recent[i].lastMs < recent[oldest].lastMs) oldest = i;
  }
  int slot = freeSlot >= 0 ? freeSlot : oldest;
  strncpy(recent[slot].addr, addr, sizeof(recent[slot].addr) - 1);
  recent[slot].lastMs = now;
  return true;
}

void publishAdv(const Adv& a) {
  char mac[13]; int j = 0;
  for (int i = 0; a.addr[i] && j < 12; i++) if (a.addr[i] != ':') mac[j++] = a.addr[i];
  mac[j] = 0;

  char hex[63] = {0};
  for (int i = 0; i < a.mfgLen; i++) sprintf(hex + i * 2, "%02x", a.mfg[i]);

  char topic[96], payload[200];
  snprintf(topic, sizeof(topic), "%s/devices/%s", PREFIX, mac);
  snprintf(payload, sizeof(payload), "{\"rssi\":%d,\"name\":\"%s\",\"mfg\":\"%s\"}",
           a.rssi, a.name, hex);
  if (mqtt.publish(topic, payload)) published++;
}

void connectWifi() {
  WiFi.mode(WIFI_STA);
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
  while (WiFi.status() != WL_CONNECTED) delay(500);
}

void connectMqtt() {
  char st[96];
  snprintf(st, sizeof(st), "%s/gateway/status", PREFIX);
  while (!mqtt.connected()) {
    if (mqtt.connect(CLIENT_ID, st, 1, true, "offline")) {
      mqtt.publish(st, "online", true);
    } else {
      delay(5000);
    }
  }
}

void setup() {
  Serial.begin(115200);
  advQueue = xQueueCreate(40, sizeof(Adv));

  connectWifi();
  mqtt.setServer(MQTT_HOST, MQTT_PORT);
  mqtt.setBufferSize(512);
  mqtt.setKeepAlive(30);
  connectMqtt();

  BLEDevice::init("");
  scanner = BLEDevice::getScan();
  scanner->setAdvertisedDeviceCallbacks(new ScanCallbacks(), true); // tekrarları da bildir
  scanner->setActiveScan(false);   // pasif: scan request gönderme
  scanner->setInterval(160);       // 0,625 ms birimlerinde
  scanner->setWindow(80);          // Wi-Fi için yayın süresi bırak
}

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

  scanner->start(SCAN_SECONDS, false);   // bloklayan tarama, sonuçlar kuyruğa gider
  scanner->clearResults();               // taramanın tuttuğu belleği serbest bırak

  Adv a;
  while (xQueueReceive(advQueue, &a, 0) == pdTRUE) {
    if (shouldPublish(a.addr)) publishAdv(a);
    mqtt.loop();
  }

  char topic[96], stats[96];
  snprintf(topic, sizeof(topic), "%s/gateway/stats", PREFIX);
  snprintf(stats, sizeof(stats), "{\"seen\":%lu,\"published\":%lu,\"dropped\":%lu}",
           (unsigned long)seen, (unsigned long)published, (unsigned long)dropped);
  mqtt.publish(topic, stats);
  mqtt.loop();
}

Trafiği bilgisayarınızdan izleyin:

mosquitto_sub -h broker.connectmq.com -p 1883 -i ble-a81f-viewer -t "connectmq/demo/ble-a81f/#" -v

Birkaç saniye içinde yakındaki her cihaz için bir satır görmelisiniz. Tipik bir ev veya ofiste telefonlar, kulaklıklar, televizyonlar ve saatler de advertisement yayınlar; bu yüzden sahip olduğunuz sensörlerden daha fazla cihaz görmeyi bekleyin.

Neleri İleteceğinizi Filtrelemek

Menzildeki her advertisement'ı iletmek keşif için yararlıdır ama üretimde gürültülüdür ve paylaşımlı broker'larda hız sınırlarına takılmanıza yol açabilir (genel test alanı client başına saniyede 10 mesaja izin verir). Üç filtre trafiği kontrol altında tutar:

  1. RSSI eşiği. MIN_RSSI, uzaktaki veya duvar arkasındaki cihazları eler.
  2. Cihaz başına hız sınırı. MIN_REPUBLISH_MS, cihaz her saniye advertisement yayınlasa bile her cihazı en fazla on saniyede bir yayınlar.
  3. İzin listesi. Sensörlerinizin adreslerini veya üretici kimliklerini öğrendikten sonra yalnızca onları kabul edin. Üretici verisinin ilk iki baytı şirket tanımlayıcısıdır ve belirli bir marka için iyi bir filtre oluşturur.

Birçok telefonun ve bazı aksesuarların gizlilik için rastgele, değişen adresler kullandığını unutmayın; aynı telefon birkaç farklı MAC altında görünebilir. Sensörler genellikle sabit bir adres kullanır.

Sensör Verisini Çözümlemek

Gateway, üretici verisini hex olarak iletir ve çözümlemeyi bir aboneye bırakır. Bu bilinçli bir tercihtir: firmware güncellemek zordur, oysa bir Python veya Node-RED abonesi yeni sensör formatlarını dakikalar içinde öğrenebilir. Pek çok açık kaynak sensör firmware'i, BTHome servis verisi gibi belgelenmiş formatları izler; bir abone bunu sıcaklık, nem ve pil değerlerine çözümleyip home/livingroom/climate gibi temiz bir topic'e yeniden yayınlayabilir.

Bu ayrım (kenarda basit ve güvenilir bir gateway, bir abonede esnek mantık), IIoT edge gateway yazısında anlatılan daha büyük endüstriyel gateway'lerin kullandığı desenle aynıdır.

ESP32 BLE MQTT Gateway İçin Sorun Giderme Kontrol Listesi

  1. Hiç cihaz bulunamıyor. Kartınızın BLE'yi desteklediğini (ESP32-S2 desteklemez) ve sensörün advertisement yayınladığını bir telefon tarayıcı uygulamasıyla doğrulayın.
  2. Taramalar sırasında MQTT bağlantısı kopuyor. SCAN_SECONDS değerini kısaltın veya keep alive süresini artırın; tarama bloklarken client ping'lere yanıt veremez.
  3. Rastgele yeniden başlamalar. Tarama callback'inden yayın yapmadığınızdan ve kuyruğun yeterince büyük olduğundan emin olun. Artan bir dropped sayacı kuyruğun çok küçük olduğu anlamına gelir.
  4. Wi-Fi yavaş veya kopuyor. Wi-Fi'ın daha fazla yayın süresi alması için tarama penceresini aralığa göre küçültün.
  5. Heap zamanla küçülüyor. Sketch'in yaptığı gibi her taramadan sonra clearResults() çağırın.

Modem yeniden başlatmalarına dayanan yeniden bağlanma stratejileri için ESP32 MQTT yeniden bağlanma yazısına, daha fazla sensör projesi için ESP32 IoT eğitimine göz atın.

Gateway'i Güvenli Hale Getirmek

BLE advertisement'ları doğası gereği herkese açıktır: yakındaki herkes onları okuyabilir. Üretimde bunları herkese açık bir topic'e yayınlayarak durumu daha da kötüleştirmeyin. WiFiClientSecure ve broker portalınızdan alınan kimlik bilgileriyle TLS 8883 portundaki özel bir broker'a geçin. ConnectMQ gibi yönetilen bir MQTT broker, sunucu çalıştırmadan her gateway'e kendi kimlik bilgilerini verir; prototip aşamasında ise menzilde hassas bir şey olmadığı sürece ücretsiz genel MQTT broker yeterlidir.

ConnectMQ ile Başlayın

Gateway gerçek kullanıma hazır olduğunda ona ConnectMQ üzerinde özel, şifreli bir uç nokta verin. Portaldaki canlı topic gezgini her cihaz topic'ini ortaya çıktığı anda gösterir; bu, gateway'inizin neler duyduğunu görmenin en hızlı yoludur. app.connectmq.com adresinde ücretsiz Serverless planla başlayın, kredi kartı gerekmez.

Sık Sorulan Sorular

Tek bir ESP32 gateway kaç BLE sensörünü yönetebilir?

Pasif taramada menzilde onlarca cihaz olması normaldir; pratik sınırları yayın süresi, kuyruk boyutu ve ne sıklıkla yayın yaptığınız belirler. Cihaz başına hız sınırı uygulayın ve yalnızca ilgilendiğiniz sensörleri filtreleyin.

Dahili BLE kütüphanesi yerine NimBLE mi kullanmalıyım?

NimBLE-Arduino daha az bellek kullanır ve daha büyük gateway'ler için iyi bir seçimdir. API'si biraz farklıdır; geçiş yaparsanız callback kodunu uyarlayın.

Gateway eşleştirme gerektiren sensörlere bağlanabilir mi?

Evet, ancak bu tarama yerine GATT bağlantıları gerektirir ve aynı anda yalnızca birkaç bağlantı mümkündür. Yayın yapan sensörler için pasif tarama daha basit ve daha güvenilirdir.

Neden bana ait olmayan cihazları görüyorum?

Telefonlar, televizyonlar, kulaklıklar ve diğer pek çok ürün sürekli advertisement yayınlar. Bir RSSI eşiği ve adreslerden veya üretici kimliklerinden oluşan bir izin listesi kullanı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.