1. Apa itu LoRa & LPWAN?
LoRa (Long Range) adalah teknologi komunikasi nirkabel spread spectrum yang dirancang untuk mengirim data dalam jarak sangat jauh dengan konsumsi daya sangat rendah. LoRa merupakan salah satu implementasi dari keluarga teknologi LPWAN (Low Power Wide Area Network) yang sangat populer di dunia IoT.
Kelebihan utama LoRa dibanding teknologi nirkabel lain:
- Jangkauan Luas â Bisa mencapai 15+ km di area pedesaan (line-of-sight) dan 2-5 km di area perkotaan
- Konsumsi Daya Rendah â Perangkat sensor bisa bertahun-tahun dengan baterai coin cell CR2032
- Biaya Rendah â Modul LoRa tersedia mulai dari Rp 30.000-an
- Topologi Fleksibel â Mendukung star, mesh, dan point-to-point
- Tidak Perlu Lisensi â Menggunakan frekuensi ISM (Industrial, Scientific, Medical) yang bebas lisensi
LoRa bekerja pada frekuensi ISM yang berbeda tergantung region. Di Indonesia, frekuensi yang umum digunakan adalah 920-923 MHz (AS923). Di Eropa menggunakan 868 MHz dan di Amerika 915 MHz.
| Region | Frekuensi | Bandwidth | Keterangan |
|---|---|---|---|
| Indonesia / Asia Tenggara | 920-923 MHz (AS923) | 125 kHz | Standar yang berlaku di ID |
| Eropa | 863-870 MHz (EU868) | 125 kHz | Duty cycle 1% |
| Amerika Utara | 902-928 MHz (US915) | 125 kHz | Frequency hopping |
| Australia | 915-928 MHz (AU915) | 125 kHz | Mirip US915 |
2. Komponen Hardware yang Dibutuhkan
Untuk membangun sistem jaringan sensor LoRa, kita membutuhkan komponen-komponen berikut. Pastikan modul LoRa yang Anda beli sesuai dengan frekuensi wilayah Indonesia (920 MHz / AS923).
Modul Transceiver LoRa
Modul LoRa yang paling populer di pasaran menggunakan chip Semtech SX1276 (sub-GHz) atau SX1278 (433/470 MHz). Kedua chip ini mendukung LoRa dan FSK modulation.
| Komponen | Spesifikasi | Fungsi | Harga (Est.) |
|---|---|---|---|
| ESP32 DevKit | 240MHz, WiFi+BT, 4MB Flash | Controller utama | Rp 45.000 - 80.000 |
| Modul LoRa SX1276 | 920 MHz, SPI interface, +20dBm | Transceiver LoRa | Rp 35.000 - 65.000 |
| Antena 920 MHz | Îŧ/4 monopole / dipole | Memperkuat sinyal RF | Rp 10.000 - 25.000 |
| Sensor DHT22 | Suhu: -40~80°C, RH: 0-100% | Pembacaan suhu & kelembaban | Rp 20.000 - 35.000 |
| Protoboard + Kabel | 400 lubang, jumper wire M-M/M-F | Prototype sirkuit | Rp 15.000 - 30.000 |
| Power Supply | 3.3V-5V, LiPo / USB / Battery | Sumber daya node | Rp 10.000 - 50.000 |
Jangan pernah menghidupkan modul LoRa tanpa antena terpasang! Tanpa antena, energi RF yang dipantulkan kembali ke chip transmitter bisa merusak modul secara permanen. Selalu pastikan antena terhubung sebelum power on.
Koneksi Pin ESP32 â SX1276
ESP32 SX1276 Module âââââ âââââââââââââ GPIO 5 (SCK) âââ SCK GPIO 23 (MOSI) âââ MOSI GPIO 19 (MISO) âââ MISO GPIO 18 (NSS) âââ NSS (CS) GPIO 26 (RST) âââ RST GPIO 34 (DIO0) âââ DIO0 (Interrupt) 3.3V âââ VCC GND âââ GND
Pin DIO0 pada modul SX1276 digunakan sebagai interrupt ketika transmisi atau penerimaan data selesai. Gunakan pin GPIO yang mendukung interrupt di ESP32. GPIO 34-39 adalah input-only, cocok untuk DIO0.
3. Setup LoRa Sender (Sensor Node)
Sensor node akan membaca data suhu dan kelembaban dari sensor DHT22, lalu mengirimkannya via LoRa ke receiver/gateway. Node ini akan dalam deep sleep untuk menghemat daya.
Library yang Dibutuhkan
Instal library berikut melalui Arduino IDE Library Manager:
- Sandeep Mistry's LoRa â Library utama LoRa untuk Arduino/ESP32
- DHT sensor library oleh Adafruit â Untuk membaca sensor DHT22
#include <SPI.h>
#include <LoRa.h>
#include <DHT.h>
// ââ Pin LoRa ââ
#define LORA_SCK 5
#define LORA_MISO 19
#define LORA_MOSI 23
#define LORA_NSS 18
#define LORA_RST 26
#define LORA_DIO0 34
// ââ Pin Sensor ââ
#define DHT_PIN 4
#define DHT_TYPE DHT22
// ââ Konfigurasi LoRa ââ
#define LORA_FREQ 920.0E6 // 920 MHz untuk Indonesia
#define LORA_SF 7 // Spreading Factor 7-12
#define LORA_BW 125E3 // Bandwidth 125 kHz
#define LORA_CR 5 // Coding Rate 4/5
#define LORA_TX_POWER 17 // TX Power dalam dBm (max 20)
// ââ Variabel Global ââ
DHT dht(DHT_PIN, DHT_TYPE);
unsigned long nodeId = 1001;
unsigned long packetCounter = 0;
void setup() {
Serial.begin(115200);
while (!Serial);
Serial.println(F("=== LoRa Sender Node ==="));
// Inisialisasi DHT sensor
dht.begin();
// Inisialisasi SPI dan LoRa
SPI.begin(LORA_SCK, LORA_MISO, LORA_MOSI, LORA_NSS);
LoRa.setPins(LORA_NSS, LORA_RST, LORA_DIO0);
if (!LoRa.begin(LORA_FREQ)) {
Serial.println(F("[ERROR] Gagal memulai LoRa!"));
while (1);
}
// Konfigurasi parameter LoRa
LoRa.setSpreadingFactor(LORA_SF);
LoRa.setSignalBandwidth(LORA_BW);
LoRa.setCodingRate4(LORA_CR);
LoRa.setTxPower(LORA_TX_POWER);
LoRa.enableCrc();
Serial.println(F("[OK] LoRa terinisialisasi pada 920 MHz"));
Serial.printf("[INFO] SF=%d, BW=%d kHz, CR=4/%d\n",
LORA_SF, (int)(LORA_BW/1000), LORA_CR);
}
void loop() {
// Baca sensor
float suhu = dht.readTemperature();
float kelembaban = dht.readHumidity();
// Validasi pembacaan sensor
if (isnan(suhu) || isnan(kelembaban)) {
Serial.println(F("[WARN] Gagal membaca DHT22, retry..."));
delay(2000);
return;
}
// Bangun payload
packetCounter++;
String payload = buildPayload(suhu, kelembaban);
// Kirim via LoRa
Serial.printf("[TX] Paket #%lu: %s\n", packetCounter, payload.c_str());
LoRa.beginPacket();
LoRa.print(payload);
LoRa.endPacket();
Serial.printf("[TX] Terkirim! Suhu=%.1f°C, RH=%.1f%%\n", suhu, kelembaban);
// Delay sebelum pengiriman berikutnya
delay(30000); // Kirim setiap 30 detik
}
String buildPayload(float suhu, float kelembaban) {
// Format: NODE_ID|PACKET_NUM|SUHU|KELEMBABAN|RSSI
String payload = "";
payload += String(nodeId);
payload += "|" + String(packetCounter);
payload += "|" + String(suhu, 1);
payload += "|" + String(kelembaban, 1);
payload += "|" + String(millis() / 1000);
return payload;
}
4. Setup LoRa Receiver (Gateway)
Receiver/gateway akan menerima data dari semua sensor node via LoRa, lalu menampilkannya di Serial Monitor dan menyiapkan data untuk dikirim ke web server via WiFi. Gateway bertugas sebagai jembatan antara jaringan LoRa dan jaringan internet.
#include <SPI.h>
#include <LoRa.h>
#include <WiFi.h>
#include <WebServer.h>
// ââ Pin LoRa ââ
#define LORA_SCK 5
#define LORA_MISO 19
#define LORA_MOSI 23
#define LORA_NSS 18
#define LORA_RST 26
#define LORA_DIO0 34
// ââ Konfigurasi WiFi ââ
const char* WIFI_SSID = "NamaWiFi";
const char* WIFI_PASS = "PasswordWiFi";
// ââ Konfigurasi LoRa ââ
#define LORA_FREQ 920.0E6
#define LORA_SF 7
// ââ Variabel Global ââ
WebServer server(80);
String lastPayload = "";
String lastSenderInfo = "";
unsigned long rxCount = 0;
// ââ Buffer data sensor (max 10 node) ââ
struct SensorData {
unsigned long nodeId;
unsigned long packetNum;
float suhu;
float kelembaban;
int rssi;
unsigned long uptime;
unsigned long lastUpdate;
};
SensorData nodes[10];
int nodeCount = 0;
void setup() {
Serial.begin(115200);
Serial.println(F("=== LoRa Gateway ==="));
// Inisialisasi LoRa
SPI.begin(LORA_SCK, LORA_MISO, LORA_MOSI, LORA_NSS);
LoRa.setPins(LORA_NSS, LORA_RST, LORA_DIO0);
if (!LoRa.begin(LORA_FREQ)) {
Serial.println(F("[ERROR] Gagal memulai LoRa!"));
while (1);
}
LoRa.setSpreadingFactor(LORA_SF);
LoRa.setSignalBandwidth(125E3);
LoRa.enableCrc();
// Koneksi WiFi
WiFi.begin(WIFI_SSID, WIFI_PASS);
Serial.printf("[WiFi] Menghubungkan ke %s", WIFI_SSID);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.printf("\n[WiFi] Terhubung! IP: %s\n",
WiFi.localIP().toString().c_str());
// Setup Web Server
server.on("/", handleRoot);
server.on("/api/data", handleApiData);
server.begin();
Serial.println("[HTTP] Web server aktif di port 80");
Serial.println(F("[OK] Gateway siap menerima data LoRa\n"));
}
void loop() {
// Cek paket LoRa masuk
int packetSize = LoRa.parsePacket();
if (packetSize) {
String incoming = "";
while (LoRa.available()) {
incoming += (char)LoRa.read();
}
int rssi = LoRa.packetRssi();
float snr = LoRa.packetSnr();
rxCount++;
Serial.printf("[RX] #%lu | RSSI: %d dBm | SNR: %.1f dB\n",
rxCount, rssi, snr);
Serial.printf("[RX] Payload: %s\n", incoming.c_str());
// Parse payload
parsePayload(incoming, rssi);
}
// Handle HTTP client
server.handleClient();
}
void parsePayload(String payload, int rssi) {
// Format: NODE_ID|PACKET_NUM|SUHU|KELEMBABAN|UPTIME
int idx = 0;
String parts[5];
int partCount = 0;
while (payload.indexOf('|') >= 0 && partCount < 5) {
idx = payload.indexOf('|');
parts[partCount++] = payload.substring(0, idx);
payload = payload.substring(idx + 1);
}
if (partCount < 5) parts[partCount++] = payload;
if (partCount >= 5) {
unsigned long nId = parts[0].toInt();
SensorData* node = findOrCreateNode(nId);
node->nodeId = nId;
node->packetNum = parts[1].toInt();
node->suhu = parts[2].toFloat();
node->kelembaban = parts[3].toFloat();
node->uptime = parts[4].toInt();
node->rssi = rssi;
node->lastUpdate = millis();
lastPayload = payload;
Serial.printf("[PARSE] Node %lu: Suhu=%.1f°C RH=%.1f%% RSSI=%d\n",
nId, node->suhu, node->kelembaban, rssi);
}
}
SensorData* findOrCreateNode(unsigned long nodeId) {
for (int i = 0; i < nodeCount; i++) {
if (nodes[i].nodeId == nodeId) return &nodes[i];
}
if (nodeCount < 10) {
nodes[nodeCount].nodeId = nodeId;
return &nodes[nodeCount++];
}
return &nodes[0];
}
void handleRoot() {
String html = "<html><body style='font-family:monospace;background:#111;color:#0f0;padding:20px'>";
html += "<h1>ðĄ LoRa Gateway Dashboard</h1>";
html += "<p>Total diterima: " + String(rxCount) + " paket</p>";
html += "<hr>";
for (int i = 0; i < nodeCount; i++) {
html += "<div style='margin:10px 0;padding:10px;border:1px solid #333'>";
html += "<b>Node " + String(nodes[i].nodeId) + "</b><br>";
html += "Suhu: " + String(nodes[i].suhu, 1) + " °C<br>";
html += "Kelembaban: " + String(nodes[i].kelembaban, 1) + " %<br>";
html += "RSSI: " + String(nodes[i].rssi) + " dBm<br>";
html += "</div>";
}
html += "</body></html>";
server.send(200, "text/html", html);
}
void handleApiData() {
String json = "{\"nodes\":[";
for (int i = 0; i < nodeCount; i++) {
if (i > 0) json += ",";
json += "{\"id\":" + String(nodes[i].nodeId);
json += ",\"suhu\":" + String(nodes[i].suhu, 1);
json += ",\"kelembaban\":" + String(nodes[i].kelembaban, 1);
json += ",\"rssi\":" + String(nodes[i].rssi);
json += ",\"uptime\":" + String(nodes[i].uptime) + "}";
}
json += "],\"total_rx\":" + String(rxCount) + "}";
server.send(200, "application/json", json);
}
5. Protokol Data & Payload Format
Dalam tutorial ini kita menggunakan format payload teks sederhana yang dipisahkan dengan delimiter pipe (|). Format ini mudah di-debug dan cocok untuk prototyping. Untuk produksi, pertimbangkan format binary yang lebih efisien.
"1001|1|28.5|72.3|30"
â 25 byte
Untuk aplikasi yang lebih serius, pertimbangkan menggunakan protokol seperti CBOR (Concise Binary Object Representation) atau MessagePack untuk serialisasi data yang lebih ringkas.
LoRa memiliki batas panjang payload sekitar 256 byte per paket. Semakin pendek payload, semakin cepat transmisi dan semakin hemat daya. Hindari mengirim data teks berlebihan â kirim hanya nilai yang diperlukan.
6. Jarak & Faktor Spreading
Spreading Factor (SF) adalah parameter paling penting dalam LoRa yang menentukan trade-off antara jarak komunikasi dan data rate. SF yang lebih tinggi berarti sinyal lebih kuat (jangkauan lebih jauh) tetapi data rate lebih lambat.
SF7
SF8
SF9
SF10
SF11
SF12
Semakin tinggi SF, semakin lama waktu transmisi (Time on Air). Pada band AS923, perangkat harus mematuhi aturan duty cycle. Jika Time on Air terlalu lama, interval pengiriman harus diperpanjang untuk tetap mematuhi regulasi.
RSSI (Received Signal Strength Indicator) dan SNR (Signal-to-Noise Ratio) adalah metrik penting untuk mengevaluasi kualitas link LoRa:
- RSSI â Nilai negatif, semakin mendekati 0 semakin kuat sinyal. RSSI > -80 dBm = sangat baik, -80 s/d -110 dBm = cukup, < -120 dBm = lemah
- SNR â Rasio sinyal terhadap noise. SNR > 0 dB = bagus. LoRa bisa beroperasi bahkan dengan SNR negatif (down to -20 dB berkat forward error correction)
7. Monitoring Dashboard dengan Web Server
Setelah gateway menerima data LoRa, langkah selanjutnya adalah menampilkannya di dashboard yang bisa diakses dari browser. Kita bisa memanfakan ESP32 yang sudah terhubung WiFi sebagai web server, atau mengirim data ke platform cloud seperti MQTT, Grafana, atau ThingSpeak.
Dashboard Sederhana dengan ESP32 Web Server
ESP32 gateway yang sudah terkoneksi WiFi bisa langsung menjalankan web server. Di tutorial ini kita sudah menyertakan endpoint /api/data yang mengembalikan data JSON dari semua node.
Menghubungkan ke MQTT Broker
Untuk integrasi yang lebih robust, kirim data dari gateway ke MQTT broker agar bisa diproses oleh Node-RED, Grafana, atau aplikasi lainnya.
#include <PubSubClient.h>
// ââ Konfigurasi MQTT ââ
const char* MQTT_SERVER = "broker.hivemq.com";
const int MQTT_PORT = 1883;
const char* MQTT_TOPIC = "iothub/lora/nodes/";
WiFiClient espClient;
PubSubClient mqttClient(espClient);
void mqttConnect() {
while (!mqttClient.connected()) {
Serial.print("[MQTT] Menghubungkan...");
String clientId = "lora-gw-" + String(random(0xffff), HEX);
if (mqttClient.connect(clientId.c_str())) {
Serial.println(" terhubung!");
} else {
Serial.printf(" gagal (rc=%d). Retry...\n", mqttClient.state());
delay(5000);
}
}
}
void publishSensorData(SensorData &node) {
// Publish ke topik per-node
String topic = String(MQTT_TOPIC) + String(node.nodeId);
String json = "{";
json += "\"suhu\":" + String(node.suhu, 1) + ",";
json += "\"kelembaban\":" + String(node.kelembaban, 1) + ",";
json += "\"rssi\":" + String(node.rssi) + ",";
json += "\"uptime\":" + String(node.uptime) + ",";
json += "\"timestamp\":" + String(millis());
json += "}";
mqttClient.publish(topic.c_str(), json.c_str());
Serial.printf("[MQTT] Published ke %s\n", topic.c_str());
}
Arsitektur Sistem Lengkap
8. Tips Optimasi Jarak & Battery Life
Mengoptimalkan jangkauan dan masa pakai baterai adalah dua tantangan utama dalam deployment jaringan sensor LoRa. Berikut tips-tips yang sudah terbukti efektif:
Optimasi Jarak Komunikasi
- Gunakan antena yang tepat â Antena Îŧ/4 (â8 cm untuk 920 MHz) dengan ground plane yang baik bisa meningkatkan jangkauan signifikan
- Posisikan antena secara vertikal â Antena LoRa bekerja optimal dalam orientasi tegak (polarisasi vertikal)
- Tinggikan posisi node â Setiap meter kenaikan bisa menambah jangkauan secara proporsional. Pasang sensor di tiang atau dinding tinggi
- Tambah Spreading Factor â Naikkan dari SF7 ke SF10/11 untuk jarak lebih jauh, dengan trade-off data rate yang lebih lambat
- Kurangi bandwidth â Mengurangi BW dari 250 kHz ke 125 kHz meningkatkan sensitivitas receiver sebesar 3 dB
- Gunakan TX Power maksimum â Setting ke +20 dBm untuk range maksimum (tapi boros daya)
Optimasi Battery Life
- Deep Sleep â Gunakan
esp_deep_sleep()untuk mematikan semua modul kecuali RTC saat idle. ESP32 hanya butuh ~10 ΞA saat deep sleep - Kurangi frekuensi pengiriman â Kirim data setiap 5-10 menit bukan setiap detik. Untuk monitoring suhu, interval 1-5 menit sudah cukup
- Lower TX Power â Jika jarak node ke gateway dekat, kurangi TX power ke minimum yang dibutuhkan
- Nonaktifkan WiFi & Bluetooth â Di sensor node yang hanya mengirim via LoRa, matikan WiFi dan BT untuk hemat ~50 mA
- Tegangan operasi rendah â Operasikan modul LoRa pada 3.3V langsung dari regulator DC-DC yang efisien
#define SLEEP_DURATION 300 // 5 menit dalam detik
#define uS_TO_S_FACTOR 1000000ULL
void setup() {
Serial.begin(115200);
// Nonaktifkan WiFi & Bluetooth untuk hemat daya
WiFi.mode(WIFI_OFF);
btStop();
// ... inisialisasi LoRa dan baca sensor ...
// ... kirim data ...
Serial.printf("[SLEEP] Tidur selama %d detik...\n", SLEEP_DURATION);
Serial.flush();
// Masuk deep sleep
esp_sleep_enable_timer_wakeup(SLEEP_DURATION * uS_TO_S_FACTOR);
esp_deep_sleep_start();
}
void loop() {
// Tidak akan pernah sampai sini saat deep sleep
// Semua logic dijalankan di setup() lalu sleep
}
| Parameter | Mode Hemat Daya | Mode Performance |
|---|---|---|
| Spreading Factor | SF10-SF12 (jarak jauh) | SF7-SF8 (data rate tinggi) |
| TX Power | +10 dBm (hemat) | +20 dBm (jarak maks) |
| Interval Kirim | 5-10 menit | 10-30 detik |
| Payload Size | < 20 byte (binary) | < 50 byte (text) |
| WiFi (Gateway) | Connect per-kirim | Always connected |
Dengan konfigurasi deep sleep 5 menit dan pengiriman data ~100ms, node sensor bisa bertahan 1-2 tahun dengan baterai LiPo 3.7V 2000mAh. Gunakan rumus: Life = Capacity(mAh) / Average_Current(mA) untuk menghitung estimasi.
9. Uji Pemahaman: Quiz
Setelah membaca tutorial ini, uji pemahamanmu dengan menjawab 5 pertanyaan berikut: