├── .gitattributes ├── LICENSE ├── README.md ├── examples ├── menu_alarm_clock │ └── menu_alarm_clock.ino └── menu_irigation │ └── menu_irigation.ino └── src ├── CMenu_I2C.cpp └── CMenu_I2C.h /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 elmarmut75 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Arduino-16x2-20x4-LCD-menu 2 | 3 | Arduino menu library for LCD displays 16x2 and 20x4. 4 | 5 | Features: 6 | * multiple menu levels 7 | * control by 4 buttons keypads (4xDI or 1xAI) or by rotary encoder (4xDI) 8 | * menu texts stored in program memory 9 | * displays actual state of inputs or variable values 10 | * direct control of outputs 11 | * you can enter variable values (user input of many types: bool, byte, int, word, long, float, date, time) 12 | * rolling animation for long texts 13 | * LCD sleep or off-mode on inactivity 14 | * event recorder (memory for few events with timestamp, description and value) 15 | * date and time operations without real-time clock module using Arduino internal clock 16 | 17 | ## Usage 18 | Read Wiki or "alarm clock" and "irrigation" examples, read comments there. 19 | 20 | -------------------------------------------------------------------------------- /examples/menu_alarm_clock/menu_alarm_clock.ino: -------------------------------------------------------------------------------- 1 | // Example of simple alarm clock 2 | // Configuration 3 | // * 1 alarm time 4 | // * alarm activation setting for each day of the week 5 | // * Digital output buzz_pin is activated during alarm 6 | // * time compensation of slow up or quick up the internal arduino clock 7 | // * menu control by rotary encoder input (or 4 buttons via 4 binary inputs or 1 analog input) 8 | // * display on 16x2 LCD display (or 20x4) using LiquidCrystal_I2C.h library 9 | 10 | #include //time library used for handling with datetime variables type 11 | #include //time library used for handling with datetime variables type 12 | #include //I2C communication library 13 | #include //LCD display library 14 | 15 | // LiquidCrystal_I2C setup 16 | // Define new instance (lcd) of LiquidCrystal_I2C class 17 | // LiquidCrystal_I2C (, , ) 18 | 19 | #define display_columns 16 20 | #define display_rows 2 21 | 22 | // LiquidCrystal_I2C setup 23 | // Define new instance (lcd) of LiquidCrystal_I2C class 24 | // LiquidCrystal_I2C (, , ) 25 | 26 | LiquidCrystal_I2C lcd(0x3f,display_columns,display_rows); 27 | 28 | #include "CMenu_I2C.h" //Read the Menu library 29 | 30 | //Texts for menu dialog like on/off, open/close for whole menu 31 | const String On_label = "ON "; //set on/off labels string for menu dialog (not used in this example) 32 | const String Off_label = "OFF"; //set on/off labels string for menu dialog (not used in this example) 33 | const String New_val = "Enter:"; //set input label string for menu dialog 34 | 35 | static const byte bell[8] = { 36 | B00100, // * 37 | B01110, // *** 38 | B01110, // *** 39 | B01110, // *** 40 | B11111, //***** 41 | B00000, // 42 | B00100, // * 43 | B00000 // 44 | }; 45 | 46 | //Texts of individual menu items (variable names and programs/rutines names). Texts are stored in the program memory to spare space in the local memory 47 | const char t00[] PROGMEM = "Date and time"; 48 | const char t01[] PROGMEM = "Alarm"; 49 | const char t02[] PROGMEM = "Date"; 50 | const char t03[] PROGMEM = "Time"; 51 | const char t04[] PROGMEM = "TimeErrComp"; 52 | const char t05[] PROGMEM = "AlmActive"; 53 | const char t06[] PROGMEM = "AlmTime"; 54 | const char t07[] PROGMEM = "Monday"; 55 | const char t08[] PROGMEM = "Tuesday"; 56 | const char t09[] PROGMEM = "Wednesday"; 57 | const char t10[] PROGMEM = "Thursday"; 58 | const char t11[] PROGMEM = "Friday"; 59 | const char t12[] PROGMEM = "Saturday"; 60 | const char t13[] PROGMEM = "Sunday"; 61 | const char t14[] PROGMEM = "Next alarm"; 62 | const char t15[] PROGMEM = "AlmDate"; 63 | const char t16[] PROGMEM = "AlmTime"; 64 | 65 | //Construction of the menu texts array in the program memory. 66 | const char* const tt[] PROGMEM = {t00, t01, t02, t03, t04, t05, t06, t07, t08, t09, t10, t11, t12, t13, t14, t15, t16}; 67 | 68 | Menu MyMenu; //Define new instance MyMenu class 69 | 70 | //User program variables declaration 71 | byte up_pin = 2; //navigation Up pin 72 | byte down_pin = 3; //navigation Down pin 73 | byte forward_pin = 4; //navigation Forward pin 74 | byte backward_pin = 4; //navigation Backward pin 75 | byte analog_nav_pin = 0; //navigation Backward pin 76 | byte buzz_pin = 11; //Alarm pin 77 | bool play = 0; //alarm pulsation 78 | unsigned long alarm_time = 0; //00:00:00 (number of seconds since 00:00:00 1.1.1970) 79 | unsigned long next_start = 0; //Alarm start time - time stamp 80 | unsigned long midnight; //Midnight - time stamp 81 | 82 | bool alarm_active = 0; //alarm is running (activated) 83 | bool alarm_on = 0; //alarm time is set 84 | bool mo = 0; //alarms are activated on Monday 85 | bool tu = 0; //alarms are activated on Tuesday 86 | bool we = 0; //alarms are activated on Wednesday 87 | bool th = 0; //alarms are activated on Thursday 88 | bool fr = 0; //alarms are activated on Friday 89 | bool sa = 0; //alarms are activated on Saturday 90 | bool su = 0; //alarms are activated on Sunday 91 | bool valid = 0; //boolean auxiliary variable 92 | byte b = 0; 93 | unsigned long actual_time_reference = 0; // Could be set from the menu (number of seconds since 00:00:00 1.1.1970) 94 | unsigned long alarm_endtime = 0; //alarm end - time stamp 95 | int alarm_duration = 60; //alarm duration in seconds 96 | 97 | //value in seconds for compensation of slow up or quick up the internal arduino clock (for example value should be 30 when internal clock during 24 hours slow up 30 seconds) 98 | bool time_shift = 0; 99 | int daily_time_shift = 0; 100 | unsigned long time_shifts = 0; 101 | unsigned long time_shift_fraction = 0; 102 | 103 | unsigned long tm = 0; //time calculaion auxiliary variable 104 | String sval; //string auxiliary variable 105 | 106 | void setup() { 107 | 108 | //LCD initialization 109 | lcd.init(); 110 | lcd.backlight(); 111 | 112 | //I/O setup of the User IO global variables 113 | pinMode(up_pin, INPUT); // pin 2 is used for buttons/ro tary encoder input 114 | pinMode(down_pin, INPUT); // pin 3 is used for buttons/rotary encoder input 115 | pinMode(forward_pin, INPUT); // pin 4 is used for buttons/rotary encoder input 116 | pinMode(buzz_pin, OUTPUT); // pin 11 is used for buzzer 117 | 118 | //------------------------------------- 119 | //Menu initialization (CMenu.h library) 120 | //Initialize menu (LCD_object, PROGMEM_text_table, refresh_time_[ms], sleep_time [s], off_time[min] (255=off), display_columns, display_rows, date_DMY [1: date format DD.MM.YY, 0: date format MM.DD.YY, default is DD.MM.YY, max_events (0..50)]) 121 | MyMenu.begin( lcd, tt, 500, 30, 5, display_columns, display_rows, 1 , 0 ); 122 | 123 | lcd.createChar(5, (uint8_t*) bell); //Add bell icon to the LCD. Chars 1..4 are used for menu library 124 | 125 | //Menu input settings 126 | //void key_input(bool _analog_input, byte _pin, word _up, word _down, word _left, word _right) 127 | // * _analog_input - Analog or digital input type. 128 | 129 | // - Analog type: Arrays of input switches distinguish which switch was pressed by different resistors connected to each switch 130 | // - Parameters _up, _down, _left, _right represents value on analog input pin when you press corresponding key [0-1024] 131 | // * _pin - For analog input type its analog pin number used for the input. 132 | 133 | // - Digital type: 4 press buttons or rotary encoder 134 | // - 4 press buttons: parameters _up, _down, _left, _right represents digital pin number used for reading corresponding key 135 | // - rotary encoder : _up: digital pin number of CLK, _down: digital pin number of DT, _left: digital pin number of SW, _right: digital pin number of SW 136 | // - rotary encoder : move to left(back) is realized by doubleclick, move to right(front) is realized by click 137 | 138 | 139 | //Examples of the menu input settings: 140 | // key_input(analog_input, pin, up, down, left, right) 141 | //MyMenu.key_input( 1, analog_nav_pin, 40, 160, 10, 250); //analog input pin = analog_nav_pin, up, down, left, right represents value on analog input pin when you press corresponding key [0-1024] 142 | //MyMenu.key_input( 0, 0, up_pin, down_pin, backward_pin, forward_pin); //digital input pins 3 up, 4 down, 5 left, 2 right 143 | MyMenu.key_input( 0, 0, up_pin, down_pin, backward_pin, forward_pin); //rotary encoder pins 2 CLK, 3 DT, 4 SW 144 | 145 | //Creation of the new Menu item. 146 | // Maximal number of menu items is 100. 147 | // int item_id Menu::add_menu_item(int parent_id, int action_type, byte tt[], int variable_pointer (optional), byte var_type (optional)); 148 | // * Parent_id/item_id is the identificator of the menu item for making multi level menus. You can name the item_id to make menu interlinks. Root level id is -1 149 | // * tt[] is index of the menu text stored in te program memory 150 | // * Variable_pointer is the pointer to the address of the memory where is stored a variable, which should be dynamically displayed in the menu 151 | // * Valid var_point_types are bool, byte, int, unsigned int, long, unsigned long, float, datetime 152 | // * var_type - specifies number of decimal places for float and double 153 | // * var_type - for datetime specify which part (date or time) you want to display (var_type 7 or 8) 154 | 155 | //int action_types 156 | //0 - menu 157 | //1 - menu + display value 158 | //2 - menu + set int to 1/0 - thick [v]/[ ] 159 | //3 - menu + set int to 1/0 - ON/OFF 160 | //4 - display events: 1st row - datetime, 2nd row - event description + int value 161 | //5 - set value 162 | 163 | //byte var_point_type; 164 | //0 - bool 0 ... 1 165 | //1 - byte 0 ... 255 166 | //2 - int -32 768 ... 32 767 167 | //3 - word/unsigned int 0 ... 65535 168 | //4 - long -2 147 483 648 ... 2 147 483 647 169 | //5 - unsigned long 0 ... 4 294 967 295 170 | //6 - float -3.4028235E+38 ... 3.4028235E+38 171 | //7 - double 172 | //8 - datetime(unsigned long): date DD.MM.YY or MM.DD.YY (se parameter date_DMY). Unsigned long - number of seconds since 1.1.1970 00:00:00 (Time.h, TimeLib.h library) 173 | //9 - datetime(unsigned long): time hh:mm:ss. Unsigned long - number of seconds since 1.1.1970 00:00:00 (Time.h, TimeLib.h library) 174 | //10- datetime(unsigned long): date + millis(); 175 | //11- datetime(unsigned long): time + millis(); 176 | 177 | //Example: Making of the root menu items ("Sensors", "Settings", "Programs", "Commands" and "Records") 178 | //Example: menu_type 0 is the simple text menu 179 | //int item_id Menu::add_menu_item( int parent_id, int action_type, byte tt[], int variable_pointer (optional), byte var_type (optional)); 180 | // add_menu_item( root level, menu - only text, text array reference) 181 | byte act = MyMenu.add_menu_item( -1, 0, 0); 182 | byte alm = MyMenu.add_menu_item( -1, 0, 1); 183 | byte n_alm= MyMenu.add_menu_item( -1, 0, 14); 184 | 185 | //Example: "Actual date, time" submenu declaration 186 | //Example: variable Sensors contain index of the "Sensors" menu item declared above 187 | // add_menu_item(parent menu id, menu - menu + value, text array reference, pointer to the variable, variable type) 188 | byte act_dt = MyMenu.add_menu_item( act, 1, 2, &actual_time_reference, 10); 189 | byte act_tm = MyMenu.add_menu_item( act, 1, 3, &actual_time_reference, 11); 190 | byte day_sh = MyMenu.add_menu_item( act, 1, 4, &daily_time_shift ); 191 | 192 | //Example: "Alarm" submenu declaration 193 | // add_menu_item(parent menu id, menu type, text array reference, pointer to the variable, variable type) 194 | MyMenu.add_menu_item( alm, 2, 5, &alarm_active ); 195 | byte altm = MyMenu.add_menu_item( alm, 1, 6, &alarm_time, 9); 196 | MyMenu.add_menu_item( alm, 2, 7, &mo ); 197 | MyMenu.add_menu_item( alm, 2, 8, &tu ); 198 | MyMenu.add_menu_item( alm, 2, 9, &we ); 199 | MyMenu.add_menu_item( alm, 2, 10, &th ); 200 | MyMenu.add_menu_item( alm, 2, 11, &fr ); 201 | MyMenu.add_menu_item( alm, 2, 12, &sa ); 202 | MyMenu.add_menu_item( alm, 2, 13, &su ); 203 | 204 | //Example: "Next alarm" submenu declaration 205 | //Example: variable Sensors contain index of the "Sensors" menu item declared above 206 | // add_menu_item(parent menu id, menu type = menu + value, text array reference, pointer to the variable, variable type) 207 | MyMenu.add_menu_item( n_alm, 1, 15, &next_start, 8); 208 | MyMenu.add_menu_item( n_alm, 1, 16, &next_start, 9); 209 | 210 | //Example: "Actual date, time" input submenu declaration 211 | //Example: menu_type 5 enable to set/input new value for a varible. It will show input dialog. New value is set from the keyboard step by step by selecting numbers. Input is finished and value is writen to the variable by selecting "enter" symbol. 212 | // add_menu_item(parent menu id, menu type = value input dialog, text array reference, pointer to the variable, variable type) 213 | MyMenu.add_menu_item( act_dt, 5, 2, &actual_time_reference, 10); 214 | MyMenu.add_menu_item( act_tm, 5, 3, &actual_time_reference, 11); 215 | MyMenu.add_menu_item( day_sh, 5, 4, &daily_time_shift ); 216 | 217 | 218 | //Example: "Alarm" input submenu declaration 219 | //Example: menu_type 5 enable to set/input new value for a varible. It will show input dialog. New value is set from the keyboard step by step by selecting numbers. Input is finished and value is writen to the variable by selecting "enter" symbol. 220 | // add_menu_item(parent menu id, menu - value input dialog, text array reference, pointer to the variable, variable type) 221 | MyMenu.add_menu_item( altm, 5, 6, &alarm_time, 9); 222 | 223 | Serial.begin(115200); 224 | } 225 | 226 | // Use this function to print texts on LCD when menu is not active. 227 | // Function Print2LCD is called with menu refresh time period. 228 | void Print2LCD() { 229 | tm = actual_time_reference + millis()/1000; 230 | lcd.setCursor(0,0); 231 | //print actual time on the 1st LCD row 232 | sval=" "; 233 | if (hour(tm)<10) {sval+="0";sval+=hour(tm);} else {sval+=hour(tm);} 234 | sval+=":"; 235 | if (minute(tm)<10) {sval+="0";sval+=minute(tm);} else {sval+=minute(tm);} 236 | sval+=":"; 237 | if (second(tm)<10) {sval+="0";sval+=second(tm);} else {sval+=second(tm);} 238 | sval+=" "; 239 | lcd.print(sval); 240 | sval=" "; 241 | if (alarm_active) {lcd.write(byte(5));lcd.print(" ");} else {lcd.print(" ");} 242 | lcd.setCursor(0,1); 243 | if (day(tm)<10) {sval+="0";sval+=day(tm);} else {sval+=day(tm);} 244 | sval+="."; 245 | if (month(tm)<10) {sval+="0";sval+=month(tm);} else {sval+=month(tm);} 246 | sval+="."; 247 | if (year(tm)<10) {sval+="0";sval+=year(tm);} else {sval+=year(tm);} 248 | sval+=" "; 249 | lcd.print(sval); 250 | } 251 | 252 | // Function Menu_Off is called when the menu goes to inactive state 253 | void Menu_Off() { 254 | calc_start(); 255 | //calculates fraction of the day in milliseconds, when 1 sec time compensation of will be applied 256 | time_shift_fraction = 86400000 / abs(daily_time_shift); 257 | time_shifts = millis() / time_shift_fraction; 258 | } 259 | 260 | void alm() { 261 | if (alarm_active) { //time program 1 is active 262 | if (next_start < (actual_time_reference + millis()/1000) && (next_start + 60) > (actual_time_reference + millis()/1000) && !alarm_on) { // start at alarm_time (within 1 minute window) 263 | alarm_endtime = (actual_time_reference + millis()/1000) + (unsigned long)alarm_duration; 264 | alarm_on = true; 265 | } 266 | } 267 | if (alarm_on && (actual_time_reference + millis()/1000) % 2 == 0 && play == 0) {tone(buzz_pin, 2000, 1000);play = 1;} //play tone 2000 hz each even second 268 | if (alarm_on && (actual_time_reference + millis()/1000) % 2 == 1 && play == 1) {play = 0;} 269 | 270 | if (alarm_endtime < (actual_time_reference + millis()/1000) && alarm_on) {digitalWrite(buzz_pin, 0);alarm_on = false;play = 0;calc_start();} //stops the alarm 271 | } 272 | 273 | void calc_start() { //Calculates next alarm time 274 | tm = actual_time_reference + millis()/1000; 275 | midnight = tm - ((unsigned long) hour(tm)*3600 + minute(tm)*60 + second(tm)); 276 | if (actual_time_reference + millis()/1000 - midnight < alarm_time) {//program will start today at start_time1 hour 277 | next_start = midnight + alarm_time; 278 | } else {//program will start next day at start_time1 hour 279 | next_start = midnight + alarm_time + 86400; 280 | } 281 | valid = 0; 282 | b = 1; 283 | while (!valid && b <= 7) { 284 | if (weekday(next_start)==1 && su==1) {valid=1;} //next_start day is Sunday and alarm on Sunday is enabled 285 | if (weekday(next_start)==2 && mo==1) {valid=1;} //next_start day is Monday and alarm on Monday is enabled 286 | if (weekday(next_start)==3 && tu==1) {valid=1;} //next_start day is Tuesday and alarm on Tuesday is enabled 287 | if (weekday(next_start)==4 && we==1) {valid=1;} //next_start day is Wednesday and alarm on Wednesday is enabled 288 | if (weekday(next_start)==5 && th==1) {valid=1;} //next_start day is Thursday and alarm on Thursday is enabled 289 | if (weekday(next_start)==6 && fr==1) {valid=1;} //next_start day is Friday and alarm on Friday is enabled 290 | if (weekday(next_start)==7 && sa==1) {valid=1;} //next_start day is Saturday and alarm on Saturday is enabled 291 | if (!valid) {next_start += 86400;b++;} 292 | } 293 | if (!valid) {alarm_active = 0;next_start = 0;} //if not found any valid alarm start during a week, switch off alarms and set next start to 0 294 | } 295 | 296 | void time_shifts_compensation() { //Compensation of slow up or quick up the internal arduino clock. 297 | if (time_shifts != millis() / time_shift_fraction) { 298 | time_shifts = millis() / time_shift_fraction; 299 | if (daily_time_shift > 0) {actual_time_reference += 1;} else {actual_time_reference -= 1;} 300 | } 301 | } 302 | 303 | void millis_restart() { //millis() are restarting from zero each 49.7 days. Few seconds before restart actual_time_reference is shifted by 49.7 days forward 304 | if (millis() > 4294960000) { 305 | noInterrupts (); 306 | timer0_millis = 0; 307 | actual_time_reference += 4294960; 308 | interrupts (); 309 | } 310 | //if (millis()+ test > 4294960000 && time_restart == false) {time_restart=true;} //old version code 311 | //if (millis()+ test <= 4294960000 && time_restart == true) {time_restart=false;actual_time_reference += 4294967;} //old version code 312 | } 313 | 314 | void loop() { 315 | alm(); //Makes alarm in preset time 316 | time_shifts_compensation(); //Compensation of slow up or quick up the internal arduino clock. 317 | millis_restart(); //millis() are restarting from zero each 49.7 days. Few seconds before restart actual_time_reference is shifted by 49.7 days forward 318 | MyMenu.draw(); //calling method to show the menu on the screen of the display 319 | } 320 | -------------------------------------------------------------------------------- /examples/menu_irigation/menu_irigation.ino: -------------------------------------------------------------------------------- 1 | // Example of simple irigation system 2 | // Configuration 3 | // * 3 independent watering loop (3x soil humidity sensors, 3x valves) 4 | // * 2 time based watering program 5 | // * 1 low humidity based watering program 6 | // * simple event recording 7 | // * time compensation of slow up or quick up the internal arduino clock 8 | // * menu control by rotary encoder input (or 4 buttons via 4 binary inputs or 1 analog input) 9 | // * display on 20x4 LCD display (or 16x2) using LiquidCrystal_I2C.h library 10 | 11 | #include //time library used for handling with datetime variables type 12 | #include //time library used for handling with datetime variables type 13 | #include //I2C communication library 14 | #include //LCD display library 15 | 16 | #define display_columns 16 17 | #define display_rows 2 18 | 19 | // LiquidCrystal_I2C setup 20 | // Define new instance (lcd) of LiquidCrystal_I2C class 21 | // LiquidCrystal_I2C (, , ) 22 | 23 | LiquidCrystal_I2C lcd(0x3f,display_columns,display_rows); 24 | 25 | #include "CMenu_I2C.h" //Read the Menu library 26 | 27 | //Texts for menu dialog like on/off, open/close for whole menu 28 | const String On_label = "ON "; //set on/off labels string for menu dialog 29 | const String Off_label = "OFF"; //set on/off labels string for menu dialog 30 | const String New_val = "Enter:"; //set input label string for menu dialog 31 | 32 | //Texts of individual menu items (variable names and programs/rutines names). Texts are stored in the program memory to spare space in the local memory 33 | const char t00[] PROGMEM = "Sensors"; 34 | const char t01[] PROGMEM = "Settings"; 35 | const char t02[] PROGMEM = "Programs"; 36 | const char t03[] PROGMEM = "Commands"; 37 | const char t04[] PROGMEM = "Records"; 38 | const char t05[] PROGMEM = "Humidity 1"; 39 | const char t06[] PROGMEM = "Humidity 2"; 40 | const char t07[] PROGMEM = "Humidity 3"; 41 | const char t08[] PROGMEM = "Treshold"; 42 | const char t09[] PROGMEM = "Cycle time"; 43 | const char t10[] PROGMEM = "Start time1"; 44 | const char t11[] PROGMEM = "Start time2"; 45 | const char t12[] PROGMEM = "Act. time"; 46 | const char t13[] PROGMEM = "Act. date"; 47 | const char t14[] PROGMEM = "Humid.treshld"; 48 | const char t15[] PROGMEM = "Time prog.1"; 49 | const char t16[] PROGMEM = "Time prog.2"; 50 | const char t17[] PROGMEM = "Valve1"; 51 | const char t18[] PROGMEM = "Valve2"; 52 | const char t19[] PROGMEM = "Valve3"; 53 | const char t20[] PROGMEM = "Low humid. 1"; 54 | const char t21[] PROGMEM = "Low humid. 2"; 55 | const char t22[] PROGMEM = "Low humid. 3"; 56 | const char t23[] PROGMEM = "Daily time shift"; 57 | 58 | //Construction of the menu texts array in the program memory. 59 | const char* const tt[] PROGMEM = {t00, t01, t02, t03, t04, t05, t06, t07, t08, t09, t10, t11, t12, t13, t14, t15, t16, t17, t18, t19, t20, t21, t22, t23}; 60 | 61 | Menu MyMenu; //Define new instance MyMenu class 62 | 63 | //User program variables declaration 64 | int valve1_pin = 11; 65 | int valve2_pin = 12; 66 | int valve3_pin = 13; 67 | bool Valve1 = 0; 68 | bool Valve2 = 0; 69 | bool Valve3 = 0; 70 | 71 | int humidity1_pin = 1; 72 | int humidity2_pin = 2; 73 | int humidity3_pin = 3; 74 | int Humidity1, Humidity2, Humidity3; 75 | 76 | float humidity_treshold = 400; //Humidity treshold value for start of watering 77 | int cycle_time = 300; //Watering cycle period time is seconds 78 | bool watering_ON; //Watering cycle running flag 79 | 80 | unsigned long start_time1 = 28800; //Watering start day time 1 08:00:00 (number of seconds since 00:00:00 1.1.1970) 81 | unsigned long start_time2 = 68400; //Watering start day time 2 19:00:00 (number of seconds since 00:00:00 1.1.1970) 82 | unsigned long next_start1 = 0; //Watering start time 1 - time stamp 83 | unsigned long next_start2 = 0; //Watering start time 2 - time stamp 84 | unsigned long midnight; //Midnight - time stamp 85 | unsigned long watering_endtime = 0; //watering end - time stamp 86 | 87 | bool prog_low_humidity = 0; //Program of watering on low humidity level flag 88 | bool prog_time1 = 0; //Time 1 watering program flag 89 | bool prog_time2 = 0; //Time 2 watering program flag 90 | 91 | 92 | unsigned long actual_time_reference = 0; // actual time - time stamp 93 | bool time_restart = false; //millis() are restarting from zero each 49.7 days. Restart flag 94 | 95 | //value in seconds for compensation of slow up or quick up the internal arduino clock (for example value should be 30 when internal clock during 24 hours slow up 30 seconds) 96 | int daily_time_shift = 0; 97 | unsigned long time_shifts = 0; 98 | unsigned long time_shift_fraction = 0; 99 | 100 | unsigned long tm = 0; //time calculaion auxiliary variable 101 | String sval; //string auxiliary variable 102 | 103 | void setup() { 104 | 105 | //LCD initialization 106 | lcd.init(); 107 | lcd.backlight(); 108 | 109 | //I/O setup of the User IO global variables 110 | pinMode(2, INPUT); // pin 2 is used for buttons/rotary encoder input 111 | pinMode(3, INPUT); // pin 3 is used for buttons/rotary encoder input 112 | pinMode(4, INPUT); // pin 4 is used for buttons/rotary encoder input 113 | pinMode(5, INPUT); // pin 5 is used for buttons/rotary encoder input 114 | pinMode(valve1_pin, OUTPUT); // pin 11 is used for operating of valve 1 115 | pinMode(valve2_pin, OUTPUT); // pin 12 is used for operating of valve 2 116 | pinMode(valve3_pin, OUTPUT); // pin 13 is used for operating of valve 3 117 | 118 | 119 | //------------------------------------- 120 | //Menu initialization (CMenu.h library) 121 | //Initialize menu (LCD_object, PROGMEM_text_table, refresh_time_[ms], sleep_time [s], off_time[min] (255=off), display_columns, display_rows, date_DMY [1: date format DD.MM.YY, 0: date format MM.DD.YY, default is DD.MM.YY, max_events (0..50)]) 122 | MyMenu.begin( lcd, tt, 1000, 30, 5, display_columns, display_rows, 1 , 10); 123 | 124 | //Menu input settings 125 | //void key_input(bool _analog_input, byte _pin, word _up, word _down, word _left, word _right) 126 | // * _analog_input - Analog or digital input type. 127 | 128 | // - Analog type: Arrays of input switches distinguish which switch was pressed by different resistors connected to each switch 129 | // - Parameters _up, _down, _left, _right represents value on analog input pin when you press corresponding key [0-1024] 130 | // * _pin - For analog input type its analog pin number used for the input. 131 | 132 | // - Digital type: 4 press buttons or rotary encoder 133 | // - 4 press buttons: parameters _up, _down, _left, _right represents digital pin number used for reading corresponding key 134 | // - rotary encoder : _up: digital pin number of CLK, _down: digital pin number of DT, _left: digital pin number of SW, _right: digital pin number of SW 135 | // - rotary encoder : move to left(back) is realized by doubleclick, move to right(front) is realized by click 136 | 137 | 138 | //Examples of the menu input settings: 139 | // key_input(analog_input, pin, up, down, left, right) 140 | //MyMenu.key_input( 1, 0, 40, 160, 10, 250); //analog input pin A0, up, down, left, right represents value on analog input pin when you press corresponding key [0-1024] 141 | //MyMenu.key_input( 0, 0, 3, 4, 5, 2); //digital input pins 3 up, 4 down, 5 left, 2 right 142 | MyMenu.key_input( 0, 0, 2, 3, 4, 4); //rotary encoder pins 2 CLK, 3 DT, 4 SW 143 | 144 | //Creation of the new Menu item. 145 | // Maximal number of menu items is 100. 146 | //int item_id Menu::add_menu_item(int parent_id, int action_type, byte tt[], int variable_pointer (optional), byte var_type (optional)); 147 | // * Parent_id/item_id is the identificator of the menu item for making multi level menus. You can name the item_id to make menu interlinks. Root level id is -1 148 | // * tt[] is index of the menu text stored in te program memory 149 | // * Variable_pointer is the pointer to the address of the memory where is stored a variable, which should be dynamically displayed in the menu 150 | // * Valid var_point_types are bool, byte, int, unsigned int, long, unsigned long, float, datetime 151 | // * var_type - specifies number of decimal places for float and double 152 | // * var_type - for datetime specify which part (date or time) you want to display (var_type 7 or 8) 153 | 154 | //int action_types 155 | //0 - menu 156 | //1 - menu + display value 157 | //2 - menu + set int to 1/0 - thick [v]/[ ] 158 | //3 - menu + set int to 1/0 - ON/OFF 159 | //4 - display events: 1st row - datetime, 2nd row - event description + int value 160 | //5 - set value 161 | 162 | //byte var_point_type; 163 | //0 - bool 0 ... 1 164 | //1 - byte 0 ... 255 165 | //2 - int -32 768 ... 32 767 166 | //3 - word/unsigned int 0 ... 65535 167 | //4 - long -2 147 483 648 ... 2 147 483 647 168 | //5 - unsigned long 0 ... 4 294 967 295 169 | //6 - float -3.4028235E+38 ... 3.4028235E+38 170 | //7 - double 171 | //8 - datetime(unsigned long): date DD.MM.YY or MM.DD.YY (se parameter date_DMY). Unsigned long - number of seconds since 1.1.1970 00:00:00 (Time.h, TimeLib.h library) 172 | //9 - datetime(unsigned long): time hh:mm:ss. Unsigned long - number of seconds since 1.1.1970 00:00:00 (Time.h, TimeLib.h library) 173 | //10- datetime(unsigned long): date + millis(); 174 | //11- datetime(unsigned long): time + millis(); 175 | 176 | //Example: Making of the root menu items ("Sensors", "Settings", "Programs", "Commands" and "Records") 177 | //Example: menu_type 0 is the simple text menu 178 | //int item_id Menu::add_menu_item( int parent_id, int action_type, byte tt[], int variable_pointer (optional), byte var_type (optional)); 179 | // add_menu_item( root level, menu - only text, text array reference) 180 | byte Sensors = MyMenu.add_menu_item( -1, 0, 0); 181 | byte Settings = MyMenu.add_menu_item( -1, 0, 1); 182 | byte Programs = MyMenu.add_menu_item( -1, 0, 2); 183 | byte Commands = MyMenu.add_menu_item( -1, 0, 3); 184 | byte Records = MyMenu.add_menu_item( -1, 0, 4); 185 | 186 | //Example: "Sensors" submenu declaration 187 | //Example: menu_type 1 will show actual value of the Humidity1 variable in the menu 188 | //Example: &Humidity1 is pointer to the user variable Humidity1 189 | //Example: variable Sensors contain index of the "Sensors" menu item declared above 190 | // add_menu_item(parent menu id, menu - only text, text array reference, pointer to the variable) 191 | MyMenu.add_menu_item( Sensors, 1, 5, &Humidity1); 192 | MyMenu.add_menu_item( Sensors, 1, 6, &Humidity2); 193 | MyMenu.add_menu_item( Sensors, 1, 7, &Humidity3); 194 | 195 | 196 | //Example: "Settings" submenu declaration 197 | // add_menu_item(parent menu id, menu - text + value, text array reference, pointer to the variable, variable type) 198 | byte treshold = MyMenu.add_menu_item( Settings, 1, 8, &humidity_treshold ); 199 | byte cycle = MyMenu.add_menu_item( Settings, 1, 9, &cycle_time ); 200 | byte start1 = MyMenu.add_menu_item( Settings, 1, 10, &start_time1, 9); 201 | byte start2 = MyMenu.add_menu_item( Settings, 1, 11, &start_time2, 9); 202 | byte ref_time = MyMenu.add_menu_item( Settings, 1, 12, &actual_time_reference, 11); 203 | byte ref_date = MyMenu.add_menu_item( Settings, 1, 13, &actual_time_reference, 10); 204 | byte day_shift= MyMenu.add_menu_item( Settings, 1, 23, &daily_time_shift ); 205 | 206 | //Example: "Programs" submenu declaration 207 | //Example: menu_type 2 enable to set logical 0 or 1 to a variable 208 | //Example: It can be used for switching on/off some program rutines from the menu 209 | // add_menu_item(parent menu id, menu - text + set value 1/0, text array reference, pointer to the variable) 210 | MyMenu.add_menu_item( Programs, 2, 14, &prog_low_humidity); 211 | MyMenu.add_menu_item( Programs, 2, 15, &prog_time1); 212 | MyMenu.add_menu_item( Programs, 2, 16, &prog_time2); 213 | 214 | //Example: "Commands" submenu declaration 215 | //Example: menu_type 3 enable to set logical 0 or 1 to a variable 216 | //Example: It is simillar menu_type 2. It can be used for switching on/off I/O 217 | // add_menu_item(parent menu id, menu - text + set value 1/0, text array reference, pointer to the variable) 218 | MyMenu.add_menu_item( Commands, 3, 17, &Valve1); 219 | MyMenu.add_menu_item( Commands, 3, 18, &Valve2); 220 | MyMenu.add_menu_item( Commands, 3, 19, &Valve3); 221 | 222 | //Example: "Records" submenu declaration 223 | //Example: menu_type 4 displays record list. Each record contains event_time, event_name and some value at the time when event occure 224 | // add_menu_item(parent menu id, menu - display events, text array reference) 225 | MyMenu.add_menu_item( Records, 4, 4); 226 | 227 | //Example: "Settings" input submenu declaration 228 | //Example: menu_type 5 enable to set/input new value for a varible. It will show input dialog. New value is set from the keyboard step by step by selecting numbers. Input is finished and value is writen to the variable by selecting "enter" symbol. 229 | // add_menu_item(parent menu id, menu - value input dialog, text array reference, pointer to the variable, variable type) 230 | MyMenu.add_menu_item( treshold, 5, 8, &humidity_treshold ); 231 | MyMenu.add_menu_item( cycle, 5, 9, &cycle_time ); 232 | MyMenu.add_menu_item( start1, 5, 10, &start_time1, 9); 233 | MyMenu.add_menu_item( start2, 5, 11, &start_time2, 9); 234 | MyMenu.add_menu_item( ref_time, 5, 12, &actual_time_reference, 11); 235 | MyMenu.add_menu_item( ref_date, 5, 13, &actual_time_reference, 10); 236 | MyMenu.add_menu_item( day_shift, 5, 23, &daily_time_shift ); 237 | 238 | //Example: Adding some events 239 | //void add_event(unsigned long _event_time, byte _event_text_id, float event_var); 240 | // * _event_time is standard unix time (number of seconds since 1.1.1970 00:00:00) 241 | // * _event_text_id is index of the text in tt[] stored in te program memory 242 | // * event_var is value to be stored to the memory 243 | 244 | MyMenu.add_event(1557297120, 18, 532); 245 | MyMenu.add_event(1557302400, 15, 324); 246 | MyMenu.add_event(1557388800, 15, 342); 247 | MyMenu.add_event(1557475200, 15, 356); 248 | MyMenu.add_event(1557561600, 15, 379); 249 | MyMenu.add_event(1557648000, 15, 398); 250 | MyMenu.add_event(1557734400, 15, 425); 251 | MyMenu.add_event(1557820800, 15, 461); 252 | MyMenu.add_event(1557907200, 15, 492); 253 | MyMenu.add_event(1557911580, 18, 333); 254 | MyMenu.add_event(1557922394, 18, 321); 255 | 256 | Serial.begin(115200); 257 | watering_ON = false; 258 | } 259 | 260 | // Use this function to print texts on LCD when menu is not active. 261 | // Function Print2LCD is called with menu refresh time period. 262 | void Print2LCD() { 263 | //print actual time on the 1st LCD row 264 | sval=""; 265 | tm = actual_time_reference + millis()/1000; 266 | if (day(tm)<10) {sval+="0";sval+=day(tm);} else {sval+=day(tm);} 267 | sval+="."; 268 | if (month(tm)<10) {sval+="0";sval+=month(tm);} else {sval+=month(tm);} 269 | sval+="."; 270 | if (year(tm)<10) {sval+="0";sval+=year(tm);} else {sval+=year(tm);} 271 | sval+=" "; 272 | if (hour(tm)<10) {sval+="0";sval+=hour(tm);} else {sval+=hour(tm);} 273 | sval+=":"; 274 | if (minute(tm)<10) {sval+="0";sval+=minute(tm);} else {sval+=minute(tm);} 275 | sval+=":"; 276 | if (second(tm)<10) {sval+="0";sval+=second(tm);} else {sval+=second(tm);} 277 | lcd.clear(); 278 | lcd.setCursor(0,0); 279 | lcd.print(sval); 280 | 281 | //print actual humidity levels from the H1, H2, H3 sensors on 2nd LCD row 282 | lcd.setCursor(0,1); 283 | lcd.print("H1:"); 284 | lcd.print(Humidity1); 285 | lcd.print(" "); 286 | lcd.setCursor(7,1); 287 | lcd.print("H2:"); 288 | lcd.print(Humidity2); 289 | lcd.print(" "); 290 | lcd.setCursor(14,1); 291 | lcd.print("H3:"); 292 | lcd.print(Humidity3); 293 | lcd.print(" "); 294 | } 295 | 296 | // Function Menu_Off is called when the menu goes to inactive state 297 | void Menu_Off() { 298 | calc_starts(); //Calculates next watering start times 299 | time_shift_fraction = 86400000 / abs(daily_time_shift); //Calculates new constants for compensation of slow up or quick up the internal arduino clock. 300 | time_shifts = millis() / time_shift_fraction; //Calculates new constants for compensation of slow up or quick up the internal arduino clock. 301 | } 302 | 303 | //Example: reading the Arduino Uno I/O and storing values to the variables 304 | void readIO() { 305 | if (Valve1 == true) { 306 | digitalWrite(valve1_pin, HIGH); 307 | } else { 308 | digitalWrite(valve1_pin, LOW); 309 | } 310 | if (Valve2 == true) { 311 | digitalWrite(valve2_pin, HIGH); 312 | } else { 313 | digitalWrite(valve2_pin, LOW); 314 | } 315 | if (Valve3 == true) { 316 | digitalWrite(valve3_pin, HIGH); 317 | } else { 318 | digitalWrite(valve3_pin, LOW); 319 | } 320 | Humidity1 = analogRead(humidity1_pin); 321 | Humidity2 = analogRead(humidity2_pin); 322 | Humidity3 = analogRead(humidity3_pin); 323 | } 324 | 325 | void Low_Humidity() { 326 | if (prog_low_humidity) { //program for watering on low soil humidity level is enabled 327 | if (Humidity1 > humidity_treshold) { //soil humidity on sensor 1 is less then treshold (value red from the sensor is higher than the treshold) 328 | Valve1=1; //opens valve 1 329 | watering_endtime = (actual_time_reference + millis()/1000) + (unsigned long)cycle_time; 330 | watering_ON = true; 331 | MyMenu.add_event((actual_time_reference + millis()/1000), 20, Humidity1); //add event to the event log 332 | } 333 | if (Humidity2 > humidity_treshold) { //soil humidity on sensor 2 is less then treshold (value red from the sensor is higher than the treshold) 334 | Valve2=1; //opens valve 2 335 | watering_endtime = (actual_time_reference + millis()/1000) + (unsigned long)cycle_time; 336 | watering_ON = true; 337 | MyMenu.add_event((actual_time_reference + millis()/1000), 21, Humidity2); //add event to the event log 338 | } 339 | if (Humidity3 > humidity_treshold) { //soil humidity on sensor 3 is less then treshold (value red from the sensor is higher than the treshold) 340 | Valve3=1; //opens valve 3 341 | watering_endtime = (actual_time_reference + millis()/1000) + (unsigned long)cycle_time; 342 | watering_ON = true; 343 | MyMenu.add_event((actual_time_reference + millis()/1000), 22, Humidity3); //add event to the event log 344 | } 345 | } 346 | } 347 | 348 | void Time_Prog1() { 349 | if (prog_time1) { //time program 1 is active 350 | if (next_start1 < (actual_time_reference + millis()/1000) && (next_start1 + 60) > (actual_time_reference + millis()/1000) && !watering_ON) { // start at start_time1 (within 1 minute window) 351 | Valve1=1; //opens valve 1 352 | Valve2=1; //opens valve 2 353 | Valve3=1; //opens valve 3 354 | watering_endtime = (actual_time_reference + millis()/1000) + (unsigned long)cycle_time; 355 | watering_ON = true; 356 | MyMenu.add_event((actual_time_reference + millis()/1000), 10, 0); //add event to the event log 357 | } 358 | } 359 | if (watering_endtime < (actual_time_reference + millis()/1000) && watering_ON) {Valve1=0;Valve2=0;Valve3=0;watering_ON = false;calc_starts();} //stop the watering when watering cycle ends and calculate next start times 360 | } 361 | 362 | void Time_Prog2() { 363 | if (prog_time2) { //time program 2 is active 364 | //Serial.print("next_start2:");Serial.print(next_start2);Serial.print(",act_time:");Serial.println(actual_time_reference + millis()/1000); 365 | if (next_start2 < (actual_time_reference + millis()/1000) && (next_start2 + 60) > (actual_time_reference + millis()/1000) && !watering_ON) { // start at start_time1 (within 1 minute window) 366 | Valve1=1; //opens valve 1 367 | Valve2=1; //opens valve 2 368 | Valve3=1; //opens valve 3 369 | watering_endtime = (actual_time_reference + millis()/1000) + (unsigned long)cycle_time; 370 | watering_ON = true; 371 | MyMenu.add_event((actual_time_reference + millis()/1000), 11, 0); //add event to the event log 372 | } 373 | } 374 | if (watering_endtime < (actual_time_reference + millis()/1000) && watering_ON) {Valve1=0;Valve2=0;Valve3=0;watering_ON = false;calc_starts();} //stop the watering when watering cycle ends and calculate next start times 375 | } 376 | 377 | void calc_starts() { 378 | tm = actual_time_reference + millis()/1000; 379 | midnight = tm - ((unsigned long) hour(tm)*3600 + minute(tm)*60 + second(tm)); 380 | if (actual_time_reference + millis()/1000 - midnight < start_time1) {//program will start today at start_time1 hour 381 | next_start1 = midnight + start_time1; 382 | } else {//program will start next day at start_time1 hour 383 | next_start1 = midnight + start_time1 + 86400; 384 | } 385 | if (actual_time_reference + millis()/1000 - midnight < start_time2) {//program will start today at start_time1 hour 386 | next_start2 = midnight + start_time2; 387 | } else {//program will start next day at start_time2 hour 388 | next_start2 = midnight + start_time2 + 86400; 389 | } 390 | } 391 | 392 | void time_shifts_compensation() { //Compensation of slow up or quick up the internal arduino clock. 393 | if (time_shifts != millis() / time_shift_fraction) { 394 | time_shifts = millis() / time_shift_fraction; 395 | if (daily_time_shift > 0) {actual_time_reference += 1;} else {actual_time_reference -= 1;} 396 | } 397 | } 398 | 399 | void millis_restart() { //millis() are restarting from zero each 49.7 days. Few seconds before restart actual_time_reference is shifted by 49.7 days forward 400 | if (millis() > 4294960000) { 401 | noInterrupts (); 402 | timer0_millis = 0; 403 | actual_time_reference += 4294960; 404 | interrupts (); 405 | } 406 | //if (millis()+ test > 4294960000 && time_restart == false) {time_restart=true;} //old version code 407 | //if (millis()+ test <= 4294960000 && time_restart == true) {time_restart=false;actual_time_reference += 4294967;} //old version code 408 | } 409 | 410 | void loop() { 411 | Low_Humidity(); //Starts watering when soil humidity is less then preset treshold 412 | Time_Prog1(); //Starts watering on preset time 1 413 | Time_Prog2(); //Starts watering on preset time 2 414 | time_shifts_compensation(); //Compensation of slow up or quick up the internal arduino clock. 415 | millis_restart(); //millis() are restarting from zero each 49.7 days. Few seconds before restart actual_time_reference is shifted by 49.7 days forward 416 | readIO(); //Reading the Arduino Uno I/O and storing values to the variables 417 | MyMenu.draw(); //calling method to show the menu on the screen of the display 418 | } 419 | -------------------------------------------------------------------------------- /src/CMenu_I2C.cpp: -------------------------------------------------------------------------------- 1 | #include "Arduino.h" 2 | #include "CMenu_I2C.h" 3 | 4 | //01234567890123 5 | static const char in_char[] = "0123456789-.R"; //R - is custom defined character "enter". It will finish input dialog 6 | 7 | static const byte downArrow[8] = { 8 | 0b00100, // * 9 | 0b00100, // * 10 | 0b00100, // * 11 | 0b00100, // * 12 | 0b00100, // * 13 | 0b10101, // * * * 14 | 0b01110, // *** 15 | 0b00100 // * 16 | }; 17 | 18 | static const byte upArrow[8] = { 19 | 0b00100, // * 20 | 0b01110, // *** 21 | 0b10101, // * * * 22 | 0b00100, // * 23 | 0b00100, // * 24 | 0b00100, // * 25 | 0b00100, // * 26 | 0b00100 // * 27 | }; 28 | 29 | static const byte menuCursor[8] = { 30 | B01000, // * 31 | B00100, // * 32 | B00010, // * 33 | B00001, // * 34 | B00010, // * 35 | B00100, // * 36 | B01000, // * 37 | B00000 // 38 | }; 39 | 40 | 41 | static const byte thick[8] = { 42 | B00000, // 43 | B00000, // 44 | B00001, // * 45 | B00011, // ** 46 | B10110, //* ** 47 | B11100, //*** 48 | B01000, // * 49 | B00000 // 50 | }; 51 | 52 | 53 | static const byte enter[8] = { 54 | B00001, // * 55 | B00001, // * 56 | B00001, // * 57 | B00101, // * * 58 | B01001, // * * 59 | B11111, //***** 60 | B01000, // * 61 | B00100 // * 62 | }; 63 | 64 | List_item::List_item(unsigned long _item_time, byte _item_tt_id, float _item_val) { 65 | this->item_time = _item_time; 66 | this->item_tt_id = _item_tt_id; 67 | this->item_val = _item_val; 68 | } 69 | 70 | Menu_item::Menu_item(int _par_id, int _act_type, byte _t_id, void* _var_p) { 71 | this->parent_id = _par_id; 72 | this->action_type = _act_type; 73 | this->tt_id = _t_id; 74 | this->var_point = _var_p; 75 | } 76 | 77 | 78 | //Menu:------------------------------------------------------ 79 | 80 | Menu::Menu() {}; 81 | 82 | void Menu::begin(LiquidCrystal_I2C LCD, const char* const _tt_point[], unsigned int _menu_refresh, byte _sleep_time, byte _off_time, byte _display_columns, byte _display_rows, bool _date_DMY, byte _max_events) { 83 | this->LCD = &LCD; 84 | this->LCD->createChar(0, (uint8_t*) menuCursor); 85 | this->LCD->createChar(1, (uint8_t*) upArrow); 86 | this->LCD->createChar(2, (uint8_t*) downArrow); 87 | this->LCD->createChar(3, (uint8_t*) thick); 88 | this->LCD->createChar(4, (uint8_t*) enter); 89 | this->menu_max_id = 0; 90 | this->list_pos = 0; 91 | this->list_max_id = 0; 92 | this->prev = 0; 93 | this->next = 0; 94 | this->tt_point = _tt_point; 95 | this->menu_refresh = _menu_refresh; 96 | this->sleep_time = _sleep_time; 97 | this->off_time = _off_time; 98 | this->date_DMY = _date_DMY; 99 | this->Display_columns = _display_columns; 100 | this->Display_rows = _display_rows; 101 | if (_max_events < Max_Event_items){this->max_events = _max_events;} else {this->max_events = Max_Event_items;} 102 | if ( max_events < 0){this->max_events = 0;} 103 | menu_init(); 104 | } 105 | 106 | void Menu::key_input(bool _analog_input, byte _pin, word _up, word _down, word _left, word _right) { 107 | analog_input = _analog_input; 108 | input_pin = _pin; 109 | input_val[1] = 2; 110 | input_val[3] = 3; 111 | input_val[5] = 4; 112 | input_val[7] = 1; 113 | if (analog_input) { 114 | input_val[0] = (byte) _up / 4; 115 | input_val[2] = (byte) _down / 4; 116 | input_val[4] = (byte) _left / 4; 117 | input_val[6] = (byte) _right / 4; 118 | 119 | //Ascending sort inputs by analog value 120 | auxo = true; 121 | while (auxo) { 122 | auxo = false; 123 | for (auxi=0; auxi < 3; auxi++) { 124 | if (input_val[auxi*2] > input_val[auxi*2 + 2]) { 125 | auxo = true; 126 | auxb = input_val[auxi*2]; 127 | input_val[auxi*2] = input_val[auxi*2 + 2]; 128 | input_val[auxi*2 + 2] = auxb; 129 | auxb = input_val[auxi*2 + 1]; 130 | input_val[auxi*2 + 1] = input_val[auxi*2 + 3]; 131 | input_val[auxi*2 + 3] = auxb; 132 | } 133 | } 134 | } 135 | } else { 136 | if (_left == _right) {rotary = 1;} else {rotary = 0;} 137 | input_val[0] = _up; 138 | input_val[2] = _down; 139 | input_val[4] = _left; 140 | input_val[6] = _right; 141 | } 142 | } 143 | 144 | //byte var_point_type; 145 | //0 - bool 0 ... 1 146 | //1 - byte 0 ... 255 147 | //2 - int -32 768 ... 32 767 148 | //3 - word/unsigned int 0 ... 65535 149 | //4 - long -2 147 483 648 ... 2 147 483 647 150 | //5 - time/unsigned long 0 ... 4 294 967 295 151 | //6 - float -3.4028235E+38 ... 3.4028235E+38 152 | 153 | byte Menu::add_menu_item(int _par_id, int _act_type, byte _t_id, bool* _var_p, byte var_type) { 154 | this->menu_item[this->menu_max_id] = new Menu_item(_par_id, _act_type, _t_id, (void*)_var_p); 155 | this->menu_item[this->menu_max_id]->var_point_type = 0; 156 | this->menu_max_id=this->menu_max_id + 1; 157 | return menu_max_id - 1; 158 | } 159 | 160 | byte Menu::add_menu_item(int _par_id, int _act_type, byte _t_id, byte* _var_p, byte var_type) { 161 | this->menu_item[this->menu_max_id] = new Menu_item(_par_id, _act_type, _t_id, (void*)_var_p); 162 | this->menu_item[this->menu_max_id]->var_point_type = 1; 163 | this->menu_max_id=this->menu_max_id + 1; 164 | return menu_max_id - 1; 165 | } 166 | 167 | byte Menu::add_menu_item(int _par_id, int _act_type, byte _t_id, int* _var_p, byte var_type) { 168 | this->menu_item[this->menu_max_id] = new Menu_item(_par_id, _act_type, _t_id, (void*)_var_p); 169 | this->menu_item[this->menu_max_id]->var_point_type = 2; 170 | this->menu_max_id=this->menu_max_id + 1; 171 | return menu_max_id - 1; 172 | } 173 | 174 | byte Menu::add_menu_item(int _par_id, int _act_type, byte _t_id, unsigned int* _var_p, byte var_type) { 175 | this->menu_item[this->menu_max_id] = new Menu_item(_par_id, _act_type, _t_id, (void*)_var_p); 176 | this->menu_item[this->menu_max_id]->var_point_type = 3; 177 | this->menu_max_id=this->menu_max_id + 1; 178 | return menu_max_id - 1; 179 | } 180 | 181 | byte Menu::add_menu_item(int _par_id, int _act_type, byte _t_id, long* _var_p, byte var_type) { 182 | this->menu_item[this->menu_max_id] = new Menu_item(_par_id, _act_type, _t_id, (void*)_var_p); 183 | this->menu_item[this->menu_max_id]->var_point_type = 4; 184 | this->menu_max_id=this->menu_max_id + 1; 185 | return menu_max_id - 1; 186 | } 187 | 188 | byte Menu::add_menu_item(int _par_id, int _act_type, byte _t_id, unsigned long* _var_p, byte var_type) { 189 | this->menu_item[this->menu_max_id] = new Menu_item(_par_id, _act_type, _t_id, (void*)_var_p); 190 | if (var_type == 255) { 191 | this->menu_item[this->menu_max_id]->var_point_type = 5; 192 | } else { 193 | this->menu_item[this->menu_max_id]->var_point_type = var_type; 194 | } 195 | this->menu_max_id=this->menu_max_id + 1; 196 | return menu_max_id - 1; 197 | } 198 | 199 | byte Menu::add_menu_item(int _par_id, int _act_type, byte _t_id, float* _var_p, byte var_type) { 200 | this->menu_item[this->menu_max_id] = new Menu_item(_par_id, _act_type, _t_id, (void*)_var_p); 201 | this->menu_item[this->menu_max_id]->var_point_type = 6 + var_type * 20; 202 | this->menu_max_id=this->menu_max_id + 1; 203 | return menu_max_id - 1; 204 | } 205 | 206 | byte Menu::add_menu_item(int _par_id, int _act_type, byte _t_id, double* _var_p, byte var_type) { 207 | this->menu_item[this->menu_max_id] = new Menu_item(_par_id, _act_type, _t_id, (void*)_var_p); 208 | this->menu_item[this->menu_max_id]->var_point_type = 7 + var_type * 20; 209 | this->menu_max_id=this->menu_max_id + 1; 210 | return menu_max_id - 1; 211 | } 212 | 213 | void Menu::add_event(unsigned long _event_time, byte _event_text_id, float event_var) { 214 | if (list_max_id < max_events) { 215 | list_item[list_max_id] = new List_item(_event_time, _event_text_id, event_var); 216 | list_max_id = list_max_id + 1; 217 | } 218 | //Move old data by one index 219 | if (list_max_id > 1) { 220 | for (byte i = (list_max_id - 1); i > 0; i--){ 221 | list_item[i]->item_time = list_item[i-1]->item_time; 222 | list_item[i]->item_tt_id = list_item[i-1]->item_tt_id; 223 | list_item[i]->item_val = list_item[i-1]->item_val; 224 | } 225 | list_item[0]->item_time = _event_time; 226 | list_item[0]->item_tt_id = _event_text_id; 227 | list_item[0]->item_val = event_var; 228 | } 229 | } 230 | 231 | byte Menu::cnt_prev() { 232 | auxb = 0; 233 | auxi = menu_pos - 1; 234 | while (auxi > -1 and menu_item[auxi]->parent_id == menu_item[menu_pos]->parent_id) { 235 | auxb++;auxi--; 236 | } 237 | return auxb; 238 | } 239 | 240 | byte Menu::cnt_next() { 241 | auxb = 0; 242 | auxi = menu_pos + 1; 243 | while (auxi < (int)(sizeof(menu_item) / sizeof(menu_item[0])) and menu_item[auxi]->parent_id == menu_item[menu_pos]->parent_id) { 244 | auxb++;auxi++; 245 | } 246 | return auxb; 247 | } 248 | 249 | void Menu::draw_cursors() { 250 | 251 | if (menu_item[menu_pos-row_pos]->action_type < 4 ) { 252 | auxb = 0; 253 | while (auxbLCD->setCursor(0, auxb); 255 | if (auxb==row_pos){this->LCD->write(byte(0));} else {this->LCD->print(" ");} 256 | auxb++; 257 | } 258 | //Draw the menu cursors (arrows symbols on right side of the display) 259 | auxb = 0; 260 | while (auxbLCD->setCursor(Display_columns - 1, auxb); 262 | if (prev - row_pos > 0 && auxb==0) {this->LCD->write(byte(1));} 263 | else if (next + row_pos - Display_rows + 1 > 0 && auxb+1 == Display_rows) {this->LCD->write(byte(2));} 264 | else {this->LCD->print(" ");} 265 | auxb++; 266 | } 267 | } 268 | if (menu_item[menu_pos-row_pos]->action_type == 4 ) { 269 | //Draw the menu cursors (arrows symbols on right side of the display) 270 | this->LCD->setCursor(Display_columns - 1, 0); 271 | if (list_pos > 0) {this->LCD->write(byte(1));} else {this->LCD->print(" ");} 272 | this->LCD->setCursor(Display_columns - 1, Display_rows - 1); 273 | if (list_pos < (list_max_id - 1)) {this->LCD->write(byte(2));} else {this->LCD->print(" ");} 274 | } 275 | } 276 | 277 | int Menu::evaluate_button_analog(int but) { 278 | //Serial.println(but); 279 | if (but < input_val[0]*4) { 280 | auxi = input_val[1]; 281 | } else if (but < input_val[2]*4) { 282 | auxi = input_val[3]; 283 | } else if (but < input_val[4]*4) { 284 | auxi = input_val[5]; 285 | } else if (but < input_val[6]*4) { 286 | auxi = input_val[7]; 287 | } 288 | return auxi; 289 | } 290 | 291 | void Menu::draw() { 292 | //Menu will be redrawn each LCD_refresh time 293 | if (refresh_time < millis() || refresh_time > (millis() + (unsigned long) menu_refresh)) { 294 | refresh_time = millis() + (unsigned long) menu_refresh; 295 | if (LCD_off == 0) { 296 | if (sleep == 1) { 297 | Print2LCD(); 298 | } else { 299 | extra_read = 1; 300 | menu_draw(); 301 | } 302 | } 303 | } 304 | read_keys(); 305 | } 306 | 307 | void Menu::menu_init() { 308 | LCD_off = 0; 309 | row_pos = 0; 310 | list_pos = 0; 311 | input_pos_x = 0; 312 | input_pos_y = 0; 313 | tmps[0] = '\0'; 314 | txt_pos = 0; 315 | txt_pos_dir = 1; 316 | runn_txt_init(); 317 | extra_read = 0; 318 | this->LCD->noBlink(); 319 | } 320 | 321 | void Menu::runn_txt_init() { 322 | row_sh[0] = 0; 323 | row_sh[1] = 0; 324 | row_sh[2] = 0; 325 | row_sh[3] = 0; 326 | row_dir[0] = 1; 327 | row_dir[1] = 1; 328 | row_dir[2] = 1; 329 | row_dir[3] = 1; 330 | } 331 | 332 | void Menu::navigate_2submenu() { 333 | for (auxi=menu_pos; auxiparent_id == menu_pos) { 335 | menu_pos = auxi; 336 | break; 337 | } 338 | } 339 | menu_init(); 340 | } 341 | 342 | void Menu::fill_s() { 343 | auxb = s.length(); 344 | while (auxb < Display_columns -1) { 345 | s += " "; 346 | auxb++; 347 | } 348 | } 349 | 350 | void Menu::read_keys() { 351 | if (analog_input) { //reading pressed key from analog value 352 | auxi = analogRead(input_pin); 353 | if (auxi < 1000 && (lastkey >=1000 || extra_read)) {auxb = evaluate_button_analog(auxi);} else {auxb = 0;} 354 | lastkey = auxi; 355 | } else { //reading pressed key from binary values 356 | if (rotary) { //reading rotary encoder 357 | //input_val[0] = _up; =CLK 358 | //input_val[1] = 2; =UP 359 | //input_val[2] = _down; =DT 360 | //input_val[3] = 3; =DWN 361 | //input_val[4] = _left; =SW 362 | //input_val[5] = 4; =BACKWARD 363 | //input_val[6] = _right; =SW 364 | //input_val[7] = 1; =FORWARD 365 | 366 | auxb = 0; 367 | auxo = digitalRead(input_val[0]); 368 | //if (CLK != DT) 369 | if (auxo != auxo1) { 370 | if (auxo != digitalRead(input_val[2])) { 371 | if (millis() - last_keypress > 180) { 372 | if (last_direction == 1) { //CW 373 | auxb = input_val[3]; 374 | } else { 375 | last_direction = 1; //CW 376 | } 377 | } 378 | } else { 379 | if (millis() - last_keypress > 180) { 380 | if (last_direction == 2) { //CCW 381 | auxb = input_val[1]; 382 | } else { 383 | last_direction = 2; //CCW 384 | } 385 | } 386 | } 387 | } 388 | auxo1 = auxo; 389 | if (auxb == 0) { 390 | if (digitalRead(input_val[4]) == 1 && millis() - last_keypress > 100) { 391 | if (double_click == 0) {auxl1 = millis(); double_click = 1;} 392 | if (double_click == 2) {double_click = 3;} 393 | } else { 394 | if (double_click == 1 && (millis() - auxl1) < (unsigned long)double_click_delay) {double_click = 2;} 395 | } 396 | } 397 | if ((millis() - auxl1) >= (unsigned long)double_click_delay && (double_click == 1 || double_click == 2)) {double_click = 0; auxb = input_val[7];} 398 | if ((millis() - auxl1) >= (unsigned long)double_click_delay && double_click == 3) {double_click = 0; auxb = input_val[5];} 399 | lastkey = auxb; 400 | } else { //reading pressed key from 4 binary inputs 401 | auxi = 0; 402 | if (digitalRead(input_val[6]) == 1) {auxi = input_val[7];} 403 | if (digitalRead(input_val[2]) == 1) {auxi = input_val[3];} 404 | if (digitalRead(input_val[0]) == 1) {auxi = input_val[1];} 405 | if (digitalRead(input_val[4]) == 1) {auxi = input_val[5];} 406 | if (auxi != 0 && (lastkey == 0 || extra_read)) {auxb = (byte) auxi;} else {auxb = 0;} 407 | lastkey = auxi; 408 | } 409 | } 410 | extra_read = 0; 411 | if (auxb!=0) { 412 | last_keypress = millis(); 413 | if (LCD_off) { 414 | this->LCD->backlight(); 415 | this->LCD->display(); 416 | LCD_off = 0; 417 | } else { 418 | if (sleep==1) { 419 | sleep=0; 420 | } 421 | } 422 | } else { 423 | if (millis() > (last_keypress + ((unsigned long)off_time * 60000)) && LCD_off == 0 && off_time < 255) { 424 | LCD_off = 1; 425 | this->LCD->noDisplay(); 426 | this->LCD->noBacklight(); 427 | } 428 | } 429 | if (sleep==0) { 430 | auxb2 = menu_item[menu_pos]->var_point_type % 20; 431 | switch (auxb) { 432 | case 0: // When button returns as 0 there is no action taken 433 | if (millis() > last_keypress + ((unsigned long)sleep_time * 1000)) { 434 | Menu_Off(); 435 | sleep = 1; 436 | auxb=0; 437 | } 438 | break; 439 | //**********"Forward" button is pressed************************************* 440 | case 1: // "Forward" button is pressed 441 | switch (menu_item[menu_pos]->action_type) { 442 | case 0: //menu type: 0 - menu 443 | navigate_2submenu(); 444 | break; 445 | case 1: //1 - menu + display int value 446 | navigate_2submenu(); 447 | break; 448 | case 2: //2 - menu + set int to 1/0 - tick [v]/[ ] 449 | if (*(bool*)menu_item[menu_pos]->var_point == 0) {*(bool*)menu_item[menu_pos]->var_point = 1;} else {*(bool*)menu_item[menu_pos]->var_point = 0;} 450 | break; 451 | case 3: //3 - menu + set int to 1/0 - ON/OFF 452 | if (*(bool*)menu_item[menu_pos]->var_point == 0) {*(bool*)menu_item[menu_pos]->var_point = 1;} else {*(bool*)menu_item[menu_pos]->var_point = 0;} 453 | break; 454 | case 5: //5 - set value 455 | if ((byte)in_char[input_pos_y]==82) { //ENTER character selected 456 | s = tmps; 457 | auxl = s.toInt(); 458 | switch (auxb2) { 459 | case 0: 460 | if (auxl==0) {*(bool*)menu_item[menu_pos]->var_point = auxl;} 461 | if (auxl==1) {*(bool*)menu_item[menu_pos]->var_point = auxl;} 462 | case 1: 463 | if (auxl > -1 && auxl < 256) {*(byte*)menu_item[menu_pos]->var_point = auxl;} 464 | break; 465 | case 2: 466 | if (auxl > -32769 && auxl < 32768) {*(int*)menu_item[menu_pos]->var_point = auxl;} 467 | break; 468 | case 3: 469 | if (auxl > -1 && auxl < 65536) {*(unsigned int*)menu_item[menu_pos]->var_point = auxl;} 470 | break; 471 | case 4: 472 | *(long*)menu_item[menu_pos]->var_point = auxl; 473 | break; 474 | case 5: 475 | *(unsigned long*)menu_item[menu_pos]->var_point = auxl; 476 | break; 477 | case 6: 478 | *(float*)menu_item[menu_pos]->var_point = s.toFloat(); 479 | break; 480 | case 7: 481 | *(double*)menu_item[menu_pos]->var_point = s.toDouble(); 482 | break; 483 | } 484 | if (auxb2 == 8 || auxb2 == 10) { 485 | if (date_DMY) { //DD.MM.YY 486 | tm.Day = ((byte)tmps[0]-48)*10+((byte)tmps[1]-48); 487 | tm.Month = ((byte)tmps[3]-48)*10+((byte)tmps[4]-48); 488 | } else { //MM.DD.YY 489 | tm.Month = ((byte)tmps[0]-48)*10+((byte)tmps[1]-48); 490 | tm.Day = ((byte)tmps[3]-48)*10+((byte)tmps[4]-48); 491 | } 492 | tm.Year = ((byte)tmps[6]-48)*10+((byte)tmps[7]-48) + 30; 493 | if (auxb2 == 8) {auxl2=*(unsigned long*)menu_item[menu_pos]->var_point;} else {auxl2=*(unsigned long*)menu_item[menu_pos]->var_point + millis()/1000;} 494 | tm.Hour = hour(auxl2); 495 | tm.Minute = minute(auxl2); 496 | tm.Second = second(auxl2); 497 | if (auxb2 == 8) {*(unsigned long*)menu_item[menu_pos]->var_point = makeTime(tm);} else {*(unsigned long*)menu_item[menu_pos]->var_point = makeTime(tm) - millis()/1000;} 498 | } 499 | if (auxb2 == 9 || auxb2 == 11) { 500 | tm.Hour = ((byte)tmps[0]-48)*10+((byte)tmps[1]-48); 501 | tm.Minute = ((byte)tmps[3]-48)*10+((byte)tmps[4]-48); 502 | tm.Second = ((byte)tmps[6]-48)*10+((byte)tmps[7]-48); 503 | if (auxb2 == 9) {auxl2=*(unsigned long*)menu_item[menu_pos]->var_point;} else {auxl2=*(unsigned long*)menu_item[menu_pos]->var_point + millis()/1000;} 504 | tm.Day = day(auxl2); 505 | tm.Month = month(auxl2); 506 | tm.Year = year(auxl2)-1970; 507 | if (auxb2 == 9) {*(unsigned long*)menu_item[menu_pos]->var_point = makeTime(tm);} else {*(unsigned long*)menu_item[menu_pos]->var_point = makeTime(tm) - millis()/1000;} 508 | } 509 | if (menu_item[menu_pos]->parent_id > -1) { 510 | menu_pos = menu_item[menu_pos]->parent_id; 511 | prev = this->cnt_prev(); 512 | next = this->cnt_next(); 513 | while (row_pos < prev && (row_pos + next) < Display_rows) {row_pos++;} 514 | } 515 | } else { 516 | tmps[input_pos_x] = in_char[input_pos_y]; 517 | input_pos_x++; 518 | if (auxb2 == 8 || auxb2 == 10) { 519 | if (input_pos_x == 2) {tmps[input_pos_x] = 46;input_pos_x++;} 520 | if (input_pos_x == 5) {tmps[input_pos_x] = 46;input_pos_x++;} 521 | } 522 | if (auxb2 == 9 || auxb2 == 11) { 523 | if (input_pos_x == 2) {tmps[input_pos_x] = 58;input_pos_x++;} 524 | if (input_pos_x == 5) {tmps[input_pos_x] = 58;input_pos_x++;} 525 | } 526 | } 527 | tmps[input_pos_x] = '\0'; 528 | input_pos_y = sizeof(in_char) - 2; 529 | input_pos_y = move_input_pos_y(input_pos_y, menu_item[menu_pos]->var_point_type, 1); 530 | break; 531 | } 532 | menu_draw(); 533 | break; 534 | case 2: // "Up" button is pressed 535 | if (menu_item[menu_pos]->action_type < 4) { 536 | if (prev > 0) { 537 | menu_pos--; 538 | } 539 | if (row_pos > 0) { 540 | row_pos--; 541 | } 542 | } else { 543 | if (menu_item[menu_pos]->action_type == 4) { 544 | if (list_pos > 0) {list_pos--;} 545 | } 546 | if (menu_item[menu_pos]->action_type == 5) { 547 | if (rotary) { 548 | input_pos_y = move_input_pos_y(input_pos_y, menu_item[menu_pos]->var_point_type, 0); 549 | } else { 550 | input_pos_y = move_input_pos_y(input_pos_y, menu_item[menu_pos]->var_point_type, 1); 551 | } 552 | } 553 | } 554 | runn_txt_init(); 555 | menu_draw(); 556 | break; 557 | case 3: // "Down" button is pressed 558 | if (menu_item[menu_pos]->action_type < 4) { 559 | if (next > 0) { 560 | menu_pos++; 561 | } 562 | if (row_pos < Display_rows - 1) { 563 | row_pos++; 564 | } 565 | } else { 566 | if (menu_item[menu_pos]->action_type == 4) { 567 | if (list_pos < (list_max_id - 1)) {list_pos++;} 568 | } 569 | if (menu_item[menu_pos]->action_type == 5) { 570 | if (rotary) { 571 | input_pos_y = move_input_pos_y(input_pos_y, menu_item[menu_pos]->var_point_type, 1); 572 | } else { 573 | input_pos_y = move_input_pos_y(input_pos_y, menu_item[menu_pos]->var_point_type, 0); 574 | } 575 | } 576 | } 577 | runn_txt_init(); 578 | menu_draw(); 579 | break; 580 | case 4: // "Left" button is pressed 581 | if (menu_item[menu_pos]->parent_id > -1) { 582 | menu_pos = menu_item[menu_pos]->parent_id; 583 | menu_init(); 584 | prev = this->cnt_prev(); 585 | next = this->cnt_next(); 586 | while (row_pos < prev && (row_pos + next) < Display_rows) {row_pos++;} 587 | } else { 588 | last_keypress = millis() - (unsigned long) sleep_time * 1000; 589 | } 590 | menu_draw(); 591 | break; 592 | } 593 | } 594 | //if (sleep==1 && auxb!=0) {sleep=0;menu_pos = 0;menu_init();} 595 | } 596 | 597 | void Menu::get_val() { 598 | switch (auxi1) { 599 | case 0: 600 | s = String(*(bool*)menu_item[auxb1]->var_point); 601 | break; 602 | case 1: 603 | s = String(*(byte*)menu_item[auxb1]->var_point); 604 | break; 605 | case 2: 606 | s = String(*(int*)menu_item[auxb1]->var_point); 607 | break; 608 | case 3: 609 | s = String(*(unsigned int*)menu_item[auxb1]->var_point); 610 | break; 611 | case 4: 612 | s = String(*(long*)menu_item[auxb1]->var_point); 613 | break; 614 | case 5: 615 | s = String(*(unsigned long*)menu_item[auxb1]->var_point); 616 | break; 617 | case 6: 618 | s = String(*(float*)menu_item[auxb1]->var_point, int(menu_item[auxb1]->var_point_type / 20)); 619 | break; 620 | case 7: 621 | s = String(*(double*)menu_item[auxb1]->var_point, int(menu_item[auxb1]->var_point_type / 20)); 622 | break; 623 | } 624 | if (auxi1 == 8 || auxi1 == 10) { 625 | if (auxi1 == 8) {auxl = *(unsigned long*)menu_item[auxb1]->var_point;} else {auxl = *(unsigned long*)menu_item[auxb1]->var_point + millis()/1000;} 626 | s=""; 627 | if (date_DMY) { 628 | if (day(auxl)<10) {s+="0";s+=day(auxl);} else {s+=day(auxl);} 629 | s+="."; 630 | } 631 | if (month(auxl)<10) {s+="0";s+=month(auxl);} else {s+=month(auxl);} 632 | s+="."; 633 | if (!date_DMY) { 634 | if (day(auxl)<10) {s+="0";s+=day(auxl);} else {s+=day(auxl);} 635 | s+="."; 636 | } 637 | s+=String(year(auxl)).substring(2); 638 | } 639 | if (auxi1 == 9 || auxi1 == 11) { 640 | if (auxi1 == 9) {auxl = *(unsigned long*)menu_item[auxb1]->var_point;} else {auxl = *(unsigned long*)menu_item[auxb1]->var_point + millis()/1000;} 641 | s=""; 642 | if (hour(auxl)<10) {s+="0";s+=hour(auxl);} else {s+=hour(auxl);} 643 | s+=":"; 644 | if (minute(auxl)<10) {s+="0";s+=minute(auxl);} else {s+=minute(auxl);} 645 | s+=":"; 646 | if (second(auxl)<10) {s+="0";s+=second(auxl);} else {s+=second(auxl);} 647 | } 648 | } 649 | 650 | 651 | /*******************************************************************************/ 652 | /************** draw DRAW ******************************************************/ 653 | /*******************************************************************************/ 654 | // action_type; 655 | //0 - menu 656 | //1 - menu + display value 657 | //2 - menu + set int to 1/0 - thick [v]/[ ] 658 | //3 - menu + set int to 1/0 - ON/OFF 659 | //4 - display events: 1st row - datetime, 2nd row - event description + int value 660 | //5 - set value 661 | void Menu::menu_draw() { 662 | refresh_time = millis() + (unsigned long) menu_refresh; 663 | 664 | prev = this->cnt_prev(); 665 | next = this->cnt_next(); 666 | 667 | if (next == 0) {if (prev < Display_rows - 1) {row_pos = prev;} else {row_pos = Display_rows - 1;}} 668 | auxb = row_pos + next + 1; 669 | if (auxb > Display_rows) {auxb = Display_rows;} 670 | auxb1 = menu_pos-row_pos; 671 | 672 | if (menu_item[auxb1]->action_type < 4) { 673 | for(auxi = 0; auxi < auxb; auxi++) { 674 | auxb1 = menu_pos-row_pos+auxi; 675 | auxi1 = menu_item[auxb1]->var_point_type % 20; 676 | switch (menu_item[auxb1]->action_type) { 677 | //------------ 678 | case 0: //menu 679 | strcpy_P(tt_text, (char*)pgm_read_word(&tt_point[menu_item[auxb1]->tt_id])); 680 | lcdprint(auxi, 1, tt_text); 681 | break; 682 | //---------------------------- 683 | case 1: //menu + display value 684 | strcpy_P(tt_text, (char*)pgm_read_word(&tt_point[menu_item[auxb1]->tt_id])); 685 | get_val(); 686 | lcdprint(auxi, 1, tt_text, Display_columns - 3 - s.length()); 687 | this->LCD->setCursor(Display_columns - 1 - s.length(), auxi); 688 | this->LCD->print(s); 689 | break; 690 | //--------------------------------------------- 691 | case 2: //menu + set int to 1/0 - thick [v]/[ ] 692 | strcpy_P(tt_text, (char*)pgm_read_word(&tt_point[menu_item[auxb1]->tt_id])); 693 | lcdprint(auxi, 1, tt_text, Display_columns - 6); 694 | this->LCD->setCursor(Display_columns - 5, auxi); 695 | this->LCD->print(" "); 696 | if (*(bool*)menu_item[auxb1]->var_point==0) { 697 | this->LCD->print("[ ]"); 698 | } else { 699 | this->LCD->print("["); 700 | this->LCD->write(byte(3)); 701 | this->LCD->print("]"); 702 | } 703 | break; 704 | //-------------------------------------- 705 | case 3: //menu + set int to 1/0 - ON/OFF 706 | strcpy_P(tt_text, (char*)pgm_read_word(&tt_point[menu_item[auxb1]->tt_id])); 707 | if (*(bool*)menu_item[auxb1]->var_point==0) { 708 | s = Off_label; 709 | } else { 710 | s = On_label; 711 | } 712 | lcdprint(auxi, 1, tt_text, Display_columns - 3 - s.length()); 713 | this->LCD->setCursor(Display_columns - 1 - s.length(), auxi); 714 | this->LCD->print(s); 715 | break; 716 | } 717 | } 718 | while (auxb < Display_rows) { 719 | this->LCD->setCursor(0, auxb); 720 | this->LCD->print(empty_str); 721 | auxb++; 722 | } 723 | } else { 724 | //----------------------------------------------------------------------------------- 725 | //display events: 1st row - datetime, 2nd row - event description + int value 726 | if (menu_item[auxb1]->action_type == 4) { 727 | //Serial.print("list_pos:");Serial.print(list_pos);Serial.print(", list_max_id:");Serial.print(list_max_id); 728 | s=""; 729 | if (date_DMY) { //DD.MM.YY 730 | s += day(list_item[list_pos]->item_time); 731 | s += "."; 732 | } 733 | s += month(list_item[list_pos]->item_time); 734 | s += "."; 735 | if (!date_DMY) { //DD.MM.YY 736 | s += day(list_item[list_pos]->item_time); 737 | s += "."; 738 | } 739 | s += String(year(list_item[list_pos]->item_time)).substring(2); 740 | auxi = s.length(); 741 | lcdprint(0, 0, s, auxi); 742 | //lcdprint(0, 0, s, 7); 743 | s=""; 744 | s += hour(list_item[list_pos]->item_time); 745 | s += ":"; 746 | if (minute(list_item[list_pos]->item_time)<10) {s += "0";} 747 | s += minute(list_item[list_pos]->item_time); 748 | s += ":"; 749 | if (second(list_item[list_pos]->item_time)<10) {s += "0";} 750 | s += second(list_item[list_pos]->item_time); 751 | 752 | lcdprint(0, auxi + 1, s, Display_columns - 2 - auxi); 753 | //lcdprint(0, auxi + 1, s, 7); 754 | 755 | strcpy_P(tt_text, (char*)pgm_read_word(&tt_point[list_item[list_pos]->item_tt_id])); 756 | s = String(list_item[list_pos]->item_val); 757 | lcdprint(1, 0, tt_text, Display_columns - 2 - s.length()); 758 | this->LCD->setCursor(Display_columns - 1 - s.length(), 1); 759 | this->LCD->print(s); 760 | if (Display_rows > 2) { 761 | this->LCD->setCursor(0, 2); 762 | this->LCD->print(empty_str); 763 | this->LCD->setCursor(0, 3); 764 | this->LCD->print(empty_str); 765 | } 766 | } 767 | //----------------- 768 | //Set value 769 | 770 | if (menu_item[auxb1]->action_type == 5) { 771 | auxb1 = menu_pos; 772 | auxi1 = menu_item[auxb1]->var_point_type % 20; 773 | get_val(); 774 | strcpy_P(tt_text, (char*)pgm_read_word(&tt_point[menu_item[menu_pos]->tt_id])); 775 | lcdprint(0, 0, tt_text, Display_columns - 2 - s.length()); 776 | this->LCD->setCursor(Display_columns - 1 - s.length(), 0); 777 | this->LCD->print(s); 778 | lcdprint(1, 0, New_val); 779 | this->LCD->setCursor(New_val.length() + 1, 1); 780 | this->LCD->print(tmps); 781 | this->LCD->setCursor(New_val.length() + 1 + input_pos_x, 1); 782 | if ((byte) in_char[input_pos_y] ==82) {this->LCD->write(byte(4));} else {this->LCD->print(in_char[input_pos_y]);} 783 | if (Display_rows > 2) { 784 | this->LCD->setCursor(0, 2); 785 | this->LCD->print(empty_str); 786 | this->LCD->setCursor(0, 3); 787 | this->LCD->print(empty_str); 788 | } 789 | this->LCD->setCursor(New_val.length() + 1 + input_pos_x, 1); 790 | this->LCD->blink(); 791 | } 792 | } 793 | draw_cursors(); 794 | } 795 | 796 | void Menu::lcdprint(byte row, byte col, String str, byte chrs) { 797 | if (chrs==255) {chrs = Display_columns - 1;} 798 | this->LCD->setCursor(col, row); 799 | if (str.length() <= chrs) { 800 | this->LCD->print(str); 801 | byte mb = 0; 802 | while (mb <= (chrs - str.length())) {this->LCD->print(" "); mb++;} 803 | } else { 804 | this->LCD->print(str.substring(row_sh[row], row_sh[row] + chrs)); 805 | this->LCD->print(" "); 806 | switch (row_dir[row]) { 807 | /* case 1: 808 | row_dir[row] = 2; 809 | break; 810 | case 2: 811 | if (row_sh[row] >= str.length() - chrs) { 812 | row_sh[row] = str.length() - chrs; 813 | row_dir[row] = 3; 814 | } else { 815 | row_sh[row]++; 816 | } 817 | break; 818 | case 3: 819 | row_dir[row] = 4; 820 | break; 821 | case 4: 822 | if (row_sh[row] == 0) { 823 | row_dir[row] = 1; 824 | } else { 825 | row_sh[row]--; 826 | } 827 | break;*/ 828 | case 1: 829 | if (row_sh[row] >= str.length() - chrs) { 830 | row_sh[row] = str.length() - chrs; 831 | row_dir[row] = 2; 832 | } else { 833 | row_sh[row]++; 834 | } 835 | break; 836 | case 2: 837 | if (row_sh[row] == 0) { 838 | row_dir[row] = 1; 839 | } else { 840 | row_sh[row]--; 841 | } 842 | break; 843 | } 844 | } 845 | } 846 | 847 | byte Menu::move_input_pos_y(byte input_pos_y, byte var_type, bool fwd) { 848 | auxo = false; 849 | auxi1 = var_type % 20; 850 | while (!auxo) { 851 | if (input_pos_y == 0 && !fwd) {input_pos_y = sizeof(in_char) - 1;} 852 | if (fwd) {input_pos_y++;} else {input_pos_y--;} 853 | if (input_pos_y > sizeof(in_char) - 2) {input_pos_y = 0;} 854 | if (auxi1 == 0) { 855 | if (input_pos_y == 0) {auxo = true;}; 856 | if (input_pos_y == 1) {auxo = true;}; 857 | if (input_pos_y == sizeof(in_char) - 2) {auxo = true;}; 858 | if (input_pos_x > 0) {input_pos_y = sizeof(in_char) - 2;auxo = true;}; //Maximum of input chars is 1 859 | } 860 | if (auxi1 == 1 || auxi1 == 3 || auxi1 == 5) {//unsigned 861 | if (auxi1 == 1) {auxb2 = 2;} 862 | if (auxi1 == 3) {auxb2 = 4;} 863 | if (auxi1 == 5) {auxb2 = 9;} 864 | if (input_pos_y < 10) {auxo = true;}; //numbers 0123456789 865 | if (input_pos_x > 0 && input_pos_y == sizeof(in_char) - 2) {auxo = true;}; //from second char Enter will be visible 866 | if (input_pos_x > auxb2) {input_pos_y = sizeof(in_char) - 2;auxo = true;}; 867 | } 868 | if (auxi1 == 2 || auxi1 == 4) {//signed 869 | if (auxi1 == 2) {auxb2 = 4;} else {auxb2 = 9;} 870 | if (input_pos_y < 10) {auxo = true;}; //numbers 0123456789 871 | if (input_pos_y == 10 && input_pos_x == 0) {auxo = true;}; //first char can be "-" 872 | if (input_pos_x > 0 && input_pos_y == sizeof(in_char) - 2) {auxo = true;}; //from second char Enter will be visible 873 | if (input_pos_x > auxb2) {input_pos_y = sizeof(in_char) - 2;auxo = true;}; 874 | } 875 | if (auxi1 == 6 || auxi1 == 7) { 876 | if (input_pos_y < 10) {auxo = true;}; //numbers 0123456789 877 | if (input_pos_y == 10 && input_pos_x == 0) {auxo = true;}; //first char can be "-" 878 | auxo1=false;for(auxi=0;auxi<19;auxi++){if((byte)tmps[auxi]==46){auxo1=true;}} 879 | if (input_pos_y == sizeof(in_char) - 3 && !auxo1 && input_pos_x > 0) {auxo = true;} //from second char decimal places separator "." can writen 880 | if (input_pos_x > 0 && input_pos_y == sizeof(in_char) - 2) {auxo = true;}; //from second char Enter will be visible 881 | if (input_pos_x > 9) {input_pos_y = sizeof(in_char) - 2;auxo = true;}; //Maximum of input chars is 10 882 | } 883 | if (auxi1 > 7 && auxi1 < 12) { 884 | if (auxi1 == 8 || auxi1 == 10) { 885 | if (date_DMY) { //DD.MM.YY 886 | if (input_pos_x == 0 && input_pos_y < 4) {auxo = true;}; //numbers 0123 887 | if (input_pos_x == 3 && input_pos_y < 2) {auxo = true;}; //numbers 01 888 | } else { 889 | if (input_pos_x == 0 && input_pos_y < 2) {auxo = true;}; //numbers 01 890 | if (input_pos_x == 3 && input_pos_y < 4) {auxo = true;}; //numbers 0123 891 | } 892 | if (input_pos_x == 6 && input_pos_y < 10) {auxo = true;}; //numbers 0123456789 893 | } 894 | if (auxi1 == 9 || auxi1 == 11) { 895 | if (input_pos_x == 0 && input_pos_y < 3) {auxo = true;}; //numbers 012 896 | if (input_pos_x == 3 && input_pos_y < 6) {auxo = true;}; //numbers 012345 897 | if (input_pos_x == 6 && input_pos_y < 6) {auxo = true;}; //numbers 012345 898 | } 899 | if (input_pos_x == 1 && input_pos_y < 10) {auxo = true;}; //numbers 0123456789 900 | if (input_pos_x == 4 && input_pos_y < 10) {auxo = true;}; //numbers 0123456789 901 | if (input_pos_x == 7 && input_pos_y < 10) {auxo = true;}; //numbers 0123456789 902 | if (input_pos_x > 7) {input_pos_y = sizeof(in_char) - 2;auxo = true;}; //Maximum of input chars is 10 903 | } 904 | if (input_pos_x == 0 && input_pos_y == sizeof(in_char) - 2) {auxo = false;} 905 | } 906 | return input_pos_y; 907 | } 908 | -------------------------------------------------------------------------------- /src/CMenu_I2C.h: -------------------------------------------------------------------------------- 1 | #ifndef CMenu_I2C_h 2 | #define CMenu_I2C_h 3 | 4 | #include "Arduino.h" 5 | #include 6 | #include 7 | #include 8 | 9 | extern const char* const tt[] PROGMEM; 10 | 11 | extern const String On_label; 12 | extern const String Off_label; 13 | extern const String New_val; 14 | 15 | const char* const empty_str = " "; 16 | 17 | #ifndef Max_Menu_items 18 | #define Max_Menu_items 100 19 | #endif 20 | 21 | #ifndef Max_Event_items 22 | #define Max_Event_items 10 23 | #endif 24 | 25 | extern void Print2LCD(); 26 | extern void Menu_Off(); 27 | 28 | class List_item { 29 | public: 30 | unsigned long item_time; 31 | byte item_tt_id; 32 | float item_val; 33 | List_item(unsigned long _item_time, byte _item_tt_id, float _item_val); 34 | }; 35 | 36 | class Menu_item { 37 | public: 38 | int parent_id; 39 | int action_type; 40 | //0 - menu 41 | //1 - menu + display value 42 | //2 - menu + set int to 1/0 - thick [v]/[ ] 43 | //3 - menu + set int to 1/0 - ON/OFF 44 | //4 - display events: 1st row - datetime, 2nd row - event description + int value 45 | //5 - set value 46 | 47 | 48 | byte tt_id; 49 | void* var_point; 50 | byte var_point_type; 51 | //0 - bool 0 ... 1 52 | //1 - byte 0 ... 255 53 | //2 - int -32 768 ... 32 767 54 | //3 - word/unsigned int 0 ... 65535 55 | //4 - long -2 147 483 648 ... 2 147 483 647 56 | //5 - unsigned long 0 ... 4 294 967 295 57 | //6 - float -3.4028235E+38 ... 3.4028235E+38 58 | //7 - double 59 | //8 - datetime(unsigned long): date DD.MM.YY. Unsigned long - number of seconds since 1.1.1970 00:00:00 (Time.h, TimeLib.h library) 60 | //9 - datetime(unsigned long): time hh:mm:ss. Unsigned long - number of seconds since 1.1.1970 00:00:00 (Time.h, TimeLib.h library) 61 | 62 | public: 63 | Menu_item(int _par_id, int _act_type, byte _t_id, void* _var_p); 64 | }; 65 | 66 | class Menu { 67 | 68 | private: 69 | LiquidCrystal_I2C* LCD; 70 | 71 | byte Display_columns; 72 | byte Display_rows; 73 | 74 | //Menu Items 75 | Menu_item* menu_item[Max_Menu_items]; //Max_Menu_items value is defined in the Sketch 76 | List_item* list_item[Max_Event_items]; //Max_Event_items value is defined in the Sketch 77 | const char* const *tt_point; //pointer to the text table array stored in PROGMEM 78 | 79 | //1: date format DD.MM.YY, 0: date format MM.DD.YY 80 | bool date_DMY; 81 | 82 | //Running text 83 | byte row_sh[4]; //running text - actual shift 84 | byte row_dir[4]; //running text - direction: 1 - right_ready, 2 - right_go, 3 - left_ready, 4 - left_go 85 | 86 | char tt_text[20]; //buffer for actual menu item text 87 | 88 | //Actual positions 89 | byte row_pos; //actual row possition on the display: 0 - first row, 1 - second row 90 | int menu_pos; //actual menu possition in Menu_item[] array 91 | byte menu_max_id; //size of the Menu_item[] array 92 | int list_pos; //actual list possition in List_item[] array 93 | byte list_max_id; //size of the List_item[] array 94 | byte input_pos_x; //actual input string position x axis (input text) 95 | byte input_pos_y; //actual input string position y axis (input menu) 96 | byte prev; //Amount of previous menu rows to the actual menu position 97 | byte next; //Amount of next menu rows after the actual menu position 98 | byte max_events; //Maximal number of events records in the list 99 | 100 | tmElements_t tm; 101 | 102 | byte txt_pos; 103 | int txt_pos_dir; 104 | 105 | char tmps[20] = ""; 106 | String s; 107 | 108 | //User inputs 109 | bool analog_input; 110 | byte input_pin; 111 | byte input_val[8]; 112 | bool rotary; 113 | int lastkey; //Last pressed key 114 | byte double_click; 115 | int double_click_delay = 700; 116 | 117 | unsigned long refresh_time; 118 | unsigned int menu_refresh; 119 | unsigned long last_keypress; 120 | byte last_direction; //direction of last rotary movement: 0 - no rotation, 1 - CW, 2 - CCW 121 | bool extra_read; 122 | byte sleep_time; 123 | byte off_time; 124 | bool LCD_off; 125 | bool sleep = 0; 126 | 127 | //Auxiliary variables 128 | bool auxo; 129 | bool auxo1; 130 | byte auxb; 131 | byte auxb1; 132 | byte auxb2; 133 | int auxi; 134 | int auxi1; 135 | long auxl; 136 | unsigned long auxl1; 137 | unsigned long auxl2; 138 | 139 | //Methods 140 | byte cnt_prev(); 141 | byte cnt_next(); 142 | void draw_cursors(); 143 | int evaluate_button_analog(int but); 144 | void menu_draw(); 145 | void navigate_2submenu(); 146 | void read_keys(); 147 | void menu_init(); 148 | void runn_txt_init(); 149 | byte move_input_pos_y(byte input_pos_y, byte var_type, bool fwd); 150 | void lcdprint(byte row, byte col, String str, byte chrs = 255); 151 | void t_fl(); 152 | void t_us(); 153 | void t_si(); 154 | void t_d(); 155 | void t_t(); 156 | void d_s(unsigned long dt, byte mls); 157 | void t_s(unsigned long dt, byte mls); 158 | void fill_s(); 159 | void get_val(); 160 | 161 | public: 162 | Menu(); 163 | void begin(LiquidCrystal_I2C LCD, const char* const _tt_point[], unsigned int _menu_refresh, byte _sleep_time, byte _off_time, byte _display_columns, byte _display_rows, bool _date_DMY = 1, byte _max_events = 10); 164 | void key_input(bool _analog_input, byte _pin, word _up, word _down, word _left, word _right); 165 | 166 | byte add_menu_item(int _par_id, int _act_type, byte _t_id, bool* _var_p = 0, byte var_type = 0); 167 | byte add_menu_item(int _par_id, int _act_type, byte _t_id, byte* _var_p, byte var_type = 1); 168 | byte add_menu_item(int _par_id, int _act_type, byte _t_id, int* _var_p, byte var_type = 2); 169 | byte add_menu_item(int _par_id, int _act_type, byte _t_id, word* _var_p, byte var_type = 3); 170 | byte add_menu_item(int _par_id, int _act_type, byte _t_id, long* _var_p, byte var_type = 255); 171 | byte add_menu_item(int _par_id, int _act_type, byte _t_id, unsigned long* _var_p, byte var_type = 5); 172 | byte add_menu_item(int _par_id, int _act_type, byte _t_id, float* _var_p, byte var_type = 3); 173 | byte add_menu_item(int _par_id, int _act_type, byte _t_id, double* _var_p, byte var_type = 3); 174 | 175 | void add_event(unsigned long _event_time, byte _event_text_id, float event_var); 176 | void draw(); 177 | }; 178 | 179 | #endif 180 | --------------------------------------------------------------------------------