#include <Wire.h>
#include <WiFi.h>
#include <PubSubClient.h>
#include <LiquidCrystal_I2C.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>

#define ssid "Wokwi-GUEST"
#define password ""
#define channel 6
const char *mqtt_server = "test.mosquitto.org";

WiFiClient espClient;
PubSubClient client(espClient);

#define ULTRASONIC_TOPIC "ultrasonic/room"
#define SOIL_TOPIC "soil/room"
#define ULTRASONIC_TOPIC_CHECK "ultrasonic/check"
#define SOIL_TOPIC_CHECK "soil/check"

char msg[20];
long lastMsg = 0;

LiquidCrystal_I2C lcd(0x27, 16, 2);

#define LED_ON HIGH
#define LED_OFF LOW

const int trigPin = 13;
const int echoPin = 12;

const int led_ultrasonic_work = 27;
const int led_ultrasonic_fail = 14;
const int led_soil_work = 2;
const int led_soil_fail = 15;

// define sound speed in cm/uS
#define SOUND_SPEED 0.034
#define CM_TO_INCH 0.393701

long duration;
float distanceCm;
float distanceInch;

SemaphoreHandle_t xMutex;

void ultrasonicTask(void *pvParameters)
{
  while (1)
  {
    // Clears the trigPin
    digitalWrite(trigPin, LOW);
    delayMicroseconds(2);

    // Sets the trigPin on HIGH state for 10 microseconds
    digitalWrite(trigPin, HIGH);
    delayMicroseconds(10);
    digitalWrite(trigPin, LOW);

    // Reads the echoPin, returns the sound wave travel time in microseconds
    duration = pulseIn(echoPin, HIGH);

    // Calculate the distance
    distanceCm = duration * SOUND_SPEED / 2;

    // Convert to inches
    distanceInch = distanceCm * CM_TO_INCH;

    // Use the mutex to protect LCD printing
    if (xSemaphoreTake(xMutex, portMAX_DELAY))
    {
      lcd.clear();
      lcd.setCursor(0, 0);
      lcd.print("Soil: ");
      lcd.print(analogRead(34));
      lcd.setCursor(0, 1);
      lcd.print("Level: ");
      lcd.print(distanceCm);
      lcd.print(" cm");
      xSemaphoreGive(xMutex);
    }

    delay(500);
  }
}

void mqttTask(void *pvParameters)
{
  while (1)
  {
    if (!client.connected())
    { /* try to reconnect */
      mqttconnect();
    }

    client.loop(); /* listen for incoming subscribed receivedCallback */

    long now = millis();
    if (now - lastMsg > 500)
    {
      lastMsg = now;
      snprintf(msg, sizeof(msg), "%.2f", distanceCm);
      client.publish(ULTRASONIC_TOPIC, msg);
      snprintf(msg, sizeof(msg), "%d", analogRead(34));
      client.publish(SOIL_TOPIC, msg);
    }

    delay(500);
  }
}

void receivedCallback(char *topic, byte *payload, unsigned int length)
{
  Serial.print("Message received: ");
  Serial.println(topic);

  Serial.print("payload: ");
  for (int i = 0; i < length; i++)
  {
    Serial.print((char)payload[i]);
  }

  Serial.println();

  // Check the topic and perform actions accordingly
  if (strcmp(topic, ULTRASONIC_TOPIC_CHECK) == 0)
  {
    if ((char)payload[0] == '1')
    {
      digitalWrite(led_ultrasonic_work, LED_ON);
      digitalWrite(led_ultrasonic_fail, LED_OFF);
    }
    else if ((char)payload[0] == '0')
    {
      digitalWrite(led_ultrasonic_work, LED_OFF);
      digitalWrite(led_ultrasonic_fail, LED_ON);
    }
  }
  else if (strcmp(topic, SOIL_TOPIC_CHECK) == 0)
  {
    if ((char)payload[0] == '1')
    {
      digitalWrite(led_soil_work, LED_ON);
      digitalWrite(led_soil_fail, LED_OFF);
    }
    else if ((char)payload[0] == '0')
    {
      digitalWrite(led_soil_work, LED_OFF);
      digitalWrite(led_soil_fail, LED_ON);
    }
  }
}

void mqttconnect()
{
  /* Loop until reconnected */
  while (!client.connected())
  {
    Serial.print("MQTT connecting ...");
    /* client ID */
    String clientId = "BigClient"; //ESP32Client
    /* connect now */
    if (client.connect(clientId.c_str()))
    {
      Serial.println("connected");
      /* subscribe topic with default QoS 0*/
      client.subscribe(ULTRASONIC_TOPIC);
      client.subscribe(SOIL_TOPIC);
      client.subscribe(ULTRASONIC_TOPIC_CHECK);
      client.subscribe(SOIL_TOPIC_CHECK);
    }
    else
    {
      Serial.print("failed, status code =");
      Serial.print(client.state());
      Serial.println("try again in 5 seconds");
      /* Wait 5 seconds before retrying */
      delay(5000);
    }
  }
}

void setup()
{
  Serial.begin(9600);

  Serial.print("Connecting to ");
  Serial.println(ssid);

  WiFi.begin(ssid, password, channel);
  while (WiFi.status() != WL_CONNECTED)
  {
    delay(500);
    Serial.print(".");
  }

  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());

  client.setServer(mqtt_server, 1883);
  client.setCallback(receivedCallback);

  Wire.begin(23, 22);
  lcd.init();
  lcd.backlight();
  pinMode(trigPin, OUTPUT); // Sets the trigPin as an Output
  pinMode(echoPin, INPUT);  // Sets the echoPin as an Input

  pinMode(led_ultrasonic_work, OUTPUT);
  pinMode(led_ultrasonic_fail, OUTPUT);
  pinMode(led_soil_work, OUTPUT);
  pinMode(led_soil_fail, OUTPUT);

  // Create a mutex to protect LCD printing
  xMutex = xSemaphoreCreateMutex();

  // Create FreeRTOS tasks
  xTaskCreate(ultrasonicTask, "UltrasonicTask", 4096, NULL, 1, NULL);
  xTaskCreate(mqttTask, "MqttTask", 4096, NULL, 1, NULL);

  Serial.println("");
  delay(100);
}

void loop()
{
  // This loop is intentionally left empty as the tasks handle the functionality
}
lcd1:GND
lcd1:VCC
lcd1:SDA
lcd1:SCL
ultrasonic1:VCC
ultrasonic1:TRIG
ultrasonic1:ECHO
ultrasonic1:GND
esp1:VIN
esp1:GND.2
esp1:D13
esp1:D12
esp1:D14
esp1:D27
esp1:D26
esp1:D25
esp1:D33
esp1:D32
esp1:D35
esp1:D34
esp1:VN
esp1:VP
esp1:EN
esp1:3V3
esp1:GND.1
esp1:D15
esp1:D2
esp1:D4
esp1:RX2
esp1:TX2
esp1:D5
esp1:D18
esp1:D19
esp1:D21
esp1:RX0
esp1:TX0
esp1:D22
esp1:D23
Soil SensorBreakout
chip1:GND
chip1:VCC
chip1:A0
led1:A
led1:C
led2:A
led2:C
led3:A
led3:C
led4:A
led4:C