#include <GxEPD2_BW.h>
#include <GxEPD2_3C.h>
#include <Fonts/FreeMonoBold9pt7b.h>
#include <Fonts/FreeSansBold9pt7b.h>
#include <Fonts/FreeSerif9pt7b.h>
#include <WiFi.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>
#include <time.h>
#include <U8g2_for_Adafruit_GFX.h>
#include <Preferences.h>
#define ENABLE_GxEPD2_GFX 0

// Cấu hình kết nối WiFi
const char* ssid = "Tên Wifi";
const char* password = "Pass Wifi";

// Cấu hình OpenWeatherMap API
const String openWeatherMapApiKey = "api OpenWeatherMap";
const String city = "Ho%20Chi%20Minh";
const String countryCode = "VN";

// Cấu hình chân kết nối màn hình ePaper
#define EPD_BUSY 15  // to EPD BUSY
#define EPD_CS 5    // to EPD CS
#define EPD_DC 0   // to EPD DC
#define EPD_RESET 2 // to EPD Reset
#define EPD_MOSI 23 // to EPD MOSI
#define EPD_SCK 18  // to EPD SCK

// Khởi tạo màn hình - sử dụng model GDEH0154D67
GxEPD2_BW<GxEPD2_154_D67, GxEPD2_154_D67::HEIGHT> display(GxEPD2_154_D67(EPD_CS, EPD_DC, EPD_RESET, EPD_BUSY));

// Biến lưu thông tin thời tiết
String weatherDescription = "";
float temperature = 0;
int humidity = 0;

// Thêm biến để lưu thông tin thời tiết chi tiết hơn
float feels_like = 0;
float wind_speed = 0;
String wind_direction = "";
float rain_chance = 0;

// Thêm biến để lưu thông tin vị trí
String detectedCity = "";
String detectedCountry = "";

// Thêm hàm chuyển đổi UTF-8 sang Unicode
String utf8ToCP1252(String str) {
  String result = "";
  for (unsigned int i = 0; i < str.length(); i++) {
    unsigned char c = str[i];
    if (c < 0x80) {
      result += (char)c;
    } else if (c < 0xE0) {
      unsigned char c2 = str[++i];
      uint16_t ch = ((c & 0x1F) << 6) | (c2 & 0x3F);
      result += (char)ch;
    } else if (c < 0xF0) {
      unsigned char c2 = str[++i];
      unsigned char c3 = str[++i];
      uint16_t ch = ((c & 0x0F) << 12) | ((c2 & 0x3F) << 6) | (c3 & 0x3F);
      result += (char)(ch & 0xFF);
    }
  }
  return result;
}

U8G2_FOR_ADAFRUIT_GFX u8g2_for_adafruit_gfx;

// Thêm hàm để lấy vị trí từ IP
void getLocationFromIP() {
  if (WiFi.status() == WL_CONNECTED) {
    HTTPClient http;
    
    // Sử dụng ipapi.co để lấy thông tin vị trí
    String url = "http://ip-api.com/json/";
    
    Serial.println("Đang lấy thông tin vị trí...");
    http.begin(url);
    int httpCode = http.GET();
    
    if (httpCode == HTTP_CODE_OK) {
      String payload = http.getString();
      DynamicJsonDocument doc(1024);
      DeserializationError error = deserializeJson(doc, payload);
      
      if (!error) {
        detectedCity = doc["city"].as<String>();
        detectedCountry = doc["countryCode"].as<String>();
        
        // Encode tên thành phố cho URL
        detectedCity.replace(" ", "%20");
        
        Serial.println("=== Thông tin vị trí ===");
        Serial.println("Thành phố: " + detectedCity);
        Serial.println("Quốc gia: " + detectedCountry);
      } else {
        Serial.println("Lỗi parse JSON location");
      }
    } else {
      Serial.println("Lỗi kết nối IP API");
    }
    http.end();
  }
}

Preferences preferences;

// Thêm hàm lưu vị trí
void saveLocation() {
  preferences.begin("weather", false);
  preferences.putString("city", detectedCity);
  preferences.putString("country", detectedCountry);
  preferences.end();
}

// Thêm hàm đọc vị trí đã lưu
void loadLocation() {
  preferences.begin("weather", true);
  detectedCity = preferences.getString("city", "");
  detectedCountry = preferences.getString("country", "");
  preferences.end();
  
  if(detectedCity.length() > 0) {
    Serial.println("Đã tải vị trí đã lưu: " + detectedCity + ", " + detectedCountry);
  }
}

void setup() {
  Serial.begin(115200);
  Serial.println("Khởi động hệ thống...");
  
  // Khởi tạo SPI
  Serial.println("Khởi tạo SPI...");
  SPI.begin(EPD_SCK, -1, EPD_MOSI, EPD_CS);
  Serial.println("SPI đã khởi tạo");
  
  // Thêm LED built-in
  pinMode(LED_BUILTIN, OUTPUT);
  digitalWrite(LED_BUILTIN, HIGH);
  
  // Khởi tạo màn hình
  Serial.println("Khởi tạo màn hình ePaper...");
  display.init(115200, true); // Enable diagnostic output
  display.setRotation(1);
  display.setFont(&FreeMonoBold9pt7b);
  display.setTextColor(GxEPD_BLACK);
  Serial.println("Màn hình đã khởi tạo");
  
  // Test màn hình
  testDisplay();
  
  // Clear màn hình khi khởi động
  display.setFullWindow();
  display.firstPage();
  do {
    display.fillScreen(GxEPD_WHITE);
    display.setCursor(10, 30);
    display.print("Dang ket noi...");
  } while (display.nextPage());
  
  // Kết nối WiFi với thông tin debug
  Serial.print("Đang kết nối WiFi");
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
    digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
  }
  digitalWrite(LED_BUILTIN, HIGH);
  Serial.println("\nĐã kết nối WiFi!");
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());
  
  // Tải vị trí đã lưu
  loadLocation();
  
  // Nếu không có vị trí đã lưu, lấy từ IP
  if(detectedCity.length() == 0) {
    getLocationFromIP();
    if(detectedCity.length() > 0) {
      saveLocation();
    }
  }
  
  // Cấu hình thời gian
  Serial.println("Đang đồng bộ thời gian...");
  configTime(7 * 3600, 0, "pool.ntp.org"); // GMT+7

  u8g2_for_adafruit_gfx.begin(display);
}

void loop() {
  digitalWrite(LED_BUILTIN, HIGH);
  Serial.println("\n=== Bắt đầu cập nhật dữ liệu ===");
  
  // Lấy dữ liệu thời tiết trước
  getWeatherData();
  
  // Cập nhật màn hình trong khoảng thời gian chờ
  unsigned long startMillis = millis();
  unsigned long updateInterval = 300000; // 5 phút
  
  while(millis() - startMillis < updateInterval) {
    updateDisplay();
    delay(100); // Delay ngắn để không tốn CPU
  }
  
  digitalWrite(LED_BUILTIN, LOW);
  Serial.println("Hoàn tất chu kỳ cập nhật");
}

void getWeatherData() {
  if (WiFi.status() == WL_CONNECTED) {
    HTTPClient http;
    
    // Sử dụng vị trí đã phát hiện nếu có
    String currentCity = detectedCity.length() > 0 ? detectedCity : city;
    String currentCountry = detectedCountry.length() > 0 ? detectedCountry : countryCode;
    
    String url = "http://api.openweathermap.org/data/2.5/weather";
    url += "?q=" + currentCity + "," + currentCountry;
    url += "&appid=" + openWeatherMapApiKey;
    url += "&units=metric";
    url += "&lang=vi";
    
    Serial.println("URL thời tiết: " + url);
    http.begin(url);
    int httpCode = http.GET();
    
    if (httpCode == HTTP_CODE_OK) {
      String payload = http.getString();
      DynamicJsonDocument doc(2048);
      DeserializationError error = deserializeJson(doc, payload);
      
      if (!error) {
        temperature = doc["main"]["temp"].as<float>();
        feels_like = doc["main"]["feels_like"].as<float>();
        humidity = doc["main"]["humidity"].as<int>();
        wind_speed = doc["wind"]["speed"].as<float>();
        weatherDescription = doc["weather"][0]["description"].as<String>();
        
        // Xác định hướng gió
        float deg = doc["wind"]["deg"].as<float>();
        if(deg >= 337.5 || deg < 22.5) wind_direction = "Bắc";
        else if(deg >= 22.5 && deg < 67.5) wind_direction = "Đông Bắc";
        else if(deg >= 67.5 && deg < 112.5) wind_direction = "Đông";
        else if(deg >= 112.5 && deg < 157.5) wind_direction = "Đông Nam";
        else if(deg >= 157.5 && deg < 202.5) wind_direction = "Nam";
        else if(deg >= 202.5 && deg < 247.5) wind_direction = "Tây Nam";
        else if(deg >= 247.5 && deg < 292.5) wind_direction = "Tây";
        else wind_direction = "Tây Bắc";
        
        Serial.println("=== Thông tin thời tiết ===");
        Serial.println("Nhiệt độ: " + String(temperature, 1) + "°C");
        Serial.println("Cảm giác như: " + String(feels_like, 1) + "°C");
        Serial.println("Độ ẩm: " + String(humidity) + "%");
        Serial.println("Gió: " + String(wind_speed, 1) + "m/s - Hướng: " + wind_direction);
        Serial.println("Thời tiết: " + weatherDescription);
      }
    }
    http.end();
  }
}

void updateDisplay() {
  struct tm timeinfo;
  static String lastTimeStr = "";
  static int lastMinute = -1;
  
  if(getLocalTime(&timeinfo)) {
    char timeStr[9];
    strftime(timeStr, sizeof(timeStr), "%H:%M:%S", &timeinfo);
    
    if(String(timeStr) != lastTimeStr) {
      lastTimeStr = String(timeStr);
      
      bool fullUpdate = (timeinfo.tm_min != lastMinute);
      if(fullUpdate) {
        lastMinute = timeinfo.tm_min;
        display.setFullWindow();
      } else {
        display.setPartialWindow(130, 10, 50, 30);
      }
      
      display.firstPage();
      do {
        if(fullUpdate) {
          display.fillScreen(GxEPD_WHITE);
          
          // Hiển thị thời gian
          display.setFont(&FreeMonoBold9pt7b);
          display.setTextSize(2);
          display.setCursor(20, 35);
          char hourMin[6];
          strftime(hourMin, sizeof(hourMin), "%H:%M", &timeinfo);
          display.print(hourMin);
          
          // Hiển thị giây
          display.setFont(&FreeSansBold9pt7b);
          display.setTextSize(1);
          display.setCursor(130, 35);
          char sec[3];
          strftime(sec, sizeof(sec), "%S", &timeinfo);
          display.print(sec);
          
          // Hiển thị ngày tháng
          display.setFont(&FreeSansBold9pt7b);
          char dateStr[20];
          strftime(dateStr, sizeof(dateStr), "%d/%m/%Y", &timeinfo);
          display.setCursor(40, 60);
          display.print(dateStr);
          
          // Thêm hiển thị vị trí
          display.setCursor(10, 75);
          String locationStr = "";
          if(detectedCity.length() > 0) {
            // Decode URL encoding (thay %20 thành dấu cách)
            String cityName = detectedCity;
            cityName.replace("%20", " ");
            locationStr = cityName + ", " + detectedCountry;
          } else {
            String cityName = city;
            cityName.replace("%20", " ");
            locationStr = cityName + ", " + countryCode;
          }
          display.print("Vi tri: " + locationStr);
          
          // Hiển thị thông tin thời tiết (điều chỉnh vị trí y +15)
          display.setFont(&FreeSansBold9pt7b);
          
          // Nhiệt độ và độ ẩm
          display.setCursor(10, 120);
          display.print("Nhiet do: ");
          display.print(temperature, 1);
          display.print("C");
          
          display.setCursor(10, 140);
          display.print("Do am: ");
          display.print(humidity);
          display.print("%");
          
          // Thông tin gió
          display.setCursor(10, 160);
          display.print("Gio: ");
          display.print(wind_speed, 1);
          display.print("m/s ");
          display.print(wind_direction);
          
          // Mô tả thời tiết
          //display.setCursor(10, 160);
          //String plainDesc = convertToPlainText(weatherDescription);
          //display.print(plainDesc);
        } else {
          // Chỉ cập nhật giây
          display.fillRect(130, 10, 50, 30, GxEPD_WHITE);
          display.setFont(&FreeSansBold9pt7b);
          display.setTextSize(1);
          display.setCursor(130, 35);
          char sec[3];
          strftime(sec, sizeof(sec), "%S", &timeinfo);
          display.print(sec);
        }
      } while (display.nextPage());
    }
  }
}

// Thêm hàm chuyển đổi text có dấu sang không dấu
String convertToPlainText(String input) {
  static const char* diacritic[] = {
    "áàảãạăắằẳẵặâấầẩẫậ", "a",
    "éèẻẽẹêếềểễệ", "e",
    "íìỉĩị", "i",
    "óòỏõọôốồổỗộơớờởỡợ", "o",
    "úùủũụưứừửữự", "u",
    "ýỳỷỹỵ", "y",
    "đ", "d"
  };
  
  String output = input;
  output.toLowerCase();
  
  for(int i = 0; i < 14; i += 2) {
    String accents = diacritic[i];
    String without = diacritic[i + 1];
    for(unsigned int j = 0; j < accents.length(); j++) {
      output.replace(String(accents[j]), without);
    }
  }
  
  return output;
}

// Thêm hàm test màn hình
void testDisplay() {
  Serial.println("Bắt đầu test màn hình...");
  
  display.setFullWindow();
  display.firstPage();
  do {
    display.fillScreen(GxEPD_WHITE);
    Serial.println("Đã clear màn hình");
    
    display.setCursor(10, 30);
    display.print("Test man hinh");
    Serial.println("Đã in text test");
    
    // Vẽ một số hình cơ bản
    display.drawLine(10, 40, 100, 40, GxEPD_BLACK);
    display.drawRect(10, 50, 90, 30, GxEPD_BLACK);
    display.fillRect(110, 50, 40, 30, GxEPD_BLACK);
    Serial.println("Đã vẽ các hình test");
    
  } while (display.nextPage());
  
  Serial.println("Test màn hình hoàn tất");
  delay(2000);
} 

Có trong danh mục:

Arduino,