Panduan · 20 mnt baca

Tutorial ESP-NOW: Komunikasi Nirkabel ESP32 Tanpa Router

ESP-NOW dengan ESP32: cara mencari alamat MAC, mengirim struct sampai 250 byte satu arah atau dua arah, memakai hingga 20 peer, jangkauan, perubahan core 3.x, dan kanal Wi-Fi.

ESP-NOW adalah protokol nirkabel buatan Espressif yang memungkinkan board ESP32 dan ESP8266 mengirim data langsung satu sama lain tanpa router, access point, atau koneksi internet. Board memakai radio Wi-Fi-nya, tapi tidak pernah bergabung ke jaringan mana pun; setiap perangkat mengenali lawannya lewat alamat MAC dan mengirim paket kecil hingga 250 byte dalam beberapa milidetik. Karena tidak ada koneksi yang perlu dibangun, tidak ada alamat IP yang perlu diminta, dan tidak ada kata sandi yang perlu dimasukkan, tombol yang ditekan di satu board bisa langsung menyalakan relay di board lain, dan sensor bertenaga baterai bisa bangun, mengirim hasil ukurnya, lalu segera tidur lagi.

Di panduan ini kamu akan menyiapkan ESP-NOW dari nol: pertama kamu membaca alamat MAC board-mu, lalu memakai contoh satu arah dan dua arah untuk mengirim berbagai nilai seperti suhu, penghitung, dan teks dalam satu paket memakai struct. Setelah itu kamu akan belajar soal batas 20 peer, pesan broadcast, jangkauan dan kecepatan, fungsi-fungsi yang berubah di ESP32 Arduino core 3.x, serta penyamaan kanal, satu-satunya aturan untuk memakai ESP-NOW dan Wi-Fi bersamaan. Di bagian terakhir kamu akan mengirim hasil ukur sensor jarak jauh bertenaga baterai yang memakai deep sleep ke gateway yang terhubung ke Wi-Fi, lalu memantaunya dari ponselmu. Kalau ini proyek ESP32 pertamamu, selesaikan dulu pengaturan Arduino IDE di Apa Itu ESP32?.

Board pengembangan ESP32 yang berkomunikasi dengan ESP-NOW
ESP-NOW tidak butuh modul tambahan: radio Wi-Fi di dalam ESP32 membuat board bisa berkomunikasi langsung tanpa router.

Apa itu ESP-NOW dan bagaimana cara kerjanya?

Dengan Wi-Fi biasa, ESP32 harus terhubung dulu ke router, memeriksa kata sandi, mendapat alamat IP lewat DHCP, dan baru setelah itu bisa mengirim data; proses ini biasanya makan 1–3 detik. ESP-NOW melewati semua langkah itu. Datamu dimasukkan ke paket Wi-Fi pendek yang disebut vendor-specific action frame dari standar IEEE 802.11, lalu dikirim langsung ke alamat MAC board lawan. Board penerima membalas dengan frame konfirmasi perangkat keras (ACK), dan fungsi callback di sisi pengirim memberi tahu apakah paket berhasil diterima. Kalau ACK tidak datang, lapisan Wi-Fi otomatis mencoba mengirim ulang paket beberapa kali. Singkatnya, ESP-NOW bekerja seperti kotak surat tak terlihat di antara dua board: kamu tulis alamatnya, masukkan paketnya, dan kamu dapat kabar begitu pihak lain menerimanya.

FiturNilai
Dikembangkan olehEspressif Systems
Frekuensi2,4 GHz (radio Wi-Fi, kanal 1–13)
Data per paket250 byte (ESP-NOW v1); 1470 byte dengan v2 di ESP-IDF 5.4 ke atas
Peer terdaftarHingga 20; secara default hingga 7 peer terenkripsi
Kecepatan data default1 Mbps
Konfirmasi pengirimanAda untuk unicast (ACK), tidak ada untuk broadcast
EnkripsiOpsional, CCMP (AES-128); kunci PMK dan LMK masing-masing 16 byte
Router / access pointTidak perlu
Chip yang mendukungChip ESP32 dengan Wi-Fi (ESP32, S2, S3, C2, C3, C6…) dan ESP8266

Keunggulan utama ESP-NOW adalah respons cepat dan hemat energi. Begitu kamu menekan tombol, LED di sisi lain menyala dalam beberapa milidetik; untuk melakukan hal yang sama lewat Wi-Fi, kamu harus terhubung dulu lalu mengirim permintaan ke server. Bedanya makin besar pada sensor bertenaga baterai: ketika terhubung ke Wi-Fi, radio menarik arus lebih dari 100 mA selama beberapa detik, sedangkan dengan ESP-NOW board bangun, mengirim hasil ukurnya, dan tidur lagi dalam total beberapa ratus milidetik. Di sisi lain, ESP-NOW tidak bisa menjangkau internet; kalau datamu mau dibawa ke internet atau ke ponsel, harus ada satu ESP32 gateway yang terhubung ke Wi-Fi di suatu tempat dalam jaringan. Susunan itu akan kita bangun di bagian selanjutnya.

ESP-NOW vs Wi-Fi vs Bluetooth vs nRF24L01

MetodeModul tambahanRouterUkuran paketJangkauan umum (ruang terbuka)Paling cocok untuk
ESP-NOWTidak perluTidak perlu250 byte100–200 m, lebih jauh di mode LRPerintah dan data sensor antar-board
Wi-Fi (HTTP, MQTT)Tidak perluWajibPraktis tak terbatasCakupan routerInternet, halaman web, cloud
Bluetooth (BLE)Tidak perluTidak perlu20 byte secara default10–20 m di dalam ruanganTerhubung ke ponsel
nRF24L01Wajib (SPI)Tidak perlu32 bytePuluhan meter, jauh lebih jauh dengan versi PA+LNABoard tanpa nirkabel seperti Arduino Uno dan Nano
LoRa (SX1278)Wajib (SPI)Tidak perluHingga 255 byteBeberapa kilometerJarak sangat jauh, data jarang

