#include <WiFi.h>
#include <PubSubClient.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <DHT.h>
#include <HardwareSerial.h>

// Pin Definitions
#define DHTPIN 4      // GPIO4 connected to DHT22
#define DHTTYPE DHT22 // Specify the sensor type
#define LED_PIN 25    // GPIO25 for LED (Simulating watering)
#define POT_PIN 34    // GPIO34 connected to potentiometer wiper (simulating soil moisture sensor)
#define NPK_RX_PIN 16 // GPIO16 for NPK sensor RX
#define NPK_TX_PIN 17 // GPIO17 for NPK sensor TX

// Create DHT and LCD objects
DHT dht(DHTPIN, DHTTYPE);
LiquidCrystal_I2C lcd(0x27, 16, 2);  // 0x27 is the I2C address, 16x2 LCD screen

// Create HardwareSerial object for NPK communication
HardwareSerial npkSerial(1);

// MQTT Configuration
const char* ssid = "Wokwi-GUEST";         // Replace with your WiFi SSID
const char* password = ""; // Replace with your WiFi password
const char* mqttServer = "broker.hivemq.com"; // MQTT Broker URL
const int mqttPort = 1883;                  // MQTT Broker Port
const char* temperatureTopic = "agriculture/temperature";
const char* humidityTopic = "agriculture/humidity";
const char* moistureTopic = "agriculture/moisture";
const char* wateringTopic = "agriculture/watering";
const char* npkTopic = "agriculture/npk";
const char* nitrogenTopic = "agriculture/nitrogen";
const char* phosphorusTopic = "agriculture/phosphorus";
const char* potassiumTopic = "agriculture/potassium";

WiFiClient wifiClient;
PubSubClient client(wifiClient);

// Variables for NPK sensor data
uint8_t nitrogen = 0;
uint8_t phosphorus = 0;
uint8_t potassium = 0;

// Variables for scrolling and watering simulation
unsigned long previousMillis = 0;
unsigned long scrollInterval = 5000;  // Interval for text scroll (in milliseconds) set to 5 seconds
unsigned long wateringMillis = 0;  // Track watering duration
bool watering = false;
String tempStr = "";
String humidityStr = "";
String moistureStr = "";
String wateringStr = "";

unsigned long lastPublishTime = 0;
unsigned long publishInterval = 10000; // Publish every 10 seconds

void setup() {
  // Initialize Serial Monitor
  Serial.begin(115200);
  Serial.println("Starting DHT22, Soil Moisture, NPK Sensor, and LCD Test");

  // Initialize WiFi
  WiFi.begin(ssid, password);
  unsigned long startAttemptTime = millis();
  while (WiFi.status() != WL_CONNECTED) {
    if (millis() - startAttemptTime > 10000) {  // 10 seconds timeout
      Serial.println("Failed to connect to WiFi, restarting...");
      ESP.restart();  // Restart the ESP32
    }
    delay(500);
    Serial.print(".");
  }
  Serial.println("Connected to WiFi");

  // Set up MQTT
  client.setServer(mqttServer, mqttPort);

  // Initialize NPK sensor communication
  npkSerial.begin(15200, SERIAL_8N1, NPK_RX_PIN, NPK_TX_PIN);

  // Initialize the DHT sensor
  dht.begin();

  // Initialize the LED pin
  pinMode(LED_PIN, OUTPUT);
  digitalWrite(LED_PIN, LOW); // LED off initially

  // Initialize potentiometer pin
  pinMode(POT_PIN, INPUT);

  // Initialize LCD
  lcd.init();  // Initialize the LCD
  lcd.backlight(); // Turn on the LCD backlight

  // Initial message on LCD
  lcd.setCursor(0, 0); // Start at top left
  lcd.print("Starting...");
  delay(2000); // Delay for startup
  lcd.clear(); // Clear the screen after startup
}

uint8_t getNPKValue(uint8_t command) {
  uint8_t response = 0xFF; // Default error value
  npkSerial.write(command); // Send the command
  delay(100); // Wait for response
  if (npkSerial.available()) {
    response = npkSerial.read(); // Read the response
  }
  return response;
}

