#include <Arduino.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <DHT.h>
#include <esp_task_wdt.h>
#include <BH1750.h>

/* Gán chân */
#define DHT11_PIN 19 // Chân đọc giá trị DHT là 19
#define YELLOW_LED_PIN 33 // Chân điều khiển LED vàng là 33
#define WHITE_LED_PIN 32 // Chân điều khiển LED trắng là 32

#define BUTTON_ON 26 // Chân 26 lấy tín hiệu nút ON
#define BUTTON_OFF 25 // Chân 25 lấy tín hiệu nút OFF
#define BUTTON_SEMA 27 // Chân 27 lấy tín hiệu cấp SEMA
#define BUZZER_PIN 23 // Chân 23 điều khiển Buzzer

#define I2C_ADDR 0x27  // Địa chỉ của màn hình LCD I2C
#define LCD_COLS 16    // Số cột của màn hình LCD
#define LCD_ROWS 2     // Số hàng của màn hình LCD

/* Cảm biến ánh sáng */
BH1750 lightSensor;

/* LCD */
LiquidCrystal_I2C lcd(I2C_ADDR, LCD_COLS, LCD_ROWS);

/* DHT22 */
DHT dht(DHT11_PIN, DHT11);

/* Biến toàn cục */
int temperature = 0;  // biến lưu nhiệt độ
int humidity = 0; // biến lưu độ ẩm
float lightStrength = 0; // biến lưu ánh sáng

SemaphoreHandle_t sensorMutex; // Tạo biến Mutex

SemaphoreHandle_t countingSemaLed5; // Tạo biến Semaphore đếm

/* Task Functions */
void getTemperatureAndHumidity(void *); // Đọc giá trị DHT
void displayLCD(void *); // Hiển thị trên LCD
void getLight(void *); // Đọc giá trị cảm biến ánh sáng
void readOnOffButton(void *); // Đọc tín hiệu từ nút ON, OFF
void readSemaButton(void *); // Đọc tín hiệu nút SEMA
void writeSemaLed(void *); // Xuất tín hiệu từ nút SEMA ra LED
void buzzerControl(void *); // Buzzer chạy thụ động

void setup() {
  Serial.begin(115200); // Khởi tạo Serial với baund rate 115200
  Wire.begin(); // Khởi tạo thư viện Wire để giao tiếp I2C
  Serial.println("Khoi Tao Cam Bien");

  lightSensor.begin(); // Khởi tạo cảm biến ánh sáng

  lcd.init(); // Khởi tạo LCD
  lcd.backlight(); // Đảm bảo đèn nền LCD được bật
  lcd.setCursor(0, 0); // Con trỏ LCD ở hàng 0 cột 0
  dht.begin(); // Khởi tọa cảm biến DHT

  /* Khởi tạo bộ watchdog timer giúp hệ thống không bị treo */
  esp_task_wdt_init(portMAX_DELAY, true); // Watchdog timer sẽ được chạy gần như mãi mãi, có nghĩa là hệ thống sẽ không bị time out 

  /* Thiết lập chế độ chân */
  pinMode(BUTTON_ON, INPUT_PULLUP);
  pinMode(BUTTON_OFF, INPUT_PULLUP);
  pinMode(BUTTON_SEMA, INPUT_PULLUP);
  pinMode(YELLOW_LED_PIN, OUTPUT);
  pinMode(WHITE_LED_PIN, OUTPUT);
  pinMode(BUZZER_PIN, OUTPUT);
  
  Serial.println("Khoi Tao thanh cong");

  /* Mutex */
  sensorMutex = xSemaphoreCreateMutex(); // Tạo Mutex, khởi tạo sensorMutex = 1

  /* Semaphore */
  countingSemaLed5 = xSemaphoreCreateCounting(5, 0); // Tạo Semaphore đếm được tối đa 5, khởi tạo countingSemaLed = 0

  /* tạo RTOS Tasks */
  /* xTaskCreate(   TaskFunction_t pvTaskCode,  // Hàm thực hiện nhiệm vụ
                    const char * const pcName, // Tên của task
                    unsigned short usStackDepth, // Kích thước stack
                    void *pvParameters, // Tham số truyền vào task
                    UBaseType_t uxPriority, // Độ ưu tiên của task
                    TaskHandle_t *pxCreatedTask ); // Handle của task
 */
  xTaskCreate(getTemperatureAndHumidity, "nhietdodoam", 4096, NULL, 5, NULL);
  xTaskCreate(displayLCD, "hienthi", 4096, NULL, 6, NULL);

  xTaskCreate(readOnOffButton, "nutonoff", 4096, NULL, 7, NULL);
  xTaskCreate(getLight, "light", 4096, NULL, 7, NULL);
  
  xTaskCreate(readSemaButton, "nutsema", 1024, NULL, 5, NULL);
  xTaskCreate(writeSemaLed, "ghiSemaLed", 1024, NULL, 5, NULL); 
  
  xTaskCreate(buzzerControl, "buzzer", 1024, NULL, 5, NULL);
}