Kalau perlu berkomunikasi dengan ponsel, Bluetooth adalah pilihan yang tepat; kalau butuh internet, pakai Wi-Fi. Tapi kalau kamu punya beberapa board ESP32 yang hanya perlu saling bicara, ESP-NOW biasanya cara paling sederhana: tanpa modul tambahan, kabel, atau router, dan kodenya cuma beberapa baris. Untuk board tanpa nirkabel seperti Arduino Uno, nRF24L01 lebih cocok, dan untuk jangkauan berkilo-kilometer, modul LoRa. Untuk membandingkan semua pilihan berdampingan, lihat pilihan komunikasi nirkabel Arduino, dan untuk terhubung ke ponsel, cek panduan Bluetooth ESP32 kami.

Board apa saja yang mendukung ESP-NOW?

Board / chipESP-NOWCatatan
ESP32 (WROOM-32, DevKit)YaJuga mendukung mode Long Range (LR)
ESP32-S2, S3, C3, C6YaKode Arduino sama, hanya pilihan board yang berubah
ESP32-C2 (ESP8684)YaTanpa mode LR
ESP32-H2TidakTidak punya radio Wi-Fi, jadi tidak ada ESP-NOW
ESP8266 (NodeMCU, Wemos D1 Mini)YaLibrary berbeda (espnow.h), hanya v1 dan 250 byte
Raspberry Pi Pico W / Pico 2 WTidakChip Wi-Fi-nya (CYW43439) tidak bisa menjalankan ESP-NOW
Arduino Uno, Nano, MegaTidakBisa dihubungkan ke ESP32 lewat port serial (UART)

ESP-NOW juga bekerja antar-chip ESP yang berbeda: sensor ESP32-C3 bisa mengirim paket ke gateway ESP32 klasik atau ke board ESP8266. Syaratnya hanya dua: kedua sisi berada di kanal Wi-Fi yang sama, dan struktur datanya didefinisikan persis sama di kedua sisi. Contoh di bawah memakai dua board ESP32 DevKit; pin board-mu yang aman dipakai bisa kamu lihat di alat Pinout.

Langkah 1: Cari alamat MAC ESP32

Di ESP-NOW setiap board menemukan lawannya lewat alamat MAC 6 byte; alamat ini ditulis ke setiap ESP32 di pabrik dan tidak pernah berubah. Upload sketch di bawah ke board yang akan kamu pakai sebagai penerima, lalu buka Serial Monitor di 115200 baud. Sketch ini mencetak alamatnya dalam bentuk yang mudah dibaca sekaligus sebagai baris array yang bisa langsung kamu tempel ke kode board lain. Untuk contoh dua board, lakukan ini di kedua board dan catat alamatnya.

C++ (Arduino IDE)
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}
ESP32 punya dua alamat MAC Wi-Fi terpisah: station (STA) dan access point (AP). Alamat AP biasanya sama dengan alamat STA, hanya byte terakhirnya ditambah satu. Semua contoh di panduan ini menjalankan board dalam mode WIFI_STA, jadi selalu masukkan alamat STA di board lawan; memakai alamat dari antarmuka yang salah adalah penyebab paling umum munculnya pesan “failed”. Di ESP32 Arduino core 3.x, WiFi.macAddress() bisa mengembalikan 00:00:00:00:00:00 kalau dipanggil sebelum Wi-Fi benar-benar menyala; panggilan esp_wifi_get_mac() di sketch di atas tidak punya masalah ini.

Mengirim data dengan struct dan batas 250 byte

ESP-NOW hanya mengirim deretan byte dan tidak tahu apa isinya. Cara paling rapi untuk mengirim beberapa nilai berbeda sekaligus adalah mengelompokkannya dalam struct lalu mengirim byte struct itu persis seperti yang tersimpan di memori. Penerima mendefinisikan struct yang sama dan menyalin byte yang masuk ke struct itu dengan memcpy. Agar ini berhasil, struct harus punya urutan, tipe, dan ukuran yang sama di kedua sisi. Compiler bisa menyisipkan byte padding tak terlihat di antara field agar aksesnya lebih cepat: struct Paket di contoh-contoh di bawah biasanya memakan 36 byte, tapi turun jadi tepat 30 byte begitu kamu menambahkan __attribute__((packed)). Memakai packed menjamin setiap byte berada di posisi yang sama antara ESP32 dan ESP8266, atau antara Arduino dan MicroPython.

TipeUkuranCatatan
bool, uint8_t, int8_t, char1 byteFlag, angka kecil, satu karakter
int16_t, uint16_t2 byte−32768…32767 atau 0…65535
int, int32_t, uint32_t4 byteint berukuran 4 byte di ESP32 dan ESP8266, tapi 2 byte di Arduino Uno
float4 bytePresisi sekitar 7 digit
double8 bytePakai float kecuali benar-benar perlu
char mesaj[16]16 byteTeks dengan panjang tetap; harus diakhiri '\0'
String, char*, pointerTidak bisa dikirimYang terkirim hanya alamat memori, yang tidak berarti apa-apa di sisi lain

Satu paket memuat paling banyak 250 byte. Batas ini berasal dari versi pertama ESP-NOW (v1) dan berlaku untuk semua board, termasuk ESP8266. Arduino core 3.2 ke atas, yang berbasis ESP-IDF 5.4, bisa mengirim paket hingga 1470 byte dengan ESP-NOW v2; tetapi board yang menjalankan v1 (ESP8266 atau ESP32 dengan core lama) akan memotong paket yang lebih panjang dari 250 byte atau membuangnya sama sekali. Jadi di jaringan campuran, tetap di bawah 250 byte adalah pilihan paling aman. Baris static_assert di sketch pengirim menghentikan kompilasi dengan pesan yang jelas kalau struct melebihi 250 byte. Kalau kamu perlu mengirim data yang lebih besar, pecah menjadi potongan bernomor lalu susun kembali sesuai urutan di penerima; untuk nilai sensor, status tombol, atau perintah, beberapa puluh byte sudah lebih dari cukup.

ESP-NOW satu arah: sketch pengirim

  1. Pastikan kedua board ESP32 dipilih sebagai ESP32 Dev Module di Arduino IDE. Library ESP-NOW sudah termasuk dalam paket board, jadi tidak ada lagi yang perlu diinstal.
  2. Masukkan alamat MAC yang kamu baca dari board penerima ke array aliciMac di kode di bawah.
  3. Hubungkan push button antara GPIO 4 dan GND di board pengirim; karena kita memakai INPUT_PULLUP, tidak perlu resistor tambahan. Detailnya ada di Cara Menghubungkan Push Button ke Arduino.
  4. Upload sketch ke board pengirim dan buka Serial Monitor di 115200 baud. Selama penerima belum berjalan, wajar kalau setiap dua detik muncul “Teslim edilemedi / failed” (bahasa Turki untuk “gagal terkirim”).
C++ (Arduino IDE)
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}

Sketch ini pertama-tama menyalakan Wi-Fi dalam mode WIFI_STA; ESP-NOW tidak bisa bekerja tanpa radio Wi-Fi, jadi esp_now_init() harus selalu datang setelah baris WiFi.mode(). Lalu penerima ditambahkan ke daftar sebagai peer. Mengosongkan struct esp_now_peer_info_t dengan = {} itu penting; sisa nilai acak di field yang tidak dikosongkan memicu error “Peer interface is invalid”. channel = 0 berarti “pakai kanal board yang sedang aktif”. esp_now_send() memasukkan paket ke antrean dan langsung kembali; ESP_OK hanya berarti paket diterima untuk dikirim. Hasil sebenarnya datang beberapa milidetik kemudian di fungsi gonderimSonucu(): ESP_NOW_SEND_SUCCESS berarti penerima mengonfirmasi paket di tingkat perangkat keras. Blok #if di bagian atas memilih bentuk callback pengiriman yang tepat, yang bergantung pada versi core; ini kami jelaskan di bagian core 3.x.

Sketch penerima ESP-NOW

C++ (Arduino IDE)
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}

Di penerima, paket masuk ke fungsi veriGeldi(). Fungsi ini tidak berjalan di loop()-mu, melainkan di task Wi-Fi yang berprioritas tinggi, jadi jangan memakai Serial.print, delay, atau perhitungan panjang di dalamnya. Cara yang benar adalah memeriksa apakah ukuran paket cocok dengan struct yang diharapkan, menyalin datanya ke variabelmu sendiri dengan memcpy, lalu memberi tahu loop() lewat flag volatile. Di core 3.x, parameter bilgi juga memberi tahu siapa pengirim paket (bilgi->src_addr) dan kekuatan sinyalnya (bilgi->rx_ctrl->rssi, dalam dBm); informasi ini hanya berlaku di dalam fungsi, jadi salin kalau kamu membutuhkannya nanti. Penerima tidak perlu menambahkan pengirim sebagai peer: paket unicast tanpa enkripsi dan broadcast tetap diterima dari board yang tidak ada di daftar.

“Delivered” berarti paket sudah sampai di perangkat keras Wi-Fi penerima, bukan berarti program di penerima sudah memprosesnya. Kalau board lain menyala dan berada di kanal yang sama, ACK tetap kembali walaupun board itu tidak pernah menjalankan ESP-NOW. Untuk perintah penting, pakai pesan konfirmasi terpisah di tingkat perangkat lunak dan tambahkan penghitung ke paketmu: kalau penerima melihat penghitungnya melompat, ia tahu berapa paket yang hilang, dan kalau nomor yang sama datang dua kali, duplikatnya diabaikan.

Komunikasi dua arah: dua ESP32 saling mengendalikan

Dalam komunikasi dua arah, setiap board adalah pengirim sekaligus penerima. Sketch di bawah kamu upload ke kedua board; bedanya hanya satu: di setiap board, isi array karsiMac dengan alamat board yang lain. Begitu kamu menekan tombol di satu board, LED di board lain menyala atau mati, dan board yang mengubah LED-nya mengirim balik pesan konfirmasi. Pengkabelannya sama di kedua board:

KomponenPin ESP32Catatan
TombolGPIO 4 dan GNDMemakai pull-up internal, tidak perlu resistor tambahan
LEDGPIO 2LED biru bawaan di kebanyakan DevKit; untuk LED eksternal pakai resistor 220 Ω
DayaUSBBoard bisa diberi daya dari port USB atau adaptor yang terpisah
C++ (Arduino IDE)
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}

Field tur di struct pesan menunjukkan jenis paket: 1 adalah perintah, 2 adalah konfirmasi. Board yang tombolnya ditekan mengirim status yang diinginkan untuk LED di board lain; board yang menerima perintah mengatur LED-nya lalu mengirim balik status yang sama dengan tipe 2. Dengan begitu pengirim mendapat ACK perangkat keras sekaligus kepastian bahwa “LED-nya memang berubah”. Perintah yang masuk lagi-lagi diteruskan ke loop() lewat flag volatile, dan digitalWrite serta balasannya dikerjakan di sana. Cara ini lebih aman daripada memanggil esp_now_send() dari dalam callback. Jeda 30 ms pada tombol menyaring pantulan kontak (bounce). Pasang relay sebagai pengganti LED, dan kode yang sama berubah jadi sakelar lampu nirkabel; untuk pengkabelan relay, lihat Apa Itu Modul Relay?.

Banyak perangkat: batas 20 peer dan broadcast

ESP-NOW mendukung ketiga susunan: satu pengirim ke banyak penerima (satu remote ke lima board relay), banyak pengirim ke satu penerima (lima sensor ke satu gateway), dan jaringan tempat semua perangkat saling berkomunikasi. Daftar peer sebuah board bisa memuat paling banyak 20 entri, dan secara default hanya 7 di antaranya yang bisa dienkripsi. Batas ini hanya berlaku untuk board yang kamu kirimi paket. Gateway tidak wajib menambahkan sensor yang mengirim paket tanpa enkripsi kepadanya, jadi gateway bisa mendengarkan lebih dari 20 sensor. Kalau daftarnya penuh, esp_now_add_peer() mengembalikan ESP_ERR_ESPNOW_FULL; kamu bisa menghapus peer yang sudah tidak dibutuhkan dengan esp_now_del_peer() lalu menambahkan yang baru. Sketch di bawah menambahkan tiga penerima dan alamat broadcast ke daftar, lalu mengirim satu per satu sekaligus lewat broadcast:

C++ (Arduino IDE)
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}

Alamat broadcast FF:FF:FF:FF:FF:FF juga harus ditambahkan sebagai peer sebelum kamu mengirim ke sana. Satu paket broadcast sampai ke semua penerima ESP-NOW di kanal yang sama; kamu tidak perlu tahu alamat MAC penerima, sehingga ini cara yang paling mudah disiapkan. Harganya: broadcast tidak punya konfirmasi pengiriman, hasil pengiriman selalu terlihat sukses, dan paket broadcast tidak bisa dienkripsi. Dengan pengiriman satu per satu, setiap paket mendapat ACK sendiri. Kalau kamu memberikan NULL sebagai alamat ke esp_now_send(), paket dikirim terpisah ke setiap peer di daftar; kalau alamat broadcast juga ada di daftar, paket itu juga keluar sekali sebagai broadcast. Cara praktis untuk banyak proyek adalah membiarkan board saling menemukan lewat broadcast ketika pertama kali menyala, lalu mempelajari alamat MAC masing-masing dan beralih ke unicast yang dikonfirmasi.

Jangkauan dan kecepatan ESP-NOW

UkuranNilai umumCatatan
Jangkauan di dalam ruangan20–40 m (1–3 dinding)Dinding beton bertulang dan benda logam paling banyak melemahkan sinyal
Jangkauan di ruang terbuka100–200 mDengan antena PCB bawaan; bertambah jika board diletakkan lebih tinggi dari tanah
Mode LR (Long Range)Hingga 1 kmAngka Espressif untuk garis pandang; kecepatan turun ke 0,5 atau 0,25 Mbps
Kecepatan data default1 MbpsKecepatan lebih tinggi berarti jangkauan lebih pendek
Waktu di udara untuk paket 250 byteSekitar 2,5 msSekitar 3 ms per paket termasuk ACK dan waktu tunggu
Laju paket teoretis~300 paket per detik (~70 kB/s)Dengan paket 250 byte; dalam praktik lebih rendah
Latensi perintahBeberapa milidetikTidak perlu membangun koneksi atau server

Nilai-nilai ini sangat bergantung pada lingkungan, tapi hitungannya sederhana. Pada kecepatan default 1 Mbps, setiap byte berada di udara selama 8 mikrodetik; 250 byte data beserta header dan preamble sinkronisasi butuh sekitar 2,5 ms. Ditambah ACK dari penerima dan waktu menunggu kanal kosong, kamu butuh sekitar 3 ms per paket, jadi paling banyak sekitar 300 paket bisa dikirim per detik. Itu lebih dari cukup untuk data sensor dan perintah, tapi ESP-NOW tidak cocok untuk streaming audio atau video. Untuk menambah jangkauan, letakkan board tinggi dari tanah, jangan tutupi antena dengan kotak logam, baterai, atau kabel, pakai modul dengan antena eksternal (seperti ESP32-WROOM-32U), dan aktifkan mode Long Range (LR) dari Espressif di kedua sisi dengan esp_wifi_set_protocol(WIFI_IF_STA, WIFI_PROTOCOL_LR). LR hanya bekerja antara dua board ESP32; antarmuka yang hanya diatur ke LR tidak bisa terhubung ke router biasa, dan ESP8266 serta ESP32-C2 tidak mendukung mode ini. Ketika menguji jangkauan di core 3.x, cetak bilgi->rx_ctrl->rssi di penerima: −50 dBm adalah sinyal yang sangat kuat, −80 dBm sudah dekat batas, dan paket mulai hilang di sekitar −90 dBm.

Perubahan di ESP32 Arduino core 3.x

Sebagian besar contoh ESP-NOW di internet ditulis untuk ESP32 Arduino core 2.x. Kalau kamu menginstal versi 3.x dari Boards Manager, kode itu tidak akan bisa dikompilasi, karena Espressif mengubah bentuk fungsi callback di ESP-IDF 5. Perubahannya datang dalam dua tahap: core 3.0 mengubah callback penerimaan, dan core 3.3, yang berbasis ESP-IDF 5.5, mengubah callback pengiriman. Versi yang kamu pakai bisa dilihat di samping paket esp32 di Tools > Board > Boards Manager.

TopikCore 2.xCore 3.0–3.2Core 3.3 ke atas
Callback penerimaan(const uint8_t *mac, const uint8_t *veri, int uzunluk) (parameter bentuk lama)(const esp_now_recv_info_t *bilgi, const uint8_t *veri, int uzunluk) (parameter bentuk baru)Sama dengan 3.0
Alamat pengirimmacbilgi->src_addrbilgi->src_addr
Callback pengiriman(const uint8_t *mac, esp_now_send_status_t durum) (parameter bentuk lama)Sama dengan 2.x(const esp_now_send_info_t *bilgi, esp_now_send_status_t durum) (parameter bentuk baru)
Paket terbesar250 byte250 byte; 1470 byte dengan v2 di 3.21470 byte dengan v2
Mengatur kanalesp_wifi_set_channel()Juga WiFi.setChannel()Sama dengan 3.0
Library kelas siap pakaiTidak adaESP32_NOW.h (ESP_NOW_Peer, ESP_NOW_Serial)ESP32_NOW.h dengan dukungan v2

Ketika kamu mengompilasi kode lama di 3.x, kamu akan melihat error invalid conversion from 'void (*)(const uint8_t*, const uint8_t*, int)' ... to 'esp_now_recv_cb_t' untuk callback penerimaan. Solusinya: jadikan parameter pertama fungsi itu const esp_now_recv_info_t * dan baca alamat pengirim dari bilgi->src_addr. Begitu pindah ke core 3.3, muncul error serupa, kali ini untuk esp_now_send_cb_t; parameter pertama callback pengiriman harus const esp_now_send_info_t *, dan alamat penerima ada di bilgi->des_addr. Sketch di panduan ini memakai makro ESP_IDF_VERSION untuk memilih bentuk yang tepat sendiri, jadi bisa dikompilasi tanpa perubahan di 2.x, 3.0–3.2, dan 3.3+. Core 3.x juga membawa library berbasis kelas bernama ESP32_NOW.h: kamu menulis onReceive() dan onSent() di kelas turunan ESP_NOW_Peer, dan kelas ESP_NOW_Serial membuatmu bisa memakai link ESP-NOW persis seperti Serial. Contoh-contohnya ada di bawah ESP_NOW di menu File > Examples. Kami memakai fungsi esp_now.h secara langsung karena tetap kompatibel dengan ESP8266 dan dengan kode lama.

ESP-NOW dan Wi-Fi bersamaan: menyamakan kanal

