//======================================================================================================================================
// INCLUDE LIBARY AND HEADER FILE
//======================================================================================================================================
#include <Wire.h> // [Wire Libary for I2C].
#include <LiquidCrystal_I2C.h> // [I2C Libary for LCD 20x4].
#include <Preferences.h> // FOR EROM MEMORY.
#include <WiFi.h> // [ESP32] USE WiFi LIBRARY.
#include <HTTPClient.h> // [ESP32] USE HTTPClient.
#include <WiFiClientSecure.h> // FOR HTTPS SUPPORT.
#include "time.h" // TIME HEADER FILE FOR PI SYSTEMS.
#include "base64.h" // BASE 64 HEADER FILE FOR PI SYSTEMS.
//======================================================================================================================================
// DELCARE VARIABLE
//======================================================================================================================================
//**************************************************************** RGB *****************************************************************
#define RXD2 16 // ESP32 RX (CONNECT TX BY EZO).
#define TXD2 17 // ESP32 TX (CONNECT RX BY EZO).
struct RGBValue { // STRUCTURE FOR GET R, G, B VALUE FROM EZO.
int r; // [r] INT DATA TYPE.
int g; // [g] INT DATA TYPE.
int b; // [b] INT DATA TYPE.
bool valid; // [valid] BOOL FLAG.
};
struct LastestRGB { // STRUCTURE FOR GET LASTEST R, G, B VALUES TO CALCULATE.
int lastr; // [lastr] INT DATA TYPE.
int lastg; // [lastg] INT DATA TYPE.
int lastb; // [lastb] INT DATA TYPE.
}lastestRGB;
float ICUMSA = 0.0; // DECLARE VARIABLE VALUE IN FLOAT FOR KEEPING CALCULATE [ICUMSA].
RGBValue lastRGB; // KEEP SANPSHOT.
String inputBufferRGB = ""; // KEEP BUFFER.
bool snapShot = false; // FLAG.
// String sendEZOCommand(const String &cmd, unsigned long timeoutMs); // FUNCTION DEF.
// RGBValue snapRGB(bool verbose); // FUNCTION DEF.
// void calibrateSensor(); // FUNCTION DEF.
// void handleUserCommand(String cmd); // FUNCTION DEF.
//**************************************************************** LCD *****************************************************************
LiquidCrystal_I2C lcd(0x27, 20, 4); // CONFIGURATION LCD ADDRESS 0X27.
//******************************************************* PUSH BOTTON SWITCH ***********************************************************
const int btnCount = 6; // NUMBER OF BUTTON.
const int btnPin[btnCount] = {14, 27, 26, 25, 33, 32}; // BUTTON 5 PIN.
const String btnNames[6] = {"SELECT", "EXIST", "UP", "DOWN", "LEFT", "RIGHT"}; // NAME OF BUTTON PIN .
const int modePin = 13; // DECLARE PIN OF SELECTOR (MAN/AUTO).
//*************************************************** OUTPUT PIN DRIVE RELAY ***********************************************************
const int outputCount = 5; // NUMBER OF OUTPUT.
const int outputPin[outputCount] = {15, 2, 4, 18, 19}; // OUTPUT PIN.
String sequenceStepRemark[7] = {"CY ON","CLN","AIR","CY OFF","SMP","PRE","READ"}; // STEP OF SEQUENCE.
int btnState[6], lastBtnState[6]; // KEEP BUTTON STATE.
unsigned long lastDebounce[6]; // KEEP LAST DEBOUNCE TIME.
int ledStateManual[outputCount] = {LOW, LOW, LOW, LOW, LOW}; // OUTPUT STATE TO MAN.
bool output4State = LOW, output5State = LOW; // STATE OUTPUT.
unsigned long lastOutput4Press = 0, lastOutput5Press = 0; // CHECK PRESS BUTTON.
unsigned long output4TurnOffTime = 0, output5TurnOffTime = 0; // CHECK TURN OFF TIME.
//*************************************************** COUNT DOWN STEP SEQUENCE ***********************************************************
unsigned long stepDuration = 0; // เวลาของ step ปัจจุบัน (ms)
unsigned long stepStartTime = 0; // เวลาเริ่ม step
bool stepCountdownActive = false; // flag ว่ากำลังนับอยู่
bool sendingToPiFlag = false;
int sequenceStep = 0; // INDEX SEQUENCE.
unsigned long lastStepTime = 0; // INDEX LAST STEP.
bool sequenceRunning = false; // FLAG CHECK STEP RUNNING.
//********************************************************* TIME CONSTANTS **************************************************************
const unsigned long debounceDelay = 50; // debounceDelay OF BUTTON.
const unsigned long outputDebounceDelay = 200;
const unsigned long outputPulseWidth = 500;
const unsigned long wifiCheckInterval = 5000; // DELAY FOR WIFI CHECKING.
//********************************************************* MENU STATE **************************************************************
int currentMenu = 0; // KEEP CURRENT MENU, INITAIL IS 0 : HOME
bool inMenu = false, inSubMenu = false, inSubSubMenu = false, editingDigits = false; // FLAG CHECK STATUS.
bool inSetPointRangeMenu = false; // FLAG SET POINT RANGE FUNCTION.
String activeLabel = ""; // KEEP ACTIVE LABEL.
// Menu Configuration
const int menuCount = 8; // NUMBER MENU.
const String MENU_ITEMS[menuCount] = { // MENU NAME.
"1.CYLINDER PERIOD", "2.SAMPLING TIME", "3.CLEANING",
"4.AIR PUEGE TIME", "5.POINT RANGE", "6.CALIBRATE",
"7.EQUATION", "8.WiFi CONNECT"
};
int currentMenuIndex = 0; // INDEX MENU.
int menuScrollOffset = 0; // INDEX SCROLL OFFSET.
int selectedIndex = 0; // INDEX SELECT.
int subSubMenuIndex = 0; // INDEX SUBMENU.
int subMenuScrollOffest = 0;
int subMenuScrollOffset = 0;
static String subOptions[6] = {"UP HIGH", "UP LOW", "MID HIGH","MID LOW","UNDER HIGH","UNDER LOW"};
// Digit Input
const int digitTotal = 5; // INDEX DIGIT [0 0 0 0 0].
int digitInput[digitTotal] = {0, 0, 0, 0, 0}; // INITIAL INDEX.
int selectedDigit = 0; // INDEX POINT.
bool isNegative = false; // FLAG NEGATIVE NUMBER [FLAS(+) TRUE(-)].
bool isEditingSign = false; // FLAG CHECK EDIT MODE.
// Mode and Settings
String modeOptions[2] = {"AUTO","MAN"}; // MANUAL/AUTO SELECT.
int modeStatus; // ค่าปัจจุบัน
int lastStableStateSelector = 1;
unsigned long lastDebounceTimeSelector = 0;
const unsigned long debounceDelaySelector = 50;
int bufferOffset = 0; // KEEP BUFFER OFFSET.
//********************************************************* PI/WIFI CONNECT **************************************************************
const char* ssid = "OPPO Reno12 5G"; // SSID WIFI.
const char* password = "210244mas"; // PASSWORD WIFI.
bool wifiConnected = false; // FLAG CHECK WIFI CONNECT.
unsigned long lastWifiCheck = 0; // KEEP TIME FOR LOSS CONNECT WIFI.
const char* pi_url = "https://piazu.mitrphol.com/piwebapi/streams/F1DP8tjJ9B_JP0eVL3P0Petkpg0jkAAATVBBWlUtUElEQ0RCXDkwOTEtQ0hLLU1MQTFFTENFTEMwMURSSTAxLU1PVE1MTUlMMDAwNTVUMw/value"; // URL
const char* pi_username = "piadministrator"; // PI USERNAME.
const char* pi_password = "ZAQ!23wsx"; // PI PASSWORD.
const char* ntpServer = "pool.ntp.org"; // SYNC TIME BY NTP FOR CORRECTLY TIMESTAMP.
const long gmtOffset_sec = 0; // NO GMT OFFSET.
const int daylightOffset_sec = 0; // NO DAYLIGHT OFFSET.
//********************************************************* PERSISTENT STORAGE **************************************************************
Preferences preferences; // CALL [PREFERENCE FOR PERSISTENT STORAGE]
struct Settings { // STRUCTURE SET DEFAULT VALUES.
int CylinderValue = 0; // LET settings.CylinderValue = 0
int SamplingValue = 0; // LET settings.SamplingValue = 0
int CleaningValue = 0; // LET settings.CleaningValue = 0
int AirPurgeValue = 0; // LET settings.AirPurgeValue = 0
int UpHighRangeValue = 0; // LET settings.UpHighRange = 0
int UpLowRangeValue = 0; // LET settings.HightRangeValue = 0
int MidHighRangeValue = 0; // LET settings.LowRangeValue = 0
int MidLowRangeValue = 0; // LET settings.UpHighRange = 0
int UnderHighRangeValue = 0; // LET settings.HightRangeValue = 0
int UnderLowRangeValue = 0; // LET settings.LowRangeValue = 0
int InterceptValue = 0; // LET settings.InterceptValue = 0
int SloveValue = 0; // LET settings.SloveValue = 0
} settings;
//======================================================================================================================================
// PERSISTENT STORAGE FUNCTIONS
//======================================================================================================================================
//======================================================================================================================================
// SETUP
//======================================================================================================================================
void setup() {
Serial.begin(115200);
Serial2.begin(9600, SERIAL_8N1, RXD2, TXD2);
delay(200);
for (int i = 0; i < btnCount; i++) { // COUNTER LOOP TO INITIALIZE BUTTON SWITCH 6 ITEM.
pinMode(btnPin[i], INPUT_PULLUP); // SET PIN TO BE INPUT PULLUP MODE.
lastBtnState[i] = HIGH; // SET INITIAL BUTTON STATE TO BE [HIGH].
lastDebounce[i] = 0; // SET LAST DEBOUNCE TIME TO BE 0.
}
for (int i = 0; i < outputCount; i++) { // COUNTER LOOP TO INITIALIZE OUTPUT PIN.
pinMode(outputPin[i], OUTPUT); // SET PIN TO BE OUTPUT MODE.
digitalWrite(outputPin[i], LOW); // SET INITIAL OUTPUT TO BE [LOW].
}
lcd.init(); // INITIALIZE LCD.
lcd.backlight(); // OPEN LCD BLACKGROUND LIGHT.
pinMode(modePin, INPUT_PULLUP); // SET PIN (MANUAL/AUTO) TO BE INPUT MODE.
checkWifiStatus(); // CALL [checkWifiStatus()] TO CHECK WIFI CONNECT OR NOT.
lcdShowHome(); // CALL [lcdShowHome()] TO VISUALIZE LCD HOME SCREEN.
}
//======================================================================================================================================
// MAIN LOOP
//======================================================================================================================================
void loop() {
String btn = getKey(); // CALL [getKey()] -> [btn] WHEN BUTTON WAS PRESSED.
int currentMode = updateMode(); // เรียกใช้ฟังก์ชัน
static int lastMode = -1;
if (currentMode != lastMode) {
if (!inMenu && !inSubMenu && !editingDigits) { // UPDATE HOME SCREEN TO SHOW CURRENT MODE.
Serial.print("modeStatus = ");
Serial.println(currentMode);
lastMode = currentMode;
lcdShowHome();
}
}
if (millis() - lastWifiCheck > wifiCheckInterval) { // CHECK WIFI STATUS PERIODICALLY.
//checkWifiStatus(); // CALL [checkWifiStatus()] TO CHECK WIFI CONNECT OR NOT.
lastWifiCheck = millis(); // UPDATE LASTET TIME TO CHECK WIFI.
}
//checkOutputPulseTimeout(); // CHECK OUTPUT PULSE TIME OUT.
if (btn != "") { // IS BUTTON PRESS ?
handleInput(btn); // CALL [handleInput(btn)] HANDLE OUTPUT CONTOL (ONLY MANUAL MODE AND NOT IN MENU)
}
//updateLampsStatus(); // UPDATE LAMP STATUS FOR MANUAL MODE AND AUTO MODE.
if (modeStatus == 0) { // RUN SEQUENCE IF AUTO MODE.
//runSequence(); // CALL [runSequence()].
} else if(modeStatus == 1){ // IN MANUAL MODE.
if (sequenceRunning) { // WAS [sequenceRunning] FLAG TRUE ?
//resetSequence(); // CALL [resetSequence()] FOR RESET FLAG TO FALSE.
}
}
while (Serial.available()) {
char c = Serial.read();
if (c == '\r' || c == '\n') {
if (inputBufferRGB.length() > 0) {
//handleUserCommand(inputBufferRGB);
inputBufferRGB = "";
}
} else {
//inputBufferRGB += c;
}
}
}
//======================================================================================================================================
// RGB GROUP FUNCTIONS
//======================================================================================================================================
//======================================================================================================================================
// COUNT DOWN STEP FUNCTIONS
//======================================================================================================================================
//======================================================================================================================================
// SEQUENCE RUNNING FUNCTIONS
//======================================================================================================================================
//======================================================================================================================================
// INPUT FUNCTIONS
//======================================================================================================================================
String getKey() { // GETKEY FUNCTION FOR RECEIVE USER PRESSED BUTTON.
int pressedBtn = debounce(); // GET KEY BY DEBOUNCE.
return (pressedBtn != -1) ? btnNames[pressedBtn] : ""; // CHECK PRESSED BUTTON TO SHOW BUTTON NAME.
}
int debounce() { // DEBOUNCE FUNCRION.
for (int i = 0; i < 6; i++) {
int reading = digitalRead(btnPin[i]); // READ SIGNAL FROM BUTTON PIN.
if (reading != lastBtnState[i]) { // CHECK LASTET STATE OF BUTTON NOT BE [LOW] (PRESSED : LOW, NOT PRESS : HIGH).
lastDebounce[i] = millis(); // KEEP TIME.
}
if ((millis() - lastDebounce[i]) > debounceDelay) { // CHECK INTERVAL TIME BETWEEN SCOND PRESSED.
if (reading != btnState[i]) { // CHECK BUTTON PRESSED OR NOT AND IF NOT EQUAL [btnState[i]].
btnState[i] = reading; // KEEP LASTEST BUTTON PRESSED TO [btnState[i]].
if (btnState[i] == LOW) { // IF [btnState[i]] (PRESSED).
lastBtnState[i] = reading; // [reading] -> [lastBtnState[i]].
return i; // RETURN BUTTON PIN.
}
}
}
lastBtnState[i] = reading; // IF FINISH INTERVAL TIME LET [reading] -> [lastBtnState[i]].
}
return -1; // RETURN -1 (FOR CHECK IF GOT ERROR IN PUSH BUTTON SWITCH).
}
//======================================================================================================================================
// OUTPUT ACTION FUNCTIONS
//======================================================================================================================================
//======================================================================================================================================
// HANDLE MODE/MENU FUNCTIONS
//======================================================================================================================================
int updateMode() {
int reading = digitalRead(modePin);
if (reading != lastStableStateSelector) {
lastDebounceTimeSelector = millis(); // เริ่มจับเวลาใหม่
lastStableStateSelector = reading; // อัปเดตเฉพาะตอนเจอการเปลี่ยน
}
if ((millis() - lastDebounceTimeSelector) > debounceDelaySelector) {
if (reading != modeStatus) {
modeStatus = reading;
}
}
return (modeStatus == HIGH ? 0 : 1);
}
void handleInput(String btn) {
// Handle LED controls (only in Manual mode and not in menu)
if (modeStatus == 1 && !inMenu && !inSubMenu && !editingDigits && currentMenu == 0) {
if (btn == "EXIST") {
ledStateManual[0] = !ledStateManual[0];
Serial.println("LED 0 (EXIST) toggled: " + String(ledStateManual[0]));
return;
} else if (btn == "UP") {
ledStateManual[1] = !ledStateManual[1];
Serial.println("LED 1 (UP) toggled: " + String(ledStateManual[1]));
return;
} else if (btn == "DOWN") {
ledStateManual[2] = !ledStateManual[2];
Serial.println("LED 2 (DOWN) toggled: " + String(ledStateManual[2]));
return;
} else if (btn == "LEFT") {
//handleIncreasePulse();
return;
} else if (btn == "RIGHT") {
//handleDecreasePulse();
return;
}
}
// Handle digit input
if (editingDigits) {
handleDigitInput(btn, activeLabel);
return;
}
// Handle submenu
if (inSubMenu && !inMenu) {
if (btn == "EXIST") {
inMenu = true;
inSubMenu = false;
inSubSubMenu = false;
lcdShowMenu();
} else {
handleSubMenu(btn, currentMenuIndex + 1);
}
return;
}
// Handle main menu navigation
if (currentMenu == 0) {
if (btn == "SELECT" && !inMenu) {
currentMenu = 1;
inMenu = true;
lcdShowMenu();
}
} else {
if (btn == "UP" && currentMenuIndex > 0) {
currentMenuIndex--;
lcdShowMenu();
} else if (btn == "DOWN" && currentMenuIndex < menuCount - 1) {
currentMenuIndex++;
lcdShowMenu();
} else if (btn == "SELECT") {
inSubMenu = true;
inMenu = false;
handleSubMenu("", currentMenuIndex + 1);
} else if (btn == "EXIST") {
inMenu = false;
currentMenu = 0;
currentMenuIndex = 0;
menuScrollOffset = 0;
lcdShowHome();
}
}
}
void handleSubMenu(String btn, int menuNum) {
switch (menuNum) {
case 1: startDigitInput("CYLINDER PERIOD"); break;
case 2: startDigitInput("SAMPLING TIME"); break;
case 3: startDigitInput("CLEANING"); break;
case 4: startDigitInput("AIR PURGE TIME"); break;
case 5: handleSetPointRange(btn); break;
case 6: Serial.println("cal"); break;
case 7: handleEquation(btn); break;
case 8: handleWifiMenu(btn); break;
}
}
void startDigitInput(String label) {
activeLabel = label;
editingDigits = true;
for (int i = 0; i < digitTotal; i++) digitInput[i] = 0;
selectedDigit = 0;
// Check if can have negative value
if (label == "SLOVE" || label == "INTERCEPT") {
isEditingSign = true;
// isNegative = (currentValue < 0);
} else {
isEditingSign = false;
isNegative = false;
}
handleDigitInput("", activeLabel);
}
void handleDigitInput(String btn, String label) {
bool canHaveNegative = (label == "SLOVE" || label == "INTERCEPT");
if (canHaveNegative && isEditingSign) {
// Sign editing mode
if (btn == "UP" || btn == "DOWN") {
isNegative = !isNegative;
} else if (btn == "RIGHT") {
isEditingSign = false;
selectedDigit = 0;
} else if (btn == "LEFT") {
// Do nothing (already at sign)
}
} else {
// Digit editing mode
if (btn == "UP") {
digitInput[selectedDigit] = (digitInput[selectedDigit] + 1) % 10;
} else if (btn == "DOWN") {
digitInput[selectedDigit] = (digitInput[selectedDigit] + 9) % 10;
} else if (btn == "RIGHT") {
if (selectedDigit < digitTotal - 1) {
selectedDigit++;
}
} else if (btn == "LEFT") {
if (selectedDigit > 0) {
selectedDigit--;
} else if (canHaveNegative) {
isEditingSign = true;
}
}
}
if (btn == "SELECT") {
int value = digitInput[0]*10000 + digitInput[1]*1000 + digitInput[2]*100 + digitInput[3]*10 + digitInput[4];
if (canHaveNegative && isNegative) {
value = -value;
}
//saveSettingValue(label, value);
editingDigits = false;
activeLabel = "";
isEditingSign = false;
isNegative = false;
Serial.print("Setting updated: ");
Serial.print(label);
Serial.print(" = ");
Serial.println(value);
return;
}
if (btn == "EXIST") {
editingDigits = false;
activeLabel = "";
isEditingSign = false;
isNegative = false;
if (label.startsWith("UP ") || label.startsWith("MID ") || label.startsWith("UNDER ")) {
inSetPointRangeMenu = true;
handleSetPointRange("");
} else if (label == "SLOVE" || label == "INTERCEPT") {
handleEquation("");
} else {
inMenu = true;
inSubMenu = false;
lcdShowMenu();
}
return;
}
// Display input screen
lcd.clear();
lcd.setCursor(3, 0);
lcd.print(label);
//int currentValue = getCurrentValue(label);
lcd.setCursor(0, 1);
lcd.print("CURRENTLY: ");
// lcd.print(currentValue);
if (label != "SLOVE" && label != "INTERCEPT" && label != "SAMPLING TIME" && !(label.startsWith("UP ") || label.startsWith("MID ") || label.startsWith("UNDER "))) {
lcd.setCursor(16, 1);
lcd.print(" SEC");
} else if (label == "SAMPLING TIME") {
lcd.setCursor(16, 1);
lcd.print("MIN");
}else if (label.startsWith("UP ") || label.startsWith("MID ") || label.startsWith("UNDER ")) {
lcd.setCursor(17, 1);
lcd.print("ICS");
}
// Display input area
lcd.setCursor(4, 2);
// Show sign for SLOVE and INTERCEPT
if (canHaveNegative) {
if (isEditingSign) {
lcd.print("[");
lcd.print(isNegative ? "-" : "+");
lcd.print("]");
} else {
lcd.print(" ");
lcd.print(isNegative ? "-" : "+");
lcd.print(" ");
}
}
// Show digits
for (int i = 0; i < digitTotal; i++) {
if (!isEditingSign && i == selectedDigit) lcd.print("[");
lcd.print(digitInput[i]);
if (!isEditingSign && i == selectedDigit) lcd.print("]");
if (i < digitTotal - 1) lcd.print(" ");
}
// Show unit
if (label != "SLOVE" && label != "INTERCEPT" && label != "SAMPLING TIME" && !(label.startsWith("UP ") || label.startsWith("MID ") || label.startsWith("UNDER "))) {
lcd.setCursor(17, 2);
lcd.print("SEC");
}
if (label == "SAMPLING TIME") {
lcd.setCursor(17, 2);
lcd.print("MIN");
}else if (label.startsWith("UP ") || label.startsWith("MID ") || label.startsWith("UNDER ")) {
lcd.setCursor(17, 2);
lcd.print("ICS");
}
lcd.setCursor(0, 3);
lcd.print("EXIST SELECT");
}
void handleSetPointRange(String btn) {
inSetPointRangeMenu = true;
if (editingDigits) {
handleDigitInput(btn, subOptions[subSubMenuIndex]);
return;
}
if (btn == "UP") {
subSubMenuIndex = (subSubMenuIndex - 1 + 6) % 6; // แก้ไข: เปลี่ยนเป็น -1 สำหรับ UP
lcdShowSubMenu();
} else if (btn == "DOWN") {
subSubMenuIndex = (subSubMenuIndex + 1) % 6; // DOWN ใช้ +1
lcdShowSubMenu();
} else if (btn == "SELECT") {
startDigitInput(subOptions[subSubMenuIndex]);
return;
} else if (btn == "EXIST") { // แก้ไข: เปลี่ยนจาก "EXIST" เป็น "EXIT"
inMenu = true;
inSubMenu = false;
inSetPointRangeMenu = false;
subSubMenuIndex = 0;
lcdShowSubMenu(); // รีเซ็ต scroll offset
lcdShowMenu();
return;
}
lcdShowSubMenu();
}
void handleEquation(String btn) {
static String subOptions[2] = {"SLOVE", "INTERCEPT"};
if (editingDigits) {
handleDigitInput(btn, subOptions[subSubMenuIndex]);
return;
}
if (btn == "UP") subSubMenuIndex = (subSubMenuIndex + 1) % 2;
else if (btn == "DOWN") subSubMenuIndex = (subSubMenuIndex + 1) % 2;
else if (btn == "SELECT") {
startDigitInput(subOptions[subSubMenuIndex]);
return;
} else if (btn == "EXIST") {
inMenu = true;
inSubMenu = false;
subSubMenuIndex = 0;
lcdShowMenu();
return;
}
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("-- SET EQUATION --");
for (int i = 0; i < 2; i++) {
lcd.setCursor(0, i + 1);
lcd.print((i == subSubMenuIndex) ? ">> " : " ");
lcd.print(i + 1);
lcd.print(".");
lcd.print(subOptions[i]);
}
}
//======================================================================================================================================
// WIFI FUNCTIONS
//======================================================================================================================================
void checkWifiStatus() { // CHECK STATUS WIFI FUNCTION.
wifiConnected = (WiFi.status() == WL_CONNECTED); // CHECK CONNECT WIFI.
}
void connectWifi() { // CONNECT WIFI FUNCTION.
lcd.clear();
lcd.setCursor(0, 0); // SET CURSOR.
lcd.print("CONNECTING WiFi..."); // PRINT MESSAGE ON LCD.
lcd.setCursor(0, 1); // SET CURSOR.
lcd.print("PLEASE WAIT..."); // PRINT MESSAGE ON LCD.
WiFi.begin(ssid, password); // SET SSID AND PASSWORD WIFI FOR START CONNECT WIFI.
unsigned long startTime = millis(); // KEEP TIME.
while (WiFi.status() != WL_CONNECTED && millis() - startTime < 10000) { // WAITING UNTIL WIFI CONNECTED
delay(500); // DELAY 0.5 SEC
Serial.print("."); // PRINT MESSAGE ON SERIAL MONITOR.
}
if (WiFi.status() == WL_CONNECTED) { // FUNCTION SHOWING STATUS ON LCD AND SERIAL MONITOR WHEN WIFI CONNECTED.
wifiConnected = true; // FLAG [wifiConnected] TRUE
lcd.clear(); // CLEAR LCD.
lcd.setCursor(0, 0); // SET CURSOR LCD.
lcd.print("WiFi CONNECTED!"); // PRINT MESSAGE ON LCD.
lcd.setCursor(0, 1); // PRINT MESSAGE ON LCD.
lcd.print("IP: "); // PRINT MESSAGE ON LCD.
lcd.print(WiFi.localIP().toString()); // PRINT MESSAGE ON LCD.
Serial.println("\nWiFi CONNECTED!"); // PRINT MESSAGE ON SERIAL MONITOR.
Serial.print("IP ADDRESS: "); // PRINT MESSAGE ON SERIAL MONITOR.
Serial.println(WiFi.localIP()); // PRINT MESSAGE ON SERIAL MONITOR.
} else {
wifiConnected = false; // FLAG [wifiConnected] FALSE IF CONNECTED FAIL.
lcd.clear(); // CLEAR LCD.
lcd.setCursor(0, 0); // SET CURSOR LCD.
lcd.print("WiFi FAILED!"); // PRINT MESSAGE ON LCD.
lcd.setCursor(0, 1); // SET CURSOR LCD.
lcd.print("CONNECTION TIMEOUT"); // PRINT MESSAGE ON LCD.
Serial.println("\nWiFi CONNECTION FAILED!"); // PRINT MESSAGE ON SERIAL MONITOR.
}
configTime(gmtOffset_sec, daylightOffset_sec, ntpServer); // CONFIGULATION TIME.
delay(2000); // DELAY TIME
lcdShowMenu(); // SHOW MENU ON LCD.
}
void disconnectWifi() { // FUNCTION FOR DISCONNECT WIFI
WiFi.disconnect(); // FORCE DISCONNECT WIFI
wifiConnected = false; // FLAG [wifiConnected] FALSE
lcd.clear(); // CLEAR LCD.
lcd.setCursor(0, 0); // SET CURSOR LCD.
lcd.print("WiFi DISCONNECTED!"); // PRINT MESSAGE ON SERIAL MONITOR.
lcd.setCursor(0, 1); // SET CURSOR LCD.
lcd.print("SUCCESSFULLY"); // PRINT MESSAGE ON SERIAL MONITOR.
Serial.println("WiFi DISCONNECTED!"); // PRINT MESSAGE ON SERIAL MONITOR.
delay(2000); // DELAY TIME
lcdShowMenu(); // SHOW MENU ON LCD.
}
void handleWifiMenu(String btn) { // SELECT TURN-ON/TURN-OFF FUNCTION.
static int wifiMenuIndex = 0; // DECLARE STATIC VARIABLE [wifiMenuIndex]
if (btn == "UP") wifiMenuIndex = max(0, wifiMenuIndex - 1); //
else if (btn == "DOWN") wifiMenuIndex = min(1, wifiMenuIndex + 1);
else if (btn == "SELECT") {
if (wifiConnected) {
if (wifiMenuIndex == 0) disconnectWifi();
} else {
if (wifiMenuIndex == 0) connectWifi();
}
inSubMenu = false;
wifiMenuIndex = 0;
return;
} else if (btn == "EXIST") {
inSubMenu = false;
wifiMenuIndex = 0;
return;
}
lcd.clear(); // CLEAR LCD.
lcd.setCursor(0, 0); // SET CURSOR LCD.
lcd.print("-- WiFi SETTING --"); // PRINT MESSAGE ON SERIAL MONITOR.
lcd.setCursor(0, 1); // SET CURSOR LCD.
lcd.print("STATUS: "); // PRINT MESSAGE ON SERIAL MONITOR.
lcd.print(wifiConnected ? "CONNECTED" : "DISCONNECTED"); // PRINT MESSAGE ON SERIAL MONITOR.
lcd.setCursor(0, 2); // SET CURSOR LCD.
lcd.print((wifiMenuIndex == 0) ? ">> " : " "); // PRINT MESSAGE ON SERIAL MONITOR.
lcd.print(wifiConnected ? " DISCONNECT" : " CONNECT"); // PRINT MESSAGE ON SERIAL MONITOR.
lcd.setCursor(0, 3); // SET CURSOR LCD.
lcd.print("EXIST SELECT"); // PRINT MESSAGE ON SERIAL MONITOR.
}
//======================================================================================================================================
// LCD DISPLAY FUNCTIONS
//======================================================================================================================================
void lcdShowHome() {
lcd.clear();
lcd.setCursor(2, 0);
lcd.print(ICUMSA);
lcd.setCursor(13, 1);
lcd.print(modeStatus == 0 ? "AUTO" : "MANUAL");
// Show WiFi status
lcd.setCursor(13, 2);
lcd.print(wifiConnected ? "ONLINE" : "OFFLINE");
lcd.setCursor(0, 1);
lcd.print("R:");
lcd.setCursor(3, 1);
lcd.print(lastestRGB.lastr);
lcd.setCursor(0, 2);
lcd.print("G:");
lcd.setCursor(3, 2);
lcd.print(lastestRGB.lastg);
lcd.setCursor(0, 3);
lcd.print("B:");
lcd.setCursor(3, 3);
lcd.print(lastestRGB.lastb);
lcd.setCursor(14, 3);
lcd.print(settings.SamplingValue);
lcd.setCursor(17, 3);
lcd.print("MIN");
}
void lcdShowMenu() {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(" MENU SCREEN ");
if (currentMenuIndex < menuScrollOffset) {
menuScrollOffset = currentMenuIndex;
} else if (currentMenuIndex > menuScrollOffset + 2) {
menuScrollOffset = currentMenuIndex - 2;
}
for (int i = 0; i < 3; i++) {
int menuIdx = menuScrollOffset + i;
if (menuIdx < menuCount) {
lcd.setCursor((menuIdx == currentMenuIndex) ? 0 : 1, i + 1);
lcd.print((menuIdx == currentMenuIndex) ? ">" : " ");
lcd.print(MENU_ITEMS[menuIdx]);
}
}
}
void lcdShowMessage(String msg) {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(msg);
}
void lcdShowSubMenu() {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("--SET POINT RANGE-- ");
// แก้ไข: ใช้ชื่อตัวแปรเดียวกัน และปรับลอจิก scroll
if (subSubMenuIndex < subMenuScrollOffset) {
subMenuScrollOffset = subSubMenuIndex;
} else if (subSubMenuIndex >= subMenuScrollOffset + 3) { // แสดงได้ 3 รายการ (แถว 1-3)
subMenuScrollOffset = subSubMenuIndex - 2;
}
// แสดงเมนู 3 รายการ (ใช้แถว 1, 2, 3)
for (int i = 0; i < 3; i++) {
int subMenuIdx = subMenuScrollOffset + i;
if (subMenuIdx < 6) { // ตรวจสอบไม่เกิน 6 รายการ
lcd.setCursor(0, i + 1); // แถว 1, 2, 3
// แสดง cursor ">" หรือ space " "
if (subMenuIdx == subSubMenuIndex) {
lcd.print(">> ");
} else {
lcd.print(" ");
}
// แสดงชื่อเมนู
lcd.print(subMenuIdx+1);
lcd.print(".");
lcd.print(subOptions[subMenuIdx]);
} else {
// ล้างแถวที่เหลือ
lcd.setCursor(0, i + 1);
lcd.print(" "); // ล้าง 16 ตัวอักษร
}
}
}
void updateLampsStatus() { // UPDATE OUTPUT AND LAMP STATUS
if (modeStatus == 0) { // Auto mode - LEDs controlled by sequence // AUTO MODE - OUTPUT CONTROLLED BY SEQUENCE.
} else { // MANUAL MODE - UPDATE ACCORDING TO MANUAL STATE.
for (int i = 0; i < outputCount; i++) { // ACTIVE OUTPUT.
if (i == 3) { // LED4 // OUTPUT 4.
digitalWrite(outputPin[i], output4State); // ACTIVE OUTPUT 4.
} else if (i == 4) { // OUTPUT 5.
digitalWrite(outputPin[i], output5State); // ACTIVE OUTPUT 5.
} else {
digitalWrite(outputPin[i], ledStateManual[i]); // ACTIVE MANUAL.
}
}
}
}
void checkOutputPulseTimeout() { // CHECK PULSE FOR OUTPUT 4, 5 (INCREASE, DECREASE).
unsigned long currentTime = millis(); // DECLARE VARIABLE TO KEEP TIME
// Check LED4
if (output4State == HIGH && currentTime >= output4TurnOffTime) { // CHECK ACTIVE OUTPUT 4 AND TIMEOUT.
output4State = LOW; // DISACTIVE OUTPUT 4.
Serial.print("bufferOffset : "); // PRINT MESSAGE ON SERIAL MONITOR.
Serial.println(bufferOffset); // PRINT MESSAGE ON SERIAL MONITOR.
}
// Check LED5
if (output5State == HIGH && currentTime >= output5TurnOffTime) { // CHECK ACTIVE OUTPUT 5 AND TIMEOUT.
output5State = LOW; // DISACTIVE OUTPUT 5.
Serial.print("bufferOffset : "); // PRINT MESSAGE ON SERIAL MONITOR.
Serial.println(bufferOffset); // PRINT MESSAGE ON SERIAL MONITOR.
}
}
void handleIncreasePulse() { // INCREASE FUNCTION (OUTPUT 4) ACTIVE
unsigned long currentTime = millis(); // KEEP INTERVAL TIME.
if (currentTime - lastOutput4Press > outputDebounceDelay) { // CHECK INPUT SIGNAL.
output4State = HIGH; // ACTIVE OUTPUT 4.
output4TurnOffTime = currentTime + outputPulseWidth; // CALCULATE OFFSET TIME.
Serial.println("LED4 (LEFT) turned ON for 500ms"); // PRINT MESSAGE ON SERIAL MONITOR.
lastOutput4Press = currentTime; // UPDATE INTERVAL TIME.
bufferOffset++; // [bufferOffset] + 1
}
}
void handleDecreasePulse() { // DECREASE FUNCTION (OUTPUT 5) ACTIVE
unsigned long currentTime = millis(); // KEEP INTERVAL TIME.
if (currentTime - lastOutput5Press > outputDebounceDelay) { // CHECK INPUT SIGNAL.
output5State = HIGH; // ACTIVE OUTPUT 4.
output5TurnOffTime = currentTime + outputPulseWidth; // CALCULATE OFFSET TIME.
Serial.println("LED5 (RIGHT) turned ON for 500ms"); // PRINT MESSAGE ON SERIAL MONITOR.
lastOutput5Press = currentTime; // UPDATE INTERVAL TIME.
bufferOffset = max(0, bufferOffset - 1); // [bufferOffset] - 1 (PROTECT VALUE < 0).
}
}
UP/HW
DOWN/AIR
LEFT/INCREASE
RIGHT/DECREASE
EXIST/CY
SELECT
CY.
HW.
AIR
ICD
DCD
AUTO-MAN