#include #include #include #include #include #include #include "time.h" // ========== OLED ========== #define SCREEN_WIDTH 128 #define SCREEN_HEIGHT 64 #define OLED_RESET -1 #define SCREEN_ADDRESS 0x3C Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); // ========== WIFI ========== #define WIFI_SSID "AGIL" #define WIFI_PASSWORD "agil2626" // ========== FIREBASE ========== #define API_KEY "AIzaSyCefzDqzoTLblHtOsxMiDw3BB1QAox0VIQ" #define DATABASE_URL "https://nutriplant-579c9-default-rtdb.firebaseio.com/" #define LEGACY_TOKEN "yEFKFhgPmBoLWrdkRSf2pgiYY3ebS12uWDoikwkl" // ========== LED & BUTTON ========== #define LED_PIN 15 #define BUTTON_PIN 14 bool lastButtonState = HIGH; unsigned long lastDebounceTime = 0; const unsigned long debounceDelay = 50; bool manualMode = false; // ========== MODBUS RS485 ========== #define RXD2 16 #define TXD2 17 #define RE_PIN 19 #define DE_PIN 18 HardwareSerial MySerial(2); ModbusMaster node; // ========== SOIL SENSOR ========== #define SOIL_PIN 34 // Firebase objects FirebaseData fbdo; FirebaseAuth auth; FirebaseConfig config; // Variables uint16_t nitrogen = 0, phosphorus = 0, potassium = 0; float soilPercent = 0; int currentPage = 0; const int totalPages = 4; unsigned long lastSensorDisplayTime = 0; // Fungsi Modbus TX control void preTransmission() { digitalWrite(DE_PIN, HIGH); digitalWrite(RE_PIN, HIGH); } void postTransmission() { digitalWrite(DE_PIN, LOW); digitalWrite(RE_PIN, LOW); } // Fungsi teks rata tengah void centerText(const String &text, int y, int textSize = 2) { display.setTextSize(textSize); int16_t x1, y1; uint16_t w, h; display.getTextBounds(text, 0, y, &x1, &y1, &w, &h); int x = (SCREEN_WIDTH - w) / 2; display.setCursor(x, y); display.print(text); } // Fungsi menampilkan ikon WiFi void drawWiFiIcon() { display.drawPixel(118, 0, WHITE); display.drawLine(117, 1, 119, 1, WHITE); display.drawLine(116, 2, 120, 2, WHITE); display.drawLine(115, 3, 121, 3, WHITE); } // Fungsi waktu update ke string String getFormattedTime() { time_t now; struct tm timeinfo; if (!getLocalTime(&timeinfo)) { return "unknown"; } char buffer[30]; strftime(buffer, sizeof(buffer), "%d/%m/%Y %H:%M:%S", &timeinfo); return String(buffer); } void setup() { Serial.begin(115200); // OLED if (!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) { Serial.println(F("OLED failed")); while (true); } display.clearDisplay(); display.setTextColor(WHITE); centerText("Nutriplant", 20, 2); display.display(); delay(1500); display.clearDisplay(); // Pin setup pinMode(LED_PIN, OUTPUT); pinMode(BUTTON_PIN, INPUT_PULLUP); pinMode(RE_PIN, OUTPUT); pinMode(DE_PIN, OUTPUT); digitalWrite(RE_PIN, LOW); digitalWrite(DE_PIN, LOW); // WiFi WiFi.begin(WIFI_SSID, WIFI_PASSWORD); Serial.print("Connecting to WiFi"); unsigned long start = millis(); while (WiFi.status() != WL_CONNECTED && millis() - start < 10000) { Serial.print("."); digitalWrite(LED_PIN, !digitalRead(LED_PIN)); delay(500); } digitalWrite(LED_PIN, LOW); if (WiFi.status() == WL_CONNECTED) { Serial.println("\nWiFi connected"); manualMode = false; configTime(25200, 0, "pool.ntp.org"); // GMT+7 (WIB) } else { Serial.println("\nWiFi not connected - Manual Mode"); manualMode = true; } // Firebase config.api_key = API_KEY; config.database_url = DATABASE_URL; config.signer.tokens.legacy_token = LEGACY_TOKEN; Firebase.begin(&config, &auth); Firebase.reconnectWiFi(true); // RS485 setup MySerial.begin(4800, SERIAL_8N1, RXD2, TXD2); node.begin(1, MySerial); node.preTransmission(preTransmission); node.postTransmission(postTransmission); delay(1000); } void loop() { unsigned long currentMillis = millis(); static unsigned long wifiDisconnectedSince = 0; // === Read NPK === uint8_t result = node.readHoldingRegisters(0x0000, 3); if (result == node.ku8MBSuccess) { nitrogen = node.getResponseBuffer(0); phosphorus = node.getResponseBuffer(1); potassium = node.getResponseBuffer(2); if (!manualMode) { Firebase.RTDB.setInt(&fbdo, "sensor_data/nitrogen", nitrogen); Firebase.RTDB.setInt(&fbdo, "sensor_data/phosphorus", phosphorus); Firebase.RTDB.setInt(&fbdo, "sensor_data/potassium", potassium); Firebase.RTDB.setString(&fbdo, "sensor_data/error_npk", ""); // Set waktu update String timeNow = getFormattedTime(); Firebase.RTDB.setString(&fbdo, "sensor_data/update", timeNow); } } else { if (!manualMode) { Firebase.RTDB.setString(&fbdo, "sensor_data/error_npk", "Modbus Error: " + String(result)); } } // === Read Soil Moisture === int soilAnalog = analogRead(SOIL_PIN); soilPercent = map(soilAnalog, 4095, 1500, 0, 100); soilPercent = constrain(soilPercent, 0, 100); if (!manualMode) { Firebase.RTDB.setFloat(&fbdo, "sensor_data/soil_moisture", soilPercent); // Update waktu juga String timeNow = getFormattedTime(); Firebase.RTDB.setString(&fbdo, "sensor_data/update", timeNow); } // === Tombol untuk reconnect WiFi === bool reading = digitalRead(BUTTON_PIN); if (reading != lastButtonState) { lastDebounceTime = currentMillis; } if ((currentMillis - lastDebounceTime) > debounceDelay) { if (lastButtonState == HIGH && reading == LOW) { Serial.println("Reconnect button pressed"); display.clearDisplay(); centerText("Connecting WiFi", 10); display.display(); WiFi.disconnect(); WiFi.begin(WIFI_SSID, WIFI_PASSWORD); unsigned long reconnectStart = millis(); bool reconnected = false; while (millis() - reconnectStart < 10000) { if (WiFi.status() == WL_CONNECTED) { reconnected = true; break; } delay(500); } manualMode = !reconnected; display.clearDisplay(); if (reconnected) { centerText("WiFi Connected", 10); Serial.println("Reconnected to WiFi, online mode."); } else { centerText("Reconnect Failed", 10); Serial.println("Reconnect failed, still in manual mode."); } display.display(); delay(1500); } } lastButtonState = reading; // === Auto-switch ke Manual jika WiFi putus === if (WiFi.status() != WL_CONNECTED) { if (wifiDisconnectedSince == 0) { wifiDisconnectedSince = currentMillis; } else if (!manualMode && currentMillis - wifiDisconnectedSince >= 10000) { manualMode = true; display.clearDisplay(); centerText("No WiFi - Manual", 10); display.display(); Serial.println("Auto-switched to Manual Mode"); } } else { wifiDisconnectedSince = 0; } // === Tampilkan Sensor Setiap 5 Detik === if (currentMillis - lastSensorDisplayTime >= 5000) { lastSensorDisplayTime = currentMillis; currentPage = (currentPage + 1) % totalPages; display.clearDisplay(); switch (currentPage) { case 0: centerText("N (mg/kg)", 5, 2); centerText(String(nitrogen), 30, 3); break; case 1: centerText("P (mg/kg)", 5, 2); centerText(String(phosphorus), 30, 3); break; case 2: centerText("K (mg/kg)", 5, 2); centerText(String(potassium), 30, 3); break; case 3: centerText("Soil Moisture", 5, 1); centerText(String(soilPercent, 1) + "%", 30, 3); break; } if (!manualMode) { drawWiFiIcon(); } display.display(); } digitalWrite(LED_PIN, manualMode ? LOW : HIGH); delay(100); }