#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);
}
Bài viết trước
[Resolved] xfs_quota: cannot set limits: Function not implemented Directadmin
Bài tiếp theo