1. Apa Itu ESP-NOW?
ESP-NOW adalah protokol komunikasi nirkabel yang dikembangkan oleh Espressif Systems, dirancang khusus untuk memungkinkan perangkat ESP32 dan ESP8266 berkomunikasi secara langsung (peer-to-peer) tanpa memerlukan koneksi WiFi tradisional ke router atau access point.
Protokol ini bekerja pada frekuensi 2.4 GHz menggunakan teknologi yang sama dengan WiFi (IEEE 802.11), tetapi dengan pendekatan yang jauh lebih ringan. ESP-NOW menghilangkan kebutuhan akan DHCP, TCP/IP stack, dan overhead jaringan WiFi biasa, sehingga menghasilkan latensi yang sangat rendah dan konsumsi daya yang minimal.
WiFi Tradisional: ESP-NOW:
ββββββββββ ββββββββββ
β ESP32 β β ESP32 β
β Node A β β Node A β
βββββ¬βββββ βββββ¬βββββ
β WiFi β Langsung!
βΌ β (No Router)
ββββββββββ β
β Router β βββ Perlu Router β
β / AP β βΌ
βββββ¬βββββ ββββββββββ
β WiFi β ESP32 β
βΌ β Node B β
ββββββββββ ββββββββββ
β ESP32 β
β Node B β Kelebihan ESP-NOW:
ββββββββββ β
Tidak perlu router
β
Latensi rendah (~2ms)
β
Hemat daya
β
Setup cepat
β
Hingga 20 peer
Karakteristik Utama ESP-NOW
| Parameter | Spesifikasi |
|---|---|
| Frekuesi | 2.4 GHz (ISM Band) |
| Maksimum Payload | 250 byte per paket |
| Peer Maksimum | 20 perangkat terdaftar |
| Latensi | ~2 milidetik |
| Jarak Efektif | ~200m (open air), ~30m (indoor) |
| Enkripsi | AES-128 (opsional, 6 peer terenkripsi) |
| Kecepatan Data | 1 Mbps (PHY rate) |
| Kompatibilitas | ESP32, ESP8266, ESP32-S2, ESP32-S3, ESP32-C3 |
| Koneksi WiFi | Bisa dijalankan bersamaan dengan WiFi STA |
ESP-NOW sangat cocok untuk proyek yang membutuhkan komunikasi cepat antar ESP32 tanpa infrastruktur jaringan. Misalnya: remote control drone, jaringan sensor pertanian, sistem notifikasi rumah, dan robot swarm. Bahkan, ESP-NOW bisa dijalankan bersamaan dengan WiFi STA, sehingga ESP32 bisa meneruskan data ke internet.
- β Tanpa Router
- β Latensi ~2ms
- β Sangat Hemat Daya
- β Payload 250 byte
- β Setup Instan
- β Range Terbatas
- β Tidak ada IP/TCP
- β Perlu Router
- β Latensi ~10-50ms
- β Konsumsi Daya Tinggi
- β Payload Tak Terbatas
- β Setup Kompleks
- β Range Lebih Jauh
- β Kompatibel HTTP/MQTT
- β Tanpa Router
- β Latensi Tinggi
- β Sangat Hemat Daya
- β Payload ~50 byte
- β Perlu Modul Tambahan
- β Range 10+ km
- β Butuh LoRa Module
2. Setup Peer ESP-NOW
Sebelum ESP32 dapat saling berkomunikasi via ESP-NOW, setiap perangkat harus mendaftarkan peer (perangkat tujuan) menggunakan MAC address-nya. Proses ini disebut pairing atau peer registration.
Langkah 1: Mendapatkan MAC Address ESP32
Setiap ESP32 memiliki MAC address unik 6-byte. Untuk mengetahuinya, upload kode berikut:
// Mendapatkan MAC Address ESP32
// BeebaneLabs - https://beebanelabs.pages.dev
#include "WiFi.h"
void setup() {
Serial.begin(115200);
delay(1000);
// Mode STA diperlukan untuk mendapatkan MAC address
WiFi.mode(WIFI_STA);
// Cetak MAC address
Serial.println("================================");
Serial.print("MAC Address: ");
Serial.println(WiFi.macAddress());
Serial.println("================================");
Serial.println();
Serial.println("Catat MAC address ini!");
Serial.println("Ini dibutuhkan untuk mendaftarkan peer ESP-NOW.");
}
void loop() {
// Tidak perlu looping
}
Jalankan kode ini di SEMUA ESP32 yang akan terlibat dalam komunikasi ESP-NOW. Catat MAC address masing-masing karena akan digunakan saat mendaftarkan peer.
Langkah 2: Inisialisasi ESP-NOW dan Mendaftarkan Peer
// Inisialisasi ESP-NOW dan Mendaftarkan Peer
// BeebaneLabs - https://beebanelabs.pages.dev
#include "esp_now.h"
#include "WiFi.h"
// MAC Address tujuan (ganti dengan MAC Address ESP32 receiver)
uint8_t peerAddress[] = {0xA4, 0xCF, 0x12, 0xE8, 0x45, 0xA1};
// Channel WiFi yang digunakan (1-13, default 1)
const uint8_t CHANNEL = 1;
// Informasi peer
esp_now_peer_info_t peerInfo;
void onSent(const uint8_t *mac_addr, esp_now_send_status_t status) {
Serial.print("Status Kirim: ");
Serial.println(status == ESP_NOW_SEND_SUCCESS ? "BERHASIL" : "GAGAL");
}
void onReceive(const uint8_t *mac_addr, const uint8_t *data, int len) {
char macStr[18];
snprintf(macStr, sizeof(macStr), "%02X:%02X:%02X:%02X:%02X:%02X",
mac_addr[0], mac_addr[1], mac_addr[2],
mac_addr[3], mac_addr[4], mac_addr[5]);
Serial.printf("Diterima dari %s: %d byte\n", macStr, len);
}
void setup() {
Serial.begin(115200);
WiFi.mode(WIFI_STA);
// Inisialisasi ESP-NOW
if (esp_now_init() != ESP_OK) {
Serial.println("[ERROR] Gagal inisialisasi ESP-NOW!");
return;
}
Serial.println("[OK] ESP-NOW berhasil diinisialisasi");
// Registrasi callback
esp_now_register_send_cb(onSent);
esp_now_register_recv_cb(onReceive);
// Daftarkan peer
memset(&peerInfo, 0, sizeof(peerInfo));
memcpy(peerInfo.peer_addr, peerAddress, 6);
peerInfo.channel = CHANNEL;
peerInfo.encrypt = false; // Tanpa enkripsi
if (esp_now_add_peer(&peerInfo) != ESP_OK) {
Serial.println("[ERROR] Gagal menambahkan peer!");
return;
}
Serial.println("[OK] Peer berhasil didaftarkan");
Serial.print("Peer MAC: ");
for (int i = 0; i < 6; i++) {
Serial.printf("%02X", peerAddress[i]);
if (i < 5) Serial.print(":");
}
Serial.println();
}
void loop() {
// Akan diisi di section berikutnya
}
Proses esp_now_add_peer() harus dilakukan di kedua sisi β baik pengirim maupun penerima harus saling mendaftarkan MAC address satu sama lain. Jika hanya satu sisi yang mendaftar, komunikasi tidak akan berhasil.
3. Mengirim dan Menerima Data
ESP-NOW mendukung pengiriman data dalam bentuk byte array. Kita bisa mengirim struct, string, atau array data apa saja selama ukurannya tidak melebihi 250 byte. Berikut adalah contoh lengkap pengiriman dan penerimaan data antara dua ESP32.
ESP32 SENDER ESP32 RECEIVER ββββββββββββββββ ββββββββββββββββ β β β β β Baca Data β β onReceive() β β Sensor DHT β β Callback β β β β β β β β βΌ β β βΌ β β Format β ESP-NOW β Parse β β Struct β ββββββββββββΊ β Struct β β β β 2.4 GHz β β β β βΌ β β βΌ β β esp_now_ β β Tampilkan β β send() β β di Serial β β β β β β β βΌ β β β β onSent() β β β β Callback β β β ββββββββββββββββ ββββββββββββββββ
Program Pengirim (Sender)
// ESP-NOW Sender - Mengirim Data Sensor
// BeebaneLabs - https://beebanelabs.pages.dev
#include "esp_now.h"
#include "WiFi.h"
#include "DHT.h"
#define DHT_PIN 4
#define DHT_TYPE DHT11
DHT dht(DHT_PIN, DHT_TYPE);
// MAC Address ESP32 Receiver
uint8_t receiverMAC[] = {0xA4, 0xCF, 0x12, 0xE8, 0x45, 0xA1};
// Struct untuk data yang akan dikirim
// Pastikan struct IDENTIK di sender dan receiver!
typedef struct struct_sensor_data {
float suhu;
float kelembaban;
uint16_t id;
uint32_t uptime;
} struct_sensor_data;
struct_sensor_data dataKirim;
// Status pengiriman
volatile bool terkirim = false;
void onSent(const uint8_t *mac_addr, esp_now_send_status_t status) {
terkirim = (status == ESP_NOW_SEND_SUCCESS);
Serial.printf("[SEND] Status: %s\n",
status == ESP_NOW_SEND_SUCCESS ? "BERHASIL" : "GAGAL");
}
void setup() {
Serial.begin(115200);
dht.begin();
// Inisialisasi WiFi & ESP-NOW
WiFi.mode(WIFI_STA);
WiFi.disconnect(); // Pastikan tidak terhubung ke AP
if (esp_now_init() != ESP_OK) {
Serial.println("[ERROR] ESP-NOW init gagal!");
ESP.restart();
}
esp_now_register_send_cb(onSent);
// Daftarkan peer
esp_now_peer_info_t peerInfo = {};
memcpy(peerInfo.peer_addr, receiverMAC, 6);
peerInfo.channel = 1;
peerInfo.encrypt = false;
if (esp_now_add_peer(&peerInfo) != ESP_OK) {
Serial.println("[ERROR] Gagal tambah peer!");
return;
}
Serial.println("[OK] Sender ESP-NOW siap!");
dataKirim.id = 0;
}
void loop() {
dataKirim.id++;
dataKirim.uptime = millis() / 1000;
dataKirim.suhu = dht.readTemperature();
dataKirim.kelembaban = dht.readHumidity();
// Validasi sensor
if (isnan(dataKirim.suhu) || isnan(dataKirim.kelembaban)) {
Serial.println("[WARN] Sensor error, kirim data dummy...");
dataKirim.suhu = -99.0;
dataKirim.kelembaban = -99.0;
}
// Kirim data
esp_err_t result = esp_now_send(
receiverMAC,
(uint8_t *) &dataKirim,
sizeof(dataKirim)
);
if (result == ESP_OK) {
Serial.printf("[%d] Suhu: %.1fΒ°C | RH: %.1f%% | Uptime: %lus\n",
dataKirim.id, dataKirim.suhu,
dataKirim.kelembaban, dataKirim.uptime);
} else {
Serial.println("[ERROR] Gagal mengirim data!");
}
delay(5000); // Kirim setiap 5 detik
}
Program Penerima (Receiver)
// ESP-NOW Receiver - Menerima Data Sensor
// BeebaneLabs - https://beebanelabs.pages.dev
#include "esp_now.h"
#include "WiFi.h"
// Struct HARUS IDENTIK dengan sender!
typedef struct struct_sensor_data {
float suhu;
float kelembaban;
uint16_t id;
uint32_t uptime;
} struct_sensor_data;
struct_sensor_data dataDiterima;
// Statistik
uint32_t totalDiterima = 0;
uint32_t totalError = 0;
void onReceive(const uint8_t *mac_addr, const uint8_t *data, int len) {
// Konversi MAC address ke string
char macStr[18];
snprintf(macStr, sizeof(macStr), "%02X:%02X:%02X:%02X:%02X:%02X",
mac_addr[0], mac_addr[1], mac_addr[2],
mac_addr[3], mac_addr[4], mac_addr[5]);
// Validasi ukuran data
if (len != sizeof(struct_sensor_data)) {
Serial.printf("[ERROR] Ukuran data tidak sesuai: %d byte\n", len);
totalError++;
return;
}
// Copy data ke struct
memcpy(&dataDiterima, data, sizeof(struct_sensor_data));
totalDiterima++;
// Tampilkan data
Serial.println("ββββββββββββββββββββββββββββββββββ");
Serial.printf(" Dari : %s\n", macStr);
Serial.printf(" Paket ke : %d\n", dataDiterima.id);
Serial.printf(" Suhu : %.1f Β°C\n", dataDiterima.suhu);
Serial.printf(" Kelembaban: %.1f %%\n", dataDiterima.kelembaban);
Serial.printf(" Uptime : %lu detik\n", dataDiterima.uptime);
Serial.printf(" Total Rx : %lu | Error: %lu\n", totalDiterima, totalError);
Serial.println("ββββββββββββββββββββββββββββββββββ");
}
void setup() {
Serial.begin(115200);
WiFi.mode(WIFI_STA);
WiFi.disconnect();
if (esp_now_init() != ESP_OK) {
Serial.println("[ERROR] ESP-NOW init gagal!");
ESP.restart();
}
esp_now_register_recv_cb(onReceive);
Serial.println("ββββββββββββββββββββββββββββββββ");
Serial.println("β ESP-NOW Receiver Siap! β");
Serial.println("ββββββββββββββββββββββββββββββββ");
Serial.println("Menunggu data...");
Serial.println();
}
void loop() {
// Semua dikerjakan di callback onReceive()
delay(1000);
}
Gunakan struct untuk mengirim data terstruktur. Pastikan definisi struct 100% identik di sender dan receiver β termasuk urutan dan tipe data. Perbedaan sekecil apa pun akan menyebabkan data yang diterima salah atau corrupt.
4. Mode Broadcast
ESP-NOW mendukung mode broadcast, yaitu mengirim data ke semua perangkat ESP-NOW dalam jangkauan tanpa perlu mendaftarkan peer secara spesifik. Untuk broadcast, cukup gunakan MAC address FF:FF:FF:FF:FF:FF.
Unicast: Kirim ke satu perangkat spesifik (perlu daftar peer). Broadcast: Kirim ke semua perangkat dalam range (tidak perlu daftar peer, tapi perlu inisialisasi peer broadcast). Perangkat yang menggunakan mode broadcast tidak perlu saling mendaftarkan MAC address β cukup arahkan ke FF:FF:FF:FF:FF:FF.
// ESP-NOW Broadcast Sender
// BeebaneLabs - https://beebanelabs.pages.dev
#include "esp_now.h"
#include "WiFi.h"
// Broadcast MAC Address
uint8_t broadcastMAC[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
typedef struct struct_broadcast_msg {
char pesan[32];
uint16_t counter;
uint8_t node_id;
} struct_broadcast_msg;
struct_broadcast_msg msgBroadcast;
uint16_t counter = 0;
void onSent(const uint8_t *mac_addr, esp_now_send_status_t status) {
Serial.printf("[BROADCAST] %s\n",
status == ESP_NOW_SEND_SUCCESS ? "Terkirim" : "Gagal");
}
void setup() {
Serial.begin(115200);
WiFi.mode(WIFI_STA);
WiFi.disconnect();
if (esp_now_init() != ESP_OK) {
Serial.println("[ERROR] ESP-NOW init gagal!");
ESP.restart();
}
esp_now_register_send_cb(onSent);
// Daftarkan broadcast peer
esp_now_peer_info_t broadcastPeer = {};
memcpy(broadcastPeer.peer_addr, broadcastMAC, 6);
broadcastPeer.channel = 1;
broadcastPeer.encrypt = false;
if (esp_now_add_peer(&broadcastPeer) != ESP_OK) {
Serial.println("[ERROR] Gagal daftar broadcast peer!");
return;
}
// Isi data tetap
strcpy(msgBroadcast.pesan, "Hello dari ESP32!");
msgBroadcast.node_id = 1; // ID unik node ini
Serial.println("[OK] Broadcast Sender siap!");
}
void loop() {
counter++;
msgBroadcast.counter = counter;
esp_err_t result = esp_now_send(
broadcastMAC, // Kirim ke semua
(uint8_t *) &msgBroadcast,
sizeof(msgBroadcast)
);
Serial.printf("[%d] Mengirim broadcast...\n", counter);
delay(3000);
}
// ESP-NOW Broadcast Receiver
// BeebaneLabs - https://beebanelabs.pages.dev
#include "esp_now.h"
#include "WiFi.h"
typedef struct struct_broadcast_msg {
char pesan[32];
uint16_t counter;
uint8_t node_id;
} struct_broadcast_msg;
struct_broadcast_msg msgDiterima;
void onReceive(const uint8_t *mac_addr, const uint8_t *data, int len) {
if (len != sizeof(struct_broadcast_msg)) return;
memcpy(&msgDiterima, data, sizeof(struct_broadcast_msg));
char macStr[18];
snprintf(macStr, sizeof(macStr), "%02X:%02X:%02X:%02X:%02X:%02X",
mac_addr[0], mac_addr[1], mac_addr[2],
mac_addr[3], mac_addr[4], mac_addr[5]);
Serial.printf("[BROADCAST RX] Dari Node %d (%s)\n",
msgDiterima.node_id, macStr);
Serial.printf(" Pesan : %s\n", msgDiterima.pesan);
Serial.printf(" Counter : %d\n", msgDiterima.counter);
Serial.println();
}
void setup() {
Serial.begin(115200);
WiFi.mode(WIFI_STA);
WiFi.disconnect();
if (esp_now_init() != ESP_OK) {
Serial.println("[ERROR] ESP-NOW init gagal!");
ESP.restart();
}
esp_now_register_recv_cb(onReceive);
Serial.println("[OK] Broadcast Receiver siap! Menunggu...");
}
void loop() {
delay(1000);
}
Mode broadcast tidak mendukung enkripsi dan tidak memiliki mekanisme konfirmasi pengiriman. Data yang dikirim via broadcast juga tidak bisa melebihi ukuran payload default (250 byte). Gunakan unicast untuk komunikasi yang membutuhkan keandalan.
5. Komunikasi Terenkripsi
ESP-NOW mendukung enkripsi menggunakan AES-128 (Advanced Encryption Standard) untuk melindungi data yang dikirim. Dengan enkripsi, hanya perangkat yang memiliki primary key dan local key yang sesuai yang dapat mendekripsi data.
ESP32 mendukung maksimal 6 peer terenkripsi (dari total 20 peer). Perangkat yang terenkripsi dan tidak terenkripsi bisa hidup berdampingan β cukup set parameter encrypt saat mendaftarkan peer.
SENDER (terenkripsi) RECEIVER (terenkripsi)
ββββββββββββββββββββ ββββββββββββββββββββ
β β β β
β Data Plaintext β β Data Ciphertext β
β β β β β β
β βΌ β β βΌ β
β ββββββββββββ β β ββββββββββββ β
β β AES-128 β β 2.4GHz β β AES-128 β β
β β Encrypt β β βββββββββββΊ β β Decrypt β β
β β(LMK+PMK) β β Encrypted β β(LMK+PMK) β β
β ββββββββββββ β Payload β ββββββββββββ β
β β β β β
ββββββββββββββββββββ β βΌ β
β Data Plaintext β
Key: PMK = Primary Master Key β β
LMK = Local Master Key ββββββββββββββββββββ
16-byte AES key each
// ESP-NOW dengan Enkripsi AES-128
// BeebaneLabs - https://beebanelabs.pages.dev
#include "esp_now.h"
#include "WiFi.h"
// MAC Address peer (ganti dengan MAC address ESP32 lawan)
uint8_t peerMAC[] = {0xA4, 0xCF, 0x12, 0xE8, 0x45, 0xA1};
// Kunci enkripsi (16 byte = 128 bit)
// Kunci ini HARUS SAMA di kedua perangkat!
uint8_t primaryKey[16] = {
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10
};
uint8_t localKey[16] = {
0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20
};
typedef struct struct_secure_data {
float suhu;
uint8_t status;
char id[8];
} struct_secure_data;
struct_secure_data dataKirim;
void onSent(const uint8_t *mac_addr, esp_now_send_status_t status) {
Serial.printf("[ENCRYPTED] Terkirim: %s\n",
status == ESP_NOW_SEND_SUCCESS ? "BERHASIL" : "GAGAL");
}
void onReceive(const uint8_t *mac_addr, const uint8_t *data, int len) {
struct_secure_data dataDiterima;
memcpy(&dataDiterima, data, sizeof(dataDiterima));
Serial.printf("[DECRYPTED] Suhu: %.1fΒ°C | Status: %d | ID: %s\n",
dataDiterima.suhu, dataDiterima.status, dataDiterima.id);
}
void setup() {
Serial.begin(115200);
WiFi.mode(WIFI_STA);
WiFi.disconnect();
if (esp_now_init() != ESP_OK) {
Serial.println("[ERROR] ESP-NOW init gagal!");
ESP.restart();
}
esp_now_register_send_cb(onSent);
esp_now_register_recv_cb(onReceive);
// Set primary key untuk seluruh sistem
esp_now_set_pmk(primaryKey);
// Daftarkan peer DENGAN enkripsi
esp_now_peer_info_t peerInfo = {};
memcpy(peerInfo.peer_addr, peerMAC, 6);
peerInfo.channel = 1;
peerInfo.encrypt = true; // AKTIFKAN enkripsi
memcpy(peerInfo.lmk, localKey, 16); // Set local key
if (esp_now_add_peer(&peerInfo) != ESP_OK) {
Serial.println("[ERROR] Gagal daftar encrypted peer!");
return;
}
strcpy(dataKirim.id, "NODE-01");
dataKirim.status = 1;
Serial.println("[OK] Komunikasi terenkripsi siap!");
}
void loop() {
dataKirim.suhu = 25.0 + random(0, 100) / 10.0;
esp_now_send(peerMAC, (uint8_t *) &dataKirim, sizeof(dataKirim));
Serial.printf("[KIRIM] Suhu: %.1fΒ°C (terenkripsi)\n", dataKirim.suhu);
delay(5000);
}
Jangan menyimpan kunci enkripsi langsung di kode untuk produksi. Gunakan NVS atau eFuse untuk menyimpan kunci. Anda juga bisa menghasilkan kunci secara acak saat first boot dan menyimpannya di memori non-volatile. Hindari menggunakan kunci yang terlalu sederhana seperti 0x00, 0x01, 0x02, ...
6. Pengujian Jarak (Range Testing)
Mengetahui jarak maksimum komunikasi ESP-NOW sangat penting untuk merancang sistem yang andal. Jarak efektif ESP-NOW dipengaruhi oleh beberapa faktor: antenna, lingkungan, interferensi, dan daya transmit.
Faktor yang Mempengaruhi Jarak
| Faktor | Pengaruh | Tips Optimasi |
|---|---|---|
| Jenis Antenna | PCB antenna: ~100m, External: ~200m+ | Gunakan modul dengan antenna eksternal (IPEX/U.FL) |
| Lingkungan | Indoor: ~30m, Outdoor: ~200m | Hindari tembok tebal dan logam |
| Tinggi Antenna | Semakin tinggi, semakin jauh | Pasang di tempat tinggi, hindari lantai |
| Interferensi | WiFi, Bluetooth, microwave | Gunakan channel yang sepi (channel 1, 6, atau 11) |
| Daya Transmit | Default 20dBm, bisa diatur | Tambahkan esp_wifi_set_max_tx_power() |
| Kecepatan Data | Lebih lambat = lebih jauh | ESP-NOW fixed di 1 Mbps (tidak bisa diubah) |
Program Range Test
// ESP-NOW Range Testing - Pengirim
// Kirim paket, hitung berapa yang berhasil diterima
// BeebaneLabs - https://beebanelabs.pages.dev
#include "esp_now.h"
#include "WiFi.h"
uint8_t receiverMAC[] = {0xA4, 0xCF, 0x12, 0xE8, 0x45, 0xA1};
typedef struct struct_range_test {
uint32_t packet_id;
int8_t rssi_sender;
uint8_t tx_power;
} struct_range_test;
struct_range_test testPacket;
// Statistik
uint32_t totalSent = 0;
uint32_t totalAck = 0;
uint32_t totalFail = 0;
void onSent(const uint8_t *mac_addr, esp_now_send_status_t status) {
totalSent++;
if (status == ESP_NOW_SEND_SUCCESS) {
totalAck++;
} else {
totalFail++;
}
// Hitung Packet Delivery Ratio (PDR)
float pdr = (totalSent > 0) ?
(float)totalAck / totalSent * 100.0 : 0;
Serial.printf("[%lu] Sent: %lu | ACK: %lu | Fail: %lu | PDR: %.1f%%\n",
totalSent, totalSent, totalAck, totalFail, pdr);
}
void setup() {
Serial.begin(115200);
WiFi.mode(WIFI_STA);
WiFi.disconnect();
// Atur daya transmit ke maksimum (20 dBm = 78)
esp_wifi_set_max_tx_power(78);
if (esp_now_init() != ESP_OK) {
Serial.println("[ERROR] ESP-NOW init gagal!");
ESP.restart();
}
esp_now_register_send_cb(onSent);
esp_now_peer_info_t peerInfo = {};
memcpy(peerInfo.peer_addr, receiverMAC, 6);
peerInfo.channel = 1;
peerInfo.encrypt = false;
esp_now_add_peer(&peerInfo);
Serial.println("βββββββββββββββββββββββββββββββββββββ");
Serial.println("β ESP-NOW Range Test β");
Serial.println("β Kirim 100 paket, ukur PDR β");
Serial.println("βββββββββββββββββββββββββββββββββββββ");
Serial.println();
// Dapatkan info WiFi
wifi_country_t country;
esp_wifi_get_country(&country);
int8_t power;
esp_wifi_get_max_tx_power(&power);
Serial.printf("TX Power: %d (0.25 dBm = %.2f dBm)\n",
power, power * 0.25);
Serial.printf("MAC Address: %s\n", WiFi.macAddress().c_str());
Serial.println();
}
void loop() {
// Kirim 100 paket, lalu rekap
if (totalSent >= 100) {
float pdr = (float)totalAck / totalSent * 100.0;
Serial.println("βββββββββββββββββββββββββββββββββββ");
Serial.println(" HASIL RANGE TEST ");
Serial.println("βββββββββββββββββββββββββββββββββββ");
Serial.printf("Total Kirim : %lu\n", totalSent);
Serial.printf("ACK Diterima : %lu\n", totalAck);
Serial.printf("Gagal : %lu\n", totalFail);
Serial.printf("PDR : %.1f%%\n", pdr);
Serial.printf("RSSI : - dBm\n");
Serial.println("βββββββββββββββββββββββββββββββββββ");
if (pdr >= 90) {
Serial.println("β
Koneksi: SANGAT BAIK");
} else if (pdr >= 70) {
Serial.println("β οΈ Koneksi: CUKUP (sesuaikan posisi)");
} else if (pdr >= 50) {
Serial.println("π΄ Koneksi: LEMAH (terlalu jauh)");
} else {
Serial.println("β Koneksi: GAGAL (di luar jangkauan)");
}
// Reset statistik
totalSent = 0;
totalAck = 0;
totalFail = 0;
delay(10000); // Jeda sebelum test berikutnya
}
testPacket.packet_id = totalSent + 1;
testPacket.tx_power = 78;
esp_now_send(receiverMAC, (uint8_t *) &testPacket, sizeof(testPacket));
delay(100); // Kirim setiap 100ms
}
Saat melakukan range test, lakukan secara bertahap: mulai dari jarak 10m, lalu 20m, 50m, 100m, dst. Catat PDR (Packet Delivery Ratio) di setiap titik. PDR β₯ 90% dianggap koneksi stabil. Hindari testing di dekat router WiFi aktif karena interferensi 2.4 GHz bisa mempengaruhi hasil.
7. Konsep Mesh Network
ESP-NOW secara native tidak mendukung mesh network, tetapi kita bisa membangun konsep mesh sederhana dengan cara membuat ESP32 berperan sebagai relay β meneruskan data dari node lain ke tujuan akhir. Konsep ini memperluas jangkauan ESP-NOW secara signifikan.
Topologi Star (Tanpa Mesh): Topologi Mesh (Dengan Relay):
βββββββ βββββββ
βNode1ββββ βNode1ββββ
βββββββ β βββββββ β
β β
βββββββ β ββββββββββ βββββββ β ββββββββββ
βNode2ββββΌββββΊβ Gatewayβ βNode2βββΌβββΊβ Relay ββββ
βββββββ β ββββββββββ βββββββ β β ESP32 β β
β β ββββββββββ β
βββββββ β β Node3 terlalu βββββββ β β
βNode3ββββ jauh dari Gateway βNode3βββ βΌ
βββββββ βββββββ ββββββββββ
β Gatewayβ
Jangkauan terbatas! ββββββββββ
β
Semua
Setiap node harus dalam terjangkau!
range langsung gateway.
// ESP-NOW Mesh Relay - Meneruskan data antar node
// BeebaneLabs - https://beebanelabs.pages.dev
#include "esp_now.h"
#include "WiFi.h"
// Konfigurasi: Ganti sesuai topologi Anda
#define NODE_ID 2 // ID unik node ini (1=gateway, 2=relay, 3+=sensor)
#define IS_RELAY true // Apakah node ini berfungsi sebagai relay?
// MAC Address node berikutnya (relay -> gateway)
uint8_t nextHopMAC[] = {0xA4, 0xCF, 0x12, 0xE8, 0x45, 0xA0}; // Gateway
// Struct standar untuk semua node
typedef struct struct_mesh_packet {
uint8_t source_id; // ID node asal
uint8_t hop_count; // Jumlah hop
uint8_t ttl; // Time To Live (maks hop)
float suhu;
float kelembaban;
uint32_t sensor_uptime;
} struct_mesh_packet;
struct_mesh_packet packetRx;
// Track paket yang sudah diteruskan (hindari loop)
#define MAX_TRACKED 50
uint32_t seenPackets[MAX_TRACKED];
uint8_t seenIndex = 0;
bool sudahDilihat(uint32_t uid) {
for (int i = 0; i < MAX_TRACKED; i++) {
if (seenPackets[i] == uid) return true;
}
return false;
}
void tandaiDilihat(uint32_t uid) {
seenPackets[seenIndex] = uid;
seenIndex = (seenIndex + 1) % MAX_TRACKED;
}
void onReceive(const uint8_t *mac_addr, const uint8_t *data, int len) {
if (len != sizeof(struct_mesh_packet)) return;
memcpy(&packetRx, data, sizeof(struct_mesh_packet));
// Buat UID dari kombinasi source + uptime
uint32_t uid = (packetRx.source_id << 24) |
(packetRx.sensor_uptime & 0xFFFFFF);
if (sudahDilihat(uid)) {
Serial.printf("[SKIP] Paket dari node %d sudah dilihat\n",
packetRx.source_id);
return;
}
tandaiDilihat(uid);
Serial.printf("[RX] Dari Node %d | Hop: %d/%d | Suhu: %.1fΒ°C\n",
packetRx.source_id, packetRx.hop_count, packetRx.ttl,
packetRx.suhu);
// Teruskan jika ini relay dan TTL masih tersisa
if (IS_RELAY && packetRx.hop_count < packetRx.ttl) {
packetRx.hop_count++;
esp_err_t result = esp_now_send(
nextHopMAC,
(uint8_t *) &packetRx,
sizeof(packetRx)
);
Serial.printf("[RELAY] Diteruskan ke gateway (hop %d/%d) - %s\n",
packetRx.hop_count, packetRx.ttl,
result == ESP_OK ? "OK" : "GAGAL");
} else if (!IS_RELAY || packetRx.hop_count >= packetRx.ttl) {
Serial.println("[GATEWAY] Data diproses di gateway");
// Di sini bisa kirim ke WiFi/MQTT/internet
}
}
void setup() {
Serial.begin(115200);
WiFi.mode(WIFI_STA);
WiFi.disconnect();
if (esp_now_init() != ESP_OK) {
Serial.println("[ERROR] ESP-NOW init gagal!");
ESP.restart();
}
esp_now_register_recv_cb(onReceive);
// Jika relay, daftarkan peer berikutnya
if (IS_RELAY) {
esp_now_peer_info_t peerInfo = {};
memcpy(peerInfo.peer_addr, nextHopMAC, 6);
peerInfo.channel = 1;
peerInfo.encrypt = false;
esp_now_add_peer(&peerInfo);
Serial.printf("[OK] Relay Node %d siap -> Gateway\n", NODE_ID);
} else {
Serial.printf("[OK] Gateway Node %d siap\n", NODE_ID);
}
}
void loop() {
delay(1000);
}
Implementasi mesh sederhana di atas menggunakan flood forwarding dengan deduplikasi. Untuk jaringan mesh yang lebih besar dan kompleks, pertimbangkan menggunakan library khusus seperti painlessMesh atau ESP-NOW ESP-MESH dari Espressif yang menyediakan routing otomatis dan self-healing.
8. Proyek: Jaringan Sensor Nirkabel (Wireless Sensor Network)
Sekarang kita akan membangun proyek lengkap: jaringan sensor nirkabel menggunakan ESP-NOW. Sistem ini terdiri dari beberapa node sensor yang mengirim data suhu dan kelembaban ke satu gateway, yang kemudian meneruskan data ke MQTT broker via WiFi.
Arsitektur Sistem
ββββββββββββββββ ESP-NOW (2.4 GHz) βββββββββββββββββββ
β β
β ββββββββββββ ββββββββββββ ββββββββββββ β
β β Node 1 β β Node 2 β β Node 3 β β
β β DHT11 β β DHT11 β β BMP280 β β
β β + LED β β + LED β β + LED β β
β ββββββ¬ββββββ ββββββ¬ββββββ ββββββ¬ββββββ β
β β β β β
βββββββββΌβββββββββββββββΌβββββββββββββββΌβββββββββββββββββ
β β β
βΌ βΌ βΌ
βββββββββββββββββββββββββββββββββββββββββββ
β GATEWAY ESP32 β
β βββββββββββββββ βββββββββββββββββββ β
β β ESP-NOW β β WiFi β β
β β Receiver ββββ MQTT Publisher β β
β βββββββββββββββ ββββββββββ¬βββββββββ β
β β β
ββββββββββββββββββββββββββββββΌββββββββββββββ
β Internet
βΌ
βββββββββββββββ
β MQTT Broker β
β (Mosquitto) β
ββββββββ¬βββββββ
β
ββββββββββΌβββββββββ
βΌ βΌ βΌ
ββββββββ ββββββββ ββββββββ
β Grafanaβ β App β βEmail β
ββββββββ ββββββββ ββββββββ
Komponen yang Dibutuhkan
- 4x ESP32 DevKit (1 gateway + 3 node sensor)
- 3x Sensor DHT11 (atau DHT22)
- 3x Resistor 10kΞ© (pull-up untuk DHT)
- 3x LED + Resistor 220Ξ© (indikator status)
- Breadboard dan kabel jumper
- Power supply USB untuk masing-masing node
Program Node Sensor
// ==================================================
// Proyek WSN: Node Sensor dengan ESP-NOW
// Kirim data suhu & kelembaban ke gateway
// BeebaneLabs - https://beebanelabs.pages.dev
// ==================================================
#include "esp_now.h"
#include "WiFi.h"
#include "DHT.h"
// === KONFIGURASI NODE ===
#define NODE_ID 1 // Ubah: 1, 2, atau 3
#define NODE_NAME "Ruang Tamu" // Nama lokasi sensor
#define DHT_PIN 4 // GPIO sensor DHT
#define DHT_TYPE DHT11
#define LED_PIN 2 // LED indikator
#define INTERVAL_KIRIM 15000 // Kirim setiap 15 detik
// MAC Address Gateway (GANTI DENGAN YANG BENAR!)
uint8_t gatewayMAC[] = {0xA4, 0xCF, 0x12, 0xE8, 0x45, 0xA0};
// === Struct Data ===
typedef struct struct_wsn_data {
uint8_t node_id;
char node_name[16];
float suhu;
float kelembaban;
float heat_index;
int8_t wifi_rssi;
uint32_t uptime;
uint32_t packet_num;
} struct_wsn_data;
struct_wsn_data dataSensor;
DHT dht(DHT_PIN, DHT_TYPE);
// Statistik
uint32_t totalKirim = 0;
uint32_t totalBerhasil = 0;
unsigned long waktuTerakhir = 0;
void kedipLED(int kali, int delayMs) {
for (int i = 0; i < kali; i++) {
digitalWrite(LED_PIN, HIGH);
delay(delayMs);
digitalWrite(LED_PIN, LOW);
delay(delayMs);
}
}
void onSent(const uint8_t *mac_addr, esp_now_send_status_t status) {
if (status == ESP_NOW_SEND_SUCCESS) {
totalBerhasil++;
kedipLED(1, 100);
}
float pdr = totalKirim > 0 ?
(float)totalBerhasil / totalKirim * 100.0 : 0;
Serial.printf("[SEND #%lu] %s | PDR: %.1f%%\n",
totalKirim,
status == ESP_NOW_SEND_SUCCESS ? "OK" : "GAGAL",
pdr);
}
void setup() {
Serial.begin(115200);
pinMode(LED_PIN, OUTPUT);
dht.begin();
WiFi.mode(WIFI_STA);
WiFi.disconnect();
if (esp_now_init() != ESP_OK) {
Serial.println("[FATAL] ESP-NOW init gagal!");
while (true) { kedipLED(5, 100); delay(1000); }
}
esp_now_register_send_cb(onSent);
esp_now_peer_info_t peerInfo = {};
memcpy(peerInfo.peer_addr, gatewayMAC, 6);
peerInfo.channel = 1;
peerInfo.encrypt = false;
if (esp_now_add_peer(&peerInfo) != ESP_OK) {
Serial.println("[FATAL] Gagal daftar gateway!");
while (true) { kedipLED(10, 50); delay(1000); }
}
strcpy(dataSensor.node_name, NODE_NAME);
dataSensor.node_id = NODE_ID;
Serial.println("βββββββββββββββββββββββββββββββββββββββββ");
Serial.printf( "β WSN Node #%d: %-18s β\n", NODE_ID, NODE_NAME);
Serial.println("β BeebaneLabs ESP-NOW Sensor Node β");
Serial.println("βββββββββββββββββββββββββββββββββββββββββ");
kedipLED(3, 200);
}
void loop() {
unsigned long sekarang = millis();
if (sekarang - waktuTerakhir >= INTERVAL_KIRIM) {
waktuTerakhir = sekarang;
totalKirim++;
// Baca sensor
dataSensor.suhu = dht.readTemperature();
dataSensor.kelembaban = dht.readHumidity();
dataSensor.uptime = sekarang / 1000;
dataSensor.packet_num = totalKirim;
dataSensor.wifi_rssi = 0; // Placeholder
// Validasi
if (isnan(dataSensor.suhu)) {
dataSensor.suhu = -99.0;
dataSensor.kelembaban = -99.0;
}
dataSensor.heat_index = dht.computeHeatIndex(
dataSensor.suhu, dataSensor.kelembaban, false);
// Kirim ke gateway
esp_err_t result = esp_now_send(
gatewayMAC,
(uint8_t *) &dataSensor,
sizeof(dataSensor)
);
Serial.printf("[%lu] %s | %.1fΒ°C | %.1f%% | HI: %.1fΒ°C\n",
totalKirim, NODE_NAME, dataSensor.suhu,
dataSensor.kelembaban, dataSensor.heat_index);
}
}
Program Gateway (Penerima + MQTT)
// ==================================================
// Proyek WSN: Gateway ESP-NOW -> MQTT
// Terima data dari semua node, kirim ke MQTT
// BeebaneLabs - https://beebanelabs.pages.dev
// ==================================================
#include "esp_now.h"
#include "WiFi.h"
#include "PubSubClient.h"
// === WiFi Config ===
const char* WIFI_SSID = "NamaWiFiAnda";
const char* WIFI_PASS = "PasswordWiFi";
// === MQTT Config ===
const char* MQTT_SERVER = "test.mosquitto.org";
const int MQTT_PORT = 1883;
const char* MQTT_TOPIC = "wsn/gateway/data";
const char* CLIENT_ID = "wsn_gateway_01";
// === Struct (sama dengan node) ===
typedef struct struct_wsn_data {
uint8_t node_id;
char node_name[16];
float suhu;
float kelembaban;
float heat_index;
int8_t wifi_rssi;
uint32_t uptime;
uint32_t packet_num;
} struct_wsn_data;
WiFiClient espClient;
PubSubClient mqtt(espClient);
// Buffer untuk data dari multiple node
struct_wsn_data nodeData[10]; // Max 10 node
bool nodeOnline[10] = {false};
void setupWiFi() {
WiFi.mode(WIFI_STA);
WiFi.begin(WIFI_SSID, WIFI_PASS);
Serial.print("WiFi: Connecting");
int attempts = 0;
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
if (++attempts > 40) {
Serial.println(" GAGAL!");
ESP.restart();
}
}
Serial.printf(" OK! IP: %s\n", WiFi.localIP().toString().c_str());
}
void hubungkanMQTT() {
while (!mqtt.connected()) {
Serial.print("MQTT: Connecting...");
if (mqtt.connect(CLIENT_ID)) {
Serial.println(" OK!");
} else {
Serial.printf(" FAIL (rc=%d)\n", mqtt.state());
delay(3000);
}
}
}
void onReceive(const uint8_t *mac_addr, const uint8_t *data, int len) {
if (len != sizeof(struct_wsn_data)) {
Serial.printf("[WARN] Ukuran data tidak sesuai: %d\n", len);
return;
}
struct_wsn_data rxData;
memcpy(&rxData, data, sizeof(rxData));
// Simpan data
if (rxData.node_id < 10) {
nodeData[rxData.node_id] = rxData;
nodeOnline[rxData.node_id] = true;
}
// Format MAC
char macStr[18];
snprintf(macStr, sizeof(macStr), "%02X:%02X:%02X:%02X:%02X:%02X",
mac_addr[0], mac_addr[1], mac_addr[2],
mac_addr[3], mac_addr[4], mac_addr[5]);
Serial.printf("[RX] Node %d (%s) | %.1fΒ°C | %.1f%% | #%lu\n",
rxData.node_id, rxData.node_name, rxData.suhu,
rxData.kelembaban, rxData.packet_num);
// Kirim ke MQTT
if (mqtt.connected()) {
char payload[256];
snprintf(payload, sizeof(payload),
"{\"node_id\":%d,\"node_name\":\"%s\","
"\"suhu\":%.1f,\"kelembaban\":%.1f,"
"\"heat_index\":%.1f,\"uptime\":%lu,"
"\"packet\":%lu,\"mac\":\"%s\"}",
rxData.node_id, rxData.node_name,
rxData.suhu, rxData.kelembaban,
rxData.heat_index, rxData.uptime,
rxData.packet_num, macStr);
// Publish ke topik per-node
char topic[64];
snprintf(topic, sizeof(topic), "wsn/node/%d/data", rxData.node_id);
if (mqtt.publish(topic, payload)) {
Serial.printf("[MQTT] Published ke %s\n", topic);
} else {
Serial.println("[MQTT] Gagal publish!");
}
// Juga publish ke topik utama
mqtt.publish(MQTT_TOPIC, payload);
}
}
void setup() {
Serial.begin(115200);
Serial.println("βββββββββββββββββββββββββββββββββββββββββ");
Serial.println("β WSN Gateway - ESP-NOW -> MQTT β");
Serial.println("β BeebaneLabs β");
Serial.println("βββββββββββββββββββββββββββββββββββββββββ");
setupWiFi();
mqtt.setServer(MQTT_SERVER, MQTT_PORT);
mqtt.setBufferSize(512);
hubungkanMQTT();
// Inisialisasi ESP-NOW (setelah WiFi)
if (esp_now_init() != ESP_OK) {
Serial.println("[FATAL] ESP-NOW init gagal!");
ESP.restart();
}
esp_now_register_recv_cb(onReceive);
Serial.println("[OK] Gateway siap menerima data dari node sensor!");
}
void loop() {
if (!mqtt.connected()) {
hubungkanMQTT();
}
mqtt.loop();
// Status check setiap 30 detik
static unsigned long lastStatus = 0;
if (millis() - lastStatus > 30000) {
lastStatus = millis();
Serial.println("βββββββββββββββββββββββββββββββββββ");
Serial.println(" STATUS NODE AKTIF ");
for (int i = 0; i < 10; i++) {
if (nodeOnline[i]) {
Serial.printf(" Node %d: %-14s | %.1fΒ°C | %.1f%%\n",
nodeData[i].node_id, nodeData[i].node_name,
nodeData[i].suhu, nodeData[i].kelembaban);
}
}
Serial.printf(" Heap bebas: %lu bytes\n", ESP.getFreeHeap());
Serial.println("βββββββββββββββββββββββββββββββββββ");
}
}
Untuk produksi, tambahkan fitur: 1) Deep sleep pada node sensor untuk menghemat baterai, 2) Auto-discovery node baru, 3) Dashboard Grafana untuk visualisasi, 4) Alert Telegram saat suhu melewati threshold, 5) OTA update firmware via ESP-NOW.
9. Quiz: Uji Pemahamanmu!
Setelah membaca tutorial di atas, jawablah 5 pertanyaan berikut untuk menguji pemahamanmu tentang ESP-NOW: