ESP-NOW ile Haberleşme

Merhabalar,

Bu konuda Espressif firması tarafından kendi cihazları için üretilen bir haberleşme protokolü olan ESP-NOW’ u inceleyeceğiz. Öncelikle ESP-NOW’ un ne olduğu ile başlayalım. ESP-NOW, ESP cihazlarının Wi-Fi’ den bağımsız olarak haberleşmesini sağlayan bir haberleşme protokolüdür. Protokol düşük güçte çalışan 2.4Ghz cihazlara benzerlik gösterir. Haberleşme gerçekleşebilmesi için eşleşme gereklidir. Eşleşme sağlandıktan sonra eşler arası güvenli ve handshake gerektirmeyen haberleşme gerçekleştirilebilir. Eşler arasının yanı sıra çok cihaz arası haberleşmeyi de destekler. Hem tek yönlü hem çift yönlü iletişim ESP-NOW ile mümkündür. Espressif tarafından hazırlanan ESP-NOW’ u daha detaylı anlatan bir anlatım videosuna bağlantıdan ulaşabilirsiniz. Burada anlatılanları kısaca şu şekilde özetleyebiliriz:

  • Eşler arası ya da çoklu haberleşmeyi destekler
  • Hem tek yönlü hem çift yönlü haberleşme gerçekleştirebilir
  • Gönderilen her veri paketi 250 Byte’ a kadar veri taışyabilir
  • Şifreli ve şifresiz haberleşmeyi destekler
  • Protokol onay(acknowledgment) destekler
  • ESP-NOW ağında en fazla 20 cihaz olabilir
  • ESP-NOW ağında en fazla 10 şifreli haberleşme gerçekleştiren cihaz olabilir

Yukarıda verilen özelliklerden de anlaşılacağı üzere bir çok uygulamanızda ESP-NOW kullanarak ESP cihazları ile az maliyetli kablosuz haberleşen bir sistem oluşturabilirsiniz. Bu konuda anlatılan içerikde referans olarak bağlantıdaki site kullanılmıştır. Daha önceki konularda da belirttiğim üzere referans site içerisinde ESP32 ve ESP8266 hakkında bir çok içerik paylaşılmış. İncelemenizi tavsiye ederim. Biz bu konu içeriğini biraz değiştirerek gerçekleştireceğiz.

Yukarıdaki videoda verilen ESP-NOW’ da anlatılan tüm özellikler Arduino kütüphanesi tarafından sağlanamasa da Arduino ile yapılabileceklerde oldukça geniş. Bu konu içerisinde basit bir eşler arası iletişim örneği gerçekleştireceğiz. Sonraki konularda 2′ den fazla cihaz bulunması durumundaki haberleşmeyi gerçekleştireceğiz. Arduino ile ESP-NOW kullanırken kullanabileceğiniz fonksiyonlara bağlantıdaki sayfadan ulaşabilirsiniz. Bu fonksiyonlardan kullanacağımız bazı önemli fonksiyonlar aşağıdaki gibidir:

  • esp_now_init() => ESP-NOW için temel kurulumu gerçekleştirir. Bu fonksiyon çağrılmadan önce Wi-Fi kurulumu gerçekleştirilmiş olmalıdır.
  • esp_now_add_peer(MAC) => Parametre olarak aldığı MAC adresindeki cihaz ile eşleşir.
  • esp_now_send(MAC, data, len) => Parametre olarak MAC adresine sahip cihaza len uzunluğundaki data’ yı gönderir. Fonksiyonun dönüş değerine bakılarak veri alıcıya ulaşdı mı kontrolü gerçekleştirilebilir.
  • esp_now_register_send_cb(callback) => Parametre olarak aldığı fonksiyonu gönderme işleminden sonra çağırır. Callback fonksiyonun parametre olarak aldığı değere bakarak verinin alıcıya ulaşıp ulşamadığı kontrol edilebilir.
  • esp_now_register_rcv_cb(callback) => Veri alındığı zaman parametre olarak atanan callback fonksiyonu çağrılır.
  • esp_now_is_peer_exist(MAC) => parametre olarak aldığı cihaz adresi ile eşleşmiş mi kontrolü yapar. Zaten eşleşmiş ise true değer döndürür.

Bunların dışındaki diğer fonksiyonları yapacağımız uygulamada kullanmayacağız. O sebeple açıklama gereği duymadım. Merak ederseniz yukarıdaki bağlantıdan ulaşabilirsiniz.

Şimdi yapacağımız uygulamaya gelelim. Yapacağımız uygulamada iki adet ESP32 kullanarak haberleşme gereçekleştireceğiz. Master cihaz, Slave ile eşleşince Slave cihaz kendisine bağlı olan DHT11′ dn okuduğu sıcaklık ve nem bilgilerini Master cihaza gönderecek. Master cihaz da belirli aralıklar ile Slave cihaza LED yakıp söndürme komutu gönderecek. Yapacağımız uygulamada cihazlar eşleşmeyi otomatik olarak yapacaklar ve veriler bir kuyruk yardımıyla gönderilecek. Yapacağımız uygulamanın bağlantı şemasına bakalım.

Bağlantı şemasında Master ve Slave cihaz ayrı ayrı verilmiştir. Master ya da Slave cihazları olarak kendi ağ topolojinize göre şekillendirebilirsiniz. Eğer 2 cihazımız olacaksa bunları Master ya da Slave diye isimlendirmemize gerek yok. Birbinin MAC adreslerini elle yazarak, bu bu MAC adresine sahip cihaza veri gönder diyebiliriz. Benim oluşturduğum senaryoda Master etrafındaki cihazları tarayıp onlara otomatik olarak bağlanabilecek. Bu sebeple Master ve Slave diye ayrım yapma gereksinimi duydum.

Master Yazılımı

İki cihaz arasında haberleşme gerçekleşebilmesi için, olması gereken önemli bir nokta mevcut. ESP32 cihazlarının ESPNOW ile veri iletebilmesi için “station” modda olması gerekir. Aksi taktide station modda olmayan cihazdan veri göndermeye çalıştığınızda send komutu “ESP_ERR_ESPNOW_IF ” hatası döndürecektir. Ancak Slave cihazın taranarak bulunabilmesi için “access point” olması gerekecektir. Bu yine ESPNOW tarafından gerekmemektedir. Sadece Master etrafını taradığında çevresindeki cihazları ve cihazların MAC adresini görebilmesi için gereklidir. Yine elle veri gönderilecek MAC adresi girilirse herhangi bir ESP’ yi access point olarak ayarlamaya gerek kalmaz. Sonuç olarak Master cihaz sadece access point olması gerekirken, Slave cihaz hem access point hem de station olması gerekecektir. Master etrafı tarayıp Slave’ lere veri göndermeyi başlayacağı için station olması yeterlidir. Çünkü eşleşip veri gönderdiğinde Slave onun MAC adresini öğrenecektir. Bu kısmı anlaması ilk başta zor olabiliyor. Ancak uygulmayı yaparken daha net anlaşılacağını düşünüyorum. Başta benimde oturtmam çok kolay olmadı.

Şimdi gelelim yazılım kısmına. Öncelikle Master’ ın yazılımının setup kısmından başlayalım. İlk olarak ESP32′ yi ESPNOW ile veri gönderebilmesi için station mod’ a alıyoruz.

  // make esp32 station device
  WiFi.mode(WIFI_STA);

Daha sonra ESPNOW kurulumunu yapmalı ve çevredeki cihazları taramalıyız. Tarama işleminin en az 1 slave bulunana kadar yapmalıyız ki eğer Slave cihaz ilk taramada bulunumazsa tekrar tarasın. Bu yüzden bu işlemi bir döngü içerisinde yapmalıyız.

  // ESPNOW initialization function
  InitESPNow();

  // scan nearby slave devices 
  do
  {
    ScanForSlave();
    delay(2000);

    // if no slave is found, restart the scan
  } while (slaveFound == 0);

Daha sonrasında bulduğumuz slave cihaz ile eşleşmeliyiz. Böylece Master cihaz veri göndermeye hazır hale gelecektir. Eşleşmediğiniz cihazlara veri gönderemezsiniz. Bu sebeple burası da yazılımda önemli bir noktadır.

  // Add peer
  if (esp_now_add_peer(&slave) != ESP_OK)
  {
    Serial.println("Failed to add peer");
    return;
  }

Genel ayarlama işlemlerimiz bu kadar. Şimdi yukarıda kullandığımız ScanForSlave fonksiyonun içerisine bakalım. Bu fonksiyon çevredeki Slave cihazları tarıyor demiştik. Peki ya bunu nasıl yapıyor. Aslında etrafdaki tüm Wi-Fi cihazlarını tarıyor. Sadece bizim ismini Slave ile başlayan bir şey koyduğumuz cihazlar ile eşleşebilmek için onların MAC adresini kayıt ediyor. Fonksiyonun içerisine bakalım.

void ScanForSlave()
{
  // get nearby wifi devices number 
  int8_t scanResults = WiFi.scanNetworks();

  // clear slave struct object
  memset(&slave, 0, sizeof(slave));

  // check any slave device found
  if (scanResults == 0)
  {
    Serial.println("No WiFi devices in AP Mode found");
  }
  else
  {
    Serial.print("Found ");
    Serial.print(scanResults);
    Serial.println(" devices ");

    // show found devices informations
    for (int i = 0; i < scanResults; ++i)
    {
      // Get SSID and RSSI for each device found
      String SSID = WiFi.SSID(i);
      int32_t RSSI = WiFi.RSSI(i);
      String BSSIDstr = WiFi.BSSIDstr(i);

      // check found device name is start with "Slave"
      if (SSID.indexOf("Slave") == 0)
      {
        // Show Slave devices informations
        Serial.println("Found a Slave.");
        Serial.print(i + 1);
        Serial.print(": ");
        Serial.print(SSID);
        Serial.print(" [");
        Serial.print(BSSIDstr);
        Serial.print("]");
        Serial.print(" (");
        Serial.print(RSSI);
        Serial.print(")");
        Serial.println("");
        // Get BSSID => Mac Address of the Slave
        int mac[6];
        if (6 == sscanf(BSSIDstr.c_str(), "%x:%x:%x:%x:%x:%x", &mac[0], &mac[1], &mac[2], &mac[3], &mac[4], &mac[5]))
        {
          for (int ii = 0; ii < 6; ++ii)
          {
            slave.peer_addr[ii] = (uint8_t)mac[ii];
          }
        }

        slave.channel = CHANNEL; // pick a channel
        slave.encrypt = 0;       // no encryption

        slaveFound = 1;
        digitalWrite(peerLEDPin, HIGH);   
      }
    }
  }
}

Yukarıda verilen fonksiyonda da görüldüğü üzere öncelikle yakındaki WiFi cihazları tarıyoruz. Daha sonra da taranan cihazların SSID’ lerine bakarak “Slave” ile başlıyor mu diye kontrol ediyoruz. SSID içerisinde Slave olup olmadığı kontrolü “if (SSID.indexOf(“Slave”) == 0)” satırında gerçekleştirilmektedir. Eğer SSID’ si Slave ile başlıyorsa MAC adresini kayıt ediyoruz. Böylece bir sonraki aşamada bu MAC adresi ile eşleşeceğiz.

Bu fonksiyonun dışında 2 adet de Callback fonksiyonu mevcut. Bunlardan bir tanesi veri gönderildiği zaman çalışan, diğeri ise veri alındığı zaman çalışan callback fonksiyonlarıdır. Bu fonksiyonları içeriği oldukça basit. Veri geldiği zaman çalışan “OnDataRecv” fonksiyonu, gelen verileri Seri portta yazdırıyor. Veri gönderildiği zaman çalışan “OnDataSent” fonksiyonu ise veri alıcı adrese ulaşıp ulaşmadığını kontrolünü yapıp Seri portta bildirimini yapmaktadır. Konunun fazla uzamaması için bu fonksiyonları daha fazla açmayacağım.

Son olarak master yazılımında veri gönderme işlemine bakalım. Veri gönderimi referans konudaki gibi struct olarak yapacağız. Veriyi struct olarak tanımlayıp göndermek ile dizi şeklinde göndermek aslında aynı şeyler. Sadece Struct olarak gönderirken farklı veri tiplerini göndermek daha kolay oluyor. Ben Struct içerisinde biraz değiştirip aşağıdaki gibi yaptım.

