Displays user interactions with menus displayed on a console or a serial terminal.
Dependents: EVAL-AD568x-AD569x EVAL-AD7124 EVAL-AD5592R EVAL-AD717x-AD411x ... more
adi_console_menu.c
00001 /*! 00002 ***************************************************************************** 00003 @file: adi_console_menu.c 00004 00005 @brief: A simple console menu manager handler 00006 00007 @details: A way to define using arrays of structs a set of menus that can 00008 be displayed to a user, easily, with all user interaction handled 00009 by the library, leaving only the implementation of the menu actions 00010 to be done by the library user. 00011 ----------------------------------------------------------------------------- 00012 Copyright (c) 2019-2022 Analog Devices, Inc. 00013 All rights reserved. 00014 00015 This software is proprietary to Analog Devices, Inc. and its licensors. 00016 By using this software you agree to the terms of the associated 00017 Analog Devices Software License Agreement. 00018 00019 *****************************************************************************/ 00020 00021 /****************************************************************************/ 00022 /***************************** Include Files ********************************/ 00023 /****************************************************************************/ 00024 #include <stdio.h> 00025 #include <stdlib.h> 00026 #include <ctype.h> 00027 #include <string.h> 00028 #include <stdbool.h> 00029 #include <assert.h> 00030 00031 #include "adi_console_menu.h" 00032 00033 /****************************************************************************/ 00034 /********************** Macros and Constants Definition *********************/ 00035 /****************************************************************************/ 00036 #define DIV_STRING "\t==================================================" 00037 00038 /******************************************************************************/ 00039 /*************************** Types Declarations *******************************/ 00040 /******************************************************************************/ 00041 // Saves the state of console menu library 00042 console_menu_state adi_console_menu_state = { 00043 .last_error_code = 0 00044 }; 00045 00046 /****************************************************************************/ 00047 /***************************** Function Definitions *************************/ 00048 /****************************************************************************/ 00049 /*! 00050 * @brief displays the text of a console menu 00051 * 00052 * @details 00053 */ 00054 static void adi_display_console_menu(const console_menu * menu) 00055 { 00056 adi_clear_console(); 00057 00058 // call headerItem to allow display of other content 00059 if (menu->headerItem != NULL) { 00060 menu->headerItem(); 00061 printf(DIV_STRING EOL); 00062 } 00063 00064 /* 00065 * Display the menu title and menuItems 00066 * The shortcutKey is used to display '[A]' before the dispayText 00067 */ 00068 printf("\t%s" EOL "\t", menu->title); 00069 // show an underline to distinguish title from item 00070 for (uint8_t i = 0; i < strlen(menu->title); i++) { 00071 putchar('-'); 00072 } 00073 // Extend underline past end of string, and then new line 00074 printf("--" EOL); 00075 00076 // If the shortcutKey is not unique, first found is used 00077 for (uint8_t i = 0; i < menu->itemCount; i ++) { 00078 if (menu->items[i].shortcutKey == '\00') { 00079 // No shortcut key defined, but display item text if available 00080 printf("\t%s" EOL, menu->items[i].text); 00081 } else { 00082 printf("\t[%c] %s" EOL, toupper(menu->items[i].shortcutKey), 00083 menu->items[i].text); 00084 } 00085 } 00086 if (menu->enableEscapeKey) { 00087 printf(EOL "\t[ESC] Exit Menu" EOL); 00088 } 00089 00090 printf(EOL "\tPlease make a selection." EOL); 00091 00092 // call footerItem to allow display of other content 00093 if (menu->footerItem != NULL) { 00094 printf(DIV_STRING EOL); 00095 menu->footerItem(); 00096 } 00097 } 00098 00099 /*! 00100 * @brief Display a consoleMenu and handle User interaction 00101 * 00102 * @details This displays the menuItems defined by the console menu, and 00103 * handles all user interaction for the menu. 00104 * 00105 * @note The function will return either the item selected or error code 00106 * from the last action. One at a time. Either define the menu action 00107 * or sub menu. If both are defined the function would return error. 00108 * If both menu action and sub menu are defined NULL, then the 00109 * function will return the item selected 00110 */ 00111 int32_t adi_do_console_menu(const console_menu * menu) 00112 { 00113 int32_t itemSelected = MENU_ESCAPED; 00114 bool enableKeyScan = true; 00115 int32_t ret = MENU_DONE; 00116 00117 adi_display_console_menu(menu); 00118 00119 /* 00120 * Loop waiting for valid user input. menuItem index is returned if 00121 * user presses a valid menu option. 00122 */ 00123 do { 00124 char keyPressed = toupper(getchar()); 00125 00126 if (menu->enableEscapeKey) { 00127 if (keyPressed == ESCAPE_KEY_CODE) { 00128 itemSelected = MENU_ESCAPED; 00129 enableKeyScan = false; 00130 break; 00131 } 00132 } 00133 00134 for (uint8_t i = 0; i < menu->itemCount; i ++) { 00135 if (toupper(menu->items[i].shortcutKey) == keyPressed) { 00136 itemSelected = i; 00137 00138 // If the menuAction function pointer is NULL and 00139 // the sub console menu pointer is not NULL, 00140 // call the sub console menu. 00141 if (menu->items[i].action == NULL 00142 && menu->items[i].submenu != NULL) { 00143 ret = adi_do_console_menu(menu->items[i].submenu); 00144 } 00145 // If the menuAction function pointer is not NULL and sub console menu 00146 // pointer is NULL, call the action. 00147 else if (menu->items[i].action != NULL && menu->items[i].submenu == NULL) { 00148 ret = menu->items[i].action(menu->items[i].id); 00149 } 00150 // If both are NULL, then return Not Supported action. 00151 else if (menu->items[i].action != NULL && menu->items[i].submenu != NULL) { 00152 ret = -1; 00153 } 00154 00155 // Store the return value if it is negative. 00156 if (ret < 0) { 00157 adi_console_menu_state.last_error_code = ret; 00158 ret = MENU_CONTINUE; 00159 } 00160 00161 switch (ret) { 00162 case MENU_DONE : { 00163 enableKeyScan = false; 00164 break; 00165 } 00166 case MENU_CONTINUE: 00167 default: { 00168 enableKeyScan = true; 00169 adi_display_console_menu(menu); 00170 break; 00171 } 00172 } 00173 break; 00174 } 00175 } 00176 // If both the action and sub menu are defined NULL, and the item selected 00177 // is not MENU_ESCAPED, then break the loop to return selected item. 00178 if(itemSelected != MENU_ESCAPED && 00179 menu->items[itemSelected].action == NULL && 00180 menu->items[itemSelected].submenu == NULL) { 00181 break; 00182 } 00183 } while (enableKeyScan); 00184 00185 return (itemSelected); 00186 } 00187 00188 /*! 00189 * @brief Reads a decimal string from the user 00190 * 00191 * @param input_len max number of character to accept from the user 00192 * 00193 * @return The integer value entered 00194 * 00195 * @details Allows a user to type in number, echoing back to the user, 00196 * up to input_len chars 00197 * 00198 * @note Only positive integer numbers are supported currently 00199 */ 00200 int32_t adi_get_decimal_int(uint8_t input_len) 00201 { 00202 char buf[20] = {0}; 00203 uint8_t buf_index = 0; 00204 char ch; 00205 bool loop = true; 00206 00207 assert(input_len < 19); 00208 00209 do { 00210 ch = getchar(); 00211 if (isdigit(ch) && buf_index < (input_len)) { 00212 // echo and store it as buf not full 00213 buf[buf_index++] = ch; 00214 putchar(ch); 00215 } 00216 if ((ch == '\x7F') && (buf_index > 0)) { 00217 //backspace and at least 1 char in buffer 00218 buf[buf_index--] = '\x00'; 00219 putchar(ch); 00220 } 00221 if ((ch == '\x0D') || (ch == '\x0A')) { 00222 // return key pressed, all done, null terminate string 00223 buf[buf_index] = '\x00'; 00224 loop = false; 00225 } 00226 } while(loop); 00227 00228 return atoi(buf); 00229 } 00230 00231 /*! 00232 * @brief Reads a hexadecimal number from the user 00233 * 00234 * @param input_len max number of character to accept from the user 00235 * 00236 * @return The integer value entered 00237 * 00238 * @details Allows a user to type in a hexnumber, echoing back to the user, 00239 * up to input_len chars 00240 */ 00241 uint32_t adi_get_hex_integer(uint8_t input_len) 00242 { 00243 char buf[9] = {0}; 00244 uint8_t buf_index = 0; 00245 char ch; 00246 bool loop = true; 00247 00248 assert(input_len < 8); 00249 00250 do { 00251 ch = getchar(); 00252 if (isxdigit(ch) && buf_index < (input_len)) { 00253 // echo and store it as buf not full 00254 buf[buf_index++] = ch; 00255 putchar(ch); 00256 } 00257 if ((ch == '\x7F') && (buf_index > 0)) { 00258 //backspace and at least 1 char in buffer 00259 buf[buf_index--] = '\x00'; 00260 putchar(ch); 00261 } 00262 if ((ch == '\x0D') || (ch == '\x0A')) { 00263 // return key pressed, all done, null terminate string 00264 buf[buf_index] = '\x00'; 00265 loop = false; 00266 } 00267 } while(loop); 00268 00269 return strtol(buf, NULL, 16); 00270 } 00271 00272 /*! 00273 * @brief Reads a floating string from the user 00274 * 00275 * @param input_len max number of character to accept from the user 00276 * 00277 * @return The float value entered 00278 * 00279 * @details Allows a user to type in number, echoing back to the user, 00280 * up to input_len chars 00281 * 00282 * @note Only positive floating point numbers are supported currently 00283 */ 00284 float adi_get_decimal_float(uint8_t input_len) 00285 { 00286 char buf[20] = { 0 }; 00287 uint8_t buf_index = 0; 00288 char ch; 00289 bool loop = true; 00290 00291 assert(input_len < 19); 00292 00293 do { 00294 ch = getchar(); 00295 if ((isdigit(ch) || (ch == '.')) && buf_index < (input_len)) { 00296 // echo and store it as buf not full 00297 buf[buf_index++] = ch; 00298 putchar(ch); 00299 } 00300 if ((ch == '\x7F') && (buf_index > 0)) { 00301 //backspace and at least 1 char in buffer 00302 buf[buf_index--] = '\x00'; 00303 putchar(ch); 00304 } 00305 if ((ch == '\x0D') || (ch == '\x0A')) { 00306 // return key pressed, all done, null terminate string 00307 buf[buf_index] = '\x00'; 00308 loop = false; 00309 } 00310 } while (loop); 00311 00312 return atof(buf); 00313 } 00314 00315 /** 00316 * @brief Handles the integer type input from the user by displaying 00317 * the menu message and provides a set number of 00318 * input attempts for the user. 00319 * @param menu_prompt[in] - User specified prompt. 00320 * @param min_val[in] - minimum input value. 00321 * @param max_val[in] - maximum input value. 00322 * @param input_val[in, out] - User provided input value. 00323 * @param input_len[in] - User provided input length. 00324 * @param max_attempts[in] - Maximum number of input attempts. 00325 * @param clear_lines[in] - lines to clear in case of 00326 invalid input. 00327 * @return 0 in case of success. -1 otherwise. 00328 */ 00329 int32_t adi_handle_user_input_integer(const char* menu_prompt, 00330 uint16_t min_val, 00331 uint16_t max_val, 00332 uint16_t *input_val, 00333 uint8_t input_len, 00334 uint8_t max_attempts, 00335 uint8_t clear_lines) 00336 { 00337 uint8_t count = 0; 00338 00339 if (menu_prompt == NULL || input_val == NULL) { 00340 return -1; 00341 } 00342 00343 do { 00344 /* Gets the input from the user and allows 00345 * reattempts in-case of incorrect input */ 00346 printf("%s (%d - %d): ", menu_prompt, min_val, max_val); 00347 *input_val = (uint16_t)adi_get_decimal_int(input_len); 00348 00349 if ((*input_val >= min_val) && (*input_val <= max_val)) { 00350 // break out of the loop in-case of a correct input 00351 break; 00352 } else { 00353 if (count == max_attempts) { 00354 printf(EOL "Maximum try limit exceeded" EOL); 00355 adi_press_any_key_to_continue(); 00356 return -1; 00357 } 00358 00359 printf(EOL "Please enter a valid selection" EOL); 00360 adi_press_any_key_to_continue(); 00361 /* Moves up the cursor by specified lines and 00362 * clears the lines below it */ 00363 for (uint8_t i = 0; i < clear_lines; i++) { 00364 printf(VT100_CLEAR_CURRENT_LINE); 00365 printf(VT100_MOVE_UP_N_LINES, 1); 00366 } 00367 } 00368 } while (++count <= max_attempts); 00369 00370 return 0; 00371 } 00372 00373 /** 00374 * @brief Handles the float type input from the user by displaying 00375 * the menu message and provides a set number of 00376 * input attempts for the user. 00377 * @param menu_prompt[in] - User specified prompt. 00378 * @param min_val[in] - minimum input value. 00379 * @param max_val[in] - maximum input value. 00380 * @param input_val[in, out] - User provided input value. 00381 * @param input_len[in] - User provided input length. 00382 * @param max_attempts[in] - Maximum number of input attempts. 00383 * @param clear_lines[in] - lines to clear in case of 00384 invalid input. 00385 * @return 0 in case of success. -1 otherwise. 00386 */ 00387 int32_t adi_handle_user_input_float(const char* menu_prompt, 00388 float min_val, 00389 float max_val, 00390 float *input_val, 00391 uint8_t input_len, 00392 uint8_t max_attempts, 00393 uint8_t clear_lines) 00394 { 00395 uint8_t count = 0; 00396 00397 if (menu_prompt == NULL || input_val == NULL) { 00398 return -1; 00399 } 00400 00401 do { 00402 /* Gets the input from the user and allows 00403 * reattempts in-case of incorrect input */ 00404 printf("%s (%0.3f - %0.3f): ", menu_prompt, min_val, max_val); 00405 *input_val = adi_get_decimal_float(input_len); 00406 00407 if ((*input_val >= min_val) && (*input_val <= max_val)) { 00408 // break out of the loop in-case of a correct input 00409 break; 00410 } 00411 else { 00412 if (count == max_attempts) { 00413 printf(EOL "Maximum try limit exceeded" EOL); 00414 adi_press_any_key_to_continue(); 00415 return -1; 00416 } 00417 00418 printf(EOL "Please enter a valid selection" EOL); 00419 adi_press_any_key_to_continue(); 00420 /* Moves up the cursor by specified lines and 00421 * clears the lines below it */ 00422 for (uint8_t i = 0; i < clear_lines; i++) { 00423 printf(VT100_CLEAR_CURRENT_LINE); 00424 printf(VT100_MOVE_UP_N_LINES, 1); 00425 } 00426 } 00427 } while (++count <= max_attempts); 00428 00429 return 0; 00430 } 00431 00432 /*! 00433 * @brief Clears the console terminal 00434 * 00435 * @details Clears the console terminal using VT100 escape code, or can be changed to 00436 * output blank lines if serial link doesn't support VT100. 00437 */ 00438 void adi_clear_console(void) 00439 { 00440 /* 00441 * clear console and move cursor to home location, followed by move to home location. 00442 * Dedicated call to move home is because sometimes first move home doesn't work 00443 * \r\n required to flush the uart buffer. 00444 */ 00445 printf(VT100_CLEAR_CONSOLE VT100_MOVE_TO_HOME EOL); 00446 00447 /* 00448 * if VT100 is not supported, this can be enabled instead, but menu display may not work well 00449 */ 00450 // for (uint8_t = 0; i < 100; i++) 00451 // printf("\r\n\r"); 00452 } 00453 00454 /*! 00455 * @brief Clears the error code from the last menu 00456 * 00457 * @details 00458 */ 00459 void adi_clear_last_menu_error(void) 00460 { 00461 adi_console_menu_state.last_error_code = 0; 00462 } 00463 00464 /*! 00465 * @brief Returns the error code from the last menu 00466 * 00467 * @return The error code value 00468 */ 00469 int32_t adi_get_last_menu_error(void) 00470 { 00471 return adi_console_menu_state.last_error_code; 00472 } 00473 00474 /*! 00475 * @brief waits for any key to be pressed, and displays a prompt to the user 00476 * 00477 * @details 00478 */ 00479 void adi_press_any_key_to_continue(void) 00480 { 00481 printf("\r\nPress any key to continue...\r\n"); 00482 getchar(); 00483 }
Generated on Wed Jul 13 2022 14:24:06 by 1.7.2