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.
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:
flowchart TB
Send["esp_now_send()"] -->|"MAC + Data"| Recv["ESP-NOW Protocol"]
Recv --> CB["onReceive() Callback"]
CB --> Parse["Parse Struct"]
Parse --> Process["Process Data"]
Process --> Act["Aksi: LED, Motor, dll"]
style Send fill:#0f2a1f,stroke:#3ecf8e,stroke-width:1.5px
style Recv fill:#0f1d3a,stroke:#60a5fa,stroke-width:1.5px
style CB fill:#2a1f0a,stroke:#f59e0b,stroke-width:1.5px
style Act fill:#2a0f1e,stroke:#f472b6
Program Pengirim (Sender)
flowchart TB
GW["đ Gateway Node"]
GW --> N1["Node 1"]
GW --> N2["Node 2"]
GW --> N3["Node 3"]
N1 <-->|"Relay"| N2
N2 <-->|"Relay"| N3
N1 <-->|"Direct"| N3
style GW fill:#2a1f0a,stroke:#f59e0b,stroke-width:2px
style N1 fill:#0f2a1f,stroke:#3ecf8e
style N2 fill:#0f1d3a,stroke:#60a5fa
style N3 fill:#1a0f2e,stroke:#a78bfa
flowchart TB
subgraph nodes["đĄ Sensor Nodes"]
N1["Node 1\nDHT11"]
N2["Node 2\nBMP280"]
N3["Node 3\nLDR"]
end
nodes -->|"ESP-NOW"| GW["Gateway ESP32"]
GW -->|"WiFi"| MQTT["MQTT Broker\n(Mosquitto)"]
MQTT --> DB["InfluxDB"]
DB --> Grafana["đ Grafana\nDashboard"]
style nodes fill:#0f2a1f,stroke:#3ecf8e,stroke-width:2px
style GW fill:#2a1f0a,stroke:#f59e0b,stroke-width:2px
style MQTT fill:#0f1d3a,stroke:#60a5fa
style DB fill:#1a0f2e,stroke:#a78bfa
style Grafana fill:#2a0f1e,stroke:#f472b6,stroke-width:2px
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: