From ca38c140080b89ea8eaf88e8545b7e3cccd498df Mon Sep 17 00:00:00 2001 From: Fauzan Date: Mon, 29 Jun 2026 12:31:40 +0700 Subject: [PATCH] Kode Program Arduino IDE untuk IoT --- node1_lora++_v2.ino | 437 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 437 insertions(+) create mode 100644 node1_lora++_v2.ino diff --git a/node1_lora++_v2.ino b/node1_lora++_v2.ino new file mode 100644 index 0000000..454017e --- /dev/null +++ b/node1_lora++_v2.ino @@ -0,0 +1,437 @@ +#include +#include +#include +#include "HX711.h" + +// ========================================== +// ========================================== +// PIN DEFINITIONS (LoRa SPI - Custom Layout) +// ========================================== +#define LORA_SCK 22 // D22 +#define LORA_MISO 21 // D21 +#define LORA_MOSI 19 // D19 +#define LORA_SS 18 // D18 +#define LORA_RST 23 // D23 +#define LORA_DIO0 34 // D34 (Sisi Kiri, Input-Only, Sangat Aman) + +// ========================================== +// PIN DEFINITIONS (SENSOR & LED - SISI KANAN ESP32) +// ========================================== +// SENSOR (Dipindah ke KIRI karena D15 & D2 adalah Strapping Pin) +#define TRIG_PIN 25 // D25 +#define ECHO_PIN 26 // D26 + +// LED & BUZZER (SISI KANAN - Aman) +#define LED_BLUE 17 // TX2 (D17) +#define LED_GREEN 16 // RX2 (D16) +#define LED_RED 4 // D4 +#define BUZZER_PIN 15 // D15 (Buzzer untuk indikator LoRa) + +// ========================================== +// PIN DEFINITIONS (LOADCELL - SISI KIRI ESP32) +// ========================================== +// Dipindah ke D32 & D33 karena D18 & D19 sekarang dipakai LoRa +#define HX711_DT 32 // D32 +#define HX711_SCK 33 // D33 + +// ========================================== +// SETTINGS +// ========================================== +#define MIN_DIST 2.0 +#define MAX_DIST 200.0 +#define TIMEOUT_MS 3000 + +// ========================================== +// KALIBRASI HC-SR04 +// ========================================== +#define TINGGI_ALAT 79.27 // Tinggi sensor dari lantai ke alas (cm) - diukur manual +float SENSOR_OFFSET = -4.94; // Offset koreksi hardware sensor (dari kalibrasi) +float TINGGI_OFFSET = 0.0; // Offset koreksi tambahan jika ada selisih pembacaan + +// ========================================== +// KALIBRASI LOAD CELL +// ========================================== +float calibration_factor = -103.8; // Dari hasil kalibrasi (loadcell_ultrasonik_kalibrasi.ino) +float weightThreshold = 2.0; // Ambang batas dianggap kosong (gram) + +unsigned long emptyStartTime = 0; +bool isEmptyScale = false; + +// ========================================== +// VARIABLES +// ========================================== +bool isConnected = false; +bool isBalitaSelected = false; +unsigned long lastHeartbeatTime = 0; +bool isLedLocked = false; +char jsonBuffer[800]; + +// Measuring Variables +float lastDistance = 0; +unsigned long lastChangeTime = 0; +unsigned long lastScaleRead = 0; + +// Worker Modes +enum WorkMode { MODE_MEASURE, MODE_ANALYZE }; +WorkMode currentMode = MODE_MEASURE; + +// Objects +HX711 scale; + +// Packet Structure +typedef struct struct_message { + char text[220]; // Dikurangi untuk memberi ruang pada data sinyal + int msgType; // 0=Request, 1=Response, 2=Ping, 3=Pong + int packetId; + int totalPackets; + int senderId; // 1=Node1, 2=Node2, 3=Repeater + int rssi_gw; // Sinyal Hop 1 (A -> GW) + float snr_gw; + int rssi_b; // Sinyal Hop 2 (GW -> B) + float snr_b; +} struct_message; + +struct_message myData; +struct_message recvData; + +// ========================================== +// HELPER FUNCTIONS +// ========================================== +void setStatus(int r, int g, int b) { + digitalWrite(LED_RED, r); + digitalWrite(LED_GREEN, g); + digitalWrite(LED_BLUE, b); +} + +// Send Data via LoRa +bool sendDataLoRa(struct_message *data) { + // Bunyikan Buzzer saat mengirim data + digitalWrite(BUZZER_PIN, HIGH); + delay(50); + digitalWrite(BUZZER_PIN, LOW); + + LoRa.beginPacket(); + LoRa.write((uint8_t*)data, sizeof(struct_message)); + LoRa.endPacket(); + return true; +} + +// ========================================== +// SETUP +// ========================================== +void setup() { + Serial.begin(115200); + + // Sensor & LED Pins + pinMode(TRIG_PIN, OUTPUT); + pinMode(ECHO_PIN, INPUT_PULLDOWN); // Gunakan PULLDOWN agar tidak menangkap noise saat sensor tidak terhubung + pinMode(LED_RED, OUTPUT); + pinMode(LED_GREEN, OUTPUT); + pinMode(LED_BLUE, OUTPUT); + pinMode(BUZZER_PIN, OUTPUT); + digitalWrite(BUZZER_PIN, LOW); + + // Matikan semua LED di awal + setStatus(0, 0, 0); + + // 1. Setup Loadcell (HX711) + scale.begin(HX711_DT, HX711_SCK); + scale.set_scale(calibration_factor); + + // Wajib: Beri Pull-Up pada pin DT agar jika timbangan tidak dicolok, dia akan diam di HIGH (Not Ready). + // Jika tidak di-pullup, pin yang mengambang akan dianggap LOW (Ready) oleh ESP32. + pinMode(HX711_DT, INPUT_PULLUP); + + // Beri waktu maksimal 3 detik untuk modul HX711 menyala dan siap (sama seperti kalibrasi) + unsigned long startWait = millis(); + while (!scale.is_ready() && millis() - startWait < 3000) { + delay(10); + } + + // Cek apakah sensor timbangan benar-benar terhubung + if (scale.is_ready()) { + scale.tare(); // Zero the scale on startup + digitalWrite(LED_GREEN, HIGH); // Hijau = HX711 Ready + } else { + Serial.println("Warning: HX711 Loadcell not detected or not ready yet!"); + } + + // 2. Setup SPI & LoRa + SPI.begin(LORA_SCK, LORA_MISO, LORA_MOSI, LORA_SS); + LoRa.setPins(LORA_SS, LORA_RST, LORA_DIO0); + + if (!LoRa.begin(433E6)) { + Serial.println("Error initializing LoRa"); + } else { + digitalWrite(LED_RED, HIGH); // Merah = LoRa Ready + } + + delay(500); // Jeda tambahan agar sensor ultrasonik stabil sebelum dites + + // 3. Cek Ultrasonik (Kirim 1x Ping) + digitalWrite(TRIG_PIN, LOW); delayMicroseconds(2); + digitalWrite(TRIG_PIN, HIGH); delayMicroseconds(10); + digitalWrite(TRIG_PIN, LOW); + long initDuration = pulseIn(ECHO_PIN, HIGH, 30000); + if (initDuration > 0) digitalWrite(LED_BLUE, HIGH); // Biru = Ultrasonik Ready + + Serial.println("LoRa Node A (ESP32-Integrated-Pro) Ready (V2 Piggybacking)"); +} + +// ========================================== +// LOOP +// ========================================== +void loop() { + // 1. CEK DATA DARI WEB APP (SERIAL) -------------------- + if (Serial.available() > 0) { + String cmd = Serial.readStringUntil('\n'); + cmd.trim(); + + if (cmd == "CMD:CONNECT") { + isConnected = true; + isBalitaSelected = false; + lastHeartbeatTime = millis(); + isLedLocked = false; + + // Buzzer Beep Cepat saat Terkoneksi ke Web + for(int i=0; i<3; i++) { + digitalWrite(BUZZER_PIN, HIGH); delay(100); + digitalWrite(BUZZER_PIN, LOW); delay(100); + } + + } else if (cmd == "CMD:PING") { + if (!isConnected) isConnected = true; + lastHeartbeatTime = millis(); + + } else if (cmd == "CMD:DISCONNECT") { + isConnected = false; + isBalitaSelected = false; + isLedLocked = false; + + } else if (cmd == "CMD:TARE") { + scale.tare(); + Serial.println("TARE:OK"); + + } else if (cmd == "CMD:LOCK_LED") { + isLedLocked = true; + + } else if (cmd == "CMD:UNLOCK_LED") { + isLedLocked = false; + + } else if (cmd == "CMD:SET_MODE:MEASURE") { + currentMode = MODE_MEASURE; + isLedLocked = false; + Serial.println("MODE:MEASURE:OK"); + + } else if (cmd == "CMD:SET_MODE:ANALYZE") { + currentMode = MODE_ANALYZE; + isLedLocked = true; + Serial.println("MODE:ANALYZE:OK"); + + } else if (cmd == "CMD:BALITA_SELECTED") { + isBalitaSelected = true; + Serial.println("BALITA:SELECTED:OK"); + + } else if (cmd == "CMD:BALITA_UNSELECTED") { + isBalitaSelected = false; + Serial.println("BALITA:UNSELECTED:OK"); + + } else if (cmd == "CMD:PING_LOCAL") { + // 1. Cek Sensor Ultrasonik Lokal + digitalWrite(TRIG_PIN, LOW); delayMicroseconds(2); + digitalWrite(TRIG_PIN, HIGH); delayMicroseconds(10); + digitalWrite(TRIG_PIN, LOW); + long duration = pulseIn(ECHO_PIN, HIGH, 30000); + float distance = duration * 0.0343 / 2; + + // 2. Cek Loadcell Lokal + bool loadcellReady = scale.is_ready(); + + String resp = "PONG:A:READY"; + if (distance > MIN_DIST && distance < MAX_DIST) resp += " PONG:SENSOR:DETECTED"; + else resp += " PONG:SENSOR:EMPTY"; + + if (loadcellReady) resp += " PONG:LOADCELL:READY"; + else resp += " PONG:LOADCELL:ERROR"; + + Serial.println(resp); + + } else if (cmd == "CMD:PING_LORA") { + // Konfirmasi ke Web App bahwa Node A (Local) juga Ready + Serial.println("PONG:A:READY"); + + // Ping Node B via LoRa + myData.msgType = 2; // PING + myData.packetId = 0; + myData.totalPackets = 1; + myData.senderId = 1; // Node 1 + strcpy(myData.text, "PING"); + sendDataLoRa(&myData); + + } else if (cmd.startsWith("CMD:SEND_DATA:")) { + String json = cmd.substring(14); + int totalLength = json.length(); + int chunkSize = 215; // Dikurangi agar muat di text[220] + int totalPackets = (totalLength + chunkSize - 1) / chunkSize; + + for (int i = 0; i < totalPackets; i++) { + memset(&myData, 0, sizeof(struct_message)); + myData.msgType = 0; // REQUEST + myData.packetId = i; + myData.totalPackets = totalPackets; + myData.senderId = 1; // Node 1 + + String chunk = json.substring(i * chunkSize, min((i + 1) * chunkSize, totalLength)); + strncpy(myData.text, chunk.c_str(), sizeof(myData.text) - 1); + myData.text[sizeof(myData.text) - 1] = '\0'; + + sendDataLoRa(&myData); + + if (i < totalPackets - 1) { + delay(400); // Beri jeda 400ms agar Repeater sempat memantulkan chunk sebelumnya + } + } + } + } + + // 2. CEK DATA MASUK DARI LORA (NODE B) ------------------ + int packetSize = LoRa.parsePacket(); + if (packetSize == sizeof(struct_message)) { + uint8_t buffer[sizeof(struct_message)]; + int i = 0; + while (LoRa.available() && i < sizeof(struct_message)) { + buffer[i] = LoRa.read(); + i++; + } + memcpy(&recvData, buffer, sizeof(struct_message)); + recvData.text[219] = '\0'; + + // STRICT ROUTING: HANYA terima data dari Node Perantara (Repeater / ID 3) + // Abaikan jika data datang langsung dari Node 2 atau pantulan Node 1 sendiri + if (recvData.senderId != 3) return; + + // Abaikan PING yang dipantulkan kembali oleh Repeater (agar Node A tidak beep dua kali) + if (recvData.msgType == 2) return; + + // Abaikan REQUEST (Data Anda sendiri) yang dipantulkan kembali oleh Repeater + if (recvData.msgType == 0) return; + + // Bunyikan Buzzer saat menerima data + digitalWrite(BUZZER_PIN, HIGH); + delay(50); + digitalWrite(BUZZER_PIN, LOW); + + if (recvData.msgType == 1) { // RESPONSE (AI) + if (recvData.packetId == 0) memset(jsonBuffer, 0, sizeof(jsonBuffer)); + int currentLen = strlen(jsonBuffer); + int spaceLeft = sizeof(jsonBuffer) - currentLen - 1; + if (spaceLeft > 0) strncat(jsonBuffer, recvData.text, spaceLeft); + + if (recvData.packetId == recvData.totalPackets - 1) { + // Tangkap Downlink RSSI dan SNR + int rssi_a = LoRa.packetRssi(); + float snr_a = LoRa.packetSnr(); + + String payload = String(jsonBuffer); + int lastBrace = payload.lastIndexOf('}'); + if (lastBrace != -1) { + payload.remove(lastBrace); + payload += ",\"rssi_a\":" + String(rssi_a) + + ",\"snr_a\":" + String(snr_a) + + ",\"rssi_gw\":" + String(recvData.rssi_gw) + + ",\"snr_gw\":" + String(recvData.snr_gw) + "}"; + } + + Serial.print("AI_RESPONSE:"); + Serial.println(payload); + isLedLocked = true; + } + } else if (recvData.msgType == 3) { // PONG + String status = String(recvData.text); + if (status.indexOf("WIFI_ERROR") != -1) Serial.println("PONG:B:WIFI_ERROR"); + else if (status.indexOf("N8N_ERROR") != -1) Serial.println("PONG:B:N8N_ERROR"); + // Cek REPEATER_READY DULU sebelum READY, agar tidak salah tangkap (karena kata READY ada di dalamnya) + else if (status.indexOf("REPEATER_READY") != -1) Serial.println("PONG:REPEATER:READY"); + else if (status.indexOf("READY") != -1) Serial.println("PONG:B:READY"); + } + } + + // 3. LOGIKA BUZZER DISCONNECTED --------------------- + static unsigned long lastBuzzerLoop = 0; + + if (!isConnected) { + // Buzzer beep 2x cepat (Setiap 20 detik) + if (millis() - lastBuzzerLoop > 20000) { + for (int i = 0; i < 2; i++) { + digitalWrite(BUZZER_PIN, HIGH); + delay(50); // Beep singkat + digitalWrite(BUZZER_PIN, LOW); + delay(50); // Jeda antar beep + } + lastBuzzerLoop = millis(); + } + } else { + // Dipastikan mati jika sudah connected + } + + // 4. LOGIKA SENSOR (TINGGI & BERAT) --------------------- + // --- MODE UKUR AKTIF --- + if (!isLedLocked && isConnected && currentMode == MODE_MEASURE && isBalitaSelected) { + if (millis() - lastScaleRead > 250) { + lastScaleRead = millis(); + + // 4a. Baca Tinggi (Ultrasonik) + digitalWrite(TRIG_PIN, LOW); delayMicroseconds(2); + digitalWrite(TRIG_PIN, HIGH); delayMicroseconds(10); + digitalWrite(TRIG_PIN, LOW); + long duration = pulseIn(ECHO_PIN, HIGH, 30000); + // Konversi ke cm + koreksi offset hardware sensor (dari kalibrasi) + float jarak_sensor = (duration * 0.0343 / 2) + SENSOR_OFFSET; + + if (duration > 0 && jarak_sensor > MIN_DIST && jarak_sensor < MAX_DIST) { + // Rumus: Tinggi Badan = Tinggi Alat - Jarak Sensor + Offset Koreksi + float tinggi_badan = TINGGI_ALAT - jarak_sensor + TINGGI_OFFSET; + if (tinggi_badan < 0) tinggi_badan = 0; + + Serial.print("TINGGI:"); + Serial.println(tinggi_badan); + lastDistance = tinggi_badan; + } + + // 4b. Baca Berat (HX711) - Cek langsung hardware setiap saat + if (scale.is_ready()) { + float weight_grams = scale.get_units(20); // Rata-rata 20 bacaan (lebih akurat, sesuai kalibrasi) + + // === LOGIKA AUTO-TARE (DARI KALIBRASI) === + if (abs(weight_grams) < weightThreshold) { + if (!isEmptyScale) { + emptyStartTime = millis(); + isEmptyScale = true; + } + // Jika kosong selama lebih dari 2 detik -> Tare otomatis + if (millis() - emptyStartTime > 2000) { + scale.tare(); + weight_grams = 0; + } + } else { + isEmptyScale = false; + } + + // Konversi ke KG sesuai file kalibrasi + float weight_kg = weight_grams / 1000.0; + if (weight_kg < 0) weight_kg = 0; // Prevent negative display + + Serial.print("BERAT:"); + Serial.println(weight_kg, 2); // Kirim 2 angka di belakang koma + } + } + } + + // 4. TIMEOUT -------------------------------------------- + if (isConnected && (millis() - lastHeartbeatTime > TIMEOUT_MS)) { + isConnected = false; + isLedLocked = false; + } + + delay(30); +}