program untuk node b - esp8266

This commit is contained in:
diks14 2026-06-29 12:33:38 +07:00
parent fc095720be
commit f2748ca410
1 changed files with 378 additions and 0 deletions

View File

@ -0,0 +1,378 @@
#include <SPI.h>
#include <LoRa.h>
#include <LittleFS.h> // Memori Permanen
#include <WiFiManager.h> // Portal Config
#include <ArduinoJson.h> // Format Data
#include <Ticker.h> // Kedipan LED Status
#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>
#include <WiFiClientSecure.h>
// ==========================================
// 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<count; i++) {
digitalWrite(BUZZER_PIN, HIGH);
delay(durationMs);
digitalWrite(BUZZER_PIN, LOW);
if (i < count-1) delay(100);
}
}
// Callback Ticker untuk berkedip
void tick() {
int state = digitalRead(STATUS_LED_PIN);
digitalWrite(STATUS_LED_PIN, !state);
}
// Callback WiFiManager saat masuk mode portal
void configModeCallback (WiFiManager *myWiFiManager) {
Serial.println("Masuk Mode Konfigurasi...");
Serial.println(WiFi.softAPIP());
statusTicker.attach(0.1, tick); // Kedip super cepat saat Portal Aktif
}
// Callback Simpan Konfigurasi
void saveConfigCallback () {
Serial.println("Menerima Konfigurasi Baru...");
shouldSaveConfig = true;
}
// Load Config dari LittleFS
void loadConfig() {
if (LittleFS.begin()) {
if (LittleFS.exists("/config.json")) {
File configFile = LittleFS.open("/config.json", "r");
if (configFile) {
size_t size = configFile.size();
std::unique_ptr<char[]> 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<totalPackets; i++) {
memset(&sendData, 0, sizeof(struct_message));
sendData.msgType = 1; // RESPONSE
sendData.packetId = i;
sendData.totalPackets = totalPackets;
sendData.senderId = 2;
int startIdx = i * chunkSize;
String chunk = payload.substring(startIdx, startIdx + chunkSize);
strncpy(sendData.text, chunk.c_str(), 219);
sendDataLoRa(&sendData);
if (i < totalPackets - 1) {
delay(400); // Beri jeda 400ms agar Repeater sempat memantulkan chunk sebelumnya
}
}
}
} else {
beep(3, 200);
String err = "{\"status_gizi\":\"offline\",\"ai_narrative\":\"Gateway Belum Siap.\"}";
memset(&sendData, 0, sizeof(struct_message));
sendData.msgType = 1;
sendData.packetId = 0;
sendData.totalPackets = 1;
sendData.senderId = 2; // Node 2
strncpy(sendData.text, err.c_str(), 219);
sendDataLoRa(&sendData);
}
}
}