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

Embed: (wiki syntax)

« Back to documentation index

Show/hide line numbers adi_console_menu.c Source File

adi_console_menu.c

Go to the documentation of this file.
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 }