ESP-NOW Nedir? ESP32 ile Modemsiz Kablosuz Haberleşme Rehberi
ESP-NOW ile ESP32'ler arasında modemsiz haberleşme: MAC adresi, tek ve çift yönlü kod, struct ile 250 bayt veri, 20 eş, menzil, core 3.x ve Wi-Fi kanal uyumu.
ESP-NOW, Espressif'in geliştirdiği ve ESP32 ile ESP8266 kartlarının modem, router ya da internet olmadan birbirine doğrudan veri göndermesini sağlayan kablosuz bir haberleşme protokolüdür. Kartlar Wi-Fi radyosunu kullanır ama hiçbir ağa bağlanmaz; her cihaz karşı tarafı MAC adresiyle tanır ve en fazla 250 baytlık küçük paketleri birkaç milisaniyede iletir. Bağlantı kurma, IP adresi alma ya da şifre girme aşaması olmadığı için bir karttaki butona basıldığı anda başka bir kartta röle çekebilir, pille çalışan bir sensörü uyandırıp ölçümünü gönderdikten hemen sonra yeniden uyutabilirsin.
Bu rehberde ESP-NOW'u sıfırdan kuracaksın: önce kartlarının MAC adresini öğrenecek, sonra tek yönlü ve çift yönlü örneklerle sıcaklık, sayaç ve metin gibi farklı verileri bir struct (yapı) içinde tek pakette göndereceksin. Ardından 20 eş sınırını, yayın (broadcast) mesajlarını, menzil ve hız değerlerini, ESP32 Arduino çekirdeği 3.x ile değişen fonksiyonları ve ESP-NOW ile Wi-Fi'yi aynı anda kullanmanın tek kuralı olan kanal uyumunu öğreneceksin. Son bölümde derin uykuda pille çalışan uzak bir sensörün ölçümlerini Wi-Fi'ye bağlı bir ağ geçidine gönderip telefondan izleyeceksin. ESP32'yi ilk kez kullanıyorsan önce ESP32 Nedir? yazısındaki Arduino IDE kurulumunu tamamla.

ESP-NOW nedir, nasıl çalışır?
Normal Wi-Fi'de ESP32 önce modeme bağlanır, şifreyi doğrular, DHCP ile IP adresi alır ve ancak bundan sonra veri gönderebilir; bu süreç genellikle 1–3 saniye sürer. ESP-NOW bu adımların hepsini atlar. Veriyi IEEE 802.11 standardındaki üreticiye özel eylem çerçevesi (vendor-specific action frame) denen kısa bir Wi-Fi paketinin içine koyar ve doğrudan karşı kartın MAC adresine gönderir. Paketi alan kart, donanım seviyesinde bir onay (ACK) çerçevesiyle cevap verir; gönderen taraftaki geri çağırma (callback) fonksiyonu da paketin teslim edilip edilmediğini sana bildirir. Onay gelmezse Wi-Fi katmanı paketi birkaç kez kendiliğinden yeniden dener. Kısacası ESP-NOW, iki kart arasında görünmez bir posta kutusu gibi çalışır: adresi yazarsın, paketi atarsın, karşı taraf aldığında haberin olur.
| Özellik | Değer |
|---|---|
| Geliştiren | Espressif Systems |
| Frekans | 2,4 GHz (Wi-Fi radyosu, kanal 1–13) |
| Paket başına veri | 250 bayt (ESP-NOW v1); ESP-IDF 5.4 ve üzerinde v2 ile 1470 bayt |
| Kayıtlı eş sayısı | En fazla 20; şifreli eş varsayılan olarak en fazla 7 |
| Varsayılan veri hızı | 1 Mbps |
| Teslim onayı | Tekli gönderimde var (ACK), yayında yok |
| Şifreleme | İsteğe bağlı, CCMP (AES-128); PMK ve LMK anahtarları 16'şar bayt |
| Modem / router | Gerekmez |
| Destekleyen yongalar | Wi-Fi'li ESP32 yongaları (ESP32, S2, S3, C2, C3, C6…) ve ESP8266 |
ESP-NOW'un asıl avantajı hızlı tepki ve düşük enerjidir. Butona basıldığında karşı taraftaki LED'in yanma gecikmesi birkaç milisaniyedir; aynı işi Wi-Fi ile yapmak için önce bağlantı kurmak, sonra bir sunucuya istek atmak gerekir. Pille çalışan bir sensörde fark daha da büyüktür: Wi-Fi'ye bağlanırken radyo saniyelerce 100 mA'in üzerinde akım çekerken, ESP-NOW ile kart uyanıp ölçümünü gönderir ve toplamda birkaç yüz milisaniye içinde yeniden uyur. Buna karşılık ESP-NOW internete çıkmaz; veriyi internete ya da telefona taşımak istiyorsan ağın bir yerinde Wi-Fi'ye bağlı bir ağ geçidi (gateway) ESP32'si bulunmalıdır. Bu düzeni yazının ilerleyen bölümünde kuracağız.
ESP-NOW mu, Wi-Fi mı, Bluetooth mu, nRF24L01 mi?
| Yöntem | Ek modül | Modem | Paket boyutu | Tipik menzil (açık alan) | En uygun kullanım |
|---|---|---|---|---|---|
| ESP-NOW | Gerekmez | Gerekmez | 250 bayt | 100–200 m, LR modunda daha fazla | Kartlar arası komut ve sensör verisi |
| Wi-Fi (HTTP, MQTT) | Gerekmez | Gerekir | Pratikte sınırsız | Modemin kapsama alanı | İnternet, web sayfası, bulut |
| Bluetooth (BLE) | Gerekmez | Gerekmez | Varsayılan 20 bayt | Ev içinde 10–20 m | Telefonla bağlantı |
| nRF24L01 | Gerekir (SPI) | Gerekmez | 32 bayt | Onlarca metre, PA+LNA sürümüyle çok daha uzun | Arduino Uno ve Nano gibi kablosuz olmayan kartlar |
| LoRa (SX1278) | Gerekir (SPI) | Gerekmez | 255 bayta kadar | Birkaç kilometre | Çok uzak mesafe, seyrek veri |
Telefonla konuşacaksan Bluetooth, internete çıkacaksan Wi-Fi doğru seçimdir. Ama elinde birden fazla ESP32 varsa ve bunlar yalnızca birbirleriyle konuşacaksa ESP-NOW çoğu zaman en basit yoldur: ek modül, kablo ya da modem gerektirmez ve kodu birkaç satırdır. Arduino Uno gibi kablosuz bağlantısı olmayan kartlar için nRF24L01, kilometrelerce menzil için LoRa modülleri daha uygundur. Bütün seçenekleri yan yana görmek istersen Arduino kablosuz haberleşme seçenekleri yazısına, telefonla bağlantı için de ESP32 Bluetooth rehberine bakabilirsin.
Hangi kartlar ESP-NOW destekler?
| Kart / yonga | ESP-NOW | Not |
|---|---|---|
| ESP32 (WROOM-32, DevKit) | Var | Uzun menzil (LR) modunu da destekler |
| ESP32-S2, S3, C3, C6 | Var | Arduino kodu aynıdır, yalnız kart seçimi değişir |
| ESP32-C2 (ESP8684) | Var | LR modu yok |
| ESP32-H2 | Yok | Wi-Fi radyosu olmadığı için desteklemez |
| ESP8266 (NodeMCU, Wemos D1 Mini) | Var | Farklı kütüphane (espnow.h), yalnız v1 ve 250 bayt |
| Raspberry Pi Pico W / Pico 2 W | Yok | Wi-Fi yongası (CYW43439) ESP-NOW çalıştırmaz |
| Arduino Uno, Nano, Mega | Yok | Seri port (UART) üzerinden bir ESP32'ye bağlanabilir |
ESP-NOW farklı ESP türleri arasında da çalışır: bir ESP32-C3 sensörü, klasik bir ESP32 ağ geçidine ya da bir ESP8266 kartına paket gönderebilir. Tek şart, iki tarafın aynı Wi-Fi kanalında olması ve gönderilen veri yapısının iki tarafta da birebir aynı tanımlanmasıdır. Aşağıdaki örneklerde iki ESP32 DevKit kartı kullanıyoruz; kartının güvenle kullanılabilecek pinlerini Pinout aracında görebilirsin.
Adım 1: ESP32'nin MAC adresini öğrenmek
ESP-NOW'da her kart karşı tarafı 6 baytlık MAC adresiyle bulur; bu adres yonga üretilirken her ESP32'ye ayrı olarak yazılır ve değişmez. Aşağıdaki kodu alıcı olarak kullanacağın karta yükle ve Seri Monitör'ü 115200 baud hızında aç. Kod adresi hem okunabilir biçimde hem de diğer kartın koduna doğrudan yapıştırabileceğin bir dizi satırı olarak yazar. İki kartlı örneklerde bu işlemi iki kart için de yapıp adresleri bir kâğıda not et.
1#include <WiFi.h>
2#include <esp_wifi.h>
3
4void setup() {
5 Serial.begin(115200);
6 delay(1000);
7 WiFi.mode(WIFI_STA); // ESP-NOW istasyon arayüzünü kullanır / ESP-NOW uses the station interface
8
9 uint8_t mac[6];
10 if (esp_wifi_get_mac(WIFI_IF_STA, mac) == ESP_OK) {
11 Serial.printf("STA MAC: %02X:%02X:%02X:%02X:%02X:%02X\n",
12 mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
13 // Diğer karta kopyalanacak satır / line to paste into the other board's code
14 Serial.printf("uint8_t karsiMac[] = {0x%02X, 0x%02X, 0x%02X, 0x%02X, 0x%02X, 0x%02X};\n",
15 mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
16 } else {
17 Serial.println("MAC okunamadi / could not read MAC");
18 }
19}
20
21void loop() {
22}struct ile veri göndermek ve 250 bayt sınırı
ESP-NOW yalnızca bir bayt dizisi gönderir, içinde ne olduğunu bilmez. Birkaç farklı değeri tek seferde göndermenin en temiz yolu, onları bir struct (yapı) içinde toplamak ve yapının bellekteki baytlarını olduğu gibi göndermektir. Alıcı aynı yapıyı tanımlar ve gelen baytları memcpy ile bu yapının üzerine kopyalar. Bunun çalışması için yapının iki tarafta aynı sırada, aynı türlerde ve aynı boyutta olması gerekir. Derleyici hızlı erişim için alanların arasına görünmez dolgu baytları ekleyebilir: aşağıdaki örneklerdeki Paket yapısı normalde 36 bayt yer kaplar, __attribute__((packed)) eklendiğinde ise tam 30 bayta iner. packed kullanmak, ESP32 ile ESP8266 arasında ya da Arduino ile MicroPython arasında her baytın aynı yerde durmasını garanti eder.
| Tür | Boyut | Not |
|---|---|---|
| bool, uint8_t, int8_t, char | 1 bayt | Bayrak, küçük sayı, tek karakter |
| int16_t, uint16_t | 2 bayt | −32768…32767 ya da 0…65535 |
| int, int32_t, uint32_t | 4 bayt | ESP32 ve ESP8266'da int 4 bayttır, Arduino Uno'da 2 bayt |
| float | 4 bayt | Yaklaşık 7 basamak hassasiyet |
| double | 8 bayt | Gerekmedikçe float kullan |
| char mesaj[16] | 16 bayt | Sabit uzunluklu metin; sonunda '\0' bulunmalı |
| String, char*, işaretçi | Gönderilmez | Yalnız bellek adresi gider; karşı tarafta anlamsızdır |
Bir pakete en fazla 250 bayt sığar. Bu sınır ESP-NOW'un ilk sürümünden (v1) gelir ve ESP8266 dâhil bütün kartlarda geçerlidir. ESP-IDF 5.4 tabanlı Arduino çekirdeği 3.2 ve sonrası ESP-NOW v2 ile 1470 bayta kadar paket gönderebilir; ancak v1 kullanan bir kart (ESP8266 ya da eski çekirdekli bir ESP32) 250 bayttan uzun paketi keser ya da hiç almaz. Bu yüzden karışık bir ağda 250 baytta kalmak en güvenlisidir. Verici kodundaki static_assert satırı, yapı 250 baytı geçerse derlemeyi anlaşılır bir mesajla durdurur. Daha büyük bir veriyi göndermen gerekiyorsa onu numaralı parçalara bölüp alıcıda sırayla birleştirmelisin; ama sensör verisi, buton durumu ya da komut için birkaç düzine bayt fazlasıyla yeter.
Tek yönlü ESP-NOW: verici kodu
- İki ESP32'nin de Arduino IDE'de ESP32 Dev Module olarak seçildiğinden emin ol. ESP-NOW kütüphanesi kart paketinin içindedir, ayrıca bir şey kurman gerekmez.
- Alıcı karttan okuduğun MAC adresini aşağıdaki kodda aliciMac dizisine yaz.
- Vericideki GPIO 4 ile GND arasına bir buton bağla; INPUT_PULLUP kullandığımız için ek dirence gerek yok. Ayrıntılar için Arduino'ya buton nasıl bağlanır? yazısına bakabilirsin.
- Kodu vericiye yükle ve Seri Monitör'ü 115200 baud hızında aç. Alıcı henüz çalışmıyorken her iki saniyede bir "Teslim edilemedi" yazısını görmen normaldir.
1#include <WiFi.h>
2#include <esp_now.h>
3#include <esp_idf_version.h>
4
5// Alıcının STA MAC adresi (mac_oku çıktısı) / receiver's STA MAC address (from the MAC sketch)
6uint8_t aliciMac[] = {0x24, 0x6F, 0x28, 0xAA, 0xBB, 0xCC};
7
8const int BUTON_PIN = 4; // buton GPIO 4 ile GND arasında / button between GPIO 4 and GND
9
10// İki kartta da birebir aynı olmalı / must be identical on both boards
11typedef struct __attribute__((packed)) {
12 uint8_t id; // gönderen kart numarası / sender board number
13 uint32_t sayac; // paket sayacı / packet counter
14 float sicaklik; // °C
15 float nem; // % bağıl nem / % relative humidity
16 bool buton; // buton basılı mı / is the button pressed
17 char mesaj[16]; // kısa metin, String değil / short text, not String
18} Paket;
19static_assert(sizeof(Paket) <= 250, "Paket 250 bayti asamaz / packet must not exceed 250 bytes");
20
21Paket paket;
22
23// Teslim sonucu: alıcı paketi aldıysa SUCCESS / delivery result: SUCCESS if the receiver got it
24#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 0)
25void gonderimSonucu(const esp_now_send_info_t *bilgi, esp_now_send_status_t durum) { // çekirdek 3.3+ / core 3.3+
26#else
27void gonderimSonucu(const uint8_t *mac, esp_now_send_status_t durum) { // çekirdek 2.x-3.2 / core 2.x-3.2
28#endif
29 Serial.println(durum == ESP_NOW_SEND_SUCCESS ? "Teslim edildi / delivered" : "Teslim edilemedi / failed");
30}
31
32void setup() {
33 Serial.begin(115200);
34 pinMode(BUTON_PIN, INPUT_PULLUP);
35 WiFi.mode(WIFI_STA); // önce Wi-Fi, sonra ESP-NOW / Wi-Fi first, then ESP-NOW
36
37 if (esp_now_init() != ESP_OK) {
38 Serial.println("ESP-NOW baslatilamadi / ESP-NOW init failed");
39 return;
40 }
41 esp_now_register_send_cb(gonderimSonucu);
42
43 esp_now_peer_info_t es = {}; // tüm alanları sıfırla / zero every field
44 memcpy(es.peer_addr, aliciMac, 6);
45 es.channel = 0; // 0 = şu anki kanal / 0 = current channel
46 es.ifidx = WIFI_IF_STA;
47 es.encrypt = false;
48 if (esp_now_add_peer(&es) != ESP_OK) {
49 Serial.println("Es eklenemedi / failed to add peer");
50 return;
51 }
52 paket.id = 1;
53}
54
55void loop() {
56 paket.sayac++;
57 paket.sicaklik = 20.0 + random(0, 100) / 10.0; // deneme değeri / test value
58 paket.nem = 40.0 + random(0, 200) / 10.0;
59 paket.buton = (digitalRead(BUTON_PIN) == LOW);
60 snprintf(paket.mesaj, sizeof(paket.mesaj), "Selam %lu", (unsigned long)paket.sayac);
61
62 esp_err_t sonuc = esp_now_send(aliciMac, (uint8_t *)&paket, sizeof(paket));
63 if (sonuc != ESP_OK) {
64 Serial.printf("Gonderme hatasi / send error: %s\n", esp_err_to_name(sonuc));
65 }
66 delay(2000);
67}Kod önce Wi-Fi'yi WIFI_STA modunda başlatır; ESP-NOW Wi-Fi radyosu olmadan çalışmaz, bu yüzden esp_now_init() her zaman WiFi.mode() satırından sonra gelmelidir. Ardından alıcı bir eş (peer) olarak listeye eklenir. esp_now_peer_info_t yapısını = {} ile sıfırlamak önemlidir; sıfırlanmayan alanlarda kalan rastgele değerler "Peer interface is invalid" hatasına yol açar. channel = 0, "kartın şu anki kanalını kullan" demektir. esp_now_send() paketi kuyruğa koyar ve hemen döner; ESP_OK dönmesi yalnızca paketin gönderilmek üzere kabul edildiğini gösterir. Asıl sonuç birkaç milisaniye sonra gonderimSonucu() fonksiyonuna gelir: ESP_NOW_SEND_SUCCESS, alıcının paketi donanım seviyesinde onayladığı anlamına gelir. Kodun başındaki #if bloğu, gönderim fonksiyonunun çekirdek sürümüne göre değişen biçimini kendisi seçer; bunu çekirdek 3.x bölümünde açıklıyoruz.
ESP-NOW alıcı kodu
1#include <WiFi.h>
2#include <esp_now.h>
3#include <esp_idf_version.h>
4
5// Vericidekiyle birebir aynı yapı / exactly the same struct as the sender
6typedef struct __attribute__((packed)) {
7 uint8_t id;
8 uint32_t sayac;
9 float sicaklik;
10 float nem;
11 bool buton;
12 char mesaj[16];
13} Paket;
14
15Paket gelen;
16volatile bool yeniPaket = false;
17
18// Wi-Fi görevinde çalışır: kısa tut / runs in the Wi-Fi task: keep it short
19#if ESP_IDF_VERSION_MAJOR >= 5
20void veriGeldi(const esp_now_recv_info_t *bilgi, const uint8_t *veri, int uzunluk) { // çekirdek 3.x / core 3.x
21#else
22void veriGeldi(const uint8_t *mac, const uint8_t *veri, int uzunluk) { // çekirdek 2.x / core 2.x
23#endif
24 if (uzunluk != sizeof(Paket)) return; // farklı boyut: başka bir paket / wrong size: some other packet
25 memcpy(&gelen, veri, sizeof(Paket)); // veriyi hemen kopyala / copy the data right away
26 yeniPaket = true;
27}
28
29void setup() {
30 Serial.begin(115200);
31 WiFi.mode(WIFI_STA);
32
33 if (esp_now_init() != ESP_OK) {
34 Serial.println("ESP-NOW baslatilamadi / ESP-NOW init failed");
35 return;
36 }
37 esp_now_register_recv_cb(veriGeldi);
38 Serial.println("Alici hazir / receiver ready");
39}
40
41void loop() {
42 if (yeniPaket) {
43 yeniPaket = false;
44 Paket p = gelen; // kopya üzerinde çalış / work on a copy
45 p.mesaj[sizeof(p.mesaj) - 1] = '\0'; // metin her zaman sonlansın / always terminate the text
46 Serial.printf("id=%u sayac=%lu sicaklik=%.1f nem=%.1f buton=%d mesaj=%s\n",
47 (unsigned)p.id, (unsigned long)p.sayac, p.sicaklik, p.nem, (int)p.buton, p.mesaj);
48 }
49}Alıcıda paketler veriGeldi() fonksiyonuna gelir. Bu fonksiyon senin loop() döngünde değil, Wi-Fi'nin yüksek öncelikli görevinde çalışır; bu yüzden içinde Serial.print, delay ya da uzun hesaplamalar yapmamalısın. Doğru yöntem, önce gelen paketin boyutunun beklenen yapıyla aynı olup olmadığını kontrol etmek, veriyi memcpy ile kendi değişkenine kopyalamak ve volatile bir bayrakla loop()'a haber vermektir. Çekirdek 3.x'te fonksiyona gelen bilgi parametresi paketin kimden geldiğini (bilgi->src_addr) ve sinyal gücünü (bilgi->rx_ctrl->rssi, dBm cinsinden) de taşır; bu bilgi yalnızca fonksiyonun içinde geçerlidir, sonradan kullanacaksan kopyalamalısın. Alıcının vericiyi eş listesine eklemesine gerek yoktur: şifresiz tekli paketler ve yayınlar listede olmayan kartlardan da kabul edilir.
Çift yönlü haberleşme: iki ESP32 birbirini kontrol etsin
Çift yönlü haberleşmede her kart hem verici hem alıcıdır. Aşağıdaki kodu iki karta da yükleyeceksin; tek fark, her kartta karsiMac dizisine diğer kartın adresini yazmaktır. Bir karttaki butona bastığında diğer kartın LED'i yanar ya da söner; LED'i değiştiren kart da işi yaptığını bir onay mesajıyla geri bildirir. Bağlantılar iki kartta da aynıdır:
| Eleman | ESP32 pini | Not |
|---|---|---|
| Buton | GPIO 4 ve GND | Dahili pull-up kullanılır, ek direnç gerekmez |
| LED | GPIO 2 | Çoğu DevKit'te dahili mavi LED; harici LED için 220 Ω direnç |
| Besleme | USB | Kartlar ayrı USB portlarından ya da adaptörlerden beslenebilir |
1#include <WiFi.h>
2#include <esp_now.h>
3#include <esp_idf_version.h>
4
5// Bu kodu iki karta da yükle; yalnız karşı kartın MAC adresini değiştir
6// Upload to both boards; change only the other board's MAC address
7uint8_t karsiMac[] = {0x24, 0x6F, 0x28, 0xAA, 0xBB, 0xCC};
8
9const int BUTON_PIN = 4; // buton GPIO 4 ile GND arasında / button between GPIO 4 and GND
10const int LED_PIN = 2; // dahili LED / onboard LED
11
12typedef struct __attribute__((packed)) {
13 uint8_t tur; // 1 = komut, 2 = onay / 1 = command, 2 = acknowledgement
14 bool ledAcik; // istenen ya da uygulanan durum / requested or applied state
15} Mesaj;
16
17volatile bool komutGeldi = false;
18volatile bool onayGeldi = false;
19volatile bool gelenLed = false;
20bool karsiLed = false; // karşı karttaki LED için son isteğimiz / our last request for the other LED
21int sonButon = HIGH;
22
23#if ESP_IDF_VERSION_MAJOR >= 5
24void veriGeldi(const esp_now_recv_info_t *bilgi, const uint8_t *veri, int uzunluk) { // çekirdek 3.x / core 3.x
25#else
26void veriGeldi(const uint8_t *mac, const uint8_t *veri, int uzunluk) { // çekirdek 2.x / core 2.x
27#endif
28 if (uzunluk != sizeof(Mesaj)) return;
29 Mesaj m;
30 memcpy(&m, veri, sizeof(m));
31 if (m.tur == 1) {
32 gelenLed = m.ledAcik;
33 komutGeldi = true;
34 } else if (m.tur == 2) {
35 onayGeldi = true;
36 }
37}
38
39void gonder(uint8_t tur, bool ledAcik) {
40 Mesaj m = {tur, ledAcik};
41 esp_now_send(karsiMac, (uint8_t *)&m, sizeof(m));
42}
43
44void setup() {
45 Serial.begin(115200);
46 pinMode(BUTON_PIN, INPUT_PULLUP);
47 pinMode(LED_PIN, OUTPUT);
48 WiFi.mode(WIFI_STA);
49
50 if (esp_now_init() != ESP_OK) {
51 Serial.println("ESP-NOW baslatilamadi / ESP-NOW init failed");
52 return;
53 }
54 esp_now_register_recv_cb(veriGeldi);
55
56 esp_now_peer_info_t es = {};
57 memcpy(es.peer_addr, karsiMac, 6);
58 es.ifidx = WIFI_IF_STA; // kanal 0, şifresiz / channel 0, unencrypted
59 esp_now_add_peer(&es);
60}
61
62void loop() {
63 // 1) Butona basılınca karşı kartın LED'ini değiştir / on press, toggle the other board's LED
64 int buton = digitalRead(BUTON_PIN);
65 if (buton != sonButon) {
66 delay(30); // kontak sıçramasını bekle / wait out contact bounce
67 buton = digitalRead(BUTON_PIN);
68 if (sonButon == HIGH && buton == LOW) {
69 karsiLed = !karsiLed;
70 gonder(1, karsiLed);
71 Serial.println(karsiLed ? "Komut: AC / command: ON" : "Komut: KAPAT / command: OFF");
72 }
73 sonButon = buton;
74 }
75
76 // 2) Gelen komutu uygula ve onay gönder / apply an incoming command and reply
77 if (komutGeldi) {
78 komutGeldi = false;
79 digitalWrite(LED_PIN, gelenLed ? HIGH : LOW);
80 gonder(2, gelenLed);
81 }
82
83 // 3) Karşı karttan gelen onay / acknowledgement from the other board
84 if (onayGeldi) {
85 onayGeldi = false;
86 Serial.println("Karsi kart uyguladi / the other board applied it");
87 }
88}Mesaj yapısındaki tur alanı paketin ne olduğunu söyler: 1 komut, 2 onaydır. Butona basan kart, karşı tarafın LED'i için istediği durumu gönderir; komutu alan kart LED'i ayarlar ve aynı durumu tür 2 ile geri yollar. Böylece gönderen taraf hem donanım onayını hem de "LED gerçekten değişti" bilgisini alır. Gelen komut yine volatile bayraklarla loop()'a taşınır; digitalWrite ve cevap gönderme işi orada yapılır. Callback içinden esp_now_send() çağırmak yerine bu düzen daha güvenlidir. Butondaki 30 ms'lik bekleme, kontakların sıçramasını (bounce) filtreler. LED yerine bir röle bağlarsan aynı kodla kablosuz bir lamba anahtarı yapmış olursun; röle bağlantısı için Röle Modülü Nedir? yazısına bakabilirsin.
Birden çok cihaz: 20 eş sınırı ve yayın (broadcast)
ESP-NOW üç düzenin hepsini destekler: bir vericiden birçok alıcıya (bir kumandadan beş röle kartına), birçok vericiden tek alıcıya (beş sensörden tek ağ geçidine) ve herkesin herkesle konuştuğu ağlar. Bir kartın eş listesinde en fazla 20 kayıt olabilir; şifreli eşlerin sayısı ise varsayılan ayarlarla 7 ile sınırlıdır. Bu sınır yalnızca paket göndereceğin kartlar için geçerlidir. Bir ağ geçidi, kendisine şifresiz paket gönderen sensörleri listeye eklemek zorunda olmadığı için 20'den fazla sensörü dinleyebilir. Liste dolarsa esp_now_add_peer() ESP_ERR_ESPNOW_FULL döner; o zaman esp_now_del_peer() ile işi biten eşleri silip yenilerini ekleyebilirsin. Aşağıdaki kod üç alıcıyı ve yayın adresini listeye ekler, sonra hem tek tek hem de yayınla gönderir:
1#include <WiFi.h>
2#include <esp_now.h>
3
4// Üç alıcının STA MAC adresleri / STA MAC addresses of three receivers
5uint8_t alicilar[3][6] = {
6 {0x24, 0x6F, 0x28, 0x11, 0x11, 0x11},
7 {0x24, 0x6F, 0x28, 0x22, 0x22, 0x22},
8 {0x24, 0x6F, 0x28, 0x33, 0x33, 0x33}
9};
10uint8_t yayinMac[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; // yayın adresi / broadcast address
11
12typedef struct __attribute__((packed)) {
13 uint8_t hedef; // 0 = herkes, 1-3 = tek alıcı / 0 = everyone, 1-3 = one receiver
14 uint32_t sayac;
15} Komut;
16
17Komut komut;
18
19bool esEkle(const uint8_t *mac) {
20 esp_now_peer_info_t es = {};
21 memcpy(es.peer_addr, mac, 6);
22 es.ifidx = WIFI_IF_STA;
23 return esp_now_add_peer(&es) == ESP_OK;
24}
25
26void setup() {
27 Serial.begin(115200);
28 WiFi.mode(WIFI_STA);
29 if (esp_now_init() != ESP_OK) {
30 Serial.println("ESP-NOW baslatilamadi / ESP-NOW init failed");
31 return;
32 }
33 for (int i = 0; i < 3; i++) esEkle(alicilar[i]);
34 esEkle(yayinMac); // yayın da eş olarak eklenmeli / broadcast must be added as a peer too
35
36 esp_now_peer_num_t sayi;
37 esp_now_get_peer_num(&sayi);
38 Serial.printf("Kayitli es / registered peers: %d / 20\n", sayi.total_num);
39}
40
41void loop() {
42 komut.sayac++;
43
44 // 1) Tek tek: her alıcıya ayrı paket ve ayrı teslim onayı / one by one: separate packet and ACK each
45 for (int i = 0; i < 3; i++) {
46 komut.hedef = i + 1;
47 esp_now_send(alicilar[i], (uint8_t *)&komut, sizeof(komut));
48 delay(10);
49 }
50 delay(2000);
51
52 // 2) Yayın: tek paket, aynı kanaldaki herkes alır, onay yok / broadcast: one packet, everyone on the channel, no ACK
53 komut.hedef = 0;
54 esp_now_send(yayinMac, (uint8_t *)&komut, sizeof(komut));
55 delay(2000);
56}Yayın adresi FF:FF:FF:FF:FF:FF da gönderimden önce eş olarak eklenmelidir. Yayınla gönderilen tek bir paket aynı kanaldaki bütün ESP-NOW alıcılarına ulaşır; alıcıların MAC adresini bilmen gerekmez, bu yüzden kurulumu en kolay yöntemdir. Bedeli şudur: yayında teslim onayı yoktur, gönderim sonucu her zaman başarılı görünür ve yayın paketleri şifrelenemez. Tek tek gönderimde ise her paket için ayrı onay gelir. esp_now_send() fonksiyonuna adres olarak NULL verirsen paket listedeki bütün eşlere ayrı ayrı gönderilir; listede yayın adresi de varsa paket bir kez de yayın olarak çıkar. Birçok proje için pratik çözüm, kartların birbirini ilk açılışta yayınla bulması, sonra MAC adreslerini öğrenip tekli ve onaylı gönderime geçmesidir.
ESP-NOW menzili ve hızı
| Ölçüt | Tipik değer | Not |
|---|---|---|
| Ev içinde menzil | 20–40 m (1–3 duvar) | Betonarme duvarlar ve metal eşyalar sinyali en çok zayıflatır |
| Açık alanda menzil | 100–200 m | Kart üstü PCB antenle; kartlar yerden yükseğe konunca artar |
| LR (Long Range) modu | 1 km'ye kadar | Espressif'in görüş hattı değeri; hız 0,5 ya da 0,25 Mbps'e düşer |
| Varsayılan veri hızı | 1 Mbps | Daha yüksek hız, daha kısa menzil demektir |
| 250 baytlık paketin havada kalma süresi | Yaklaşık 2,5 ms | Onay ve bekleme süreleriyle paket başına yaklaşık 3 ms |
| Teorik paket hızı | Saniyede ~300 paket (~70 kB/s) | 250 baytlık paketlerle; pratikte daha düşüktür |
| Komut gecikmesi | Birkaç milisaniye | Bağlantı kurma ve sunucu gerekmez |
Bu değerler ortama göre çok değişir, ama hesabı kolaydır. Varsayılan 1 Mbps hızda her bayt havada 8 mikrosaniye kalır; 250 baytlık veri, başlıklar ve senkron dizisiyle birlikte yaklaşık 2,5 ms sürer. Alıcının onayı ve kanalın boşalmasını bekleme süresiyle paket başına yaklaşık 3 ms gerekir; yani saniyede en fazla 300 civarında paket gönderilebilir. Sensör verisi ve komutlar için bu fazlasıyla yeterlidir, ama ESP-NOW ses ya da görüntü aktarmak için uygun değildir. Menzili artırmak için kartları yerden yükseğe yerleştir, antenin üzerini metal kutu, pil ya da kablolarla kapatma, harici antenli modüller (örneğin ESP32-WROOM-32U) kullan ve iki tarafta da esp_wifi_set_protocol(WIFI_IF_STA, WIFI_PROTOCOL_LR) ile Espressif'in uzun menzil (LR) modunu aç. LR yalnızca iki ESP32 arasında çalışır; sadece LR'ye ayarlanan bir arayüz normal modemlere bağlanamaz, ESP8266 ve ESP32-C2 de bu modu desteklemez. Mesafe testi yaparken çekirdek 3.x'te alıcıdaki bilgi->rx_ctrl->rssi değerini yazdır: −50 dBm çok güçlü, −80 dBm sınıra yakın bir sinyaldir; −90 dBm civarında paket kayıpları başlar.
ESP32 Arduino core 3.x ile gelen değişiklikler
İnternetteki ESP-NOW örneklerinin çoğu ESP32 Arduino çekirdeği 2.x için yazıldı. Kart Yöneticisi'nden 3.x sürümünü kurduysan bu kodlar derlenmez, çünkü Espressif ESP-IDF 5 ile geri çağırma fonksiyonlarının biçimini değiştirdi. Değişiklikler iki aşamada geldi: çekirdek 3.0 alma fonksiyonunu, ESP-IDF 5.5 tabanlı çekirdek 3.3 ise gönderim fonksiyonunu değiştirdi. Hangi sürümü kullandığını Araçlar > Kart > Kart Yöneticisi'nde esp32 paketinin yanında görebilirsin.
| Konu | Çekirdek 2.x | Çekirdek 3.0–3.2 | Çekirdek 3.3 ve üzeri |
|---|---|---|---|
| Alma fonksiyonu | (const uint8_t *mac, const uint8_t *veri, int uzunluk) | (const esp_now_recv_info_t *bilgi, const uint8_t *veri, int uzunluk) | 3.0 ile aynı |
| Gönderenin adresi | mac | bilgi->src_addr | bilgi->src_addr |
| Gönderim fonksiyonu | (const uint8_t *mac, esp_now_send_status_t durum) | 2.x ile aynı | (const esp_now_send_info_t *bilgi, esp_now_send_status_t durum) |
| En büyük paket | 250 bayt | 250 bayt; 3.2'de v2 ile 1470 bayt | v2 ile 1470 bayt |
| Kanal ayarı | esp_wifi_set_channel() | Ek olarak WiFi.setChannel() | 3.0 ile aynı |
| Hazır sınıf kütüphanesi | Yok | ESP32_NOW.h (ESP_NOW_Peer, ESP_NOW_Serial) | ESP32_NOW.h, v2 desteğiyle |
Eski bir kodu 3.x'te derlediğinde alma fonksiyonu için invalid conversion from 'void (*)(const uint8_t*, const uint8_t*, int)' ... to 'esp_now_recv_cb_t' hatasını görürsün. Çözüm, fonksiyonun ilk parametresini const esp_now_recv_info_t * yapmak ve gönderenin adresine bilgi->src_addr ile ulaşmaktır. Çekirdek 3.3'e geçince benzer bir hata bu kez esp_now_send_cb_t için çıkar; gönderim fonksiyonunun ilk parametresi const esp_now_send_info_t * olmalıdır ve alıcının adresi bilgi->des_addr içindedir. Bu yazıdaki kodlar ESP_IDF_VERSION makrosuyla doğru biçimi kendileri seçer, bu yüzden 2.x, 3.0–3.2 ve 3.3+ çekirdeklerin hepsinde değiştirmeden derlenir. Çekirdek 3.x ayrıca ESP32_NOW.h adlı sınıf tabanlı bir kütüphane getirdi: ESP_NOW_Peer sınıfından türettiğin bir sınıfta onReceive() ve onSent() fonksiyonlarını yazarsın, ESP_NOW_Serial sınıfı ise ESP-NOW bağlantısını Serial gibi kullanmanı sağlar. Örneklerine Dosya > Örnekler menüsündeki ESP_NOW başlığından ulaşabilirsin. Biz ESP8266 ile ve eski kodlarla uyumlu kaldığı için doğrudan esp_now.h fonksiyonlarını kullanıyoruz.
ESP-NOW ve Wi-Fi aynı anda: kanal uyumu
ESP32'de tek bir 2,4 GHz radyo vardır ve bu radyo aynı anda yalnızca tek bir kanalda çalışabilir. Kart bir modeme bağlandığında kanalı artık sen değil modem belirler; modem 6. kanaldaysa ESP-NOW da 6. kanalda çalışır. Bu yüzden Wi-Fi'ye bağlı bir ağ geçidine paket gönderecek bütün sensörlerin de modemin kanalına geçmesi gerekir. Kanallar uyuşmazsa vericide "Teslim edilemedi" görürsün; eşi sıfırdan farklı bir kanalla eklediysen Seri Monitör'de "ESPNOW: Peer channel is not equal to the home channel, send fail!" hatası da çıkar. Sorunsuz bir kurulum için şu kurallara uy:
- Ağ geçidi önce Wi-Fi'ye bağlanır, ESP-NOW'u ondan sonra başlatır ve WiFi.channel() ile modemin kanalını Seri Monitör'e yazar.
- Sensörler modeme bağlanmaz; ağ adını tarayarak kanalı bulur (WiFi.scanNetworks() ve WiFi.channel(i)) ya da kanalı kodda sabit olarak kullanır. Bunun için Wi-Fi şifresini bilmeleri gerekmez.
- Modemin kanal ayarını "Otomatik" yerine sabit bir kanala (1, 6 ya da 11) çek. Otomatik modda modem kanal değiştirirse sensörler ağ geçidini kaybeder.
- Ağ geçidinde WiFi.setSleep(false) ile güç tasarrufunu kapat. Modeme bağlı bir ESP32 radyosunu varsayılan olarak aralıklarla uyutur ve bu sırada gelen ESP-NOW paketlerini kaçırabilir.
- Sensörlere ağ geçidinin STA MAC adresini yaz; aşağıdaki ağ geçidi kodu bu adresi açılışta Seri Monitör'e yazar.
Aşağıdaki ağ geçidi kodu ev ağına bağlanır, beş sensöre kadar gelen ölçümleri saklar ve aynı ağa bağlı telefonundan açabileceğin basit bir web sayfasında gösterir. Ağ adını ve şifreni kendi bilgilerinle değiştir, kodu yükledikten sonra Seri Monitör'de görünen IP adresini telefonunun tarayıcısında aç:
1#include <WiFi.h>
2#include <WebServer.h>
3#include <esp_now.h>
4#include <esp_idf_version.h>
5
6const char *ssid = "AgAdi"; // Wi-Fi adın / your Wi-Fi name
7const char *sifre = "Sifre123"; // Wi-Fi şifren / your Wi-Fi password
8
9// Sensör kodundakiyle birebir aynı / identical to the sensor sketch
10typedef struct __attribute__((packed)) {
11 uint8_t id; // sensör numarası 1-5 / sensor number 1-5
12 uint32_t sayac;
13 float sicaklik;
14 float nem;
15 float pil; // pil gerilimi (V) / battery voltage (V)
16} SensorPaketi;
17
18const int SENSOR_SAYISI = 5;
19SensorPaketi sonVeri[SENSOR_SAYISI + 1]; // 1..5 kullanılır / indexes 1..5 are used
20unsigned long sonZaman[SENSOR_SAYISI + 1]; // son paketin zamanı / time of the last packet
21QueueHandle_t kuyruk;
22WebServer server(80);
23
24#if ESP_IDF_VERSION_MAJOR >= 5
25void veriGeldi(const esp_now_recv_info_t *bilgi, const uint8_t *veri, int uzunluk) { // çekirdek 3.x / core 3.x
26#else
27void veriGeldi(const uint8_t *mac, const uint8_t *veri, int uzunluk) { // çekirdek 2.x / core 2.x
28#endif
29 if (uzunluk != sizeof(SensorPaketi)) return;
30 SensorPaketi p;
31 memcpy(&p, veri, sizeof(p));
32 xQueueSend(kuyruk, &p, 0); // kuyruğa at, Wi-Fi görevini bekletme / queue it, never block the Wi-Fi task
33}
34
35void anaSayfa() {
36 String html = "<meta name='viewport' content='width=device-width'>"
37 "<meta http-equiv='refresh' content='10'>"
38 "<h2>ESP-NOW sensorleri</h2><table border=1 cellpadding=6>"
39 "<tr><th>ID</th><th>Sicaklik</th><th>Nem</th><th>Pil</th><th>Son paket</th></tr>";
40 for (int i = 1; i <= SENSOR_SAYISI; i++) {
41 if (sonZaman[i] == 0) continue; // bu sensörden veri yok / no data from this sensor yet
42 html += "<tr><td>" + String(i) + "</td><td>" + String(sonVeri[i].sicaklik, 1) +
43 " °C</td><td>%" + String(sonVeri[i].nem, 0) + "</td><td>" + String(sonVeri[i].pil, 2) +
44 " V</td><td>" + String((millis() - sonZaman[i]) / 1000) + " sn once</td></tr>";
45 }
46 html += "</table>";
47 server.send(200, "text/html", html);
48}
49
50void setup() {
51 Serial.begin(115200);
52 kuyruk = xQueueCreate(10, sizeof(SensorPaketi));
53
54 WiFi.mode(WIFI_STA);
55 WiFi.begin(ssid, sifre);
56 while (WiFi.status() != WL_CONNECTED) delay(500);
57 WiFi.setSleep(false); // güç tasarrufu kapalı: paket kaçmaz / power save off: no missed packets
58
59 Serial.print("IP: ");
60 Serial.println(WiFi.localIP());
61 Serial.printf("Kanal / channel: %d\n", (int)WiFi.channel());
62 Serial.print("Gecit MAC / gateway MAC: ");
63 Serial.println(WiFi.macAddress()); // sensörlere bu adresi yaz / put this address in the sensors
64
65 if (esp_now_init() != ESP_OK) {
66 Serial.println("ESP-NOW baslatilamadi / ESP-NOW init failed");
67 return;
68 }
69 esp_now_register_recv_cb(veriGeldi);
70
71 server.on("/", anaSayfa);
72 server.begin();
73}
74
75void loop() {
76 SensorPaketi p;
77 while (xQueueReceive(kuyruk, &p, 0) == pdTRUE) {
78 if (p.id >= 1 && p.id <= SENSOR_SAYISI) {
79 sonVeri[p.id] = p;
80 sonZaman[p.id] = millis();
81 Serial.printf("Sensor %u: %.1f C %%%.0f %.2f V\n", (unsigned)p.id, p.sicaklik, p.nem, p.pil);
82 }
83 }
84 server.handleClient();
85}Ağ geçidi, alma fonksiyonunda paketi yalnızca bir FreeRTOS kuyruğuna (queue) atar ve hemen çıkar. loop() kuyruğu boşaltıp her sensörün son ölçümünü ve geliş zamanını kaydeder, web sunucusu da bu tabloyu gösterir. Kuyruk, aynı anda birkaç sensörden paket geldiğinde hiçbirinin kaybolmamasını sağlar; Espressif'in "callback içinde uzun iş yapma" önerisinin pratik uygulaması da budur. Sayfa her 10 saniyede bir kendini yeniler ve "Son paket" sütunu bir sensörün susup susmadığını (pili mi bitti, menzil dışında mı) hemen gösterir. Ağ geçidi Wi-Fi'ye hiç bağlanamıyorsa sorun ESP-NOW'da değildir; ESP32 Wi-Fi'ye bağlanmıyor yazısındaki kontrolleri uygula. Verileri bir ev otomasyonu sistemine aktarmak istersen web sayfası yerine MQTT ve Home Assistant kullanabilirsin.
Uzak sensör örneği: DHT22, derin uyku ve ESP-NOW
Şimdi ağ geçidine veri gönderen, pille çalışan sensörü yapalım. Sensör her uyanışta sıcaklık, nem ve pil gerilimini ölçer, tek bir ESP-NOW paketi gönderir, teslim onayını bekler ve 60 saniyeliğine derin uykuya geçer. Sıcaklık ve nem için DHT22 kullanıyoruz; Kütüphane Yöneticisi'nden Adafruit DHT sensor library kütüphanesini kurman gerekir. Sensörün çalışma mantığını DHT11 sıcaklık ve nem sensörü yazısında bulabilirsin; bağlantı DHT22 için de aynıdır, kodda yalnızca sensör türü DHT22 olarak yazılır.




| Bağlantı | ESP32 pini | Not |
|---|---|---|
| DHT22 VCC (+) | 3V3 | 3,3–5 V ile çalışır |
| DHT22 DATA | GPIO 4 | Çıplak sensörde DATA ile 3V3 arasına 10 kΩ direnç |
| DHT22 GND (−) | GND | Ortak toprak |
| Pil + ucu | GPIO 34 (bölücü üzerinden) | İki adet 100 kΩ direnç; 4,2 V pil pine 2,1 V olarak gelir |
| Pil − ucu | GND | Bölücünün alt direnci de GND'ye bağlanır |
1#include <WiFi.h>
2#include <esp_now.h>
3#include <esp_wifi.h>
4#include <esp_idf_version.h>
5#include <DHT.h>
6
7const char *AG_ADI = "AgAdi"; // ağ geçidinin bağlandığı Wi-Fi (şifre gerekmez) / Wi-Fi the gateway uses (no password needed)
8uint8_t gecitMac[] = {0x24, 0x6F, 0x28, 0xAA, 0xBB, 0xCC}; // ağ geçidinin STA MAC adresi / gateway STA MAC
9const uint8_t SENSOR_ID = 1; // her sensöre farklı numara / a different number for each sensor
10const uint64_t UYKU_US = 60ULL * 1000000ULL; // 60 saniye uyku / sleep for 60 seconds
11
12#define DHT_PIN 4
13#define PIL_PIN 34 // ADC1 pini, 100k + 100k bölücü / ADC1 pin, 100k + 100k divider
14DHT dht(DHT_PIN, DHT22);
15
16typedef struct __attribute__((packed)) {
17 uint8_t id;
18 uint32_t sayac;
19 float sicaklik;
20 float nem;
21 float pil;
22} SensorPaketi;
23
24RTC_DATA_ATTR uint8_t kanal = 0; // derin uykuda silinmez / kept during deep sleep
25RTC_DATA_ATTR uint32_t paketSayaci = 0;
26volatile bool sonucGeldi = false;
27volatile bool teslimEdildi = false;
28
29#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 0)
30void gonderimSonucu(const esp_now_send_info_t *bilgi, esp_now_send_status_t durum) { // çekirdek 3.3+ / core 3.3+
31#else
32void gonderimSonucu(const uint8_t *mac, esp_now_send_status_t durum) { // çekirdek 2.x-3.2 / core 2.x-3.2
33#endif
34 teslimEdildi = (durum == ESP_NOW_SEND_SUCCESS);
35 sonucGeldi = true;
36}
37
38uint8_t kanalBul(const char *ag) { // ağın kanalını tarayarak bul / find the network's channel by scanning
39 int n = WiFi.scanNetworks();
40 for (int i = 0; i < n; i++) {
41 if (WiFi.SSID(i) == ag) return WiFi.channel(i);
42 }
43 return 1; // bulunamazsa kanal 1 / channel 1 if not found
44}
45
46void uyu() {
47 esp_sleep_enable_timer_wakeup(UYKU_US);
48 esp_deep_sleep_start();
49}
50
51void setup() {
52 Serial.begin(115200);
53 dht.begin();
54 WiFi.mode(WIFI_STA);
55
56 if (kanal == 0) kanal = kanalBul(AG_ADI); // yalnız ilk açılışta ya da hatadan sonra tara / scan only at first boot or after a failure
57 esp_wifi_set_promiscuous(true);
58 esp_wifi_set_channel(kanal, WIFI_SECOND_CHAN_NONE);
59 esp_wifi_set_promiscuous(false);
60
61 if (esp_now_init() != ESP_OK) uyu();
62 esp_now_register_send_cb(gonderimSonucu);
63
64 esp_now_peer_info_t es = {};
65 memcpy(es.peer_addr, gecitMac, 6);
66 es.channel = kanal;
67 es.ifidx = WIFI_IF_STA;
68 esp_now_add_peer(&es);
69
70 SensorPaketi p;
71 p.id = SENSOR_ID;
72 p.sayac = ++paketSayaci;
73 p.sicaklik = dht.readTemperature();
74 p.nem = dht.readHumidity();
75 p.pil = analogReadMilliVolts(PIL_PIN) * 2 / 1000.0; // bölücü yarıya indirir / the divider halves it
76
77 esp_now_send(gecitMac, (uint8_t *)&p, sizeof(p));
78 unsigned long baslangic = millis();
79 while (!sonucGeldi && millis() - baslangic < 200) delay(1); // en fazla 200 ms bekle / wait up to 200 ms
80
81 Serial.printf("Kanal %u, %.1f C, %%%.0f, %.2f V, %s\n", (unsigned)kanal, p.sicaklik, p.nem, p.pil,
82 teslimEdildi ? "teslim edildi / delivered" : "teslim edilemedi / failed");
83 if (!teslimEdildi) kanal = 0; // teslim edilemedi: sonraki uyanışta yeniden tara / not delivered: rescan next wake
84 Serial.flush();
85 uyu();
86}
87
88void loop() {
89}Kodun püf noktası RTC_DATA_ATTR ile işaretlenmiş değişkenlerdir. Derin uykuda ESP32'nin ana belleği silinir, ama RTC belleğindeki bu değişkenler korunur. Sensör kanalı yalnızca ilk açılışta taramayla bulur (bu yaklaşık 2 saniye sürer) ve sonraki uyanışlarda doğrudan o kanalı kullanır; paket teslim edilemezse kanal değişkenini sıfırlar ve bir sonraki uyanışta yeniden tarar. Böylece modem kanal değiştirse bile sensör bir döngü sonra kendini toparlar. Pil gerilimi analogReadMilliVolts() ile kalibre edilmiş olarak okunur ve bölücü gerilimi yarıya indirdiği için 2 ile çarpılır; direnç değerlerini Gerilim bölücü aracında kontrol edebilirsin. GPIO 34 bir ADC1 pinidir; Wi-Fi radyosu açıkken ADC2 pinleri analog okuma yapamadığı için bu seçim önemlidir. Uyku modlarının ayrıntıları için ESP32 Deep Sleep yazısına bakabilirsin.
Pil ömrünü kabaca hesaplayalım. Taramasız bir uyanış (açılış, ölçüm ve gönderim) yaklaşık 0,3 saniye sürer ve bu sırada ortalama 100 mA çekilir; bu, uyanış başına yaklaşık 0,008 mAh eder. Dakikada bir uyanışla günde 1440 uyanış olur ve toplam yaklaşık 12 mAh harcanır. Çıplak bir ESP32 modülü uykuda 10 µA civarında akım çeker; DHT22'nin bekleme akımı ve bölücü dirençlerle birlikte uyku tüketimi günde 2 mAh'yi geçmez. Yani günde yaklaşık 14 mAh harcanır ve 2000 mAh'lik bir 18650 pil 4–5 ay dayanır. Kendi değerlerinle hesap yapmak için Pil ömrü hesaplayıcı aracını kullanabilirsin.
ESP8266 ile ESP-NOW

ESP8266 kartları ESP32 ile aynı ağda ESP-NOW kullanabilir, ama Arduino'daki kütüphanesi farklıdır: esp_now.h yerine espnow.h, WiFi.h yerine ESP8266WiFi.h eklenir ve fonksiyonlar ESP-IDF'den değil ESP8266 SDK'sından gelir. En önemli farklar şunlardır:
| İşlem | ESP32 (esp_now.h) | ESP8266 (espnow.h) |
|---|---|---|
| Başlatma | esp_now_init() == ESP_OK | esp_now_init() == 0 |
| Rol seçimi | Gerekmez | esp_now_set_self_role(ESP_NOW_ROLE_COMBO) |
| Eş ekleme | esp_now_add_peer(&es) ile yapı üzerinden | esp_now_add_peer(mac, ESP_NOW_ROLE_COMBO, kanal, NULL, 0) |
| Alma fonksiyonu | (const esp_now_recv_info_t *bilgi, const uint8_t *veri, int uzunluk) | (uint8_t *mac, uint8_t *veri, uint8_t uzunluk) |
| Gönderim fonksiyonu | (..., esp_now_send_status_t durum) | (uint8_t *mac, uint8_t durum); 0 başarılı demektir |
| En büyük paket | 250 bayt (v2 ile 1470) | 250 bayt |
ESP8266'da WiFi.mode(WIFI_STA) satırından sonra WiFi.disconnect() eklemeyi unutma; ESP8266 açılışta son bağlandığı modeme kendiliğinden bağlanmaya çalışır, bu sırada kanal değişir ve paketler kaybolabilir. Veri yapısını iki tarafta da packed olarak tanımlarsan ESP32 ile ESP8266 aynı baytları aynı şekilde okur, çünkü ikisi de 32 bit ve küçük bayt önce (little-endian) çalışan işlemcilerdir. ESP8266'nın pinleri ve kurulumu için ESP8266 NodeMCU yazısına bakabilirsin.
MicroPython ile ESP-NOW ve Raspberry Pi Pico
ESP32'yi MicroPython ile programlıyorsan ESP-NOW için hazır bir espnow modülü vardır; ESP32 ve ESP8266 için hazırlanan MicroPython sürümlerine dâhildir. Aşağıdaki kod, Arduino'daki verici örneğinin gönderdiği 30 baytlık Paket yapısını alır ve struct.unpack() ile çözer. Biçim dizesindeki < işareti dolgu baytı olmadan, küçük bayt önce okumayı; B 1 baytlık sayıyı, I 4 baytlık işaretsiz sayıyı, f float'u, 16s ise 16 baytlık metni temsil eder. Arduino'daki bool alan da B ile 0 ya da 1 olarak okunur. Kodu Thonny ile karta yükle ve ekrana yazdığı MAC adresini Arduino vericisindeki aliciMac dizisine yaz:
1import network
2import espnow
3import struct
4import binascii
5
6sta = network.WLAN(network.STA_IF) # ESP-NOW istasyon arayüzünü kullanır / ESP-NOW uses the station interface
7sta.active(True)
8print("MAC:", binascii.hexlify(sta.config("mac"), ":").decode()) # bu adresi vericiye yaz / put it in the sender
9
10e = espnow.ESPNow()
11e.active(True)
12
13BICIM = "<BIffB16s" # Arduino'daki Paket yapısı / the Paket struct from Arduino
14BOYUT = struct.calcsize(BICIM) # 30 bayt / 30 bytes
15
16while True:
17 mac, msg = e.recv() # paket gelene kadar bekle / wait for a packet
18 if msg and len(msg) == BOYUT:
19 kimlik, sayac, sicaklik, nem, buton, mesaj = struct.unpack(BICIM, msg)
20 metin = mesaj.split(b"\x00")[0].decode()
21 print(binascii.hexlify(mac, ":").decode(), kimlik, sayac,
22 round(sicaklik, 1), round(nem, 1), bool(buton), metin)MicroPython'dan paket göndermek de iki satırdır: e.add_peer(mac) ile alıcıyı ekler, e.send(mac, struct.pack(BICIM, 2, 1, 23.5, 45.0, 0, b"Selam")) ile aynı yapıyı gönderirsin; böylece Arduino ve MicroPython kartlar aynı ağda birlikte çalışır. Wi-Fi'ye bağlı bir MicroPython kartında paket kaçırmamak için sta.config(pm=sta.PM_NONE) ile güç tasarrufunu kapat. MicroPython'a yeniysen MicroPython temelleri yazısından başlayabilirsin. Raspberry Pi Pico ailesinde ise ESP-NOW yoktur: standart Pico'da kablosuz bağlantı bulunmaz, Pico W ve Pico 2 W'deki CYW43439 yongası ise yalnızca standart Wi-Fi ve Bluetooth çalıştırır. Pico'yu bir ESP-NOW ağına katmak istiyorsan küçük bir ESP32-C3'ü UART seri haberleşme ile Pico'ya bağlayıp köprü olarak kullanabilir ya da Pico W'yi Wi-Fi üzerinden ağ geçidiyle konuşturabilirsin; Pico W'nin Wi-Fi kullanımı Pico W ile Wi-Fi ve web sunucu yazısında anlatılıyor. Aynı köprü yöntemi Arduino Uno için de geçerlidir.
ESP-NOW paketlerini şifrelemek
Varsayılan olarak ESP-NOW paketleri şifresizdir; aynı kanaldaki herkes paketleri havada yakalayabilir ve senin yapını taklit eden sahte paketler gönderebilir. Kapı kilidi ya da garaj kapısı gibi projelerde şifreleme kullan. ESP-NOW, WPA2'deki gibi CCMP yöntemini kullanır ve iki anahtarla çalışır: bütün cihazlarda aynı olan 16 baytlık PMK (esp_now_set_pmk() ile ayarlanır) ve her eş için ayrı 16 baytlık LMK. Eşi eklerken es.encrypt = true yapar ve anahtarı es.lmk alanına kopyalarsın; karşı taraf da seni aynı LMK ile şifreli eş olarak eklemelidir. Şifreli paketleri alabilmek için alıcının vericiyi listeye eklemesi zorunludur. Yayın paketleri şifrelenemez ve şifreli eş sayısı varsayılan ayarlarla 7 ile sınırlıdır. Şifreleme, yakalanan bir paketin sonradan tekrar gönderilmesini tek başına engellemez; kritik komutlara artan bir sayaç ekleyip alıcıda eski numaraları reddetmek iyi bir ek önlemdir.
ESP-NOW proje fikirleri
- Kablosuz kapı zili: Kapıdaki ESP32 butona basılınca evin içindeki kartta buzzer çalsın; pil ömrü için butonu derin uykudan uyandıran pin olarak kullan.
- Çok odalı sıcaklık takibi: Her odaya bir DHT22 ya da BME280 sensörü koy, ölçümleri tek ağ geçidinde topla ve web sayfasında karşılaştır.
- Bahçe sulama: Toprak nemini ölçen sensör, pompanın yanındaki karta "sula" komutu göndersin; sensör bağlantısı için Toprak Nem Sensörü yazısına bak.
- Kablosuz robot kumandası: Joystick değerlerini saniyede 20 kez gönder; robotun mekanik kısmı için Arduino Robot Araba Yapımı yazısına göz at.
- Bahçe alarmı: Bahçedeki PIR sensörü hareket algılayınca ağ geçidi Telegram üzerinden bildirim göndersin.
- Eş zamanlı ışık gösterisi: Tek bir yayın paketiyle birden fazla kartın WS2812B şeritlerini aynı anda aynı renge geçir.
ESP-NOW sık hataları ve çözümleri
| Belirti / hata | Nedeni | Çözüm |
|---|---|---|
| "ESPNOW: Peer interface is invalid", eş eklenemiyor | esp_now_peer_info_t sıfırlanmadan kullanıldı | Yapıyı = {} ya da memset ile sıfırla, ifidx = WIFI_IF_STA yaz |
| Sürekli "Teslim edilemedi" | Yanlış MAC (AP adresi ya da yazım hatası), alıcı kapalı ya da başka kanalda | Alıcının STA MAC adresini yeniden oku, iki kartın kanalını kontrol et |
| Paket geliyor ama değerler anlamsız | İki taraftaki struct farklı ya da içinde String var | Yapıyı iki tarafta aynı sırayla ve packed tanımla, metin için char dizisi kullan |
| "invalid conversion ... to 'esp_now_recv_cb_t'" | Çekirdek 2.x için yazılmış kod 3.x'te derleniyor | Alma fonksiyonunu const esp_now_recv_info_t * ile yaz |
| "invalid conversion ... to 'esp_now_send_cb_t'" | Çekirdek 3.0–3.2 için yazılmış kod 3.3+'ta derleniyor | Gönderim fonksiyonunu const esp_now_send_info_t * ile yaz ya da ESP_IDF_VERSION makrosunu kullan |
| "Peer channel is not equal to the home channel, send fail!" | Wi-Fi'ye bağlı kartın kanalı ile eşin kanalı farklı | Eşi channel = 0 ile ekle ve sensörleri modemin kanalına geçir |
| Wi-Fi'ye bağlı ağ geçidi bazı paketleri kaçırıyor | Wi-Fi güç tasarrufu (modem uykusu) | WiFi.setSleep(false) kullan |
| ESP_ERR_ESPNOW_NOT_INIT | esp_now_init() çağrılmadı ya da Wi-Fi başlatılmadan çağrıldı | Önce WiFi.mode(WIFI_STA), sonra esp_now_init() |
| ESP_ERR_ESPNOW_FULL | Eş listesi 20 kayda ulaştı | esp_now_del_peer() ile eski eşleri sil ya da yayın kullan |
| ESP_ERR_ESPNOW_NO_MEM | Paketler, gönderim kuyruğu boşalmadan art arda gönderiliyor | Bir sonraki paketten önce gönderim sonucunu bekle |
| MAC adresi 00:00:00:00:00:00 görünüyor | Çekirdek 3.x'te Wi-Fi tam başlamadan WiFi.macAddress() çağrıldı | WiFi.mode(WIFI_STA) sonrasında esp_wifi_get_mac() kullan |
ESP-NOW, birden fazla ESP32'yi modem, kablo ya da ek modül olmadan konuşturmanın en hızlı yoludur: MAC adresini öğren, veri yapını iki tarafta aynı tanımla, eşi ekle ve gönder. Wi-Fi ile birlikte kullanırken tek kuralı unutma: herkes aynı kanalda olmalı. BlueGrays uygulamasında ESP32 ile LED ve buton kontrolü, DHT22 ile sıcaklık ölçümü, Wi-Fi bağlantısı, web sunucusu ve derin uyku gibi bu yazıdaki parçaların hepsini adım adım devre şeması ve hazır koduyla deneyebilirsin.
Sıkça sorulan sorular
ESP-NOW için modem ya da internet gerekir mi?
Hayır. ESP-NOW kartların Wi-Fi radyosunu kullanır ama hiçbir ağa bağlanmaz; kartlar birbirine MAC adresleriyle doğrudan paket gönderir. İnternete veri taşımak istersen ağdaki bir ESP32'yi hem Wi-Fi'ye hem ESP-NOW'a bağlı bir ağ geçidi olarak kullanman gerekir.
ESP-NOW menzili ne kadar?
Kart üstü antenli ESP32 kartlarla ev içinde duvarlara bağlı olarak genellikle 20–40 metre, açık alanda 100–200 metre civarında menzil elde edilir. Harici antenli modüllerle ve iki tarafta da açılan LR (Long Range) moduyla Espressif'e göre görüş hattında 1 kilometreye kadar ulaşılabilir.
ESP-NOW ile kaç cihaz bağlanabilir?
Bir kartın eş listesine en fazla 20 cihaz eklenebilir; bunların şifreli olanları varsayılan ayarlarla en fazla 7 tanedir. Bu sınır paket gönderilecek cihazlar içindir: bir ağ geçidi şifresiz paket gönderen 20'den fazla sensörü dinleyebilir, yayın (broadcast) ile de aynı kanaldaki bütün cihazlara tek paketle ulaşılır.
ESP-NOW ve Wi-Fi aynı anda kullanılabilir mi?
Evet, ama ESP32'nin tek radyosu aynı anda tek kanalda çalıştığı için bütün ESP-NOW cihazları modemin kanalını kullanmalıdır. Ağ geçidinde WiFi.setSleep(false) ile güç tasarrufunu kapat, sensörlerde de kanalı ağ adını tarayarak bul ya da modemi sabit bir kanala ayarla.
ESP32 ile ESP8266 ESP-NOW ile haberleşebilir mi?
Evet. İki yonga da ESP-NOW destekler ve birbirine paket gönderebilir; ESP8266'da yalnızca kütüphane (espnow.h) ve fonksiyon biçimleri farklıdır. Veri yapısını iki tarafta packed olarak aynı tanımla ve paketleri 250 baytın altında tut.
ESP-NOW ile en fazla kaç bayt gönderilebilir?
ESP-NOW v1 ile bir pakete en fazla 250 bayt sığar ve bu sınır ESP8266 dâhil bütün kartlarda geçerlidir. ESP-IDF 5.4 ve üzerini kullanan kartlar (Arduino çekirdeği 3.2+) ESP-NOW v2 ile 1470 bayta kadar gönderebilir, ancak v1 kullanan kartlar 250 bayttan uzun paketleri alamaz.
Bu yazıdan sonra yapabileceğin projeler
Tüm projeleri gör
ESP32 ile LED Yakma
ESP32 ile temel GPIO çıkışı kullanımını öğretir. Bir LED'i dijital pinden açıp kapatarak yanıp sönme efekti elde ederiz. Arduino UNO'daki klasik blink projesinin ESP32 versiyonu; Arduino ortamındaki temel blink komutları aynıdır, ancak ESP32'nin pin yerleşimi ve 3,3 V GPIO seviyeleri Arduino UNO'dan farklıdır.

ESP32 Buton ile LED Kontrol (Toggle)
Butona her basıldığında LED'in durumu değişir: açıksa kapanır, kapalıysa açılır. Klasik toggle (geçiş) pattern'i. Debounce (sıçrama önleme) ile butonun mekanik gürültüsünü temizleyerek tek basışta tek değişim sağlarız.

2 Arduino Çift Yönlü Haberleşme
İki Arduino kabloyla birbirine bağlanıp konuşur: birinin butonuna basınca öbürünün ışığı yanar, yeniden basınca söner. İki kartta da aynı program çalışır, yani ikisi de hem haber gönderir hem haber al

ESP32 DHT22 Sıcaklık ve Nem (LCD Ekranlı)
DHT22 sensörü ile ortam sıcaklığını ve nem oranını ölçüp 16x2 I2C LCD ekranda gerçek zamanlı olarak gösteren bir projedir. Adafruit DHT kütüphanesi kullanılarak tek kablo (one-wire) protokolü ile sensörden veri okunur. Bu proje, IoT hava istasyonu, akıllı ev otomasyonu veya seralarda ortam takip sistemlerinin temelini oluşturur. ESP32'nin GPIO 4 pini üzerinden DHT22 ile haberleşme sağlanır.

ESP32 Wi-Fi STA Bağlantısı (LCD Ekranlı)
Bu projede ESP32'yi Station (STA) modunda evdeki Wi-Fi ağına güvenli ve hata toleranslı şekilde bağlıyoruz. SSID/şifreyi secrets.h dosyasına ayırarak Git'e sızmasını önlüyor, otomatik yeniden bağlanma mekanizması kuruyor, RSSI sinyal seviyesini ve IP adresini I2C LCD ekranda gösteriyoruz. Dahili LED durum göstergesi olarak çalışır. Üretim seviyesi bir bağlantı yöneticisidir.

ESP32 Async Web Server (LCD'de IP Göstergeli)
Bu projede ESP32'yi aynı Wi-Fi ağındaki telefonunuzdan veya bilgisayarınızdan kontrol edebileceğiniz asenkron bir web sunucusuna dönüştürüyoruz. ESPAsyncWebServer kütüphanesi sayesinde sunucu birden fazla isteği aynı anda işleyebiliyor (klasik WebServer.h gibi tek istekte bloklamıyor). Tarayıcıdan ESP32'nin IP adresine bağlanıp ON/OFF butonlarına basarak (/on ve /off adresleri) GPIO 5'e bağlı LED'i açıp kapatabileceksiniz.

ESP32 Deep Sleep — Pil Tasarrufu (OLED + RTC RAM Sayaç)
ESP32'nin Deep Sleep modu, CPU ve çoğu çevre birimini kapatarak akım tüketimini ~10µA seviyesine düşürür ve pille çalışan IoT cihazları için kritik öneme sahiptir. Bu projede üç farklı uyandırma kaynağını kullanıyoruz: zamanlayıcı (timer, 15sn), harici pin (EXT0 — buton GPIO 33) ve touch (T0/GPIO 4). RTC_DATA_ATTR ile işaretlenen değişkenler deep sleep boyunca korunur — boot sayacı, timer wake sayacı ve buton wake sayacı kalıcı olarak izlenir. SSD1306 OLED ekranda her uyanışta wake reason, say

ESP32 Sıcaklık Loglayıcı (DHT22 + DS1307 RTC + microSD + OLED)
ESP32 ile zamanlı sıcaklık ve nem kayıt sistemi kur! Bu projede DHT22 sensörü ile dakikada bir sıcaklık/nem okuyup DS1307 RTC modülünden aldığın gerçek zaman etiketiyle microSD karta CSV formatında kaydediyoruz. OLED ekranda büyük font ile son sıcaklık değeri, nem yüzdesi, timestamp ve toplam kayıt sayısı canlı gösterilir. SD kart üzerindeki /temp_log.csv dosyası Excel/Google Sheets/Python pandas ile açılıp analiz edilebilir — sera monitörü, ev/oda sıcaklık takibi, laboratuvar veri toplama,
Diğer yazılar
BlueGrays uygulaması
Bu devreyi telefonunda adım adım kur
Devre şeması, kod ve parça listesi tek ekranda. 200'den fazla Arduino, ESP32 ve Pico projesi seni bekliyor.
Telefonunla tara
Kamerayı koda tut; telefonunun uygulama mağazası açılır.
ya da mağazada aç