ESP32 hanya punya satu radio 2,4 GHz, dan radio itu hanya bisa bekerja di satu kanal pada satu waktu. Begitu board terhubung ke router, yang menentukan kanal adalah router, bukan kamu; kalau router ada di kanal 6, ESP-NOW juga berjalan di kanal 6. Jadi setiap sensor yang mengirim paket ke gateway yang terhubung ke Wi-Fi juga harus pindah ke kanal router. Kalau kanalnya tidak cocok, pengirim mencetak “failed”; kalau kamu menambahkan peer dengan kanal selain nol, Serial Monitor juga menampilkan error “ESPNOW: Peer channel is not equal to the home channel, send fail!”. Ikuti aturan ini agar susunannya bebas masalah:

  1. Gateway terhubung ke Wi-Fi lebih dulu, baru setelah itu menyalakan ESP-NOW dan mencetak kanal router ke Serial Monitor dengan WiFi.channel().
  2. Sensor tidak terhubung ke router; sensor menemukan kanal dengan memindai nama jaringan (WiFi.scanNetworks() dan WiFi.channel(i)) atau memakai kanal tetap di kode. Untuk ini sensor tidak perlu tahu kata sandi Wi-Fi.
  3. Ubah pengaturan kanal router dari “Auto” ke kanal tetap (1, 6, atau 11). Di mode auto, kalau router berganti kanal, sensor kehilangan gateway.
  4. Matikan mode hemat daya di gateway dengan WiFi.setSleep(false). Secara default, ESP32 yang terhubung ke router menidurkan radionya secara berkala dan bisa melewatkan paket ESP-NOW yang datang selama itu.
  5. Masukkan alamat MAC STA gateway di sensor; sketch gateway di bawah mencetak alamat ini ke Serial Monitor ketika mulai menyala.

Sketch gateway di bawah terhubung ke jaringan rumahmu, menyimpan hasil ukur dari hingga lima sensor, dan menampilkannya di halaman web sederhana yang bisa kamu buka dari ponsel di jaringan yang sama. Ganti nama jaringan dan kata sandinya dengan milikmu, lalu setelah upload, buka alamat IP yang tampil di Serial Monitor di browser ponselmu:

C++ (Arduino IDE)
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            " &deg;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}

Di callback penerimaan, gateway hanya memasukkan paket ke queue (antrean) FreeRTOS lalu langsung kembali. loop() mengosongkan antrean dan menyimpan hasil ukur terbaru serta waktu tibanya untuk setiap sensor, dan web server menampilkan tabel itu. Antrean memastikan tidak ada paket yang hilang ketika beberapa sensor mengirim pada momen yang sama; ini juga cara praktis mengikuti saran Espressif agar tidak mengerjakan tugas panjang di dalam callback. Halaman memuat ulang dirinya setiap 10 detik, dan kolom “Son paket” (bahasa Turki untuk “paket terakhir”) langsung menunjukkan apakah ada sensor yang diam (baterai habis atau di luar jangkauan?). Kalau gateway sama sekali tidak bisa terhubung ke Wi-Fi, masalahnya bukan di ESP-NOW; jalankan pemeriksaan di ESP32 tidak mau terhubung ke Wi-Fi. Kalau datanya mau kamu masukkan ke sistem otomasi rumah, kamu bisa memakai MQTT dan Home Assistant sebagai pengganti halaman web.

Contoh sensor jarak jauh: DHT22, deep sleep, dan ESP-NOW

Sekarang mari buat sensor bertenaga baterai yang mengirim data ke gateway. Setiap kali bangun, sensor mengukur suhu, kelembapan, dan tegangan baterai, mengirim satu paket ESP-NOW, menunggu ACK pengiriman, lalu masuk deep sleep selama 60 detik. Untuk suhu dan kelembapan kita memakai DHT22; kamu perlu menginstal DHT sensor library dari Adafruit lewat Library Manager. Cara kerja sensornya ada di panduan sensor suhu dan kelembapan DHT11 kami; pengkabelannya sama untuk DHT22, dan di kode kamu cukup mengatur tipe sensor ke DHT22.

ESP32 DevKit
ESP32 DevKit
DHT22 (AM2302)
DHT22 (AM2302)
Baterai 18650 Li-Ion
Baterai 18650 Li-Ion
Modul charger TP4056
Modul charger TP4056
KoneksiPin ESP32Catatan
DHT22 VCC (+)3V3Bekerja di 3,3–5 V
DHT22 DATAGPIO 4Pada sensor tanpa modul, tambahkan resistor 10 kΩ antara DATA dan 3V3
DHT22 GND (−)GNDGround bersama
Baterai +GPIO 34 (lewat pembagi)Dua resistor 100 kΩ; baterai 4,2 V sampai di pin sebagai 2,1 V
Baterai −GNDResistor bawah pembagi juga ke GND
C++ (Arduino IDE)
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}

Kunci sketch ini ada pada variabel yang ditandai RTC_DATA_ATTR. Ketika deep sleep, memori utama ESP32 terhapus, tapi variabel di memori RTC ini tetap bertahan. Sensor hanya mencari kanal dengan memindai ketika pertama kali menyala (sekitar 2 detik) dan langsung memakai kanal itu di setiap bangun berikutnya; kalau paket gagal terkirim, sensor mengosongkan variabel kanal dan memindai lagi di bangun berikutnya. Jadi walaupun router berganti kanal, sensor pulih setelah satu siklus. Tegangan baterai dibaca dalam bentuk yang sudah terkalibrasi dengan analogReadMilliVolts() lalu dikalikan 2 karena pembagi membaginya dua; nilai resistornya bisa kamu cek di alat Pembagi tegangan. GPIO 34 adalah pin ADC1; ini penting karena pin ADC2 tidak bisa membaca analog selama radio Wi-Fi menyala. Untuk detail mode sleep, lihat panduan Deep Sleep ESP32 kami.

Mari perkirakan umur baterainya secara kasar. Satu kali bangun tanpa pemindaian (boot, pengukuran, dan pengiriman) butuh sekitar 0,3 detik dan menarik rata-rata sekitar 100 mA, jadi kira-kira 0,008 mAh per bangun. Bangun sekali per menit menghasilkan 1440 kali bangun sehari, totalnya sekitar 12 mAh. Modul ESP32 tanpa board menarik sekitar 10 µA ketika deep sleep; ditambah arus siaga DHT22 dan resistor pembagi, konsumsi selama tidur tetap di bawah 2 mAh sehari. Jadi sensor memakai sekitar 14 mAh sehari, dan sel 18650 2000 mAh bertahan 4–5 bulan. Untuk menghitung dengan nilaimu sendiri, pakai Kalkulator umur baterai.