void loop() {}
/* Đọc giá trị DHT */
void getTemperatureAndHumidity(void *) {
  while (1) {
    int tem = dht.readTemperature(); // Đọc giá trị nhiệt độ vào biến cục bộ tem
    int hum = dht.readHumidity(); // Đọc giá trị độ ẩm vào biến cục bộ

    xSemaphoreTake(sensorMutex, portMAX_DELAY); // Chờ sensorMutex = 1 gần như mãi mãi, sau đó sensorMutex = 0
    temperature = tem; // lưu nhiệt độ vào biến toàn cục
    humidity = hum; // lưu độ ẩm vào biến toàn cục
    xSemaphoreGive(sensorMutex); // Trả lại sensorMutex = 1

    delay(500); // Dừng 500ms rồi mới lặp
  }
}
/* Hiển thị trên LCD */
void displayLCD(void *) {
  while (1) {
    xSemaphoreTake(sensorMutex, portMAX_DELAY); // Chờ sensorMutex = 1 gần như mãi mãi, sau đó sensorMutex = 0

    lcd.setCursor(0, 0); // Con trỏ LCD ở hàng 0 cột 0
    lcd.printf("Nhiet:%2dC Am:%2d%%", temperature,humidity); // Hiển thị Nhiet:<giá trị nhiệt độ>C, Am:<giá trị độ ẩm>%

    lcd.setCursor(0, 1); // Con trỏ LCD ở hàng 1 cột 0 
    lcd.printf("Anh sang:%05dlx",(int)lightStrength); // Hiển thị Anh sang:<giá trị cường độ ánh sáng>lx

    xSemaphoreGive(sensorMutex); // Trả lại sensorMutex = 1

    delay(100);
  }
}
/* Đọc tín hiệu từ nút ON, OFF */
void readOnOffButton(void *) {
  while (1) {
    static int prevOnValue = 0; // Tạo biến lưu trữ trạng thái trước đó của nút ON
    static int prevOffValue = 0; // Tạo biến lưu trữ trạng thái trước đó của nút OFF

    int onValue = digitalRead(BUTTON_ON); // Tạo biến đọc giá trị hiện tại của nút ON
    int offValue = digitalRead(BUTTON_OFF); // Tạo biến đọc giá trị hiện tại của nút OFF

    /* Kiểm tra xem nút ON có được nhấn không */
    if (onValue == LOW && prevOnValue == HIGH) {
      digitalWrite(YELLOW_LED_PIN, HIGH); // Nếu nút ON được nhấn, điều khiển đèn LED màu vàng bật
    }
    /* Kiểm tra xem nút OFF có được nhấn không */
    if (offValue == LOW && prevOffValue == HIGH) {
      digitalWrite(YELLOW_LED_PIN, LOW); // Nếu nút OFF được nhấn, điều khiển đèn LED màu vàng tắt
    }

    prevOnValue = onValue; // Lưu trạng thái hiện tại của nút ON để dùng trong vòng lặp tiếp theo
    prevOffValue = offValue; // Lưu trạng thái hiện tại của nút OFF để dùng trong vòng lặp tiếp theo

    delay(10);
  }
}
/* Đọc giá trị cảm biến ánh sáng */
void getLight(void *) {
  while (1) {
    xSemaphoreTake(sensorMutex, portMAX_DELAY); // Chờ sensorMutex = 1 gần như mãi mãi, sau đó sensorMutex = 0
    float lightData = lightSensor.readLightLevel(); // Đọc giá trị cường độ ánh sáng
    lightStrength = lightData; // Lưu giá trị cường độ ánh sáng vào biến lightStrength
    xSemaphoreGive(sensorMutex); // Trả lại sensorMutex = 1
    delay(100);
  }
}
/* Đọc tín hiệu nút SEMA */
void readSemaButton(void *) {
  while (1) {
    int buttonValue = digitalRead(BUTTON_SEMA); // Đọc tín hiệu só lưu vào biến buttonValue
    static int prevButtonValue = 0; // Tạo biến đọc giá trị trước đó của nút SEMA

    /* Kiểm tra xem nút OFF có được nhấn không */
    if (buttonValue == LOW && prevButtonValue == HIGH) {
      xSemaphoreGive(countingSemaLed5); // Nếu nút SEMA được nhấn, cho Sema đếm thêm 1 (countingSemaLed5++)
    }

    prevButtonValue = buttonValue; // Lưu trạng thái hiện tại của nút SEMA để dùng trong vòng lặp tiếp theo
    delay(10);
  }
}
/* Xuất tin hiệu từ nút SEMA ra LED */
void writeSemaLed(void *) {
  while (1) {
    // Kiểm tra xem semaphore có sẵn không (countingSemaLed5!=0)
    if (xSemaphoreTake(countingSemaLed5, portMAX_DELAY) == pdTRUE) {
      // Nếu có thì chớp tắt Led trắng
      for (int i = 0; i < 3; i++) {
        digitalWrite(WHITE_LED_PIN, HIGH); // Bật Led trắng
        delay(100); // Dừng 100ms
        digitalWrite(WHITE_LED_PIN, LOW); // Tắt Led trắng
        delay(100); // Dừng 100ms
      }
    }
  }
}
/* Hiện thị trên Serial Monitor, hàm này để debug thôi không quan trọng*/
void displaySerial() {
  // Chờ sensorMutex = 1 gần như mãi mãi, sau đó sensorMutex = 0
  if (xSemaphoreTake(sensorMutex, portMAX_DELAY) == pdTRUE) {
    Serial.print("Nhiệt độ: ");
    Serial.print(temperature);
    Serial.println("°C");
    
    Serial.print("Độ ẩm: ");
    Serial.print(humidity);
    Serial.println("%");
    
    Serial.print("Light Strength: ");
    Serial.print(lightStrength);
    Serial.println(" lumen");

    xSemaphoreGive(sensorMutex); // Trả lại sensorMutex = 1
  }
} 
/* Buzzer chạy thụ động */
void buzzerControl(void *) {
  while (1) {
    tone(BUZZER_PIN, 1000); // Kích hoạt âm thanh trên buzzer với tần số 1000Hz
    delay(100); // Dừng trong 100ms
    noTone(BUZZER_PIN); // Tắt âm thanh trên buzzer
    delay(100); // Dừng trong 100ms
  }
}
$abcdeabcde151015202530fghijfghij
BH1750Breakout
BMP180Breakout
RainDropBreakout
HC-SR501Breakout
waterlevelBreakout
soilmoistureBreakout
firesensorBreakout