// data structure for sending data
typedef struct led_message
{
  bool led_state;
  char message[20];
} struct_led_message;

struct_led_message myLEDData;

Struct içerisinde bulunana led_state bool değişkeni her veri gönderilmesinden sonra değeri değiştirilecek. Böylece slave tarafta bu değere bakılarak bir LED’ i yakıp söndüreceğiz. İkinci değişken olan message dizisi ise tamamen keyfi oluşturulmuş bir string’ dir. Bu değişkeni kullanarak Slave cihaza string veri göndereceğiz. Veri gönderme işlemi aşağıdaki şekilde yapılacaktır.

esp_now_send(slave.peer_addr, (uint8_t *)&myLEDData, sizeof(myLEDData));

Eğer siz struct yerine tek bir değişken göndermek isterseniz bu ifadeyi şu şekilde değiştirebilirsiniz. ESP NOW ile veri göndermek için göndermek istediğiniz değişkeni uint8_t dizisi şeklinde ifade etmeniz yeterli olacaktır. Ancak verinin toplam boyutunun 250 byte’ ı geçmemesi gerektiğini unutmayın.

int val = 10;
esp_now_send(slave.peer_addr, (uint8_t *)&val, sizeof(val));

Master cihazın yazılımının tamamına bağlantıdan ulaşabilirsiniz.

Slave Yazılımı

Master tarafı bitirdiğimize göre sıra geldi Slave tarafa. Bu tarafta işler yine aynı karmaşıklı olacaktır. Eğer Master tarafını anladı iseniz burada da zorlanmayacaksınızdır diye düşünüyorum. Yine setup fonksiyonu içerisinden başlayalım.

  //Set device in AP and STA mode to begin with
  WiFi.mode(WIFI_MODE_APSTA);
  // configure device AP mode
  const char *SSID = "Slave_1";
  bool result = WiFi.softAP(SSID, "Slave_1_Password", CHANNEL, 0);
  if (!result)
  {
    Serial.println("AP Config failed.");
  }
  else
  {
    Serial.println("AP Config Success. Broadcasting with AP: " + String(SSID));
  }

Verilen yazılımda da görüldüğü üzere öncelikle ESP cihazı hem access point hem station moduna alıyoruz. Daha sonrasında Acces point SSID’ sini “Slave_1” olarak değiştiriyoruz. Bu tam olarak Master cihazda arayacağımız SSID kuralına uygun bir SSID’ dir. Daha sonrasında ESP NOW kurulumunu yapıp veri geldiğinde çalışması için callback fonksiyonunu ayarlıyoruz.

  // Init ESPNow with a fallback logic
  InitESPNow();
  // Once ESPNow is successfully Init, we will register for recv CB to
  // get recv packer info.
  esp_now_register_recv_cb(OnDataRecv);

Slave tarafında veri gönderebilmek için öncelikle Master ile eşleşmemiş gerekir. Slave cihaz, Master ile eşleşebilmesi için Master’ ın MAC adresini biliyor olması gerekir. Master cihazın MAC adresini elle Slave cihaza girmemişsek, Slave cihaz aldığı veri paketinde, gönderen MAC adresine bakarak Master cihazın MAC adresini kayıt edebilir. Biz bu yöntemi kullanarak veri gönderme işlemini gerçekleştireceğiz. Bu işlemi “OnDataRecv” fonksiyonunun içerisinde aşağıdaki gibi gerçekleştireceğiz.

// callback function that will be executed when data is received
void OnDataRecv(const uint8_t *mac, const uint8_t *incomingData, int len)
{
  // check already paired with received MAC address
  if (!esp_now_is_peer_exist(mac))
  {
    // clear peer info
    memset(&peer, 0, sizeof(peer));

    // copy received MAC address into peer info
    for (int ii = 0; ii < 6; ++ii)
    {
      peer.peer_addr[ii] = (uint8_t)mac[ii];
    }
    peer.channel = CHANNEL; // pick a channel
    peer.encrypt = 0;       // no encryption

    if(esp_now_add_peer(&peer) == ESP_OK)
    {
      Serial.println("Peer added successfully");
    }
    else
    {
      Serial.println("Failed to add peer");
    }
  }
}