Perkiraan ini berlaku untuk modul tanpa board atau board berdaya rendah. Pada ESP32 DevKit biasa, regulator, chip USB-serial, dan LED power menarik beberapa mA bahkan ketika tidur, sehingga umur baterai turun jadi beberapa minggu. Untuk sensor yang tahan lama, pakai regulator dengan arus diam rendah (seperti HT7333), lepaskan LED power, atau pilih board berdaya rendah yang sudah jadi. Memperpanjang interval pengukuran dari 60 detik ke 5 menit membuat umur baterai lebih dari tiga kali lipat. Untuk mengisi sel 18650 dengan aman, kamu bisa memakai modul TP4056 yang berproteksi.

ESP-NOW dengan ESP8266

Board Wemos D1 Mini ESP8266
Board ESP8266 seperti Wemos D1 Mini juga mendukung ESP-NOW, tapi library Arduino dan bentuk fungsinya berbeda dari ESP32.

Board ESP8266 bisa memakai ESP-NOW di jaringan yang sama dengan board ESP32, tapi library Arduino-nya berbeda: kamu memasukkan espnow.h sebagai pengganti esp_now.h dan ESP8266WiFi.h sebagai pengganti WiFi.h, dan fungsinya berasal dari SDK ESP8266, bukan ESP-IDF. Perbedaan utamanya:

TugasESP32 (esp_now.h)ESP8266 (espnow.h)
Inisialisasiesp_now_init() == ESP_OKesp_now_init() == 0
PeranTidak perluesp_now_set_self_role(ESP_NOW_ROLE_COMBO)
Menambah peeresp_now_add_peer(&es) dengan structesp_now_add_peer(mac, ESP_NOW_ROLE_COMBO, channel, NULL, 0)
Callback penerimaan(const esp_now_recv_info_t *bilgi, const uint8_t *veri, int uzunluk) (parameter ESP32)(uint8_t *mac, uint8_t *data, uint8_t len) (parameter ESP8266)
Callback pengiriman(..., esp_now_send_status_t durum)(uint8_t *mac, uint8_t status); 0 berarti sukses
Paket terbesar250 byte (1470 dengan v2)250 byte

Di ESP8266, jangan lupa menambahkan WiFi.disconnect() setelah WiFi.mode(WIFI_STA); ketika menyala, ESP8266 otomatis mencoba terhubung lagi ke router terakhir yang dipakainya, yang mengubah kanal dan bisa membuat paket hilang. Kalau struct data didefinisikan sebagai packed di kedua sisi, ESP32 dan ESP8266 membaca byte yang sama dengan cara yang sama, karena keduanya prosesor 32-bit little-endian. Untuk pin dan pengaturan ESP8266, lihat panduan ESP8266 NodeMCU kami.

ESP-NOW dengan MicroPython dan Raspberry Pi Pico

Kalau kamu memprogram ESP32 dengan MicroPython, ada modul espnow siap pakai untuk ESP-NOW; modul ini sudah termasuk dalam build MicroPython untuk ESP32 dan ESP8266. Kode di bawah menerima struct Paket 30 byte yang dikirim contoh pengirim Arduino dan menguraikannya dengan struct.unpack(). Di string format, < berarti little-endian tanpa padding; B adalah angka 1 byte, I angka tak bertanda 4 byte, f sebuah float, dan 16s teks 16 byte. Field bool dari Arduino dibaca sebagai 0 atau 1 dengan B. Upload kode ke board dengan Thonny, lalu masukkan alamat MAC yang dicetaknya ke array aliciMac di sketch pengirim Arduino:

Python
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)

Mengirim paket dari MicroPython juga cukup dua baris: tambahkan penerima dengan e.add_peer(mac) dan kirim struct yang sama dengan e.send(mac, struct.pack(BICIM, 2, 1, 23.5, 45.0, 0, b"Selam")), sehingga board Arduino dan MicroPython bisa bekerja bersama di jaringan yang sama. Di board MicroPython yang terhubung ke Wi-Fi, matikan mode hemat daya dengan sta.config(pm=sta.PM_NONE) agar tidak ada paket yang terlewat. Kalau kamu baru mengenal MicroPython, mulai dari panduan dasar-dasar MicroPython kami. Namun keluarga Raspberry Pi Pico tidak punya ESP-NOW: Pico standar sama sekali tidak punya nirkabel, dan chip CYW43439 di Pico W dan Pico 2 W hanya menjalankan Wi-Fi dan Bluetooth standar. Untuk memasukkan Pico ke jaringan ESP-NOW, hubungkan ESP32-C3 kecil ke Pico lewat komunikasi serial UART dan pakai sebagai jembatan, atau biarkan Pico W berkomunikasi dengan gateway lewat Wi-Fi; Wi-Fi di Pico W dibahas di Wi-Fi dan web server Pico W. Cara jembatan yang sama juga berlaku untuk Arduino Uno.

Mengenkripsi paket ESP-NOW

Secara default paket ESP-NOW tidak dienkripsi; siapa pun di kanal yang sama bisa menangkapnya dari udara dan mengirim paket palsu yang meniru struct-mu. Pakai enkripsi untuk proyek seperti kunci pintu atau pintu garasi. ESP-NOW memakai CCMP, seperti di WPA2, dan bekerja dengan dua kunci: PMK 16 byte yang sama di semua perangkat (diatur dengan esp_now_set_pmk()) dan LMK 16 byte tersendiri untuk setiap peer. Ketika menambahkan peer, kamu mengatur es.encrypt = true dan menyalin kuncinya ke es.lmk; sisi lain juga harus menambahkanmu sebagai peer terenkripsi dengan LMK yang sama. Untuk menerima paket terenkripsi, penerima wajib menambahkan pengirim ke daftarnya. Paket broadcast tidak bisa dienkripsi, dan secara default kamu bisa punya paling banyak 7 peer terenkripsi. Enkripsi saja tidak mencegah paket yang tertangkap dikirim ulang belakangan; menambahkan penghitung yang terus naik ke perintah penting dan menolak nomor lama di penerima adalah pengaman tambahan yang bagus.

