// Cách chú thích đa dòng (/* ... */)
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <WiFi.h>
#include <WiFiManager.h> // Thư viện WiFiManager
#include <time.h>
#include <EEPROM.h> // Thêm thư viện EEPROM
/////////////////////// ĐOẠN CODE NÀY TẠO WEBSERVER ĐỂ CẬP NHẬT CODE CHO ESP QUA WEBSERVER DÙNG PHƯƠNG THỨC OTA ///////////////////////////
#include <WiFiClient.h>
#include <WebServer.h>
#include <ESPmDNS.h>
#include <Update.h>
#include <NTPClient.h> //Moi them vao
#include <WiFiUdp.h> //Moi them vao
// Khai báo biến lưu tên máy chủ là "esp32" có thể biến hoặc tên máy chủ
const char* host = "esp32";
WebServer server(80);
/////////////////////////////////////////
// Tạo trang web để login vào Webserver nhằm bảo mật thông tin người dùng trên Webserver
/*
* Login page
*/
const char* loginIndex =
"<form name='loginForm'>"
"<table width='20%' bgcolor='A09F9F' align='center'>"
"<tr>"
"<td colspan=2>"
"<center><font size=4><b>ESP32 Login Page</b></font></center>"
"<br>"
"</td>"
"<br>"
"<br>"
"</tr>"
"<tr>"
"<td>Username:</td>"
"<td><input type='text' size=25 name='userid'><br></td>"
"</tr>"
"<br>"
"<br>"
"<tr>"
"<td>Pasword:</td>"
"<td><input type='Password' size=25 name='pwd'><br></td>"
"<br>"
"<br>"
"</tr>"
"<tr>"
"<td><input type='submit' onclick='check(this.form)' value='Login'></td>"
"</tr>"
"</table>"
"</form>"
"<script>"
"function check(form)"
"{"
"if(form.userid.value=='Chuongbao' && form.pwd.value=='kgc863530*')" // Có thể thay đổi username 'thinh' và password 'Thinh382014' để bảo mật
"{"
"window.open('/serverIndex')"
"}"
"else"
"{"
" alert('Tài khoản hoặc mật khẩu không đúng')/*displays error message*/"
"}"
"}"
"</script>";
// Tạo Webserver
/*
* Server Index Page
*/
const char* serverIndex =
"<script src='https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js'></script>"
"<style>"
"body {"
" text-align: center;" // Căn giữa trang web
"}"
"#designInfo {"
" font-size: 18px;"
" color: #666666;"
"}"
"#upload_form input[type='file'], #upload_form input[type='submit'] {"
" display: inline-block;" // Hiển thị các input cùng dòng
"}"
"</style>"
"<h1 style='color:red; font-size:24px;'>SERVER UPDATES FIRMWARE FOR ESP BY OVER THE AIR</h1>" // Đây là tiêu đề có thể thay đổi theo nội dung người dùng
"<h2 id='designInfo'>Designed by: Nguyen Vu Sy</h2>" // Dòng thông tin người thiết kế 'nổ lấy le hihi'
"<form method='POST' action='#' enctype='multipart/form-data' id='upload_form'>"
"<input type='file' name='update'>" // Mục chọn file
"<input type='submit' value='Update'>" // Nút "Update"
"</form>"
"<div id='prg'>progress: 0%</div>"
"<script>"
"$('form').submit(function(e){"
"e.preventDefault();"
"var form = $('#upload_form')[0];"
"var data = new FormData(form);"
" $.ajax({"
"url: '/update',"
"type: 'POST',"
"data: data,"
"contentType: false,"
"processData:false,"
"xhr: function() {"
"var xhr = new window.XMLHttpRequest();"
"xhr.upload.addEventListener('progress', function(evt) {"
"if (evt.lengthComputable) {"
"var per = evt.loaded / evt.total;"
"$('#prg').html('Progress: ' + Math.round(per*100) + '%');"
"}"
"}, false);"
"return xhr;"
"},"
"success:function(d, s) {"
"console.log('success!')"
"},"
"error: function (a, b, c) {"
"}"
"});"
"});"
"</script>";
////////////////////////////////// KẾT THÚC CODE BỔ SUNG TẠO WEBSERVER CẬP NHẬT CODE CHO ESP DÙNG PHƯƠNG THỨC OTA //////////////////////////////////
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
// Đây là hằng số định nghĩa chân reset của màn hình OLED. Trong trường hợp này, giá trị -1 được sử dụng để chỉ ra rằng không có chân nào được sử dụng để reset màn hình
#define OLED_RESET -1
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
// biến hằng số định nghĩa các chân GPIO mà 2 chuông được kết nối đến
const int pinChuong_th = 18;// chuông xưởng (2)
const int pinChuong_lt = 19;// chuông lý thuyết (4)
bool bell1Rang = false; // Biến cờ để đánh dấu việc chuông 1 đã reo trong tiết học hiện tại
bool bell2Rang = false; // Biến cờ để đánh dấu việc chuông 2 đã reo trong tiết học hiện tại
bool bell3Rang = false;
bool showTimeInfo = true; // Biến để xác định chế độ hiển thị, true: hiển thị thông tin thời gian, false: hiển thị thông tin tuyển sinh
unsigned long lastDisplayChangeTime = 0; // Biến để lưu thời điểm cuối cùng thực hiện việc chuyển đổi hiển thị
// Khai báo cấu trúc cho chuông thực hành
struct Tietchuongthuchanh {
int startHour;
int startMin;
int endHour;
int endMin;
};
// Khai báo cấu trúc cho chuông lý thuyết
struct Tietchuonglythuyet {
int startHour;
int startMin;
int endHour;
int endMin;
};
// Khai báo cấu trúc cho chuông ra chơi
struct Tietchuongrc {
int startHour;
int startMin;
int endHour;
int endMin;
};
// Khai báo mảng cho chuông thực hành
Tietchuongthuchanh baotiet_th[] = {
{7, 0, 8, 59},// Giờ vào xưởng buổi sáng
{9, 0, 9, 24},// Giờ ra chơi
{9, 25, 11, 9},// Giờ vào học tiếp
{11, 10, 11, 14},// Hết giờ buổi sáng
{13, 0, 14, 59},// Giờ vào xưởng buổi chiều
{15, 0, 15, 14},// Giờ ra chơi
{15, 15, 16, 59},// Giờ vào học
{17, 0, 17, 20},// Hết giờ buổi chiều*/
};
// Khai báo mảng cho chuông ra choi
Tietchuongrc baotiet_rc[] = {
{8, 30, 8, 54},// ra choi sang 1
{10, 25, 10, 29},// ra choi sang 2
{11, 15, 11, 20},// Ket thuc buoi sang
{14, 30, 14, 39},// ra choi chieu 1
{16, 10, 16, 14},// Ra choi chieu 2
{17, 0, 17, 20},// Ket thuc buoi chieu
};
Tietchuonglythuyet baotiet_lt[] = {
{7, 0, 7, 44},// tiết 1
{7, 45, 8, 54},// tiết 2
{8, 55, 9, 39},// tiết 3
{9, 40, 10, 29},// tiết 4
{10, 30, 11, 14},// tiết 5
{13, 0, 13, 44},// tiết 6
{13, 45, 14, 39},// tiết 7
{14, 40, 15, 24},// tiết 8
{15, 25, 16, 14},// tiết 9
{16, 15, 17, 20},// tiết 10
};
void setup() {
//connect from Arduino (on an ESP32) device wokwisimulation
Serial.begin(9600);
Serial.print("Connecting to WiFi");
WiFi.begin("Wokwi-GUEST", "", 6);
while (WiFi.status() != WL_CONNECTED) {
delay(100);
Serial.print(".");
}
Serial.println(" Connected!");
// giao tiếp series với tốc độ baud rate là 115200 bits mỗi giây. Thiết lập môi trường giao tiếp để gửi và nhận dữ liệu từ và đến thiết bị thông qua cổng serial
Serial.begin(115200);
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
Serial.println(F("SSD1306 allocation failed"));
for (;;)
;
}
pinMode(pinChuong_th, OUTPUT);
pinMode(pinChuong_lt, OUTPUT);
digitalWrite(pinChuong_th, LOW); //Chay mo phong de LOW
digitalWrite(pinChuong_lt, LOW); //Chay mo phong de LOW
WiFiManager wifiManager;
wifiManager.autoConnect("AutoConnectAP");
Serial.println("Connected to WiFi!");// In ra thông báo "Connected to WiFi!" qua cổng serial để xác nhận rằng thiết bị đã kết nối thành công với mạng WiFi
configTime(7 * 3600, 0, "pool.ntp.org"); // Tham số 1 là sự chênh lệch thời gian múi giờ UTC. Ở đây, 7 * 3600 biểu thị cho múi giờ UTC+7 (múi giờ Việt Nam). Tham số 2 sự chênh lệch phút so với UTC đặt là 0. Tham số 3 là địa chỉ của máy chủ NTP, trong trường hợp này là "pool.ntp.org".
//configTime(7 * 3600, 0, "time.google.com");
/////////////////////// ĐOẠN CODE BỔ SUNG VÀO HÀM SETUP DÙNG ĐỂ NẠP CODE CHO ESP DÙNG PHƯƠNG THỨC OTA ///////////////////////////
Serial.println("Connected to WiFi! IP address: " + WiFi.localIP().toString());
if (!MDNS.begin(host)) {
Serial.println("Error setting up MDNS responder!");
while (1) {
delay(1000);
}
}
Serial.println("mDNS responder started");
server.on("/", HTTP_GET, []() {
server.sendHeader("Connection", "close");
server.send(200, "text/html", loginIndex);// Đường dẫn "/" trả về nội dung loginIndex dưới dạng text/html
});
server.on("/serverIndex", HTTP_GET, []() {
server.sendHeader("Connection", "close");
server.send(200, "text/html", serverIndex);
});
server.on("/update", HTTP_POST, []() {
server.sendHeader("Connection", "close");
server.send(200, "text/plain", (Update.hasError()) ? "FAIL" : "OK");
ESP.restart();
}, []() {
HTTPUpload& upload = server.upload();
if (upload.status == UPLOAD_FILE_START) {
Serial.printf("Update: %s\n", upload.filename.c_str());
if (!Update.begin(UPDATE_SIZE_UNKNOWN)) {
Update.printError(Serial);
}
} else if (upload.status == UPLOAD_FILE_WRITE) {
if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) {
Update.printError(Serial);
}
} else if (upload.status == UPLOAD_FILE_END) {
if (Update.end(true)) {
Serial.printf("Update Success: %u\nRebooting...\n", upload.totalSize);
} else {
Update.printError(Serial);
}
}
});
struct tm lastSyncTime = getTimeFromEEPROM();
if (lastSyncTime.tm_year >= 100) { // Kiểm tra xem đã lưu trữ thời gian trước đó trong EEPROM chưa
configTime(0, 0, "pool.ntp.org");
//configTime(0, 0, "time.google.com");
// Chờ đến khi thời gian được đồng bộ
while (!getLocalTime(&lastSyncTime)) {
delay(1000);
}
// Lưu thời gian mới vào EEPROM
saveTimeToEEPROM(lastSyncTime);
}
// Khởi động máy chủ web (WebServer) để bắt đầu nhận các yêu cầu HTTP từ các thiết bị khác
server.begin();
}
///////////////////////////////// KẾT THÚC CODE BỔ SUNG TRONG HÀM SETUP //////////////////////////////////
void loop() {
// Dừng thực thi trong 1000 milliseconds (1 giây), giữa các lần lặp, để tránh việc lặp quá nhanh và tăng tải CPU không cần thiết
delay(1000);
// Khai báo một biến cấu trúc tm để lưu trữ thông tin thời gian
struct tm timeinfo;
// Sử dụng hàm getLocalTime(&timeinfo) để lấy thời gian cục bộ từ hệ thống và lưu vào biến timeinfo. Nếu không thể lấy được thời gian in ra thông báo "Failed to obtain time" và thoát khỏi hàm loop().
if (!getLocalTime(&timeinfo)) {
Serial.println("Failed to obtain time");
return;
}
// đoạn mã này hiển thị thông tin về trường và tiêu đề hệ thống chuông của trường, thông tin về thời gian và ngày hiện tại.
display.clearDisplay(); // Clear the display only once
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(10, 0);
display.println(F("Cao dang Kien Giang"));
display.setCursor(10, 20);
display.println(F("Chuong bao tiet hoc"));
display.setCursor(18, 35);
display.print(F("Gio: "));
display.print(timeinfo.tm_hour);
display.print(":");
if (timeinfo.tm_min < 10)
{
display.print("0");
}
display.print(timeinfo.tm_min);
display.print(":");
if (timeinfo.tm_sec < 10) {
display.print("0");
}
display.println(timeinfo.tm_sec);
display.setCursor(17, 50);
display.print(F("Ngay: "));
display.print(timeinfo.tm_mday);
display.print("/");
display.print(timeinfo.tm_mon + 1);
display.print("/");
display.println(timeinfo.tm_year + 1900);
checkAndDisplayLesson(timeinfo);
display.display();
checkAndRingAlarm(timeinfo);
/////////////////////// ĐOẠN CODE NÀY BỔ SUNG VÀO HÀM LOOP DÙNG ĐỂ NẠP QUA OTA ///////////////////////////
server.handleClient();
delay(1);
///////////////////////////////// KẾT THÚC CODE BỔ SUNG TRONG HÀM LOOP //////////////////////////////////
}
// Kiểm tra và chỉ hiển thị tiết học lý thuyết lên màn hình OLED
void checkAndDisplayLesson(struct tm timeinfo) {
bool lessonFound = false;
for (int i = 0; i < sizeof(baotiet_lt) / sizeof(baotiet_lt[0]); i++) {
if ((timeinfo.tm_hour > baotiet_lt[i].startHour ||
(timeinfo.tm_hour == baotiet_lt[i].startHour && timeinfo.tm_min >= baotiet_lt[i].startMin)) &&
(timeinfo.tm_hour < baotiet_lt[i].endHour ||
(timeinfo.tm_hour == baotiet_lt[i].endHour && timeinfo.tm_min <= baotiet_lt[i].endMin))) {
display.clearDisplay(); // Nếu một tiết học được tìm thấy, màn hình OLED sẽ được xóa và sau đó thông tin về tiết học được hiển thị ra màn hình
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(23, 0);
display.print(F("Gio: "));
display.print(timeinfo.tm_hour);
display.print(":");
if (timeinfo.tm_min < 10) {
display.print("0");
}
display.print(timeinfo.tm_min);
display.print(":");
if (timeinfo.tm_sec < 10) {
display.print("0");
}
display.println(timeinfo.tm_sec);
display.setTextSize(3);
display.setCursor(0, 25);
display.print("Tiet ");
display.println(i + 1);
display.display();
lessonFound = true;
break;
}
}
if (!lessonFound) {
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(10, 0); // tọa độ x, y chữ hiện thị
display.println(F("Cao dang Kien Giang"));
display.setCursor(10, 20);
display.println(F("Chuong bao tiet hoc"));
display.setCursor(18, 35);
display.print(F("Gio: "));
display.print(timeinfo.tm_hour);
display.print(":");
if (timeinfo.tm_min < 10) {
display.print("0");
}
display.print(timeinfo.tm_min);
display.print(":");
if (timeinfo.tm_sec < 10) {
display.print("0");
}
display.println(timeinfo.tm_sec);
display.setCursor(17, 50);
display.print(F("Ngay: "));
display.print(timeinfo.tm_mday);
display.print("/");
display.print(timeinfo.tm_mon + 1);
display.print("/");
display.println(timeinfo.tm_year + 1900);
display.display();
}
}
//Doan nay da kiemr tra OK không lỗi
// Vòng lặp kiểm tra mốc thời gian thực hành và các tiết học lý thuyết. Nếu thỏa thì gọi hàm chuông:
void checkAndRingAlarm(struct tm timeinfo) {
bool bell1Found = false;
bool bell2Found = false;
bool bell3Found = false;
// Kiểm tra các mốc thời gian thực hành trong mảng
for (int i = 0; i < sizeof(baotiet_th) / sizeof(baotiet_th[0]); i++) {
if (timeinfo.tm_hour == baotiet_th[i].startHour && timeinfo.tm_min == baotiet_th[i].startMin) {
if (!bell1Rang) {
ringBell_th();
bell1Rang = true;
}
bell1Found = true;
break; // Kết thúc vòng lặp ngay khi tìm thấy mốc thời gian phù hợp
}
}
// Kiểm tra các tiết học lý thuyết trong mảng
for (int i = 0; i < sizeof(baotiet_lt) / sizeof(baotiet_lt[0]); i++) {
if (timeinfo.tm_hour == baotiet_lt[i].startHour && timeinfo.tm_min == baotiet_lt[i].startMin) {
if (!bell2Rang) {
ringBell_lt();
bell2Rang = true;
}
bell2Found = true;
break; // Kết thúc vòng lặp ngay khi tìm thấy tiết học lý thuyết phù hợp
}
}
// Kiem tra cac tiet ra choi
for (int i = 0; i < sizeof(baotiet_rc) / sizeof(baotiet_rc[0]); i++) {
if (timeinfo.tm_hour == baotiet_rc[i].startHour && timeinfo.tm_min == baotiet_rc[i].startMin) {
if (!bell3Rang) {
ringBell_rc();
bell3Rang = true;
}
bell3Found = true;
break; // Kết thúc vòng lặp ngay khi tìm thấy tiết học lý thuyết phù hợp
}
}
// Đặt lại trạng thái của chuông nếu không có tiết học nào phù hợp
if (!bell1Found) {
bell1Rang = false;
}
if (!bell2Found) {
bell2Rang = false;
}
if (!bell3Found) {
bell3Rang = false;
}
}
// Kích hoạt chuông thực hành
void ringBell_th() {
// Chu kỳ lặp lại 3 lần
for (int i = 0; i < 3; i++) {
digitalWrite(pinChuong_th, LOW);
delay(2000);
digitalWrite(pinChuong_th, HIGH);
delay(1000);
}
}
// Kích hoạt chuông lý thuyết
void ringBell_lt() {
// Chu kỳ lặp lại 3 lần
for (int i = 0; i < 3; i++) {
digitalWrite(pinChuong_lt, LOW);
delay(2000);
digitalWrite(pinChuong_lt, HIGH);
delay(1000);
}
}
// Kích hoạt chuông ra chơi
void ringBell_rc() {
// Chu kỳ lặp lại 2 lần
for (int i = 0; i < 1; i++) {
digitalWrite(pinChuong_lt, LOW);
delay(2000);
digitalWrite(pinChuong_lt, HIGH);
delay(1000);
}
}
// Hàm lưu trữ thời gian vào EEPROM
void saveTimeToEEPROM(struct tm timeinfo) {
int addr = 0; // Địa chỉ bắt đầu lưu trữ trong EEPROM
for (int i = 0; i < sizeof(struct tm); i++) {
EEPROM.write(addr + i, *((char*)&timeinfo + i));
}
EEPROM.commit(); // Lưu thay đổi vào EEPROM
}
// Hàm đọc thời gian từ EEPROM
struct tm getTimeFromEEPROM() {
struct tm timeinfo;
int addr = 0; // Địa chỉ bắt đầu lưu trữ trong EEPROM
for (int i = 0; i < sizeof(struct tm); i++) {
*((char*)&timeinfo + i) = EEPROM.read(addr + i);
}
return timeinfo;
}
// Hàm đồng bộ lại thời gian từ dịch vụ NTP
void syncTimeFromNTP() {
struct tm timeinfo = getTimeFromEEPROM(); // Lấy thời gian từ EEPROM
configTime(0, 0, "pool.ntp.org"); // Cấu hình thời gian từ dịch vụ NTP
//configTime(0, 0, "time.google.com");
// Chờ cho đến khi thời gian được đồng bộ thành công
while (!getLocalTime(&timeinfo)) {
delay(1000);
}
saveTimeToEEPROM(timeinfo); // Lưu thời gian mới vào EEPROM
}