ESP32 & ESP8266

ESP-NOW: Komunikasi Nirkabel Antar ESP32

TOKEN

Pelajari cara ESP32 berkomunikasi langsung satu sama lain tanpa router WiFi menggunakan protokol ESP-NOW β€” dari dasar hingga proyek jaringan sensor nirkabel

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.

Diagram: Perbandingan WiFi Tradisional vs ESP-NOW
  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
Frekuesi2.4 GHz (ISM Band)
Maksimum Payload250 byte per paket
Peer Maksimum20 perangkat terdaftar
Latensi~2 milidetik
Jarak Efektif~200m (open air), ~30m (indoor)
EnkripsiAES-128 (opsional, 6 peer terenkripsi)
Kecepatan Data1 Mbps (PHY rate)
KompatibilitasESP32, ESP8266, ESP32-S2, ESP32-S3, ESP32-C3
Koneksi WiFiBisa dijalankan bersamaan dengan WiFi STA
πŸ’‘ Tips

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.

⚑
ESP-NOW
Peer-to-Peer
  • βœ… Tanpa Router
  • βœ… Latensi ~2ms
  • βœ… Sangat Hemat Daya
  • βœ… Payload 250 byte
  • βœ… Setup Instan
  • ❌ Range Terbatas
  • ❌ Tidak ada IP/TCP
Cepat & Ringan
πŸ“Ά
WiFi TCP/IP
Client-Server
  • ❌ Perlu Router
  • ❌ Latensi ~10-50ms
  • ❌ Konsumsi Daya Tinggi
  • βœ… Payload Tak Terbatas
  • ❌ Setup Kompleks
  • βœ… Range Lebih Jauh
  • βœ… Kompatibel HTTP/MQTT
Universal
πŸ“‘
LoRa
Long Range
  • βœ… Tanpa Router
  • ❌ Latensi Tinggi
  • βœ… Sangat Hemat Daya
  • ❌ Payload ~50 byte
  • ❌ Perlu Modul Tambahan
  • βœ… Range 10+ km
  • ❌ Butuh LoRa Module
Jarak Jauh

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:

C++ β€” get_mac_address.ino
// 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
}
Output Serial Monitor: ================================ MAC Address: A4:CF:12:E8:45:A0 ================================ Catat MAC address ini! Ini dibutuhkan untuk mendaftarkan peer ESP-NOW.
⚠️ Peringatan

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

C++ β€” espnow_peer_setup.ino
// 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
}
ℹ️ Informasi Penting

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.

Diagram: Alur Komunikasi ESP-NOW
  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)

C++ β€” sender.ino
// 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)

C++ β€” receiver.ino
// 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);
}
Output Serial Monitor (Receiver): ╔══════════════════════════════╗ β•‘ ESP-NOW Receiver Siap! β•‘ β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β• Menunggu data... ══════════════════════════════════ Dari : A4:CF:12:E8:45:A0 Paket ke : 1 Suhu : 28.5 Β°C Kelembaban: 72.0 % Uptime : 5 detik Total Rx : 1 | Error: 0 ══════════════════════════════════
πŸ’‘ Tips

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.

ℹ️ Perbedaan Unicast vs Broadcast

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.

C++ β€” broadcast_sender.ino
// 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);
}
C++ β€” broadcast_receiver.ino
// 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);
}
⚠️ Peringatan Broadcast

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.

ℹ️ Batasan Enkripsi ESP-NOW

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.

Diagram: Enkripsi ESP-NOW
  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
C++ β€” encrypted_espnow.ino
// 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);
}
πŸ’‘ Tips Keamanan

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 AntennaPCB antenna: ~100m, External: ~200m+Gunakan modul dengan antenna eksternal (IPEX/U.FL)
LingkunganIndoor: ~30m, Outdoor: ~200mHindari tembok tebal dan logam
Tinggi AntennaSemakin tinggi, semakin jauhPasang di tempat tinggi, hindari lantai
InterferensiWiFi, Bluetooth, microwaveGunakan channel yang sepi (channel 1, 6, atau 11)
Daya TransmitDefault 20dBm, bisa diaturTambahkan esp_wifi_set_max_tx_power()
Kecepatan DataLebih lambat = lebih jauhESP-NOW fixed di 1 Mbps (tidak bisa diubah)

Program Range Test

C++ β€” range_test.ino
// 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
}
πŸ’‘ Tips Range Testing

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.

Diagram: Topologi Mesh ESP-NOW
  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.
C++ β€” mesh_relay.ino
// 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);
}
⚠️ Peringatan Mesh

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

Diagram: Arsitektur Jaringan Sensor Nirkabel
  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ 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

Program Node Sensor

C++ β€” node_sensor.ino
// ==================================================
// 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)

C++ β€” gateway_wsn.ino
// ==================================================
// 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("═══════════════════════════════════");
  }
}
Output Serial Monitor (Gateway): ╔═══════════════════════════════════════╗ β•‘ WSN Gateway - ESP-NOW -> MQTT β•‘ β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β• WiFi: Connecting... OK! IP: 192.168.1.100 MQTT: Connecting... OK! [OK] Gateway siap menerima data dari node sensor! [RX] Node 1 (Ruang Tamu) | 28.5Β°C | 72.0% | #1 [MQTT] Published ke wsn/node/1/data [RX] Node 2 (Gudang) | 31.2Β°C | 80.5% | #1 [MQTT] Published ke wsn/node/2/data [RX] Node 3 (Kebun) | 29.8Β°C | 65.3% | #1 [MQTT] Published ke wsn/node/3/data
πŸ’‘ Tips Pengembangan

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:

Pertanyaan 1: Berapa maksimum payload (data) yang bisa dikirim dalam satu paket ESP-NOW?

a) 128 byte
b) 250 byte
c) 512 byte
d) 1024 byte

Pertanyaan 2: Untuk mengirim data ke semua perangkat ESP-NOW dalam range (broadcast), MAC address apa yang digunakan?

a) 00:00:00:00:00:00
b) FF:FF:FF:FF:FF:FF
c) AA:BB:CC:DD:EE:FF
d) ESP-NOW tidak mendukung broadcast

Pertanyaan 3: Berapa maksimum peer terenkripsi yang bisa didaftarkan pada satu ESP32?

a) 4 peer
b) 6 peer
c) 10 peer
d) 20 peer

Pertanyaan 4: Apa yang WAJIB dilakukan di KEDUA ESP32 agar komunikasi ESP-NOW unicast berhasil?

a) Menggunakan SSID WiFi yang sama
b) Saling mendaftarkan MAC address sebagai peer
c) Menginstal library MQTT yang sama
d) Menggunakan sensor yang identik

Pertanyaan 5: Keunggulan utama ESP-NOW dibanding WiFi TCP/IP biasa adalah...

a) Payload tidak terbatas dan bisa mengirim file besar
b) Bisa terhubung ke internet langsung tanpa router
c) Latensi sangat rendah dan tidak perlu router WiFi
d) Mendukung komunikasi jarak 10+ km
← Sebelumnya Panduan Lengkap ESP32 Selanjutnya β†’ Artikel Lainnya