Ide proyek ESP-NOW

  • Bel pintu nirkabel: Ketika tombol di ESP32 dekat pintu ditekan, buzzer berbunyi di board di dalam rumah; demi umur baterai, pakai tombol itu sebagai pin yang membangunkan board dari deep sleep.
  • Pemantauan suhu banyak ruangan: Pasang sensor DHT22 atau BME280 di setiap ruangan, kumpulkan hasil ukurnya di satu gateway, dan bandingkan di halaman web.
  • Penyiraman kebun: Sensor kelembapan tanah mengirim perintah “siram sekarang” ke board di samping pompa; untuk pengkabelannya, lihat Sensor Kelembapan Tanah.
  • Remote robot nirkabel: Kirim nilai joystick 20 kali per detik; untuk sisi mekanis robotnya, lihat Mobil Robot Arduino.
  • Alarm kebun: Ketika sensor PIR di kebun mendeteksi gerakan, gateway mengirim notifikasi lewat Telegram.
  • Pertunjukan lampu serempak: Ganti warna strip WS2812B di beberapa board ke warna yang sama sekaligus dengan satu paket broadcast.

Error ESP-NOW yang umum dan solusinya

Gejala / errorPenyebabSolusi
“ESPNOW: Peer interface is invalid”, peer tidak bisa ditambahkanesp_now_peer_info_t dipakai tanpa dikosongkanKosongkan struct dengan = {} atau memset dan atur ifidx = WIFI_IF_STA
Pesan “failed” terus-menerusMAC salah (alamat AP atau salah ketik), penerima mati atau di kanal lainBaca ulang alamat MAC STA penerima dan cek kanal kedua board
Paket datang tapi nilainya ngawurStruct berbeda di kedua sisi atau berisi StringDefinisikan struct sebagai packed dengan urutan yang sama di kedua sisi dan pakai array char untuk teks
"invalid conversion ... to 'esp_now_recv_cb_t'"Kode yang ditulis untuk core 2.x dikompilasi di 3.xTulis callback penerimaan dengan const esp_now_recv_info_t *
"invalid conversion ... to 'esp_now_send_cb_t'"Kode yang ditulis untuk core 3.0–3.2 dikompilasi di 3.3+Tulis callback pengiriman dengan const esp_now_send_info_t * atau pakai makro ESP_IDF_VERSION
“Peer channel is not equal to the home channel, send fail!” (kanal peer tidak sama dengan kanal utama)Kanal board yang terhubung ke Wi-Fi berbeda dari kanal peerTambahkan peer dengan channel = 0 dan pindahkan sensor ke kanal router
Gateway yang terhubung ke Wi-Fi melewatkan sebagian paketMode hemat daya Wi-Fi (modem sleep)Pakai WiFi.setSleep(false)
ESP_ERR_ESPNOW_NOT_INITesp_now_init() tidak dipanggil, atau dipanggil sebelum Wi-Fi menyalaPertama WiFi.mode(WIFI_STA), lalu esp_now_init()
ESP_ERR_ESPNOW_FULLDaftar peer sudah mencapai 20 entriHapus peer lama dengan esp_now_del_peer() atau pakai broadcast
ESP_ERR_ESPNOW_NO_MEMPaket dikirim beruntun sebelum antrean pengiriman kosongTunggu hasil pengiriman sebelum mengirim paket berikutnya
Alamat MAC tampil 00:00:00:00:00:00Di core 3.x, WiFi.macAddress() dipanggil sebelum Wi-Fi benar-benar menyalaPakai esp_wifi_get_mac() setelah WiFi.mode(WIFI_STA)
Ketika mencari masalah, mulai dari langkah paling sederhana: letakkan kedua board berdampingan dan kirim ke alamat broadcast dulu. Kalau paket broadcast sampai, radio, kanal, dan struct datanya sudah benar, dan masalahnya ada di alamat MAC. Kalau tidak ada yang sampai, pilih Debug di Tools > Core Debug Level; log detail dari core akan muncul di Serial Monitor. Kalau board terus restart, kemungkinan besar masalahnya di catu daya; solusi untuk lonjakan arus ketika radio menyala ada di panduan Brownout dan Guru Meditation ESP32 kami.

ESP-NOW adalah cara tercepat untuk membuat beberapa board ESP32 saling berkomunikasi tanpa router, kabel, atau modul tambahan: baca alamat MAC, definisikan struct datamu sama persis di kedua sisi, tambahkan peer, lalu kirim. Kalau kamu memakainya bersama Wi-Fi, ingat satu aturannya: semua harus berada di kanal yang sama. Di aplikasi BlueGrays kamu bisa mencoba setiap bagian dari panduan ini, termasuk kendali LED dan tombol dengan ESP32, pengukuran suhu dengan DHT22, koneksi Wi-Fi, web server, dan deep sleep, langkah demi langkah dengan skema rangkaian dan kode siap pakai.

Pertanyaan umum

Apakah ESP-NOW butuh router atau internet?

Tidak. ESP-NOW memakai radio Wi-Fi board, tapi tidak bergabung ke jaringan mana pun; board saling mengirim paket langsung ke alamat MAC masing-masing. Kalau datanya mau kamu bawa ke internet, kamu butuh satu ESP32 sebagai gateway yang terhubung ke Wi-Fi sekaligus ke ESP-NOW.

Berapa jangkauan ESP-NOW?

Dengan board ESP32 berantena bawaan, biasanya kamu mendapat 20–40 meter di dalam ruangan, tergantung dindingnya, dan sekitar 100–200 meter di ruang terbuka. Dengan modul berantena eksternal dan mode LR (Long Range) yang diaktifkan di kedua sisi, menurut Espressif jangkauan hingga 1 kilometer bisa dicapai dalam garis pandang.

Berapa banyak perangkat yang bisa terhubung dengan ESP-NOW?

Daftar peer sebuah board bisa memuat hingga 20 perangkat, dan secara default paling banyak 7 di antaranya bisa dienkripsi. Batas ini untuk perangkat yang kamu kirimi paket: gateway bisa mendengarkan lebih dari 20 sensor yang mengirim paket tanpa enkripsi, dan broadcast menjangkau setiap perangkat di kanal yang sama dengan satu paket.

Apakah ESP-NOW dan Wi-Fi bisa dipakai bersamaan?

Bisa, tapi karena radio tunggal ESP32 bekerja di satu kanal pada satu waktu, setiap perangkat ESP-NOW harus memakai kanal router. Matikan mode hemat daya di gateway dengan WiFi.setSleep(false), dan di sensor, cari kanal dengan memindai nama jaringan atau atur router ke kanal tetap.

Apakah ESP32 dan ESP8266 bisa berkomunikasi dengan ESP-NOW?

Bisa. Kedua chip mendukung ESP-NOW dan bisa saling mengirim paket; yang berbeda di ESP8266 hanya library-nya (espnow.h) dan bentuk fungsinya. Definisikan struct data sebagai packed dan identik di kedua sisi, dan jaga ukuran paket di bawah 250 byte.

Berapa ukuran data maksimal untuk ESP-NOW?

Dengan ESP-NOW v1, satu paket memuat paling banyak 250 byte, dan batas ini berlaku untuk semua board termasuk ESP8266. Board yang menjalankan ESP-IDF 5.4 ke atas (Arduino core 3.2+) bisa mengirim hingga 1470 byte dengan ESP-NOW v2, tetapi board yang memakai v1 tidak bisa menerima paket yang lebih panjang dari 250 byte.

Lihat semua proyek
LED Berkedip dengan ESP32
#103Pemula

LED Berkedip dengan ESP32

Mengajarkan penggunaan output GPIO dasar dengan ESP32. Kita membuat efek berkedip dengan menyalakan dan mematikan LED dari pin digital. Versi ESP32 dari proyek blink klasik di Arduino UNO; perintah blink dasar di lingkungan Arduino sama, tetapi susunan pin ESP32 dan level GPIO 3,3 V-nya berbeda dengan Arduino UNO.

4.7(37)
99
Kendali LED dengan Tombol Tekan ESP32 (Toggle)
#105Pemula

Kendali LED dengan Tombol Tekan ESP32 (Toggle)

Setiap kali tombol ditekan, keadaan LED berubah: jika menyala jadi mati, jika mati jadi menyala. Pola toggle klasik. Dengan debounce, kita membersihkan derau mekanis tombol tekan agar satu tekanan menghasilkan satu kali toggle.

4.6(39)
57
Komunikasi Dua Arah Antara 2 Arduino
#58Menengah
Premium

Komunikasi Dua Arah Antara 2 Arduino

Dua Arduino disambung dengan kabel dan saling berbicara: tekan tombol di satu board dan lampu di board lain menyala, tekan lagi dan lampunya mati. Kedua board menjalankan program yang sama, jadi masin

4.5(20)
26
Suhu dan Kelembapan DHT22 ESP32 (dengan Layar LCD)
#117Menengah

Suhu dan Kelembapan DHT22 ESP32 (dengan Layar LCD)

Proyek ini mengukur suhu dan kelembapan sekitar dengan sensor DHT22 dan menampilkan datanya secara real-time di LCD I2C 16x2. Data dibaca dari sensor dengan protokol satu kabel lewat library Adafruit DHT. Proyek ini menjadi dasar untuk stasiun cuaca IoT, otomasi rumah pintar, atau sistem pemantauan lingkungan di rumah kaca. Komunikasi dengan DHT22 dilakukan lewat pin GPIO 4 ESP32.

4.8(38)
56
Koneksi Wi-Fi STA ESP32 (dengan Layar LCD)
#127Mahir

Koneksi Wi-Fi STA ESP32 (dengan Layar LCD)

Di proyek ini, kita menyambungkan ESP32 ke jaringan Wi-Fi rumah dalam mode Station (STA) dengan cara yang aman dan tahan gangguan. Kita menyimpan SSID dan kata sandi di file secrets.h terpisah agar tidak bocor ke Git, menyiapkan mekanisme sambung ulang otomatis, dan menampilkan tingkat sinyal RSSI dan alamat IP di layar LCD I2C. LED bawaan bekerja sebagai indikator status. Ini adalah pengelola koneksi kelas produksi.

4.4(21)
34
Web Server Asinkron ESP32 (dengan Tampilan IP di LCD)
#129Mahir

Web Server Asinkron ESP32 (dengan Tampilan IP di LCD)

Di proyek ini, kita mengubah ESP32 menjadi web server asinkron yang bisa kamu kendalikan dari ponsel atau komputer di jaringan Wi-Fi yang sama. Berkat library ESPAsyncWebServer, server bisa memproses beberapa permintaan bersamaan (berbeda dengan WebServer.h klasik, yang memblokir pada satu permintaan). Kamu bisa membuka alamat IP ESP32 di browser dan mengklik tombol ON/OFF (rute /on dan /off) untuk menyalakan dan mematikan LED yang tersambung ke GPIO 5.

4.8(16)
26
Deep Sleep ESP32: Hemat Baterai (OLED + Penghitung RTC RAM)
#135Mahir
Premium

Deep Sleep ESP32: Hemat Baterai (OLED + Penghitung RTC RAM)

Mode Deep Sleep ESP32 menurunkan konsumsi arus hingga ~10 µA dengan mematikan CPU dan sebagian besar periferal, sehingga sangat penting untuk perangkat IoT bertenaga baterai. Di proyek ini, kita memakai tiga sumber bangun yang berbeda: timer (15 detik), pin eksternal (EXT0, tombol GPIO 33), dan sentuhan (T0/GPIO 4). Variabel yang ditandai RTC_DATA_ATTR dipertahankan selama deep sleep; penghitung boot, penghitung bangun karena timer, dan penghitung bangun karena tombol terus dilacak.

4.3(10)
16
Pencatat Data Suhu ESP32 (DHT22 + RTC DS1307 + microSD + OLED)
#144Mahir
Premium

Pencatat Data Suhu ESP32 (DHT22 + RTC DS1307 + microSD + OLED)

Buat pencatat suhu bercap waktu dengan ESP32! Baca suhu/kelembapan DHT22 setiap menit, ambil waktu sebenarnya dari RTC DS1307, dan catat ke kartu microSD sebagai CSV. OLED menampilkan suhu langsung (font besar), kelembapan, cap waktu, dan penghitung catatan. File /temp_log.csv bisa dibuka di Excel, Google Sheets, atau Python pandas; cocok untuk rumah kaca, suhu kamar, data laboratorium, penyimpanan makanan, atau sarang lebah.

4.7(7)
12