void loop() {
  unsigned long currentMillis = millis();
  
  // Ensure MQTT connection is alive
  if (!client.connected()) {
    reconnectMQTT();
  }
  client.loop(); // Keeps the connection to the broker alive

  // Read temperature and humidity
  float temperature = dht.readTemperature();
  float humidity = dht.readHumidity();

  // Read soil moisture (potentiometer value)
  int moistureValue = analogRead(POT_PIN);  // Values between 0 and 4095

  // Read NPK sensor data
  nitrogen = getNPKValue(1);  // Request Nitrogen value
  phosphorus = getNPKValue(3);  // Request Phosphorus value
  potassium = getNPKValue(5);  // Request Potassium value

  // Check if the reading failed
  if (isnan(temperature) || isnan(humidity)) {
    lcd.clear();
    lcd.print("Sensor Error!");
    Serial.println("Sensor Error!"); // Print to serial monitor as well
    delay(2000); // Wait before retrying
    return;
  }

  // Update temperature, humidity, and moisture strings
  tempStr = "Temp: " + String(temperature) + " C";
  humidityStr = "Humidity: " + String(humidity) + "%";
  moistureStr = "Moisture: " + String(moistureValue);  // Soil moisture value from potentiometer
  wateringStr = "Watering: " + String(watering ? "On" : "Off");

  // Control LED based on temperature or moisture condition
  if ((temperature > 30 || moistureValue < 2000) && !watering) {  // If temperature > 30 or moisture < 1000
    digitalWrite(LED_PIN, HIGH); // Turn on LED (simulating watering)
    watering = true;  // Mark watering as active
    wateringMillis = millis();  // Track watering start time
    Serial.println("Watering On");  // Print to serial monitor
  }

  // Check if watering duration has exceeded 15 seconds
  if (watering && millis() - wateringMillis >= 15000) {  // Water for 15 seconds
    digitalWrite(LED_PIN, LOW);  // Turn off LED
    watering = false;  // Mark watering as inactive
    Serial.println("Watering Off");  // Print to serial monitor
  }

  // Print the same information to Serial Monitor
  Serial.print("Temperature: ");
  Serial.print(temperature);
  Serial.print(" C, Humidity: ");
  Serial.print(humidity);
  Serial.print(" %, Moisture: ");
  Serial.print(moistureValue);
  Serial.print(", Watering: ");
  Serial.print(watering ? "On" : "Off");
  Serial.print(", NPK values: ");
  Serial.print("N=" + String(nitrogen) + " ");
  Serial.print("P=" + String(phosphorus) + " ");
  Serial.println("K=" + String(potassium));

  // Check if any NPK value is below 30 and display "Add NPK fertilizer" message
  if (nitrogen < 30 || phosphorus < 30 || potassium < 30) {
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("Add NPK fertilizer");
    Serial.println("Add NPK fertilizer");  // Display message in serial monitor
    delay(3000);  // Display message for 3 seconds
  }

  // Publish data to MQTT topics (limit publish to every 10 seconds)
  if (currentMillis - lastPublishTime >= publishInterval) {
    lastPublishTime = currentMillis;
    
    client.publish(temperatureTopic, String(temperature).c_str());
    client.publish(humidityTopic, String(humidity).c_str());
    client.publish(moistureTopic, String(moistureValue).c_str());
    client.publish(wateringTopic, watering ? "true" : "false");  // Using true/false for clarity

    // Publish NPK values as separate messages
    client.publish(nitrogenTopic, String(nitrogen).c_str());
    client.publish(phosphorusTopic, String(phosphorus).c_str());
    client.publish(potassiumTopic, String(potassium).c_str());
  }

  // Scroll the values every 5 seconds on the LCD
  if (millis() - previousMillis >= scrollInterval) {
    previousMillis = millis();

    // Show temperature, humidity, soil moisture, and watering status with some delay
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print(tempStr);        // Show temperature at the top of screen
    delay(3000);               // Show temperature for 3 seconds

    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print(humidityStr);    // Show humidity
    delay(3000);               // Show humidity for 3 seconds

    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print(moistureStr);    // Show moisture level
    delay(3000);               // Show moisture for 3 seconds

    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print(wateringStr);    // Show watering status
    delay(3000);               // Show watering status for 3 seconds

    // Display NPK values one below the other
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("N: " + String(nitrogen));
    lcd.setCursor(0, 1);
    lcd.print("P: " + String(phosphorus));
    delay(3000);               // Show NPK values for 3 seconds

    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("K: " + String(potassium));  // Show Potassium value
    delay(3000);               // Show Potassium for 3 seconds

    // Add a longer delay (500ms) to give a smoother transition between data displays
    delay(500);  // Adjust this delay as needed for smoother transition between screens
  }
}

void reconnectMQTT() {
  while (!client.connected()) {
    Serial.print("Attempting MQTT connection...");
    if (client.connect("ESP32_Client")) {
      Serial.println("connected");
      client.subscribe(temperatureTopic);
      client.subscribe(humidityTopic);
      client.subscribe(moistureTopic);
      client.subscribe(wateringTopic);
      client.subscribe(npkTopic);
    } else {
      Serial.print("failed, rc=");
      Serial.print(client.state());
      Serial.println(" try again in 5 seconds");
      delay(5000);
    }
  }
}
SOIL MOISTUREBreakout
NPK-SensorBreakout