Verilen yazılımda da görüldüğü üzere veri alınan cihazın MAC adresi ile eşleşildimi sorgusu yapılıp, eğer eşleşilmedi ise peer info struct’ ı içerisine kayıt edilyor. Daha sonrasında kayıtlı peer sayısına bakarak, eğer 0′ dan büyükse kayıtlı peer’ lere veri göndereceğiz. Bu uygulamada 1 Master 1′ de Slave cihaz olduğu için eşleşen peer sayısı en fazla 1 olabilecektir. Ancak daha fazla olduğu durumlar için yazılımda değişiklik yapılabilir.

void loop()
{
  //get peered devices count
  esp_now_get_peer_num(&peerNum);

  // if peer number greater then 0, get peer data and save it to peerInfo
  if (peerNum.total_num > 0)
  {
    if (isConnected == false)
    {
      esp_now_fetch_peer(1, &peerInfo);
      Serial.println("Slave: Connected to Master");
      Serial.println();
      digitalWrite(peerLEDPin, HIGH);

      delay(5000);

      isConnected = true;
    }
  }
  else
  {
    digitalWrite(peerLEDPin, LOW);
    isConnected = false;
  }

  // if conencted to peer send data to peer every 2 seconds
  if (isConnected)
  {
    if (millis() - lastTimeSend > 2000)
    {
      // send data to peer
      esp_err_t rr = esp_now_send(peerInfo.peer_addr, (uint8_t *)&mySensData, sizeof(mySensData));
      
      // check data sent successfully or not
      if (rr == ESP_OK)
      {
        Serial.println("Slave: Data sent successfully");
        digitalWrite(sendLEDPin, !digitalRead(sendLEDPin));
      }
      else
      {
        Serial.println("Slave: Failed to send data");
      }

      lastTimeSend = millis();
    }
  }
}

Yazılımın loop kısmında ilk olarak Slave cihaz eşleşdi mi kontrolü anlamak için peer sayısına bakıyoruz. Eğer peer sayısı 0′ dan büyükse peer listesinin en üstündeki cihaz bilgilerini çekip peerInfo struct’ ına atıyoruz. Aslında bunu yapmamıza gerek yok. Çünkü veriyi aldığımız callback içerisinde veri geldiği zaman zaten veriyi aldığımız cihazın bilgilerini peer struct’ ına atadık. Ancak 1′ den fazla veri gönderen cihaz olması durumunda işlemler bu şekilde gerçekleştirilmelidir. Bunun dışında son olarak Slave cihazdan Master’ a göndereceğimiz struct yapısına bakalım.

typedef struct sens_message
{
  int temp;
  int hum;
  float volt;
} struct_send_message;

struct_send_message mySensData;

Slave cihaza bağladığımız DHT11 verisini ve potansiyometred okduğumuz gerilim verisini Master cihaza göndereceğiz. Bunun için yukarıdaki gibi bir struct oluşturduk. Master’ da olduğu gibi buradaki struct’ ın amacı farklı veri tiplerindeki veriyi tek seferde kolayca göndermektedir. Tek veri tipinde veri gönderecekseniz struct kullanmak zorunda değilsiniz. Yazılımın tamamında, Master ve Slave’ de ayrı ayrı verilen struct’ ların ikiside kullanılıyor. Bunun sebebi veriyi gönderen ve alan tarafta doğru şekilde veriyi ayırabilmek için bu veri yapılarını bilmesi gerekmesidir.

Uzun bir konunun daha sonuna geldik. İçerik çok detaylı olduğu için konu içeriği de uzun oldu. Başlangıç için referans kaynağı kullanmanızı tavsiye ederim. Burada anlatılanlar onun bir kademe sonrası için daha uygun. Ancak çok zor şeyler değiller mantığı anlayınca. İlerleyen zamanlarda ESP cihazlarındaki bir diğer haberleşme teknolojisi olan ESP-MESH hakkında da konu içeriği oluşturmaya çalışacağım.

Master cihazın yazılımının tamamına bağlantıdan ulaşabilirsiniz.

Projenin github sayfasına bağlantıdan ulaşabilirsiniz.

İyi çalışmalar dilerim…

4 Comments

Add a Comment

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir