#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <EEPROM.h>
#define EEPROM_SIZE 25
LiquidCrystal_I2C lcd(0x27, 20, 4); // Change the I2C address if needed
#define ENCODER_CLK 2
#define ENCODER_DT 15
#define ENCODER_BTN 4
//EEPROM Address first prefix indicating data type
#define b_TANK_B 0
#define f_TANK_F 1
#define b_TANK_B2 5
#define s_TANK_S 6
#define MENULEV_MAX 6
//Function Declare section
void subMenu____FX(int p);
void actionFun1_FX(int p);
void actionFun2_FX(int p);
void actionFun3_FX(int p);
void actionFun4_FX(int p);
void writeByte__FX(int p);
void writeShort_FX(int p);
void writeBool__FX(int p);
void writeFloat_FX(int p);
//ccc
enum Encoder_Mode {eLIGHT, eSCREEN, eOPTION, eNUMBER};
const char* enMode[] = {"eLIGHT", "eSCREEN", "eOPTION", "eNUMBER" };
Encoder_Mode EncMode = eSCREEN;
enum screenID {sBEGIN, sMENU, sMAIN, sSENSOR, sPROC0, sPROC1, sPROC2, sPROC3, sEND};
const char* scLabel[] = {"BEGIN", "MENU", "MAIN", "SENSOR", "PROCESS 0", "PROCESS 1", "PROCESS 2", "PROCESS 3", "END"};
int scID = sMAIN; //scLabel[scID]
bool scNavigat = false; //prevent cursor run around before encode turned
int opID=0;
int mnCurLcdRow();
int CurLev =0;
//int mnPrvLev =99;
int MenuID[MENULEV_MAX]={0,-1,-1,-1,-1};
int MenuLN[MENULEV_MAX]={0,-1,-1,-1,-1};
int mnPrvLine =99; //for checking if any line change then update LCD
int mnCurPage =99;
bool mnRefresh = true;
struct MenuItem {
const byte menuID;
const char* label;
void (*const action)(int) ;
const byte para;
};
struct field{
int row;
int col;
char* label;
String valS;
};
struct getNumber {
bool on;
int row;
int col;
int sta;
int Max;
};
getNumber gNum;
enum menuName {mMAIN, mAUTO, mSETT, mCALB, mTEST, mPROC};
const char* mnLabel[] = {"mMAIN MENU", "mAUTO MENU", "mSETTING MENU", "mCALBRATION MENU", "mTEST MENU", "mPROCESS MENU"};
MenuItem Menu[] = {
//Main Menu, Max label lng is 13 writeEeprom FX
//{xXXXX, "0123456789012", xxxxxxxxxxxFX, Interger"},
{mMAIN, "M0 Option 0", actionFun1_FX, 5},
{mMAIN, "M0 Option 1", actionFun2_FX, 7},
{mMAIN, "M0 Sub Menu 2", subMenu____FX, mAUTO},
{mMAIN, "M0 Short 3", writeShort_FX, s_TANK_S},
{mMAIN, "M0 Byte 4", writeByte__FX, b_TANK_B},
{mMAIN, "M0 Float 5", writeFloat_FX, f_TANK_F},
{mAUTO, "M1 Option 6 ", actionFun3_FX, 11},
{mAUTO, "M1 Option 7 ", actionFun4_FX, 3},
{mAUTO, "M1 BOOL 8 ", writeBool__FX, b_TANK_B2},
{mAUTO, "M1 Option 9 ", subMenu____FX, mSETT},
{mAUTO, "M1 Float 10 ", writeFloat_FX, f_TANK_F},
{mSETT, "M2 Option 11 ", subMenu____FX, mCALB},
{mSETT, "M2 Option 12 ", actionFun4_FX, 3},
{mCALB, "M3 Option 13 ", subMenu____FX, mTEST},
{mTEST, "M4 Option 14 ", subMenu____FX, mPROC},
{mPROC, "M5 Option 15 ", actionFun3_FX, 3},
};
int c; //counting in main loop
// Define the variables for the rotary encoder
volatile int EncCounter = 0; // The counter value
volatile bool EncTurned = false;
volatile int EncStep = 1; // Step of counter
volatile long int Enc_stime =millis();
volatile bool EncClockwise = true; // The direction of rotation
volatile bool EncPressed = false; // The button state
//ccc
void IRAM_ATTR isr_clk() {
int dt = digitalRead(ENCODER_DT);
EncTurned = true;
// If the CLK pin is low and the DT pin is high, it means clockwise rotation
if (dt == HIGH) EncClockwise = true;
else EncClockwise = false;
}
void IRAM_ATTR isr_btn() { // Read the value of the SW pin
int sw = digitalRead(ENCODER_BTN);
if (sw == LOW) { // If the SW pin is low, it means the button is pressed
EncPressed = true;
}
}
void prn_MENU_variable(){
//print menu variable
char lev[MENULEV_MAX];
for (int i=0; i<MENULEV_MAX; i++) lev[i]=' ';
lev[CurLev]='#';
Serial.printf("\n>> CurLev:"); for (int i=0; i<MENULEV_MAX; i++) Serial.printf(" %2c", lev[i]);
Serial.printf(" mnCurLcdRow:%d", mnCurLcdRow());
Serial.printf(" mnPrvLine:%d", mnPrvLine);
Serial.printf("\n>> MenuID:"); for (int i=0; i<MENULEV_MAX; i++) Serial.printf(" %2d", MenuID[i]);
Serial.printf(" mnFirstLine:%d", mnFirstLine());
Serial.printf("\n>> MenuLN:"); for (int i=0; i<MENULEV_MAX; i++) Serial.printf(" %2d", MenuLN[i]);
Serial.printf(" mnNumLine:%d", mnNumLine());
}
void prn_ENCODER_variable(String str){
if (str=="Before")
Serial.printf("\n _____________________________________________________________________");
Serial.printf("\n %s >> EncMode:%s EncPressed:%d EncTurned:%d EncClockwise:%d EncStep:%d counter:%4d", str, enMode[EncMode], EncPressed, EncTurned, EncClockwise, EncStep, EncCounter);
Serial.printf("\n %s >> scID:%d scLabel:%s scNavigat:%d opID:%d mnRefresh:%d ", " ", scID, scLabel[scID], scNavigat, opID, mnRefresh);
}
bool mnIsBackPressed(){
bool back=false;
int lastLine=-1;
for (int i=0; i<mnQuantity(); i++){
int ID =Menu[i].menuID;
if (ID==MenuID[CurLev]) {lastLine=i;}
if (lastLine!=-1 && ID!=MenuID[CurLev]) break;
}
if (lastLine==MenuLN[CurLev]-1) back=true;
Serial.printf("\nlastLine:%d back:%d",lastLine, back);
return back;
}
int mnFirstLine(){
int mnStart=-1;
for (int i=0; i<mnQuantity(); i++){
int ID =Menu[i].menuID;
if (ID==MenuID[CurLev]) {mnStart=i; break;}
}
return mnStart;
}
int mnNumLine(){
int mnLines =0;
for (int i=0; i<mnQuantity(); i++){
int ID =Menu[i].menuID;
if (ID==MenuID[CurLev]) mnLines++;
if (ID!=MenuID[CurLev] && mnLines >0) break;
}
return mnLines;
}
int mnCurLcdRow(){
int fstLine =mnFirstLine()-1; // minus 1 to compensate header line
int numLine =mnNumLine();
int row=0;
for (int i=fstLine; i<fstLine+numLine+1; i++){
if (i==MenuLN[CurLev]) break;
if(row==3) row=0; else row++;
}
return row;
}
void setup() {
pinMode(ENCODER_CLK, INPUT);
pinMode(ENCODER_DT, INPUT);
pinMode(ENCODER_BTN, INPUT_PULLUP);
EEPROM.begin(EEPROM_SIZE);
// put your setup code here, to run once:
Serial.begin(115200);
Serial.println("Hello, ESP32!");
lcd.init();
lcd.backlight();
lcd.clear();
// Attach interrupt functions to the CLK and SW pins
attachInterrupt(digitalPinToInterrupt(ENCODER_CLK), isr_clk, FALLING);
attachInterrupt(digitalPinToInterrupt(ENCODER_BTN), isr_btn, FALLING);
Serial.printf("All menus lines:%d", mnQuantity());
EEPROM.writeFloat(f_TANK_F, 23.45);
prn_ENCODER_variable("Setup ");
}
void loop() {
static unsigned long c_stime = millis();
if (millis()-c_stime>1000) {c_stime=millis(); c++;}
encoder_loop();
SCREEN_loop();
delay(10); // this speeds up the simulation
}
byte mnQuantity(){
byte c=0;
for(auto i: Menu) {c++;}
//c=sizeof(Menu)/sizeof(Menu[0]);
return c;
}
void encoder_loop(){
//ccc Change Mode
if(EncPressed==true) {
prn_ENCODER_variable("Before");
EncPressed=false;
switch (EncMode) {
case eLIGHT: {EncMode=eSCREEN; lcd.backlight(); return;}
case eSCREEN:
EncMode = eOPTION;
lcd.blink();
opID=0;
scNavigat =true;
Serial.print(" << OPTION SELECT >>");
break;
case eOPTION:
if(opID!=0) {
EncMode = eNUMBER;
lcd.noBlink();
lcd.cursor();
Serial.printf("\n EDIT");
}
else {
EncMode = eSCREEN;
lcd.noBlink();
}
break;
case eNUMBER:
EncMode = eOPTION;
lcd.blink();
lcd.noCursor();
break;
}
prn_ENCODER_variable("After ");
}
//ccc Select Option
if(EncTurned ==true) {
prn_ENCODER_variable("Before");
EncTurned=false;
EncStep=1;
switch (EncMode) {
case eLIGHT:
EncTurned=false;
break;
case eSCREEN:
EncTurned=false;
lcd.clear();
if (EncClockwise == true) scID++;
if (EncClockwise == false) scID--;
if (scID==sBEGIN) scID=sEND-1;
if (scID==sEND) scID=sBEGIN+1;
if (scID==sMENU) mnPrvLine=99; // force to refresh lcd
break;
case eOPTION:
EncTurned=false;
scNavigat=true;
break;
case eNUMBER:
if (millis()-Enc_stime<200) {Enc_stime=millis(); EncStep=100;}
else if (millis()-Enc_stime<400) EncStep=20; else EncStep=1;
gNum.on =true;
break;
}
if(EncClockwise==true) EncCounter=EncCounter +EncStep;
if(EncClockwise==false) EncCounter=EncCounter -EncStep;
prn_ENCODER_variable("After ");
}
}
void SCREEN_loop(){ //alway run not up to Encoder
static unsigned long sc_stime = millis();
int holdtime;
if (EncMode==eSCREEN) holdtime=500;
if (EncMode==eOPTION) holdtime=50;
if (millis()-sc_stime < holdtime) return;
sc_stime =millis();
sMENU_loop();
sMAIN_loop();
sSENSOR_loop();
sPROC0_loop();
sPROC1_loop();
sPROC2_loop();
sPROC3_loop();
// get number
if (gNum.on==true) {
gNum.on=false;
bool exit=false;
//lcd.blink();
lcd.setCursor(gNum.col, gNum.row); lcd.print(" ");
lcd.setCursor(gNum.col, gNum.row); lcd.print(EncCounter);
lcd.setCursor(gNum.col, gNum.row);
}
}
void lcdField(int row, int col, String label, int val){
lcd.setCursor(col,row); lcd.print(label); lcd.print(val);
}
void screenAction(field fd[],int size){
// Print Screen Option
int e=size;
if (EncMode==eSCREEN) {
for (int i=0; i<e; i++){
lcd.setCursor(fd[i].col, fd[i].row); lcd.print(fd[i].label); if(fd[i].valS!="") lcd.print(fd[i].valS);
if (i==0) lcd.print(' ');
}
}
// Navigate Screen Option
if (EncMode==eOPTION) {
if (scNavigat==true) {
scNavigat=false;
if (EncClockwise==true) if (opID==e-1) opID=0; else opID++;
if (EncClockwise==false) if (opID==0) opID=e-1; else opID--;
String s = fd[opID].label;
int row =fd[opID].row;
int col =fd[opID].col+s.length();
lcd.setCursor(col, row);
if (opID==0) {lcd.print("<"); lcd.setCursor(col, row); }
if (opID==1 || opID==e-1) {
String j=fd[0].label;
lcd.setCursor(fd[0].col+j.length(), fd[0].row); lcd.print(" "); lcd.setCursor(col, row); }
Serial.printf("\n Navigat>> e:%d opID:%d row:%d col:%d", e, opID, row, col);
gNum.row =row;
gNum.col =col;
gNum.sta =0;
gNum.Max =10;
}
}
}
String i2c(int val){
char buf[5];
sprintf(buf, "%d", val);
return buf;
}
String f2c(float val){
char buf[5];
sprintf(buf, "%.1f", val);
return buf;
}
//ccc
void sMAIN_loop(){
if (scID != sMAIN) return;
String S[]={
i2c(0),
i2c(1),
i2c(c),
f2c(3.45),
f2c(7.245),
"STR",
};
field fd[] {
{0, 0, "sMain", ""}, {0, 12, "", S[0]}, {0, 13, "", S[1]},
{1, 0, "Val 1:", S[2]}, {1, 11, "Val 4:", S[3]},
{2, 0, "Val 2:", S[4]}, {2, 11, "Val 5:", S[5]},
{3, 0, "Val 3:", "789"}, {3, 11, "Val 6:", "777"}
};
int e=sizeof(fd)/sizeof(fd[0]);
screenAction(fd, e);
}
void sSENSOR_loop(){
if (scID != sSENSOR) return;
field fd[] {
{0, 0, "sSENSOR", ""}, {0, 12, "", "0"}, {0, 13, "", "1"},
{1, 0, "Temp :", "123"}, {1, 11, "EC :", "555"},
{2, 0, "Moist:", "456"}, {2, 11, "PH :", "666"}
};
int e=sizeof(fd)/sizeof(fd[0]);
screenAction(fd, e);
}
void sPROC0_loop(){
if (scID != sPROC0) return;
field fd[] {
{0, 0, "sPROC0", ""}, {0, 12, "", "0"}, {0, 13, "", "1"},
{1, 0, "Lev:", "12"}, {1, 7, "EC:", "555"}, {1, 14, "PH:", "55"},
{2, 0, "Moist:", "456"}, {2, 11, "PH :", "666"}
};
int e=sizeof(fd)/sizeof(fd[0]);
screenAction(fd, e);
}
void sPROC1_loop(){
if (scID != sPROC1) return;
lcd.setCursor(0,1); lcd.print("sPROC1 c:"); lcd.print(c);
}
void sPROC2_loop(){
if (scID != sPROC2) return;
lcd.setCursor(0,2); lcd.print("sPROC2 c:"); lcd.print(c);
}
void sPROC3_loop(){
if (scID != sPROC3) return;
lcd.setCursor(0,3); lcd.print("sPROC3 c:"); lcd.print(c);
}
//ccc
void sMENU_loop()
{
if (scID != sMENU) return;
lcd.setCursor(0, 0); lcd.printf("\n MENU");
} //end sMENU_loop