From f2748ca4107507ae4c259f79853df35853caf2d7 Mon Sep 17 00:00:00 2001 From: diks14 Date: Mon, 29 Jun 2026 12:33:38 +0700 Subject: [PATCH] program untuk node b - esp8266 --- Node B - ESP8266/node2_lora++_v2.ino | 378 +++++++++++++++++++++++++++ 1 file changed, 378 insertions(+) create mode 100644 Node B - ESP8266/node2_lora++_v2.ino diff --git a/Node B - ESP8266/node2_lora++_v2.ino b/Node B - ESP8266/node2_lora++_v2.ino new file mode 100644 index 0000000..f17eba5 --- /dev/null +++ b/Node B - ESP8266/node2_lora++_v2.ino @@ -0,0 +1,378 @@ +#include +#include +#include // Memori Permanen +#include // Portal Config +#include // Format Data +#include // Kedipan LED Status +#include +#include +#include + +// ========================================== +// PIN DEFINITIONS (LoRa SPI & Control) +// ========================================== +#define LORA_SS D8 // NSS/CS (Tetap di bawah) +#define LORA_RST D0 // Reset LoRa (Paling Atas) +#define LORA_DIO0 D1 // Interrupt LoRa (Urutan ke-2) +#define BUZZER_PIN D2 // Buzzer (Urutan ke-3) +#define BUTTON_PIN D3 // Pin Reset Button (Urutan ke-4) +#define STATUS_LED_PIN D4 // Pin LED Status (Urutan ke-5) + +// ========================================== +// VARIABLES +// ========================================== +char n8n_url[200] = ""; +bool shouldSaveConfig = false; + +typedef struct struct_message { + char text[220]; // Kapasitas dikurangi untuk 4 variabel 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 recvData; +struct_message sendData; +char jsonBufferReq[1024]; + +volatile bool dataReceived = false; +volatile bool isPingRequested = false; +wl_status_t lastWifiStatus = WL_IDLE_STATUS; +unsigned long lastWifiCheck = 0; +unsigned long lastDisconnectBeep = 0; + +// Reset Button Helpers +unsigned long pressStartTime = 0; +bool isPressed = false; + +// Status LED Objects +Ticker statusTicker; + +// ========================================== +// HELPER FUNCTIONS +// ========================================== +// Buzzer Beep Function +void beep(int count, int durationMs) { + for(int i=0; i buf(new char[size]); + configFile.readBytes(buf.get(), size); + DynamicJsonDocument json(1024); + deserializeJson(json, buf.get()); + strcpy(n8n_url, json["n8n_url"]); + Serial.print("Config Loaded: "); Serial.println(n8n_url); + } + } + } else { + Serial.println("Gagal mematikan LittleFS"); + } +} + +// Save Config ke LittleFS +void saveConfig() { + Serial.println("Menyimpan Config ke LittleFS..."); + DynamicJsonDocument json(1024); + json["n8n_url"] = n8n_url; + File configFile = LittleFS.open("/config.json", "w"); + if (!configFile) { + Serial.println("Gagal membuka file config untuk menulis"); + } + serializeJson(json, configFile); + configFile.close(); +} + +// Send Data via LoRa +void sendDataLoRa(struct_message *data) { + LoRa.beginPacket(); + LoRa.write((uint8_t*)data, sizeof(struct_message)); + LoRa.endPacket(); +} + +// ========================================== +// SETUP +// ========================================== +void setup() { + Serial.begin(115200); + pinMode(BUZZER_PIN, OUTPUT); + pinMode(BUTTON_PIN, INPUT_PULLUP); + pinMode(STATUS_LED_PIN, OUTPUT); + digitalWrite(BUZZER_PIN, LOW); + digitalWrite(STATUS_LED_PIN, LOW); + + // Mulai kedipan lambat (sedang mencari WiFi) + statusTicker.attach(0.6, tick); + + // 1. Baca Konfigurasi Tersimpan + loadConfig(); + + // 2. Setup WiFiManager + WiFiManager wm; + wm.setSaveConfigCallback(saveConfigCallback); + wm.setAPCallback(configModeCallback); + + // Tambahkan Input Custom untuk URL n8n + WiFiManagerParameter custom_n8n_url("n8n", "N8N Webhook URL", n8n_url, 199); + wm.addParameter(&custom_n8n_url); + + // Suara tanda portal siap + beep(1, 300); + + // Start Portal (Hotspot: Stunting-Gateway-Config) + if (!wm.autoConnect("Stunting-Gateway-Config")) { + Serial.println("Gagal konek WiFi."); + delay(3000); + ESP.restart(); + } + + // Ambil nilai dari portal + strcpy(n8n_url, custom_n8n_url.getValue()); + + // Matikan Ticker setelah konek + statusTicker.detach(); + + // Simpan jika user mengubah nilainya + if (shouldSaveConfig) { + saveConfig(); + beep(2, 100); + } + + Serial.println("Connected to WiFi!"); + digitalWrite(STATUS_LED_PIN, HIGH); // LED Nyala Diam (Sukses) + Serial.print("IP: "); Serial.println(WiFi.localIP()); + Serial.print("N8N URL: "); Serial.println(n8n_url); + beep(1, 100); + + // Setup LoRa + LoRa.setPins(LORA_SS, LORA_RST, LORA_DIO0); + if (!LoRa.begin(433E6)) { + Serial.println("Error initializing LoRa"); + while (1) { delay(1000); } + } + + Serial.println("Gateway LoRa Ready (V2 Piggybacking)"); + lastWifiStatus = WiFi.status(); +} + +// ========================================== +// LOOP +// ========================================== +void loop() { + // --- 1. RESET BUTTON MONITORING (D4) --- + if (digitalRead(BUTTON_PIN) == LOW) { + if (!isPressed) { isPressed = true; pressStartTime = millis(); } + unsigned long pressDuration = millis() - pressStartTime; + + if (pressDuration > 2000) { digitalWrite(BUZZER_PIN, (millis() / 200) % 2); } // Bunyi alarm pelan + + if (pressDuration >= 5000) { + Serial.println("!!!!!!! FACTORY RESET TRIGGERED !!!!!!!"); + beep(1, 2000); // Beep panjang tanda sukses reset + + WiFiManager wm; + wm.resetSettings(); // Hapus WiFi + LittleFS.format(); // Hapus Config n8n + + ESP.restart(); + } + } else { + if (isPressed) { isPressed = false; digitalWrite(BUZZER_PIN, LOW); } + } + + // --- 2. WIFI STATUS MONITORING --- + wl_status_t currentStatus = WiFi.status(); + if (currentStatus != lastWifiStatus) { + if (currentStatus == WL_CONNECTED) { + Serial.println("WiFi Reconnected!"); + digitalWrite(STATUS_LED_PIN, HIGH); + beep(2, 100); + } else { + Serial.println("WiFi Disconnected"); + } + lastWifiStatus = currentStatus; + } + + // Alarm WiFi Offline (Setiap 10s) + if (currentStatus != WL_CONNECTED) { + digitalWrite(STATUS_LED_PIN, LOW); // Lampu Mati jika Offline + if (millis() - lastDisconnectBeep > 10000) { + lastDisconnectBeep = millis(); + beep(1, 400); + Serial.println("Status: Offline..."); + } + } + + // --- 3. LORA RECEIVE LOGIC --- + 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'; // ensure null termination (kapasitas diubah ke 220) + + // STRICT ROUTING: HANYA terima data dari Node Perantara (Repeater / ID 3) + // Abaikan jika data datang langsung dari Node 1 atau pantulan Node 2 sendiri + if (recvData.senderId != 3) return; + + if (recvData.msgType == 0) { // DATA REQUEST + if (recvData.packetId == 0) { + memset(jsonBufferReq, 0, sizeof(jsonBufferReq)); + } + + int currentLen = strlen(jsonBufferReq); + int spaceLeft = sizeof(jsonBufferReq) - currentLen - 1; + if (spaceLeft > 0) { + strncat(jsonBufferReq, recvData.text, spaceLeft); + } + + if (recvData.packetId == recvData.totalPackets - 1) { + // Menangkap RSSI dan SNR Hop 2 di Node B ini + recvData.rssi_b = LoRa.packetRssi(); + recvData.snr_b = LoRa.packetSnr(); + + dataReceived = true; + beep(1, 50); + } + } else if (recvData.msgType == 2) { + isPingRequested = true; + beep(1, 40); + } + } + + // --- 4. HANDLE PING (UI CHECK) --- + if (isPingRequested) { + isPingRequested = false; + sendData.msgType = 3; + sendData.packetId = 0; + sendData.totalPackets = 1; + sendData.senderId = 2; + + if (WiFi.status() != WL_CONNECTED) { + strcpy(sendData.text, "WIFI_ERROR"); + } else if (strlen(n8n_url) < 10) { + strcpy(sendData.text, "N8N_ERROR"); // Belum diatur + } else { + // Cukup pastikan WiFi terhubung dan URL N8N Tersetting. + // JANGAN lakukan POST ke webhook n8n agar tidak mengirim eksekusi palsu (ghost trigger) + strcpy(sendData.text, "READY"); + } + sendDataLoRa(&sendData); + } + + // --- 5. HANDLE DATA (SEND TO n8n) --- + if (dataReceived) { + dataReceived = false; + Serial.println("Forwarding to n8n..."); + + // Inject RSSI dan SNR ke dalam JSON Payload sebelum dikirim + String payloadReq = String(jsonBufferReq); + int lastBrace = payloadReq.lastIndexOf('}'); + if (lastBrace != -1) { + payloadReq.remove(lastBrace); + payloadReq += ",\"rssi_b\":" + String(recvData.rssi_b) + + ",\"snr_b\":" + String(recvData.snr_b) + "}"; + } + + if (WiFi.status() == WL_CONNECTED && strlen(n8n_url) > 10) { + WiFiClientSecure client; + client.setInsecure(); + HTTPClient http; + + if (http.begin(client, n8n_url)) { + http.setTimeout(30000); + http.addHeader("Content-Type", "application/json"); + int httpCode = -1; + int retryCount = 0; + + while (retryCount < 3) { + httpCode = http.POST(payloadReq); // Kirim Payload yang sudah ada titipan RSSI + if (httpCode > 0) break; + retryCount++; + delay(1500); + } + + String payload = "{}"; + if (httpCode > 0) { + payload = http.getString(); + Serial.print("n8n RESPONSE: "); + Serial.println(payload); + beep(2, 100); + } else { + payload = "{\"status\":\"ERROR\",\"status_gizi\":\"error\",\"ai_narrative\":\"Gagal koneksi n8n.\"}"; + beep(1, 1000); + } + http.end(); + + // CHUNKING LORA + int payloadLen = payload.length(); + int chunkSize = 215; // Disesuaikan dengan kapasitas struct_message baru + int totalPackets = (payloadLen + chunkSize - 1) / chunkSize; + for(int i=0; i