This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

[参考译文] 一些子菜单的 CUI 库问题

Guru**** 3001220 points
请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

https://e2e.ti.com/support/wireless-connectivity/zigbee-thread-group/zigbee-and-thread/f/zigbee-thread-forum/1043380/cui-library-issue-for-a-few-sub-menu

如果每个子菜单包含几个子菜单,则 Back 函数工作不正常,因为 gPrevMenuItemEntry 仅记住先前的值,但当您转到子菜单时,上部菜单项会丢失。 在某些情况下,如果 gPrevMenuItemEntry 大于2,则当您返回顶级 MainMenu 时,应用程序崩溃并陷入 main_assert()中,因为顶级菜单上只有3个项目。

gPrevMenuItemEntry 应是 CUI_menu_s 结构的一部分。

void CUI_menuActionBack(const int32_t _itemEntry)
{
    if (NULL != gpCurrMenu->pUpper)
    {
       gpCurrMenu = gpCurrMenu->pUpper;
       gCurrMenuItemEntry = gPrevMenuItemEntry;
    }
}

固定代码示例:

void CUI_menuActionBack(const int32_t _itemEntry)
{
    if (NULL != gpCurrMenu->pUpper)
    {
       gCurrMenuItemEntry = gpCurrMenu->gPrevMenuItemEntry;
       gpCurrMenu = gpCurrMenu->pUpper;
    }
}

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    Alex、您好!

    请告知您使用的处理器。  

    谢谢、

    ~Leonard  

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    CC1352R1、SDK 4.40.0.44、DMM_zed_switch_remote_display_app

    我们添加了一些子菜单级别以在项目中实现某些功能、并发现了问题。 修复后工作正常。

    P.S. 它只是固定代码的一个示例、只是为了展示该方法、我没有提供所有固定代码。

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    Alex、您好!

    感谢您的反馈、我已将此信息传递给软件开发团队、以便在未来的 SDK 版本中解决此问题。  如果您愿意、请提供更多详细信息、了解实施建议的 CUI 改进所需的其他哪些更改。

    此致、
    Ryan

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    gPrevMenuItemEntry 已添加到 结构 CUI_menu_s 中

    独立 的 gPrevMenuItemEntry 将被删除,并在 Cui.c 代码中替换为 gpCurrent Menu->gPrevMenuItemEntry。

    Cui.c

    /******************************************************************************
    
     @file CUI.c
    
     @brief This file contains the interface implementation of the Combined User
             Interface.
    
     @detail The interface is designed to be shared between clients.
             As such a client can request access to resources whether they be
             Buttons, LEDs or UART Display without the fear that another client
             already has ownership over that resource.
    
             If a resource is already taken by another client then the interface
             will respond with that information.
    
             Only a client that has been given access to a resource may utilize
             the resource. Therefore, any calls a client makes to read/write a
             resource will be ignored if the client does not have the access
             required.
    
     Group: LPRF SW RND
     Target Device: cc13x2_26x2
    
     ******************************************************************************
     
     Copyright (c) 2016-2020, Texas Instruments Incorporated
     All rights reserved.
    
     Redistribution and use in source and binary forms, with or without
     modification, are permitted provided that the following conditions
     are met:
    
     *  Redistributions of source code must retain the above copyright
        notice, this list of conditions and the following disclaimer.
    
     *  Redistributions in binary form must reproduce the above copyright
        notice, this list of conditions and the following disclaimer in the
        documentation and/or other materials provided with the distribution.
    
     *  Neither the name of Texas Instruments Incorporated nor the names of
        its contributors may be used to endorse or promote products derived
        from this software without specific prior written permission.
    
     THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
     AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
     THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
     CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
     EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
     PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
     OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
     WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
     OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
     EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    
     ******************************************************************************
     
     
     *****************************************************************************/
    
    /******************************************************************************
     Includes
     *****************************************************************************/
    #include <stdbool.h>
    #include <string.h>
    #include <stdio.h>
    
    #include <ti/sysbios/BIOS.h>
    #include <ti/sysbios/knl/Semaphore.h>
    #include <ti/sysbios/knl/Task.h>
    #include <ti/sysbios/knl/Clock.h>
    
    #include <ti/drivers/dpl/HwiP.h>
    #include <ti/drivers/UART.h>
    #include <ti/drivers/uart/UARTCC26XX.h>
    #include <ti/drivers/utils/Random.h>
    #include <ti/drivers/apps/LED.h>
    #include <xdc/runtime/System.h>
    #include DeviceFamily_constructPath(driverlib/cpu.h)
    #include "ti_drivers_config.h"
    
    #include "cui.h"
    
    
    #define CUI_INITIAL_STATUS_OFFSET 5
    #define CUI_LABEL_VAL_SEP ": "
    #define CUI_MAX_LABEL_AND_SEP_LEN (MAX_STATUS_LINE_LABEL_LEN + (sizeof(CUI_LABEL_VAL_SEP)))
    
    /*
     * Ascii Escape characters to be used by testing scripts to bookend the
     * information being printed to the UART
     */
    #define CUI_MENU_START_CHAR         0x01 // SOH (start of heading) ascii character
    #define CUI_STATUS_LINE_START_CHAR  0x02 // SOT (start of text) ascii character
    #define CUI_END_CHAR                0x03 // ETX (end of text) ascii character
    
    #define CUI_NL_CR               "\n\r" // New line carriage return
    
    #define CUI_ESC_UP              "\033[A"
    #define CUI_ESC_DOWN            "\033[B"
    #define CUI_ESC_RIGHT           "\033[C"
    #define CUI_ESC_LEFT            "\033[D"
    #define CUI_ESC_ESC             "\033\0\0\0\0"
    
    /*
     * Escape sequences for terminal control.
     * Any sequences with '%' in them require require additional information to be used
     *  as is.
     */
    #define CUI_ESC_TRM_MODE            "\033[20"    // Set line feed mode for the terminal
    
    #define CUI_ESC_CLR                 "\033[2J"    // Clear the entire screen
    #define CUI_ESC_CLR_UP              "\033[1J"    // Clear screen from cursor up
    #define CUI_ESC_CLR_STAT_LINE_VAL   "\033[2K"    // Clear the status line
    
    #define CUI_ESC_CUR_HIDE            "\033[?25l"  // Hide cursor
    #define CUI_ESC_CUR_SHOW            "\033[?25h"  // Show cursor
    #define CUI_ESC_CUR_HOME            "\033[H"     // Move cursor to the top left of the terminal
    #define CUI_ESC_CUR_MENU_BTM        "\033[3;%dH" // Move cursor to the bottom right of the menu
    #define CUI_ESC_CUR_LINE            "\033[%d;0H" // Move cursor to a line of choice
    #define CUI_ESC_CUR_ROW_COL         "\033[%d;%dH"// Move cursor to row and col
    
    #define CUI_NUM_UART_CHARS          5
    #define CUI_MENU_START_ESCAPE_LEN   32
    #define CUI_NL_CR_LEN               2
    #define CUI_ETX_LEN                 1
    
    /******************************************************************************
     Constants
     *****************************************************************************/
    typedef enum
    {
        CUI_RELEASED = 0,
        // Some specific value so that uninitialized memory does not cause problems
        CUI_ACQUIRED = 0xDEADBEEF,
    } CUI_rscStatus_t;
    
    
    // Internal representation of a menu
    typedef struct
    {
        CUI_menu_t* pMenu;
        uint32_t clientHash;
    } CUI_menuResource_t;
    
    // Internal representation of a status line
    typedef struct
    {
        uint32_t clientHash;
        uint32_t lineOffset;
        char label[CUI_MAX_LABEL_AND_SEP_LEN];
        CUI_rscStatus_t status;
        bool refreshInd;
    } CUI_statusLineResource_t;
    
    /*******************************************************************************
     * GLOBAL VARIABLES
     */
    /*
     * [General Global Variables]
     */
    static bool gModuleInitialized = false;
    static Semaphore_Params gSemParams;
    
    static CUI_clientHandle_t gClientHandles[MAX_CLIENTS];
    static uint32_t gMaxStatusLines[MAX_CLIENTS];
    static Semaphore_Handle gClientsSem;
    static Semaphore_Struct gClientsSemStruct;
    
    /*
     * [UART Specific Global Variables]
     */
    static UART_Params gUartParams;
    static UART_Handle gUartHandle = NULL;
    #ifndef CUI_MIN_FOOTPRINT
    static uint8_t gUartTxBuffer[CUI_NUM_UART_CHARS];
    static uint8_t gUartRxBuffer[CUI_NUM_UART_CHARS];
    #endif
    static Semaphore_Handle gUartSem;
    static Semaphore_Struct gUartSemStruct;
    
    static uint8_t gRingBuff[512];
    static size_t gRingBuffHeadIdx = 0;
    static size_t gRingBuffTailIdx = 0;
    static size_t gRingBuffPendLen = 0;
    
    #ifndef CUI_MIN_FOOTPRINT
    /*
     * [Menu Global Variables]
     */
    static CUI_menu_t* gpCurrMenu;
    static CUI_menu_t* gpMainMenu;
    static size_t gCurrMenuItemEntry = 0;
    static bool gCursorActive;
    static CUI_cursorInfo_t gCursorInfo;
    
    static CUI_menuResource_t gMenuResources[MAX_REGISTERED_MENUS];
    static Semaphore_Handle gMenuSem;
    static Semaphore_Struct gMenuSemStruct;
    
    char menuBuff[CUI_MENU_START_ESCAPE_LEN             // Escape characters
                  + MAX_MENU_LINE_LEN + CUI_NL_CR_LEN   // Additional new line and return char
                  + MAX_MENU_LINE_LEN + CUI_NL_CR_LEN   // Additional new line and return char
                  + MAX_MENU_LINE_LEN + CUI_ETX_LEN];   // Additional ETX char
    
    static char gpMultiMenuTitle[] = " TI DMM Application ";
    
    /*
     * This a special menu that is only utilized when 2 or more menus have been
     * registered to the CUI. This menu will then be the top most Main Menu where
     * each menu that was registered will now be a sub menu.
     */
    uint8_t     cuiMultiMenuData[sizeof(CUI_menu_t) + ((MAX_REGISTERED_MENUS + 1) * sizeof(CUI_menuItem_t))];
    CUI_menu_t *cuiMultiMenu = (CUI_menu_t *)&cuiMultiMenuData;
    #endif /* CUI_MIN_FOOTPRINT */
    
    /*
     * [Status Line Variables]
     */
    static CUI_statusLineResource_t* gStatusLineResources[MAX_CLIENTS];
    static Semaphore_Handle gStatusSem;
    static Semaphore_Struct gStatusSemStruct;
    
    /******************************************************************************
     Local Functions Prototypes
     *****************************************************************************/
    static CUI_retVal_t CUI_publicAPIChecks(const CUI_clientHandle_t _clientHandle);
    static CUI_retVal_t CUI_acquireStatusLine(const CUI_clientHandle_t _clientHandle, const char* _pLabel, const bool _refreshInd, uint32_t* _pLineId);
    static CUI_retVal_t CUI_validateHandle(const CUI_clientHandle_t _clientHandle);
    static int CUI_getClientIndex(const CUI_clientHandle_t _clientHandle);
    static void UartWriteCallback(UART_Handle _handle, void *_buf, size_t _size);
    #ifndef CUI_MIN_FOOTPRINT
    static void UartReadCallback(UART_Handle _handle, void *_buf, size_t _size);
    #endif
    static CUI_retVal_t CUI_updateRemLen(size_t* _currRemLen, char* _buff, size_t _buffSize);
    static CUI_retVal_t CUI_writeString(void * _buffer, size_t _size);
    #ifndef CUI_MIN_FOOTPRINT
    static void CUI_menuActionNavigate(uint8_t _navDir);
    static void CUI_menuActionExecute(void);
    static void CUI_dispMenu(bool _menuPopulated);
    static void CUI_callMenuUartUpdateFn();
    static void CUI_updateCursor(void);
    static bool CUI_handleMenuIntercept(CUI_menuItem_t* _pItemEntry, uint8_t _input);
    static bool CUI_handleMenuList(CUI_menuItem_t* _pItemEntry, uint8_t _input);
    static CUI_retVal_t CUI_findMenu(CUI_menu_t* _pMenu, CUI_menu_t* _pDesiredMenu, uint32_t* _pPrevItemIndex);
    #endif
    /******************************************************************************
     * Public CUI APIs
     *****************************************************************************/
    /*********************************************************************
     * @fn          CUI_init
     *
     * @brief       Initialize the CUI module. This function must be called
     *                  before any other CUI functions.
     *
     * @params      _pParams - A pointer to a CUI_params_t struct
     *
     * @return      CUI_retVal_t representing success or failure.
     */
    CUI_retVal_t CUI_init(CUI_params_t* _pParams)
    {
        /*
         *  Do nothing if the module has already been initialized or if
         *  CUI_init has been called without trying to manage any of the three
         *  resources (btns, leds, uart)
         */
        if (!gModuleInitialized && _pParams->manageUart)
        {
    
            // Semaphore Setup
            Semaphore_Params_init(&gSemParams);
            //set all sems in this module to be binary sems
            gSemParams.mode = Semaphore_Mode_BINARY;
    
            // Client Setup
            {
                Semaphore_construct(&gClientsSemStruct, 1, &gSemParams);
                gClientsSem = Semaphore_handle(&gClientsSemStruct);
    
                for (int i = 0; i < MAX_CLIENTS; i++)
                {
                    /*
                     * A client handle of 0 indicates that the client has not been
                     * registered.
                     */
                    gClientHandles[i] = 0U;
                }
            }
    
            {
                // UART semaphore setup
                Semaphore_construct(&gUartSemStruct, 1, &gSemParams);
                gUartSem = Semaphore_handle(&gUartSemStruct);
    #ifndef CUI_MIN_FOOTPRINT
                Semaphore_construct(&gMenuSemStruct, 1, &gSemParams);
                gMenuSem = Semaphore_handle(&gMenuSemStruct);
    #endif
                {
                    // General UART setup
                    UART_init();
                    UART_Params_init(&gUartParams);
                    gUartParams.baudRate = 115200;
                    gUartParams.writeMode     = UART_MODE_CALLBACK;
                    gUartParams.writeDataMode = UART_DATA_BINARY;
                    gUartParams.writeCallback = UartWriteCallback;
    #ifndef CUI_MIN_FOOTPRINT
                    gUartParams.readMode      = UART_MODE_CALLBACK;
                    gUartParams.readDataMode  = UART_DATA_BINARY;
                    gUartParams.readCallback  = UartReadCallback;
    #endif
                    gUartHandle = UART_open(CONFIG_DISPLAY_UART, &gUartParams);
                    if (NULL == gUartHandle)
                    {
                        return CUI_FAILURE;
                    }
                    else
                    {
    #ifndef CUI_MIN_FOOTPRINT
                        UART_read(gUartHandle, gUartRxBuffer, sizeof(gUartRxBuffer));
                        UART_control(gUartHandle, UARTCC26XX_CMD_RETURN_PARTIAL_ENABLE, NULL);
    #endif
    
                        char clearScreenStr[] = CUI_ESC_CLR CUI_ESC_TRM_MODE CUI_ESC_CUR_HIDE;
    
                        if (CUI_SUCCESS != CUI_writeString(clearScreenStr, strlen(clearScreenStr)))
                        {
                            UART_close(gUartHandle);
                            return CUI_FAILURE;
                        }
                    }
                }
    
    #ifndef CUI_MIN_FOOTPRINT
                // Multi Menu Initialization
                {
                    memset(gMenuResources, 0, sizeof(gMenuResources));
                    /*
                     * No additional initialization is needed in the case of a single
                     * menu being registered to the CUI module. In the case of 2 or more
                     * menus being registered the global cuiMultiMenu object will
                     * be used as the top level menu and every registered menu will be a
                     * sub menu of the cuiMultiMenu instead.
                     */
                    memset(cuiMultiMenu, 0, sizeof(&cuiMultiMenu));
                    cuiMultiMenu->uartUpdateFn  = NULL;
                    cuiMultiMenu->pTitle        = gpMultiMenuTitle;
                    cuiMultiMenu->numItems      = MAX_REGISTERED_MENUS + 1;
                    cuiMultiMenu->pUpper        = NULL;
                }
    #endif
    
                // Status Lines Setup
                {
                    Semaphore_construct(&gStatusSemStruct, 1, &gSemParams);
                    gStatusSem = Semaphore_handle(&gStatusSemStruct);
                }
            }
    
            gModuleInitialized = true;
            return CUI_SUCCESS;
        }
    
        return CUI_FAILURE;
    }
    
    /*********************************************************************
     * @fn          CUI_paramsInit
     *
     * @brief       Initialize a CUI_clientParams_t struct to a known state.
     *                  The known state in this case setting each resource
     *                  management flag to true
     *
     * @params      _pParams - A pointer to an un-initialized CUI_params_t struct
     *
     * @return      none
     */
    void CUI_paramsInit(CUI_params_t* _pParams)
    {
        _pParams->manageUart = true;
    }
    
    /*********************************************************************
     * @fn          CUI_clientOpen
     *
     * @brief       Open a client with the CUI module. A client is required
     *                  to request/acquire resources
     *
     * @param       _pParams - Pointer to a CUI client params struct.
     *                  _pParams.clientName must be set before passing to CUI_open
     *
     * @return      NULL on failure. Otherwise success.
     */
    CUI_clientHandle_t CUI_clientOpen(CUI_clientParams_t* _pParams)
    {
        static size_t numClients = 0;
    
        if (!gModuleInitialized)
        {
            return 0U;
        }
    
        Semaphore_pend(gClientsSem, BIOS_WAIT_FOREVER);
    
        if (numClients >= MAX_CLIENTS)
        {
            return 0U;
        }
    
        uint32_t randomNumber;
        if (Random_STATUS_SUCCESS != Random_seedAutomatic()) {
             return CUI_FAILURE;
        }
    
        uint8_t attempts = 0;
        do
        {
            randomNumber = Random_getNumber();
            attempts++;
        }while(!randomNumber && (attempts <= 5));
    
    
        gClientHandles[numClients] = randomNumber;
    
        if (_pParams->maxStatusLines)
        {
            gMaxStatusLines[numClients] = _pParams->maxStatusLines;
            gStatusLineResources[numClients] = malloc(_pParams->maxStatusLines * sizeof(gStatusLineResources[0][0]));
            if (gStatusLineResources[numClients] == NULL)
            {
                return CUI_FAILURE;
            }
            memset(gStatusLineResources[numClients], 0, _pParams->maxStatusLines * sizeof(gStatusLineResources[0][0]));
        }
    
        numClients++;
    
        Semaphore_post(gClientsSem);
    
        return randomNumber;
    }
    
    /*********************************************************************
     * @fn          CUI_clientParamsInit
     *
     * @brief       Initialize a CUI_clientParams_t struct to a known state.
     *
     * @param       _pClientParams - Pointer to params struct
     *
     * @return      void
     */
    void CUI_clientParamsInit(CUI_clientParams_t* _pClientParams)
    {
        strcpy(_pClientParams->clientName, "");
        _pClientParams->maxStatusLines = 0;
    }
    
    /*********************************************************************
     * @fn          CUI_close
     *
     * @brief       Close the CUI module. Release all resources and memory.
     *
     * @return      CUI_retVal_t representing success or failure.
     */
    CUI_retVal_t CUI_close()
    {
        // Only close the module if it's been initialized
        if (gModuleInitialized)
        {
            Semaphore_pend(gStatusSem, BIOS_WAIT_FOREVER);
            char clearScreenStr[] = CUI_ESC_CLR CUI_ESC_TRM_MODE CUI_ESC_CUR_HIDE;
            CUI_writeString(clearScreenStr, strlen(clearScreenStr));
            for (uint8_t i = 0; i < MAX_CLIENTS; i++)
            {
                if (gStatusLineResources[i])
                {
                    free(gStatusLineResources[i]);
                }
            }
            UART_close(gUartHandle);
            Semaphore_post(gStatusSem);
        }
    
        gModuleInitialized = false;
    
        return CUI_SUCCESS;
    
    }
    #ifndef CUI_MIN_FOOTPRINT
    /******************************************************************************
     * Menu CUI APIs
     *****************************************************************************/
    /*********************************************************************
     * @fn          CUI_registerMenu
     *
     * @brief       Register a menu with the CUI module
     *
     * @param       _clientHandle - Client to register the menu to
     *              _pMenu - Pointer to a CUI_menu_t struct
     *
     * @return      CUI_retVal_t representing success or failure.
     */
    CUI_retVal_t CUI_registerMenu(const CUI_clientHandle_t _clientHandle, CUI_menu_t* _pMenu)
    {
        CUI_retVal_t retVal = CUI_publicAPIChecks(_clientHandle);
        if (CUI_SUCCESS != retVal)
        {
            return retVal;
        }
    
        if (NULL == _pMenu)
        {
            return CUI_INVALID_PARAM;
        }
    
        if (NULL == _pMenu->uartUpdateFn)
        {
            return CUI_MISSING_UART_UPDATE_FN;
        }
    
        Semaphore_pend(gMenuSem, BIOS_WAIT_FOREVER);
    
        int freeIndex = -1;
        int numMenus = 0;
        for (int i = 0; i < MAX_REGISTERED_MENUS; i++)
        {
            if (!(gMenuResources[i].clientHash) &&
                  (NULL == gMenuResources[i].pMenu))
            {
                if (-1 == freeIndex)
                {
                    // Find the first empty hole in the array
                    freeIndex = i;
                }
            }
            else if (_pMenu == gMenuResources[i].pMenu)
            {
                // Do not allow multiple of the same menu to be registered
                Semaphore_post(gMenuSem);
                return CUI_INVALID_PARAM;
            }
            else
            {
                numMenus++;
            }
        }
    
        if (-1 == freeIndex)
        {
            Semaphore_post(gMenuSem);
            return CUI_MAX_MENUS_REACHED;
        }
    
        gMenuResources[freeIndex].clientHash = _clientHandle;
        gMenuResources[freeIndex].pMenu = _pMenu;
    
        if (numMenus > 0)
        {
            if (1 == numMenus)
            {
                /*
                 * Someone (a rtos task) needs to own the processing time for the
                 * cuiMultiMenu. The task that first registered a menu will be
                 * that owner. Any additional menu's processing time will be owned
                 * by the task that registered them.
                 */
                cuiMultiMenu->uartUpdateFn = gpMainMenu->uartUpdateFn;
    
                /*
                 * The first menu that was registered needs to be added as the first
                 * sub menu of the cuiMultiMenu object
                 */
                cuiMultiMenu->menuItems[0].itemType = CUI_MENU_ITEM_TYPE_SUBMENU;
                cuiMultiMenu->menuItems[0].interceptActive = false;
                cuiMultiMenu->menuItems[0].pDesc = NULL;
                cuiMultiMenu->menuItems[0].item.pSubMenu = gpMainMenu;
                gpMainMenu->pUpper = cuiMultiMenu;
    
                /*
                 * Change the old main menu Help action to a back action
                 */
                gpMainMenu->menuItems[gpMainMenu->numItems - 1].itemType = CUI_MENU_ITEM_TYPE_ACTION;
                gpMainMenu->menuItems[gpMainMenu->numItems - 1].interceptActive = false;
                gpMainMenu->menuItems[gpMainMenu->numItems - 1].pDesc = CUI_MENU_ACTION_BACK_DESC;
                gpMainMenu->menuItems[gpMainMenu->numItems - 1].item.pFnAction = (CUI_pFnAction_t) CUI_menuActionBack;
            }
    
            /*
             * Add the new menu being registered to the cuiMultiMenu as a sub
             * menu object
             */
            cuiMultiMenu->menuItems[numMenus].itemType = CUI_MENU_ITEM_TYPE_SUBMENU;
            cuiMultiMenu->menuItems[numMenus].interceptActive = false;
            cuiMultiMenu->menuItems[numMenus].pDesc = NULL;
            cuiMultiMenu->menuItems[numMenus].item.pSubMenu = _pMenu;
            _pMenu->pUpper = cuiMultiMenu;
    
            /*
             * Change the registering menu Help action to a back action
             */
            _pMenu->menuItems[_pMenu->numItems - 1].itemType = CUI_MENU_ITEM_TYPE_ACTION;
            _pMenu->menuItems[_pMenu->numItems - 1].interceptActive = false;
            _pMenu->menuItems[_pMenu->numItems - 1].pDesc = CUI_MENU_ACTION_BACK_DESC;
            _pMenu->menuItems[_pMenu->numItems - 1].item.pFnAction = (CUI_pFnAction_t) CUI_menuActionBack;
    
            /*
             * The Help screen must always be the last initialized item in the
             * cuiMultiMenu
             */
            cuiMultiMenu->menuItems[numMenus + 1].itemType = CUI_MENU_ITEM_TYPE_INTERCEPT;
            cuiMultiMenu->menuItems[numMenus + 1].interceptActive = false;
            cuiMultiMenu->menuItems[numMenus + 1].pDesc = CUI_MENU_ACTION_HELP_DESC;
            cuiMultiMenu->menuItems[numMenus + 1].item.pFnIntercept = (CUI_pFnIntercept_t) CUI_menuActionHelp;
    
            if (1 == numMenus)
            {
                /*
                 * At this point there should be 3 items.
                 * [previous menu]
                 * [new menu]
                 * [help action]
                 */
                cuiMultiMenu->numItems = 3;
            }
            else
            {
                /*
                 * At this point there should be one more item.
                 * [previous menu]
                 *  ...
                 * [new menu]
                 * [help action]
                 */
                cuiMultiMenu->numItems++;
            }
    
            /*
             * The global menu pointers need to be modified
             * to reflect the new menu structure.
             */
            gpMainMenu = cuiMultiMenu;
            gpCurrMenu = cuiMultiMenu;
        }
        else
        {
            /*
             * Set global pointers to the new main menu
             */
            gpMainMenu = _pMenu;
            gpCurrMenu = _pMenu;
        }
    
        /* Default to the Help item that was given to it */
        gCurrMenuItemEntry = gpMainMenu->numItems - 1;
    
        CUI_dispMenu(false);
        Semaphore_post(gMenuSem);
    
        return CUI_SUCCESS;
    }
    
    /*********************************************************************
     * @fn          CUI_deRegisterMenu
     *
     * @brief       De-registers a menu with the CUI module
     *
     * @param       _clientHandle - Client that owns the menu
     *              _pMenu - Pointer to the CUI_menu_t struct to remove
     *
     * @return      CUI_retVal_t representing success or failure.
     */
    CUI_retVal_t CUI_deRegisterMenu(const CUI_clientHandle_t _clientHandle, CUI_menu_t* _pMenu)
    {
        char buff[32];
        CUI_retVal_t retVal = CUI_publicAPIChecks(_clientHandle);
    
        if (CUI_SUCCESS != retVal)
        {
            return retVal;
        }
    
        if (NULL == _pMenu)
        {
            return CUI_INVALID_PARAM;
        }
    
        if (NULL == _pMenu->uartUpdateFn)
        {
            return CUI_MISSING_UART_UPDATE_FN;
        }
    
        Semaphore_pend(gMenuSem, BIOS_WAIT_FOREVER);
    
        int matchingIndex = -1;
        int numMenus = 0;
        for (int i = 0; i < MAX_REGISTERED_MENUS; i++)
        {
            if ((_clientHandle == gMenuResources[i].clientHash) &&
                  (_pMenu == gMenuResources[i].pMenu))
            {
                if (-1 == matchingIndex)
                {
                    matchingIndex = i;
                }
            }
            if ((0U != gMenuResources[i].clientHash) &&
                  (NULL != gMenuResources[i].pMenu))
            {
                numMenus++;
            }
        }
    
        if (-1 == matchingIndex)
        {
            Semaphore_post(gMenuSem);
            return CUI_RESOURCE_NOT_ACQUIRED;
        }
    
        if (numMenus > 1)
        {
            /*
             * Reduce the number of menus in the multi menu by 1
             */
            if (3 == cuiMultiMenu->numItems)
            {
                /*
                 * We should go back to a single menu. Remove the multi Menu.
                 * There will only be one other valid menu in the array. Find it,
                 * and use that as the single main menu.
                 */
                uint8_t newMainMenuIndex = 0;
                for (int i = 0; i < MAX_REGISTERED_MENUS; i++)
                {
                    if (0U != gMenuResources[i].clientHash &&
                            NULL != gMenuResources[i].pMenu &&
                            i != matchingIndex)
                    {
                        newMainMenuIndex = i;
                        break;
                    }
                }
    
                gpMainMenu = gMenuResources[newMainMenuIndex].pMenu;
                gpCurrMenu = gpMainMenu;
    
                cuiMultiMenu->numItems = 0;
    
                /* Default to the Help item that was given to it */
                gCurrMenuItemEntry = gpMainMenu->numItems - 1;
    
                gpMainMenu->menuItems[gpMainMenu->numItems - 1].itemType = CUI_MENU_ITEM_TYPE_INTERCEPT;
                gpMainMenu->menuItems[gpMainMenu->numItems - 1].interceptActive = false;
                gpMainMenu->menuItems[gpMainMenu->numItems - 1].pDesc = CUI_MENU_ACTION_HELP_DESC;
                gpMainMenu->menuItems[gpMainMenu->numItems - 1].item.pFnIntercept = (CUI_pFnIntercept_t) CUI_menuActionHelp;
            }
            else
            {
                /*
                 * Locate where _pMenu(The menu to be removed) lies within the
                 * cuiMultiMenu[] array.
                 */
                int multiMenuIndex = 0;
                for (int i = 0; i < MAX_REGISTERED_MENUS; i++)
                {
                    if (cuiMultiMenu->menuItems[i].item.pSubMenu == _pMenu)
                    {
                        multiMenuIndex = i;
                        break;
                    }
                }
    
                /*
                 * Shift the remaining items in the cuiMultiMenu down to cover the
                 * menu that is being de-registered.
                 */
                for (int i = multiMenuIndex; i < MAX_REGISTERED_MENUS; i++)
                {
                    /*
                     *  It is safe to use this i+1 value because cuiMultiMenu was
                     * declared to contain MAX_REGISTERED_MENUS + 1 menuItems.
                     */
                    memcpy(&(cuiMultiMenu->menuItems[i]), &(cuiMultiMenu->menuItems[i+1]), sizeof(cuiMultiMenu->menuItems[0]));
                }
    
                // Decrement the count of items in cuiMultiMenu
                cuiMultiMenu->numItems--;
    
                if (gpCurrMenu == _pMenu)
                {
                    /*
                     * If the menu being removed was the one currently being
                     * displayed, then we need to choose a new thing to display.
                     * The easiest solution here is to display the Help action.
                     */
                    gpCurrMenu = cuiMultiMenu;
                    gCurrMenuItemEntry = cuiMultiMenu->numItems;
                }
                else if (gpCurrMenu == cuiMultiMenu)
                {
                    /*
                     * If the menu being removed was not currently being displayed,
                     * and we were currently looking at an option in cuiMultiMenu,
                     * then it's safest just to go back to the Help screen.
                     */
                    gCurrMenuItemEntry = cuiMultiMenu->numItems - 1;
                }
                else
                {
                    /*
                     * If the currently displayed menu had nothing to do with this
                     * operation, it is safe to leave the display where it is.
                     * Do not update gpCurrMenu or gCurrMenuItemEntry.
                     */
                }
            }
    
            CUI_dispMenu(false);
    
        }
        else
        {
            gpMainMenu = NULL;
            gpCurrMenu = NULL;
    
            /* Default to the Help item that was given to it */
            gCurrMenuItemEntry = 0;
    
            System_snprintf(buff, sizeof(buff),
                CUI_ESC_CUR_HIDE CUI_ESC_CUR_MENU_BTM CUI_ESC_CLR_UP CUI_ESC_CUR_HOME,
                MAX_MENU_LINE_LEN);
            CUI_writeString(buff, strlen(buff));
        }
    
        gMenuResources[matchingIndex].clientHash = 0U;
        gMenuResources[matchingIndex].pMenu = NULL;
    
        Semaphore_post(gMenuSem);
        return CUI_SUCCESS;
    }
    
    
    /*********************************************************************
     * @fn          CUI_updateMultiMenuTitle
     *
     * @brief       De-registers a menu with the CUI module
     *
     * @param       _pTitle - Pointer to the new multi-menu title
     *
     *
     * @return      CUI_retVal_t representing success or failure.
     */
    CUI_retVal_t CUI_updateMultiMenuTitle(const char* _pTitle)
    {
        if (NULL == _pTitle)
        {
            return CUI_INVALID_PARAM;
        }
    
        cuiMultiMenu->pTitle = _pTitle;
    
        /*
         * Display the updated title if the top level menu is already
         *  being shown.
         */
        if (gpCurrMenu == cuiMultiMenu)
        {
            CUI_dispMenu(false);
        }
    
        return CUI_SUCCESS;
    }
    
    /*********************************************************************
     * @fn          CUI_menuNav
     *
     * @brief       Navigate to a specific entry of a menu that has already been
     *              registered
     *
     * @param       _clientHandle - Client that owns the menu
     *              _pMenu - Pointer to an already registered menu
     *              _itemIndex - The index of the menuItems[] array to select from
     *                  the menu
     *
     *
     * @return      CUI_retVal_t representing success or failure.
     */
    CUI_retVal_t CUI_menuNav(const CUI_clientHandle_t _clientHandle, CUI_menu_t* _pMenu, const uint32_t _itemIndex)
    {
        CUI_retVal_t retVal = CUI_publicAPIChecks(_clientHandle);
        if (CUI_SUCCESS != retVal)
        {
            return retVal;
        }
    
        if (NULL == _pMenu)
        {
            return CUI_INVALID_PARAM;
        }
    
        if (_itemIndex > _pMenu->numItems - 1)
        {
            return CUI_INVALID_PARAM;
        }
    
        CUI_retVal_t menuRetVal;
        uint32_t prevItemIndex = 0;
        for (int i = 0; i < MAX_REGISTERED_MENUS; i++)
        {
            prevItemIndex = i;
            /*
             * Verify that the menu is apart of a registered Main Menu
             */
            menuRetVal = CUI_findMenu(gMenuResources[i].pMenu, _pMenu, &prevItemIndex);
            if (CUI_SUCCESS == menuRetVal)
            {
                /*
                 * Make sure that the client Attempting to navigate to this menu
                 * is the owner of the menu.
                 */
                if (gMenuResources[i].clientHash != _clientHandle)
                {
                    return CUI_INVALID_CLIENT_HANDLE;
                }
                break;
            }
        }
    
        if (menuRetVal)
        {
            return CUI_INVALID_PARAM;
        }
    
        /*
         * If the menu is found to be already registered, then it is safe to nav
         * there. It is guaranteed that the user can navigate away afterwards
         */
        gCurrMenuItemEntry = _itemIndex;
        gpCurrMenu = _pMenu;
        gpCurrMenu->gPrevMenuItemEntry = prevItemIndex;
    
        CUI_dispMenu(false);
    
        return CUI_SUCCESS;
    }
    
    /*********************************************************************
     * @fn          CUI_processMenuUpdate
     *
     * @brief       This function should be called whenever there is UART input
     *                  to be processed.
     *
     *              This update process begins by the CUI module calling the
     *                  CUI_pFnClientUartUpdate_t of the main menu that was
     *                  registered. At that point the CUI_pFnClientUartUpdate_t
     *                  function is responsible for calling CUI_processUartUpdate.
     *
     * @return      CUI_retVal_t representing success or failure.
     */
    CUI_retVal_t CUI_processMenuUpdate(void)
    {
        if (!gModuleInitialized)
        {
            return CUI_FAILURE;
        }
    
        CUI_menuItem_t* pItemEntry = &(gpCurrMenu->menuItems[gCurrMenuItemEntry]);
        uint8_t input = gUartTxBuffer[0];
        bool inputBad = false;
    
        // Decode special escape sequences
        if (input == CUI_INPUT_ESC)
        {
            /*
             * If the first character is CUI_INPUT_ESC, then look
             *  for the accepted sequences.
             */
            if (memcmp(gUartTxBuffer, CUI_ESC_UP, sizeof(CUI_ESC_UP)) == 0)
            {
                input = CUI_INPUT_UP;
            }
            else if (memcmp(gUartTxBuffer, CUI_ESC_DOWN, sizeof(CUI_ESC_DOWN)) == 0)
            {
                input = CUI_INPUT_DOWN;
            }
            else if (memcmp(gUartTxBuffer, CUI_ESC_RIGHT, sizeof(CUI_ESC_RIGHT)) == 0)
            {
                input = CUI_INPUT_RIGHT;
            }
            else if (memcmp(gUartTxBuffer, CUI_ESC_LEFT, sizeof(CUI_ESC_LEFT)) == 0)
            {
                input = CUI_INPUT_LEFT;
            }
            else if (memcmp(gUartTxBuffer, CUI_ESC_ESC, sizeof(gUartTxBuffer)))
            {
                // The rx buffer is full of junk. Let's ignore it just in case.
                inputBad = true;
            }
        }
    
        if (!inputBad)
        {
            // If it is an upper case letter, convert to lowercase
            if (input >= 'A' && input <= 'Z')
            {
                input += 32; // converts any uppercase letter to a lowercase letter
            }
            else
            {
                /*
                 * Assume that further input is intended to be handled by an
                 * interceptable action
                 */
            }
    
            bool interceptState = pItemEntry->interceptActive;
            bool updateHandled = false;
    
            /*
             *  Allow the interceptable action, if it is being shown, the chance to
             *  handle the uart input and display output if necessary.
             */
            if (pItemEntry->itemType == CUI_MENU_ITEM_TYPE_INTERCEPT)
            {
                updateHandled = CUI_handleMenuIntercept(pItemEntry, input);
            }
            else if (pItemEntry->itemType == CUI_MENU_ITEM_TYPE_LIST)
            {
                /*
                 *  Allow the list action the chance to handle the uart input
                 *  and display output if necessary.
                 */
                updateHandled = CUI_handleMenuList(pItemEntry, input);
    
                if (interceptState != pItemEntry->interceptActive)
                {
                    /*
                     * If the interceptState has changed, it means the
                     * user has finished with the list.
                     */
                    input = CUI_INPUT_ESC;
                }
            }
    
            if (false == updateHandled)
            {
                switch(input)
                {
                    // Up and Down have no effect on menu navigation
                    case CUI_INPUT_UP:
                    case CUI_INPUT_DOWN:
                        break;
                    // Left and Right navigate within a menu
                    case CUI_INPUT_RIGHT:
                    case CUI_INPUT_LEFT:
                        CUI_menuActionNavigate(input);
                        break;
                    case CUI_INPUT_EXECUTE:
                        CUI_menuActionExecute();
                        break;
                    case CUI_INPUT_BACK:
                        // if there is a upper menu, navigate to it.
                        if (gpCurrMenu->pUpper)
                        {
                            gCurrMenuItemEntry = gpCurrMenu->gPrevMenuItemEntry;
                            gpCurrMenu = gpCurrMenu->pUpper;
                        }
                        else
                        {
                            // We are already at the main menu.
                            // go back to the help screen
                            gCurrMenuItemEntry = gpCurrMenu->numItems - 1;
                        }
                        CUI_dispMenu(false);
                        break;
                    case CUI_INPUT_ESC:
                        if (interceptState && !pItemEntry->interceptActive)
                        {
                            /*
                             * Nothing special to do here. Just display the
                             * menu item outside of intercept.
                             */
                        }
                        else
                        {
                            gpCurrMenu = gpMainMenu;
                            // Display the help screen
                            gCurrMenuItemEntry = gpMainMenu->numItems - 1;
                        }
                        CUI_dispMenu(false);
                        break;
                    default :
                        break;
                }
            }
        }
    
        //Clear the buffer
        memset(gUartTxBuffer, '\0', sizeof(gUartTxBuffer));
    
        UART_read(gUartHandle, gUartRxBuffer, sizeof(gUartRxBuffer));
        return CUI_SUCCESS;
    }
    #else
    CUI_retVal_t CUI_registerMenu(const CUI_clientHandle_t _clientHandle, CUI_menu_t* _pMenu) { return CUI_SUCCESS; }
    
    CUI_retVal_t CUI_deRegisterMenu(const CUI_clientHandle_t _clientHandle, CUI_menu_t* _pMenu) { return CUI_SUCCESS; }
    
    CUI_retVal_t CUI_updateMultiMenuTitle(const char* _pTitle) { return CUI_SUCCESS; }
    
    CUI_retVal_t CUI_menuNav(const CUI_clientHandle_t _clientHandle, CUI_menu_t* _pMenu, const uint32_t _itemIndex) { return CUI_SUCCESS; }
    
    CUI_retVal_t CUI_processMenuUpdate(void) { return CUI_SUCCESS; }
    #endif
    /******************************************************************************
     * Status Line CUI APIs
     *****************************************************************************/
    /*********************************************************************
     * @fn          CUI_statusLineResourceRequest
     *
     * @brief       Request access to a new status line
     *
     * @param       _clientHandle - Valid client handle
     *              _pLabel - C string label for the new status line
     *              _pLineId - Pointer to an unsigned integer. The value of the
     *                  unsigned integer will be set and represent the line number
     *                  you were given access to.
     *
     * @return      CUI_retVal_t representing success or failure.
     */
    CUI_retVal_t CUI_statusLineResourceRequest(const CUI_clientHandle_t _clientHandle, const char _pLabel[MAX_STATUS_LINE_LABEL_LEN], const bool _refreshInd, uint32_t* _pLineId)
    {
        CUI_retVal_t retVal = CUI_publicAPIChecks(_clientHandle);
        if (CUI_SUCCESS != retVal)
        {
            return retVal;
        }
    
        if (NULL == _pLabel || NULL == _pLineId)
        {
            return CUI_INVALID_PARAM;
        }
    
        retVal = CUI_acquireStatusLine(_clientHandle, _pLabel, _refreshInd, _pLineId);
        if (CUI_SUCCESS != retVal)
        {
            /*
             * Set the value of _pLineId to a invalid lineId in case
             * the user tries to print to this line even though it was
             * not successfully acquired.
             */
            //TODO: change _pLineId to be a signed integer so that -1 may be used.
            *_pLineId = 0xFF;
            return retVal;
        }
    
        /*
         * Print a default '--' value to the line
         */
        CUI_statusLinePrintf(_clientHandle, *_pLineId, "--");
    
        return CUI_SUCCESS;
    }
    
    /*********************************************************************
     * @fn          CUI_statusLinePrintf
     *
     * @brief        Update an acquired status line
     *
     * @param       _clientHandle - Client handle that owns the status line
     *              _lineId - unsigned integer of the line that you are updating.
     *                  This is the value set by CUI_statusLineResourceRequest().
     *              _format - C string printf style format.
     *              ... - Var args to be formated by _format
     *
     * @return      CUI_retVal_t representing success or failure.
     */
    CUI_retVal_t CUI_statusLinePrintf(const CUI_clientHandle_t _clientHandle,
            const uint32_t _lineId, const char *_format, ...)
    {
        /*
         * This buffer will be passed to CUI_writeString(). The address must be
         * valid at all times. Using a ping pong buffer system will allow a second
         * quick call to CUI_statusLinePrintf to not effect the buffer of a
         * previous unfinished call.
         */
        char statusLineBuff[CUI_MAX_LABEL_AND_SEP_LEN + MAX_STATUS_LINE_VALUE_LEN + 32]; // plus 32 for cursor movement/clearing
        va_list args;
    
        CUI_retVal_t retVal = CUI_publicAPIChecks(_clientHandle);
        if (CUI_SUCCESS != retVal)
        {
            return retVal;
        }
    
        // Known to be good since the public api check was successful
        int clientIndex = CUI_getClientIndex(_clientHandle);
    
        if (_clientHandle != gStatusLineResources[clientIndex][_lineId].clientHash)
        {
            return CUI_INVALID_CLIENT_HANDLE;
        }
    
        if (CUI_ACQUIRED != gStatusLineResources[clientIndex][_lineId].status)
        {
            return CUI_RESOURCE_NOT_ACQUIRED;
        }
    
    #if !defined(CUI_SCROLL_PRINT)
        uint32_t offset;
    #ifndef CUI_MIN_FOOTPRINT
        if (MAX_REGISTERED_MENUS == 0)
        {
            offset = 1;
        }
        else
        {
            offset = CUI_INITIAL_STATUS_OFFSET;
        }
    #else
        offset = 1;
    #endif
        offset += gStatusLineResources[clientIndex][_lineId].lineOffset;
    
        //TODO: Remove magic length number
        System_snprintf(statusLineBuff, 32,
            CUI_ESC_CUR_HIDE CUI_ESC_CUR_HOME CUI_ESC_CUR_LINE CUI_ESC_CLR_STAT_LINE_VAL "%c",
             offset, CUI_STATUS_LINE_START_CHAR);
    #endif
        size_t availableLen = sizeof(statusLineBuff) - 1;
        size_t buffSize = availableLen;
    
        // Label must be printed for testing scripts to parse the output easier
        strncat(statusLineBuff, gStatusLineResources[clientIndex][_lineId].label,
                availableLen);
    
        retVal = CUI_updateRemLen(&availableLen, statusLineBuff, buffSize);
        if (CUI_SUCCESS != retVal)
        {
            return retVal;
        }
    
        va_start(args, _format);
        System_vsnprintf(&statusLineBuff[strlen(statusLineBuff)], availableLen, _format, args);
        va_end(args);
    
        retVal = CUI_updateRemLen(&availableLen, statusLineBuff, buffSize);
        if (CUI_SUCCESS != retVal)
        {
            return retVal;
        }
    
        static char refreshChars[] = {'\\', '|', '/', '-'};
        static uint8_t refreshCharIdx = 0;
    
        if (gStatusLineResources[clientIndex][_lineId].refreshInd)
        {
            System_snprintf(&statusLineBuff[strlen(statusLineBuff)], availableLen, " %c", refreshChars[refreshCharIdx]);
            refreshCharIdx = (refreshCharIdx + 1) % sizeof(refreshChars);
        }
    
    #if !defined(CUI_SCROLL_PRINT)
        char endChar[] = {CUI_END_CHAR};
        strncat(statusLineBuff, endChar, 1);
    #else
        strncat(statusLineBuff, CUI_NL_CR, sizeof(CUI_NL_CR));
    #endif
    
        CUI_writeString(statusLineBuff, strlen(statusLineBuff));
    
    #ifndef CUI_MIN_FOOTPRINT
        // This will check if a cursor is active and put the cursor back
        //  if it is necessary
        CUI_updateCursor();
    #endif
    
        return CUI_SUCCESS;
    }
    
    /*********************************************************************
     *  @fn         CUI_assert
     *
     *  @brief      Without requiring a cuiHandle_t you may print an assert
     *                string and optionally spinLock while flashing the leds.
     *
     * @param       _assertMsg - Char pointer of the message to print
     *              _spinLock - Whether or not to spinLock
     *
     * @return      CUI_retVal_t representing success or failure.
     */
    void CUI_assert(const char* _assertMsg, const bool _spinLock)
    {
        if (BIOS_ThreadType_Main == BIOS_getThreadType())
        {
            /*
             *  UART requires the bios to have been started. If you get stuck
             *  here it is because CUI_assert is being called before
             *  BIOS_start().
             */
            // TODO: solve this issue CUI_ledAssert();
            while(1){};
        }
        if (!gModuleInitialized)
        {
            CUI_params_t params;
            CUI_paramsInit(&params);
            CUI_init(&params);
        }
    
        char statusLineBuff[MAX_STATUS_LINE_VALUE_LEN];
        char tmp[32];
    
        // Display this in the line between the menu and the status lines
        uint32_t offset = CUI_INITIAL_STATUS_OFFSET - 1;
    
        System_snprintf(tmp, sizeof(tmp),
            CUI_ESC_CUR_HIDE CUI_ESC_CUR_HOME CUI_ESC_CUR_LINE CUI_ESC_CLR_STAT_LINE_VAL "%c",
            offset, CUI_STATUS_LINE_START_CHAR);
        CUI_writeString(tmp, strlen(tmp));
    
        System_snprintf(statusLineBuff, sizeof(statusLineBuff),  CUI_COLOR_RED "%s%c" CUI_COLOR_RESET, _assertMsg, CUI_END_CHAR);
    
        CUI_writeString(statusLineBuff, strlen(statusLineBuff));
    
        // If _spinLock is true, infinite loop and flash the leds
        if (_spinLock)
        {
            extern const uint_least8_t LED_count;
            extern LED_Config LED_config[2];
    
            LED_Params ledParams;
            LED_Params_init(&ledParams);
    
            for(uint8_t i = 0; i < LED_count; i++)
            {
                LED_close(&LED_config[i]);
                LED_Handle ledHandle = LED_open(i, &ledParams);
                LED_startBlinking(ledHandle, 50, LED_BLINK_FOREVER);
            }
    
            while(1){};
        }
    }
    
    #ifndef CUI_MIN_FOOTPRINT
    void CUI_menuActionBack(const int32_t _itemEntry)
    {
        if (NULL != gpCurrMenu->pUpper)
        {
           gCurrMenuItemEntry = gpCurrMenu->gPrevMenuItemEntry;
           gpCurrMenu = gpCurrMenu->pUpper;
        }
    }
    
    void CUI_menuActionHelp(char _input, char* _lines[3], CUI_cursorInfo_t* _curInfo)
    {
        if (_input == CUI_ITEM_PREVIEW)
        {
            strncat(_lines[1], "Press Enter for Help", MAX_MENU_LINE_LEN);
        }
        else
        {
            strncat(_lines[0], "[Arrow Keys] Navigate Menus | [Enter] Perform Action, Enter Submenu", MAX_MENU_LINE_LEN);
            strncat(_lines[1], "----------------------------|--------------------------------------", MAX_MENU_LINE_LEN);
            strncat(_lines[2], "[Esc] Return to Main Menu   | [Backspace] Return to Parent Menu", MAX_MENU_LINE_LEN);
        }
    }
    #endif
    
    /*********************************************************************
     * Private Functions
     */
    static CUI_retVal_t CUI_updateRemLen(size_t* _currRemLen, char* _buff, size_t _buffSize)
    {
        size_t newLen = strlen(_buff);
    
        if (newLen >= _buffSize - 1)
        {
            return CUI_FAILURE;
        }
    
        *_currRemLen = (_buffSize - newLen -1);
        return CUI_SUCCESS;
    }
    
    static void UartWriteCallback(UART_Handle _handle, void *_buf, size_t _size)
    {
    
        gRingBuffPendLen -= _size;
        gRingBuffTailIdx = (gRingBuffTailIdx + _size) % sizeof(gRingBuff);
        static size_t maxPending = 0;
    
        if (gRingBuffPendLen > maxPending)
        {
            maxPending = gRingBuffPendLen;
        }
    
    
        if (gRingBuffPendLen)
        {
            if ((sizeof(gRingBuff) - gRingBuffTailIdx) < gRingBuffPendLen)
            {
                UART_write(gUartHandle, (const void*)&gRingBuff[gRingBuffTailIdx], sizeof(gRingBuff) - gRingBuffTailIdx);
            }
            else
            {
                UART_write(gUartHandle, (const void*)&gRingBuff[gRingBuffTailIdx], gRingBuffPendLen);
            }
        }
    }
    
    #ifndef CUI_MIN_FOOTPRINT
    static void UartReadCallback(UART_Handle _handle, void *_buf, size_t _size)
    {
        // Make sure we received all expected bytes
        if (_size)
        {
            // If cleared, then read it
            if(gUartTxBuffer[0] == 0)
            {
                // Copy bytes from RX buffer to TX buffer
                for(size_t i = 0; i < _size; i++)
                {
                    gUartTxBuffer[i] = ((uint8_t*)_buf)[i];
                }
            }
            memset(_buf, '\0', _size);
            CUI_callMenuUartUpdateFn();
        }
        else
        {
            // Handle error or call to UART_readCancel()
            UART_readCancel(gUartHandle);
        }
    }
    #endif
    
    static CUI_retVal_t CUI_writeString(void * _buffer, size_t _size)
    {
        /*
         * Since the UART driver is in Callback mode which is non blocking.
         *  If UART_write is called before a previous call to UART_write
         *  has completed it will not be printed. By taking a quick
         *  nap we can attempt to perform the subsequent write. If the
         *  previous call still hasn't finished after this nap the write
         *  will be skipped as it would have been before.
         */
    
        //Error if no buffer
        if((gUartHandle == NULL) || (_buffer == NULL) )
        {
            return CUI_UART_FAILURE;
        }
    
    
        Semaphore_pend(gUartSem, BIOS_WAIT_FOREVER);
    
        UART_writeCancel(gUartHandle);
    
        if ((sizeof(gRingBuff) - gRingBuffPendLen) < _size)
        {
            uint8_t i;
            for (i = 0; i < 10; i++)
            {
                if ((sizeof(gRingBuff) - gRingBuffTailIdx) < gRingBuffPendLen)
                {
                   UART_write(gUartHandle, (const void*)&gRingBuff[gRingBuffTailIdx], sizeof(gRingBuff) - gRingBuffTailIdx);
                }
                else
                {
                   UART_write(gUartHandle, (const void*)&gRingBuff[gRingBuffTailIdx], gRingBuffPendLen);
                }
                Task_sleep(100);
                UART_writeCancel(gUartHandle);
                if ((sizeof(gRingBuff) - gRingBuffPendLen) >= _size)
                {
                    i = 0;
                    break;
                }
            }
    
            if (i) {
                //error
                while(1){}
    
            }
        }
    
    
        // update ring buff pending length
        gRingBuffPendLen += _size;
    
        // handle writing around the end of the ring buffer
        if ((sizeof(gRingBuff) - gRingBuffHeadIdx) < _size)
        {
            memcpy(&gRingBuff[gRingBuffHeadIdx], _buffer, sizeof(gRingBuff) - gRingBuffHeadIdx);
            memcpy(gRingBuff, &((uint8_t *)_buffer)[sizeof(gRingBuff) - gRingBuffHeadIdx], _size - (sizeof(gRingBuff) - gRingBuffHeadIdx));
        }
        else
        {
            memcpy(&gRingBuff[gRingBuffHeadIdx], _buffer, _size);
        }
    
        // update ring buff idx w/ wrap around logic
        gRingBuffHeadIdx = (gRingBuffHeadIdx + _size) % sizeof(gRingBuff);
    
    
        if ((sizeof(gRingBuff) - gRingBuffTailIdx) < gRingBuffPendLen)
        {
            UART_write(gUartHandle, (const void*)&gRingBuff[gRingBuffTailIdx], sizeof(gRingBuff) - gRingBuffTailIdx);
        }
        else
        {
            UART_write(gUartHandle, (const void*)&gRingBuff[gRingBuffTailIdx], gRingBuffPendLen);
        }
    
        Semaphore_post(gUartSem);
        return CUI_SUCCESS;
    }
    
    static CUI_retVal_t CUI_publicAPIChecks(const CUI_clientHandle_t _clientHandle)
    {
        if (!gModuleInitialized)
        {
            return CUI_MODULE_UNINITIALIZED;
        }
    
        return CUI_validateHandle(_clientHandle);
    }
    
    static CUI_retVal_t CUI_validateHandle(const CUI_clientHandle_t _clientHandle)
    {
        if (!_clientHandle)
        {
            return CUI_INVALID_CLIENT_HANDLE;
        }
    
        if (CUI_getClientIndex(_clientHandle) == -1)
        {
            return CUI_INVALID_CLIENT_HANDLE;
        }
        else
        {
            return CUI_SUCCESS;
        }
    }
    
    static CUI_retVal_t CUI_acquireStatusLine(const CUI_clientHandle_t _clientHandle, const char _pLabel[MAX_STATUS_LINE_LABEL_LEN], const bool _refreshInd, uint32_t* _pLineId)
    {
        Semaphore_pend(gStatusSem, BIOS_WAIT_FOREVER);
    
        int clientIndex = CUI_getClientIndex(_clientHandle);
        if (clientIndex == -1)
        {
            return CUI_INVALID_PARAM;
        }
    
        int freeIndex = -1;
        for (int i = 0; i < gMaxStatusLines[clientIndex]; i++)
        {
            if (CUI_RELEASED == gStatusLineResources[clientIndex][i].status)
            {
                freeIndex = i;
                break;
            }
        }
    
        Semaphore_post(gStatusSem);
    
        if (-1 == freeIndex)
        {
            return CUI_NO_ASYNC_LINES_RELEASED;
        }
    
        uint32_t offset = 0;
        for (uint32_t i = 0; i < clientIndex; i++)
        {
            offset += gMaxStatusLines[i];
            offset ++; // allow 1 empty line between clients
        }
    
        offset += freeIndex;
    
        //Add a ": " to every label
        memset(gStatusLineResources[clientIndex][freeIndex].label, '\0', sizeof(gStatusLineResources[clientIndex][freeIndex].label));
        System_snprintf(gStatusLineResources[clientIndex][freeIndex].label,
                 MAX_STATUS_LINE_LABEL_LEN + strlen(CUI_LABEL_VAL_SEP),
                 "%s%s", _pLabel, CUI_LABEL_VAL_SEP);
        gStatusLineResources[clientIndex][freeIndex].lineOffset = offset;
        gStatusLineResources[clientIndex][freeIndex].clientHash = _clientHandle;
        gStatusLineResources[clientIndex][freeIndex].status = CUI_ACQUIRED;
        gStatusLineResources[clientIndex][freeIndex].refreshInd = _refreshInd;
    
    
    
        /*
         * Save this "line id" as a way to directly control the line, similarly to
         * how a client can directly control a led or button through pinIds.
         */
        *_pLineId = freeIndex;
    
        return CUI_SUCCESS;
    }
    
    static int CUI_getClientIndex(const CUI_clientHandle_t _clientHandle)
    {
        for (uint32_t i = 0; i < MAX_CLIENTS; i++)
        {
            if (_clientHandle == gClientHandles[i])
            {
                return i;
            }
        }
        return -1;
    }
    
    #ifndef CUI_MIN_FOOTPRINT
    static void CUI_callMenuUartUpdateFn()
    {
        /*
         * When a menu is registered it is guaranteed to contain a non NULL
         * menu update function. So if a sub menu doesn't have a valid uart update
         * function, at some point in the menu tree between the current menu and
         * the top level menu there will be a valid uart update function.
         *
         * If somehow the menu object has been corrupted and there is no non NULL
         * uart update function then nothing will be called.
         */
        CUI_menu_t* menu = gpCurrMenu;
        while (NULL != menu)
        {
            if (menu->uartUpdateFn)
            {
                menu->uartUpdateFn();
                break;
            }
            // Try the upper/parent menu to look for a uart update function
            menu = menu->pUpper;
        }
    
        /*
         * Somehow from gpCurrMenu to the top most menu there was no valid
         * uart update function to call. There is no way to account for this.
         */
    }
    
    static void CUI_updateCursor(void)
    {
        char buff[32];
        if (gCursorActive)
        {
            System_snprintf(buff, sizeof(buff),
                CUI_ESC_CUR_HOME CUI_ESC_CUR_ROW_COL CUI_ESC_CUR_SHOW,
                gCursorInfo.row, gCursorInfo.col);
            CUI_writeString(buff, strlen(buff));
        }
    }
    
    static bool CUI_handleMenuIntercept(CUI_menuItem_t* _pItemEntry, uint8_t _input)
    {
        bool updateHandled = false;
        bool interceptStarted = false;
    
        char *line[3];
        memset(menuBuff, '\0', sizeof(menuBuff));
    
        line[0] = &menuBuff[CUI_MENU_START_ESCAPE_LEN];
        line[1] = &menuBuff[CUI_MENU_START_ESCAPE_LEN + MAX_MENU_LINE_LEN + CUI_NL_CR_LEN];
        line[2] = &menuBuff[CUI_MENU_START_ESCAPE_LEN + MAX_MENU_LINE_LEN + CUI_NL_CR_LEN + MAX_MENU_LINE_LEN + CUI_NL_CR_LEN];
    
        CUI_cursorInfo_t curInfo = {-1, -1};
    
        if (_pItemEntry->itemType == CUI_MENU_ITEM_TYPE_INTERCEPT)
        {
            if (_pItemEntry->interceptActive)
            {
                // If intercept is active, pressing CUI_INPUT_EXECUTE should
                // disable it so that normal navigation may continue
                if (CUI_INPUT_EXECUTE == _input)
                {
                    _pItemEntry->interceptActive = false;
    
                    // send key to application for handling
                    if (_pItemEntry->item.pFnIntercept)
                    {
                        _pItemEntry->item.pFnIntercept(CUI_ITEM_INTERCEPT_STOP,
                                                       line, &curInfo);
                    }
                    gCursorActive = false;
                    updateHandled = true;
                    CUI_dispMenu(false);
                }
                else if (CUI_INPUT_ESC == _input)
                {
                    _pItemEntry->interceptActive = false;
    
                    // send key to application for handling
                    if (_pItemEntry->item.pFnIntercept)
                    {
                        _pItemEntry->item.pFnIntercept(CUI_ITEM_INTERCEPT_CANCEL,
                                                       line, &curInfo);
                    }
                    gCursorActive = false;
                    updateHandled = true;
                    CUI_dispMenu(false);
                }
            }
            else if (CUI_INPUT_EXECUTE == _input)
            {
                /*
                 * Since this screen is interceptable, pressing 'E' should start
                 *  allowing the application to intercept the key presses.
                 */
                _pItemEntry->interceptActive = true;
                interceptStarted = true;
            }
    
            if (_pItemEntry->interceptActive)
            {
                // Tell the Action if interception just started, else send the input directly
                char input = (interceptStarted ? CUI_ITEM_INTERCEPT_START : _input);
    
                // send key to application for handling
                if (_pItemEntry->item.pFnIntercept)
                {
                    _pItemEntry->item.pFnIntercept(input, line, &curInfo);
                }
    
                updateHandled = true;
                CUI_dispMenu(true);
    
                // If a cursor should be shown, add this at the end of the string
                if ((curInfo.col != -1) && (curInfo.row != -1))
                {
                    gCursorActive = true;
                    gCursorInfo.col = curInfo.col;
                    gCursorInfo.row = curInfo.row;
                    CUI_updateCursor();
                }
                else
                {
                    gCursorActive = false;
                }
            }
        }
    
        return updateHandled;
    }
    
    static bool CUI_handleMenuList(CUI_menuItem_t* _pItemEntry, uint8_t _input)
    {
        bool updateHandled = false;
    
        char *line[3];
        memset(menuBuff, '\0', sizeof(menuBuff));
    
        line[0] = &menuBuff[CUI_MENU_START_ESCAPE_LEN];
        line[1] = &menuBuff[CUI_MENU_START_ESCAPE_LEN + MAX_MENU_LINE_LEN + CUI_NL_CR_LEN];
        line[2] = &menuBuff[CUI_MENU_START_ESCAPE_LEN + MAX_MENU_LINE_LEN + CUI_NL_CR_LEN + MAX_MENU_LINE_LEN + CUI_NL_CR_LEN];
    
        if (_pItemEntry->itemType == CUI_MENU_ITEM_TYPE_LIST)
        {
            if (_pItemEntry->interceptActive)
            {
                if (CUI_INPUT_ESC == _input || CUI_INPUT_BACK == _input)
                {
                    _pItemEntry->interceptActive = false;
                    return updateHandled;
                }
    
                // update index value
                if (CUI_INPUT_RIGHT ==  _input)
                {
                    _pItemEntry->item.pList->currListIndex = (_pItemEntry->item.pList->currListIndex + 1 + _pItemEntry->item.pList->maxListItems) % _pItemEntry->item.pList->maxListItems;
                }
                else if (CUI_INPUT_LEFT == _input)
                {
                    _pItemEntry->item.pList->currListIndex = (_pItemEntry->item.pList->currListIndex - 1 + _pItemEntry->item.pList->maxListItems) % _pItemEntry->item.pList->maxListItems;
                }
    
                if (_pItemEntry->item.pList->pFnListAction)
                {
                    _pItemEntry->item.pList->pFnListAction(_pItemEntry->item.pList->currListIndex, line, (CUI_INPUT_EXECUTE == _input));
                }
    
                // let normal menu navigation handle the update if input was executed
                updateHandled = !(CUI_INPUT_EXECUTE == _input);
    
                // turn off interceptActive
                _pItemEntry->interceptActive = !(CUI_INPUT_EXECUTE == _input);
            }
            else
            {
                if (CUI_INPUT_EXECUTE == _input)
                {
                    _pItemEntry->interceptActive = true;
                    if (_pItemEntry->item.pList->pFnListAction)
                    {
                        _pItemEntry->item.pList->pFnListAction(_pItemEntry->item.pList->currListIndex, line, false);
                    }
                    updateHandled = true;
                }
            }
    
            if (updateHandled)
            {
                strncpy(line[2], _pItemEntry->pDesc, MAX_MENU_LINE_LEN);
                CUI_dispMenu(true);
            }
        }
        return updateHandled;
    }
    
    static void CUI_dispMenu(bool _menuPopulated)
    {
        char *line[3];
        line[0] = &menuBuff[CUI_MENU_START_ESCAPE_LEN];
        line[1] = &menuBuff[CUI_MENU_START_ESCAPE_LEN + MAX_MENU_LINE_LEN + CUI_NL_CR_LEN];
        line[2] = &menuBuff[CUI_MENU_START_ESCAPE_LEN + MAX_MENU_LINE_LEN + CUI_NL_CR_LEN + MAX_MENU_LINE_LEN + CUI_NL_CR_LEN];
    
        if (false == _menuPopulated)
        {
            CUI_cursorInfo_t cursorInfo;
            CUI_menuItem_t* itemEntry = &(gpCurrMenu->menuItems[gCurrMenuItemEntry]);
    
            memset(menuBuff, '\0', sizeof(menuBuff));
    
            // Show the menu Title. Whenever possible to provide context
            if (gpCurrMenu == gpMainMenu)
            {
                strncpy(line[0], gpCurrMenu->pTitle, MAX_MENU_LINE_LEN);
            }
            else if (gCurrMenuItemEntry != (gpCurrMenu->numItems - 1))
            {
                /*
                 *  If the current Menu Item is the 'back' item, leave the
                 *  first line empty to keep the back screen clean.
                 */
                CUI_menu_t* pMenu = gpCurrMenu;
                while((pMenu->pUpper) && (pMenu->pUpper != cuiMultiMenu))
                {
                    pMenu = pMenu->pUpper;
                }
                strncpy(line[0], pMenu->pTitle, MAX_MENU_LINE_LEN);
            }
    
            // If this is an interceptable item, instead of the title, allow a preview
            if (itemEntry->itemType == CUI_MENU_ITEM_TYPE_INTERCEPT && itemEntry->item.pFnIntercept)
            {
                itemEntry->item.pFnIntercept(CUI_ITEM_PREVIEW, line, &cursorInfo);
            }
            else if (itemEntry->itemType == CUI_MENU_ITEM_TYPE_LIST && itemEntry->item.pList->pFnListAction)
            {
                itemEntry->item.pList->pFnListAction(itemEntry->item.pList->currListIndex, line, false);
            }
    
            // Guarantee the last line is not overwritten by the intercept function
            if (itemEntry->itemType == CUI_MENU_ITEM_TYPE_SUBMENU)
            {
                // If the curr item is a sub menu, display the sub menu title
                strncpy(line[2], itemEntry->item.pSubMenu->pTitle, MAX_MENU_LINE_LEN);
            }
            else
            {
                // If not, display the items description
                strncpy(line[2], itemEntry->pDesc, MAX_MENU_LINE_LEN);
            }
        }
    
        /*
         * Start copying the menu into the dispBuff for writing to the UART
         *
         * Copy the first line, then add the newline and carriage return.
         * Do this for all three lines.
         *
         * The memory for the carriage returns, newlines, and the final
         *  CUI_END_CHAR are accounted for in the menuBuff already.
         */
    #if !defined(CUI_SCROLL_PRINT)
        System_snprintf(menuBuff, 32,
            CUI_ESC_CUR_HIDE CUI_ESC_CUR_MENU_BTM CUI_ESC_CLR_UP CUI_ESC_CUR_HOME "%c",
            MAX_MENU_LINE_LEN, CUI_MENU_START_CHAR);
    #endif
        // Note these memory regions do not overlap
        strncat(menuBuff, line[0], MAX_MENU_LINE_LEN);
        // Set the newline and carriage return
        strncat(menuBuff, CUI_NL_CR, sizeof(CUI_NL_CR));
    
        strncat(menuBuff, line[1], MAX_MENU_LINE_LEN);
        strncat(menuBuff, CUI_NL_CR, sizeof(CUI_NL_CR));
    
        strncat(menuBuff, line[2], MAX_MENU_LINE_LEN);
    
    #if !defined(CUI_SCROLL_PRINT)
        char endChar[2] = {CUI_END_CHAR, '\0'};
        strncat(menuBuff, endChar, strlen(endChar));
    #else
        strncat(menuBuff, CUI_NL_CR, strlen(CUI_NL_CR));
    #endif
    
        CUI_writeString(menuBuff, strlen(menuBuff));
    }
    
    static void CUI_menuActionNavigate(uint8_t _navDir)
    {
        // No menu change necessary. There is only one screen
        if (1 == gpCurrMenu->numItems)
        {
            return;
        }
    
        if (CUI_INPUT_LEFT == _navDir)
        {
            // Wrap menu around from left to right
            gCurrMenuItemEntry =  (gCurrMenuItemEntry - 1 + gpCurrMenu->numItems) % (gpCurrMenu->numItems);
        }
        else if (CUI_INPUT_RIGHT == _navDir)
        {
            // Wrap menu around from right to left
            gCurrMenuItemEntry =  (gCurrMenuItemEntry + 1 + gpCurrMenu->numItems) % (gpCurrMenu->numItems);
        }
    
        CUI_dispMenu(false);
    }
    
    static void CUI_menuActionExecute(void)
    {
        if (gpCurrMenu->menuItems[gCurrMenuItemEntry].itemType == CUI_MENU_ITEM_TYPE_SUBMENU)
        {
            /*
             * If Item executed was a SubMenu, then preserve gCurrMenuItemEntry and enter the
             *  submenu.
             */
            gpCurrMenu = gpCurrMenu->menuItems[gCurrMenuItemEntry].item.pSubMenu;
            gpCurrMenu->gPrevMenuItemEntry = gCurrMenuItemEntry;
            gCurrMenuItemEntry = 0;
        }
        else if (gpCurrMenu->menuItems[gCurrMenuItemEntry].itemType == CUI_MENU_ITEM_TYPE_ACTION)
        {
            CUI_pFnAction_t actionFn;
            actionFn = gpCurrMenu->menuItems[gCurrMenuItemEntry].item.pFnAction;
            if (actionFn)
            {
                actionFn(gCurrMenuItemEntry);
            }
        }
    
        CUI_dispMenu(false);
        return;
    }
    
    static CUI_retVal_t CUI_findMenu(CUI_menu_t* _pMenu, CUI_menu_t* _pDesiredMenu, uint32_t* _pPrevItemIndex)
    {
        if (_pMenu == _pDesiredMenu)
        {
            return CUI_SUCCESS;
        }
    
        uint32_t numItems = _pMenu->numItems;
        for (int i = 0; i < numItems; i++)
        {
            *_pPrevItemIndex = i;
            /*
             * If pDesc is NULL, it is a subMenu
             */
            if (NULL == _pMenu->menuItems[i].pDesc)
            {
                CUI_menu_t* subMenu = _pMenu->menuItems[i].item.pSubMenu;
    
                if (CUI_SUCCESS == CUI_findMenu(subMenu, _pDesiredMenu, _pPrevItemIndex))
                {
                    return CUI_SUCCESS;
                }
            }
        }
        return CUI_FAILURE;
    }
    #endif /* ifndef CUI_MIN_FOOTPRINT */
    

    cui.h

    /******************************************************************************
    
     @file CUI.h
    
     @brief This file contains the interface of the Common User Interface. (CUI)
    
     Group: LPRF SW RND
     Target Device: cc13x2_26x2
    
     ******************************************************************************
     
     Copyright (c) 2016-2020, Texas Instruments Incorporated
     All rights reserved.
    
     Redistribution and use in source and binary forms, with or without
     modification, are permitted provided that the following conditions
     are met:
    
     *  Redistributions of source code must retain the above copyright
        notice, this list of conditions and the following disclaimer.
    
     *  Redistributions in binary form must reproduce the above copyright
        notice, this list of conditions and the following disclaimer in the
        documentation and/or other materials provided with the distribution.
    
     *  Neither the name of Texas Instruments Incorporated nor the names of
        its contributors may be used to endorse or promote products derived
        from this software without specific prior written permission.
    
     THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
     AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
     THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
     CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
     EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
     PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
     OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
     WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
     OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
     EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    
     ******************************************************************************
     
     
     *****************************************************************************/
    
    /**
    @headerfile  cui.h
    $Date: 2018-12-18 12:13:13 -0800 (Tue, 18 Dec 2018) $
    $Revision: 42528 $
    
    @mainpage CUI API Documentation
    
    Overview
    ============================
    
        This file contains the interface of the Common User Interface or CUI.
    
        The CUI controls the access to User Interface resources. On a launchpad
        device, these resources are made up of Buttons, LEDs and UART i/o.
    
        The CUI provides an API that can be used to request for access to resources
        and then read/write to those resources. In order to request and acquire a
        resource the notion of a 'client' is used. The Client handle is used to
        successfully communicate with the CUI API. Once you open a client with the
        CUI you may then request access to a resource. If the resource is available
        the CUI will give the client access to the resource.
    
    Resource Types
    ============================
    
        As described earlier, there are three different resources the CUI controls
        access to. (Buttons, LEDs and UART i/o).
    
        For Buttons and LEDs the process for requesting and using the resource is
        quite simple and is nearly identical to how you would use the Button or LED
        drivers to do the same.
    
        For UART i/o there are two resources to consider. Firstly there is the
        notion of a 'menu'. A menu specific to your application can be registered with
        the CUI. The CUI will handle menu navigation, reading and writing. A menu
        can be as simple or as complex as desired. The second UART i/o is the notion
        of a 'status line'. Where the menu is designed to be a display
        driven by the input of the user, the status lines are capable of displaying
        the latest value of any particular set of information the developer chooses.
        A great example of a menu item would be any sort of toggle led event your
        device can perform or maybe a commissioning action and an example of a
        status line could be current network status or any application state
        information. More details on menus and status lines can be found below.
    
    Usage
    ==========================
    
        This documentation provides a basic usage summary and a set of examples in
        the form of commented code fragments. Detailed descriptions of the APIs are
        provided in subsequent sections.
    
        Initilization is the first step.
    
        @code
        // Import the CUI definitions
        #include "cui.h"
    
        CUI_params_t cuiParams;
        CUI_paramsInit(&cuiParams);
    
        // One-time initialization of the CUI
        if (CUI_SUCCESS != CUI_init(&cuiParams)) {
            // handle failure
        }
        @endcode
    
        By calling CUI_paramsInit() you are setting the cuiParams to their default
        values.
    
            cuiParams.manageBtns = true
            cuiParams.manageLeds = true
            cuiParams.manageUart = true
    
        If your application requires special management of buttons, leds, or uart
        the developer can set the parameters appropriately before calling CUI_init()
    
        After the CUI has been initialized you can then open a client.
    
        @code
        CUI_clientParams_t clientParams;
        CUI_clientParamsInit(&clientParams);
    
        strncpy(clientParams.clientName, "My first CUI application", MAX_CLIENT_NAME_LEN);
        clientParams.maxStatusLines = 5;
    
        CUI_clientHandle_t clientHandle = CUI_clientOpen(&clientParams);
        if (clientHandle == NULL) {
            // handle failure
        }
        @endcode
    
        By Calling CUI_clientParamsInit() you are setting the clientParams to their
        default values.
    
            strcpy(clientParams.clientName, "");
            clientParams.maxStatusLines = 0;
    
        <b>It is currently required that your clientName not be empty.</b>
        It can be anything you would like as it is only used to create a simple hash
        value.
    
        If you would like to add status lines to your application, you will need to
        update clientParams.maxStatusLines as the code snippet above shows.
    
        Now that you have a valid client you may start requesting access to
        resources. When requesting a button resource you must specify which button
        you are requesting as well as a callback that the CUI can use to notify you
        of a button change event that matches the CUI_btnPressCB_t prototype.
    
        @code
        CUI_btnRequest_t rightBtnReq;
        rightBtnReq.index = CONFIG_BTN_RIGHT;
        rightBtnReq.appCB = myBtnChangeHandler;
    
        if (CUI_SUCCESS != CUI_btnResourceRequest(clientHandle, &rightBtnReq) {
            //handle failure
        }
        @endcode
    
        If the resource was available to acquire then you will now have access to the
        right button.
    
        Alternatively, if you would like to read the button state without requiring
        an application callback, you can request the button in a sort of polling mode
        by using a NULL appCB. Then simply request the button's value whenever you
        require it.
    
        @code
        CUI_btnRequest_t rightBtnReq;
        rightBtnReq.index = CONFIG_BTN_RIGHT;
        rightBtnReq.appCB = NULL;
    
        if (CUI_SUCCESS != CUI_btnResourceRequest(clientHandle, &rightBtnReq) {
            //handle failure
        }
    
        bool btnState = false;
        retVal = CUI_btnGetValue(clientHandle, CONFIG_BTN_RIGHT, &btnState);
        if (!btnState) {
            //handle button logic
        }
        @endcode
    
        If you would like to change the btnMode of a button you already have access
        to then you can use the CUI_btnSetCb() API. By using a non-NULL value for the
        third parameter you will be using a sort of call back mode. If you use a NULL
        value for the third parameter, you will be using a polling mode.
    
        @code
        CUI_retVal retVal;
        retVal = CUI_btnSetCb(clientHandle, CONFIG_BTN_RIGHT, appChangeKeyCB);
        if (retVal != CUI_SUCCESS) {
            //handle failure
        }
        @endcode
    
        Buttons are great but only part of the picture. Let's request a LED as well.
        This time the only information you need to provide is the index of the LED you
        wish to acquire.
    
        @code
        CUI_ledRequest_t greenLedReq;
        greenLedReq.index = CONFIG_LED_GREEN;
        if (CUI_SUCCESS != CUI_ledResourceRequest(clientHandle, &greenLedReq) {
            // Handle failure
        }
        @endcode
    
        If the led was available you will have gained access to it and when you wish
        to change the state of the led you need only to call the CUI LED APIs.
    
        @code
        CUI_ledToggle(clientHandle, CONFIG_LED_GREEN);
        CUI_ledBlink(clientHandle, CONFIG_LED_GREEN, 5);
        CUI_ledBlink(clientHandle, CONFIG_LED_GREEN, CUI_BLINK_CONTINUOUS);
        CUI_ledOn(clientHandle, CONFIG_LED_GREEN);
        CUI_ledOff(clientHandle, CONFIG_LED_GREEN);
        @endcode
    
        One note on CUI_ledBlink is that you can choose to blink continuously or you
        can choose to blink a specific number of times. If you call CUI_ledBlink while
        the led is already blinking. The only thing that will happen is that the
        number of blinks will be reset to the new value.
    
        Now you have full control over buttons and LEDs. But you may want to let the
        user know when you have joined a network for instance. Well this is the
        perfect use for a status line. First we must request access to an available
        status line. Status lines are handed out upon request in numeric order and the
        number of status lines available to you are determined by clientParams.maxStatusLines
        when you opened your client.
    
        @code
        uint32_t connStatusLine;
        if (CUI_SUCCESS != CUI_statusLineResourceRequest(clientHandle, "Conn Status", &connStatusLine) {
            // handle failure
        }
        @endcode
    
        If clientParams.maxStatusLines haven't already been requested from the CUI you will
        have been given access to that line. When a status line is acquired, a
        default line value will be printed in the form of "LABEL: --".
        So in the example above we will see "Conn Status: --" printed on the screen.
    
        If you wish to tell the user a different value for that status line you need
        to tell the CUI what that new value is. Using a variatic function you can
        pass in a format string and any number of optional items to be formated
        according to your format string. Similar to printf() if you are familiar.
    
        @code
        char lineFormat[MAX_STATUS_LINE_VALUE_LEN];
        strncpy(lineFormat, "Successfully Connected", MAX_STATUS_LINE_VALUE_LEN);
        if (CUI_SUCCESS != CUI_statusLinePrintf(clientHandle, connStatusLine, lineFormat) {
            //handle failure
        }
        @endcode
    
        Now you will see "Conn Status: Successfully Connected".
    
        If you wish to spice your status lines up a bit more, there are a few color
        codes available to you by default. To make use of these, simply surround the
        text you wish to be given a color between a color macro and the reset macro.
    
        @code
        CUI_COLOR_RESET
        CUI_COLOR_RED
        CUI_COLOR_GREEN
        CUI_COLOR_YELLOW
        CUI_COLOR_MAGENTA
        CUI_COLOR_CYAN
        CUI_COLOR_WHITE
        @endcode
    
        For instance, you could use red for something bad, and green for something good.
        These can be used for status lines or menu lines. Any text that is being displayed
        can be colored with these macros
    
        @code
        CUI_statusLinePrintf(clientHandle, connStatusLine, CUI_COLOR_RED "Connection Failure :(" CUI_COLOR_RESET);
    
        CUI_statusLinePrintf(clinetHandle, connStatusLine, CUI_COLOR_GREEN "Connection Successful! :)" CUI_COLOR_RESET);
        @endcode
    
        Lastly in order to create a menu you must first declare your menu layout.
        A menu can be as simple or as complicated as you want. At the root of every application's
        menu though are three things.
            Main Menu
            Sub Menus
            Menu Actions
    
        A Main menu as it sounds is the top level menu of your application, this is
        what you will use to register your entire menu with the CUI. Your main menu
        can consist of any number of sub menus and menu actions. In turn, those
        sub menus can consist of any number of sub menus and menu actions as well.
        This creates a tree topology that will go as far as you desire it.
    
        A menu action can be of two variants. Either a normal action or an
        interceptable action. A normal action is used best for triggering sections
        of code via UART input. Say for instance you want to toggle the state of
        your led or trigger your device to connect to an open network. Then a normal
        action is exactly what you want. You may have several actions you want to
        perform that are very similar. A normal action can help with this by
        providing you with the itemEntry that is calling your action. This can be
        used in order to have a single action function in your code that can handle
        multiple different situations based upon the itemEntry that is received.
        Though if you want to do something more complex like modify your device's
        channel mask or panId, then an interceptable action is required.
    
        Interceptable actions are created such that instead of triggering a one off
        section of code, they actually start to intercept the input from the UART.
        Say for instance you want to modify the channel mask. The user selects the
        action and then all UART input can be processed by your action code.
        You can handle this input however you want. Then when the user de-selects the
        action the UART input will be again handled by the CUI and menu navigation
        will continue as normal.
    
        As menus can become rather complex this file provides some helpful macros in
        order to simplify menu definition. Here's an example of declaring a main menu
        that has a normal action as well as a sub menu. Within the sub menu there is
        an interceptable action.
    
        @code
        CUI_SUB_MENU(mySubMenu, " My Sub Menu ", 1, myMainMenu)
            CUI_MENU_ITEM_INT_ACTION("< Edit Channel Mask >", editChannelMaskFn)
        CUI_SUB_MENU_END
    
        CUI_MAIN_MENU(myMainMenu, " My Main Menu ", 2, processMenuUpdateFn)
            CUI_MENU_ITEM_ACTION("<  Toggle LED   >", toggleLedFn)
            CUI_MENU_ITEM_SUBMENU(&mySubMenu)
        CUI_MAIN_MENU_END
        @endcode
    
        If you are wondering how the macros work you can look at the implementation.
        Here is the prototype for the CUI_MAIN_MENU and CUI_SUB_MENU macros though.
    
        @code
            #define CUI_MAIN_MENU(_menuSymbol, _pMenuTitle, _numItems, _pMenuUpdateFn)
            #define CUI_SUB_MENU(_menuSymbol, _pMenuTitle, _numItems, _pUpperMenu)
        @endcode
    
        Now you can see that the sub menu was defined to be the 'mySubMenu' symbol
        with a menu title of " My Sub Menu ", '1' menu item and it's parent menu is
        the 'myMainMenu' symbol.
    
        Similarly the main menu was declared to be the 'myMainMenu' symbol with a
        menu title of " My Main Menu " and 2 menu items. The last parameter is
        different between them though because of two reasons. Firstly, a main menu
        has no notion of a parent menu because it is already the top level menu.
        Secondly, '_pMenuUpdateFn' is a parameter so that the CUI can gain
        processing context whenever menu processing needs to occur.
    
        Since the CUI does not create an rtos task, it technically does not have
        it's own processing context. This means that if any CUI menu processing is
        to take place, such as the user navigating left and right through the
        different menus, some other rtos task must own the processing time for this
        to occur. This function simply needs to end up calling CUI_processMenuUpdate.
        Whether it does that directly or it posts an event to a semaphore so that
        the task can call CUI_processMenuUpdate() when there is time to do so is
        up to the developer. <b>To be clear, the '_pMenuUpdateFn' is required for your
        menu to operate correctly.</b> Without it the menu will fail to register.
    
        Now that you have a main menu defined it still needs to be registered with
        the CUI.
    
        @code
        if (CUI_SUCCESS != CUI_registerMenu(clientHandle, &myMainMenu) {
            //handle failure
        }
        @endcode
    
        As long as no more than MAX_REGISTERED_MENUS have already been registered and
        your menu was defined correctly the registration will be successful and your
        main menu will appear over UART.
    
        Notice how there is the capability to register MAX_REGISTERED_MENUS?
        This is a feature so that when two examples are merged together to create
        a DMM (Dynamic Multi protocol Manager) example, the menus of each standalone
        application's menu can exist safely without impacting the other menu. When
        multiple menus are registered the CUI will introduce a new top most main
        menu. This main menu will then have a sub menu for each menu that was
        registered with the CUI. The title of this new multi-menu will default to
        " TI DMM Application ", but can be changed by providing the address to a
        different valid c-string to the CUI_updateMultiMenuTitle() function.
    
        @code
        const char newTitle[] = " My First Multi Menu Project ";
        CUI_updateMultiMenuTitle(newTitle);
        @endcode
        @endcode
    
        When developing an interceptable action, it is important to know what to
        expect to receive as input. Your interceptable action should match the
        CUI_pFnIntercept_t typdef.
            typedef void (*CUI_pFnIntercept_t)(const char _input, char* _lines[3], CUI_cursorInfo_t * _curInfo);
        The first parameter, _input, will be a single byte indicating what type of
        input the user has pressed. There are a few special input types for your use.
    
        @code
        CUI_ITEM_PREVIEW
        CUI_ITEM_INTERCEPT_START
        CUI_ITEM_INTERCEPT_STOP
        CUI_ITEM_INTERCEPT_CANCEL
        CUI_INPUT_UP
        CUI_INPUT_DOWN
        CUI_INPUT_RIGHT
        CUI_INPUT_LEFT
        CUI_INPUT_BACK
        CUI_INPUT_ESC
        @endcode
    
        CUI_ITEM_PREVIEW allows your action  to display some information on the
        menu without the user executing your action. This is useful for
        configurable application variables such as panId. When the user navigates
        over top of the theoretical "< PAN ID >" screen, it could also display what
        the current panId is without the need for the user to execute on the action.
        In the case of a CUI_ITEM_PREVIEW event, your interceptable action will only
        be able to provide a preview on the first two lines. The third line will be
        maintained by the cui to be the item's description regardless of what you
        put in the third line.
    
        CUI_ITEM_INTERCEPT_START is an indication to your action that the user has
        decided to execute on your action. This is where you could perform some sort
        of setup steps for the lifetime of the action if necessary.
    
        CUI_ITEM_INTERCEPT_STOP is an indication to your action that the user has
        completed their time in your action. This is where you could perform some
        clean up code or finalization code for the end of the action.
    
        CUI_ITEM_INTERCEPT_CANCEL is an indication to your action that the user
        decided to cancel any current progress within your action. What
        should/can be done in this case depends on what your action is doing.
        (i.e) If your action is modifying the Pan ID. CUI_ITEM_INTERCEPT_START
        could clear the local copy of the pan ID, the user can modify the local copy.
        Then if you receive a CUI_ITEM_INTERCEPT_CANCEL input, you should clear out
        the local copy and make sure not to save the changes the user had made.
    
        CUI_ITEM_INTERCEPT_START and CUI_ITEM_INTERCEPT_STOP are useful for
        configurable variables such as panID because when CUI_ITEM_INTERCEPT_START
        occurs you can obtain the latest panId from the stack and save a static local
        copy of it during the lifetime of the action. Then while the user is modifying
        the panId you are only updating the static local version of the variable.
        Finally, when the user has completed the modifications to the panId,
        CUI_ITEM_INTERCEPT_STOP will come through and you can set the new panId as the
        official panId in the stack. This process allows you to limit the number of
        stack APIs you need to call.
    
        The UP, DOWN, RIGHT, LEFT inputs come directly from the arrow keys on the
        user's keyboard. Similarly, the BACK input is from the backspace key on the
        keyboard. As with all of these inputs. You as the developer of the
        interceptable action get to choose whether to act on them. They are only
        there if you want to use them.
    
        Besides those special inputs, any ascii characters the user types will be
        provided to you via the same _input variable. It is again up to you as the
        developer to handle this input as you deem necessary. A few more macros
        are provided to you in order to simplify your logic. You can use these
        to determine if the input is what you expect or not. They will return
        TRUE or FALSE depending on the _input provided.
            CUI_IS_INPUT_NUM(_input)
            CUI_IS_INPUT_ALPHA(_input)
            CUI_IS_INPUT_ALPHA_NUM(_input)
            CUI_IS_INPUT_HEX(_input)
            CUI_IS_INPUT_BINARY(_input)
    
        The second param to CUI_pFnIntercept_t are the _pLines[3] that the menu will
        print out. By putting c strings in these buffers you can share any
        information with the user while they are either previewing your action or
        during the interceptable period of your action. When writing data to these
        buffers take care not to write more than MAX_MENU_LINE_LEN per line. This
        will cause undefined behavior and possibly cause your application to assert.
        These buffers are provided to you out of kindness, so treat them with the
        due respect and the CUI will behave accordingly :)
    
        The last param to CUI_pFnIntercept_t is a pointer to a CUI_cursorInfo_t
        struct. This is here only if you want to use it. If you do not modify this
        struct then no behavior will change. Though if you would like a cursor to
        appear on the menu to indicate to the user where some information is, then
        you can set the cursor.col and cursor.row values. By doing this the CUI will
        maintain the cursor on the screen as you have requested. There is no need to
        turn the cursor off as after receiving an input of CUI_ITEM_INTERCEPT_STOP
        the CUI will automatically remove the cursor. In the case of CUI_ITEM_PREVIEW,
        the cursor will be ignored. This means that the cursor can only be on the
        screen as long as the user is currently using your interceptable item action.
    
    --------------------------------------------------------------------------------
    **/
    #ifndef CUI_H
    #define CUI_H
    
    #include <stdlib.h>
    #include <stdint.h>
    #include <ti/drivers/apps/Button.h>
    #include <ti/drivers/apps/LED.h>
    
    #ifdef __cplusplus
    extern "C"
    {
    #endif
    
    /*********************************************************************
     * MACROS
     */
    /*
     * CUI Module configurable defines. These can be used by the user allow for
     * things such as more clients to be opened or changing the maximum length of a
     * menu line.
     *
     * !!!!
     * !!!! Do not modify these values directly. Either use an .opt file or Project
     * !!!!     level defines
     * !!!!
     *
     * By increasing these values the cui module will need to use more memory.
     * You can therefore reduce the amount of memory this module will require by
     * decreasing these values.
     *
     * RAM Usage per client         = ~56 bytes
     * RAM Usage per menus          = ~20 bytes
     * RAM Usage per status lines   = ~76 bytes
     *
     * Reducing MAX_*_LEN defines will reduce the RAM usage, but the reduced memory
     * value will not scale to the number of clients/menus/status lines. In most
     * situations changing MAX_*_LEN defines will not produce any appreciable memory
     * savings.
     */
    
    /*
     * Client Configurable Defines
     */
    #ifndef MAX_CLIENTS
    #define MAX_CLIENTS                 2
    #endif
    #ifndef MAX_CLIENT_NAME_LEN
    #define MAX_CLIENT_NAME_LEN         64
    #endif
    
    
    /*
     * Menu Configurable Defines
     */
    #ifndef MAX_REGISTERED_MENUS
    #define MAX_REGISTERED_MENUS        4
    #endif
    #ifndef MAX_MENU_LINE_LEN
    #define MAX_MENU_LINE_LEN           128
    #endif
    
    /*
     * Status Line Configurable Defines
     */
    #ifndef MAX_STATUS_LINE_LABEL_LEN
    #define MAX_STATUS_LINE_LABEL_LEN   32
    #endif
    #ifndef MAX_STATUS_LINE_VALUE_LEN
    #define MAX_STATUS_LINE_VALUE_LEN   128
    #endif
    
    #ifndef CUI_MIN_FOOTPRINT
    /*
     * Creates a main menu. A main menu must have a non NULL uart update function.
     * This will be verified when registering the menu. numItems is incremented by
     * one to allow for a common default "Back" or "Help" menu item between menus.
     */
    #define CUI_MAIN_MENU(_menuSymbol, _pMenuTitle, _numItems, _pMenuUpdateFn) \
            CUI_menu_t _menuSymbol = { \
            .uartUpdateFn=_pMenuUpdateFn, \
            .pTitle=_pMenuTitle, \
            .numItems=_numItems + 1, \
            .pUpper=NULL, \
                .menuItems = {
    /*
     * Creates a sub menu. This will be verified when registering the menu. numItems
     *  is incremented by one to allow for a common default "Back" or "Help" menu
     *  item between menus.
     */
    #define CUI_SUB_MENU(_menuSymbol, _pMenuTitle, _numItems, _pUpperMenu) \
            extern CUI_menu_t _pUpperMenu; \
            CUI_menu_t _menuSymbol = { \
            .uartUpdateFn=NULL, \
            .pTitle=_pMenuTitle, \
            .numItems=_numItems + 1, \
            .pUpper=&_pUpperMenu, \
                .menuItems = {
    
    /*
     * Inserts _pSubMenu into the .menuItems[] of a parent menu.
     */
    #define CUI_MENU_ITEM_SUBMENU(_pSubMenu) { \
            .pDesc=NULL, \
            .itemType=CUI_MENU_ITEM_TYPE_SUBMENU, \
            .item.pSubMenu=(&_pSubMenu)},
    
    /*
     * Inserts an action into the .menuItems[] of a parent menu.
     */
    #define CUI_MENU_ITEM_ACTION(_pItemDesc, _pFnAction) { \
            .pDesc=(_pItemDesc), \
            .itemType=CUI_MENU_ITEM_TYPE_ACTION, \
            .interceptActive=false, \
            .item.pFnAction=(_pFnAction)},
    
    /*
     * Inserts an interceptable action into the .menuItems[] of a parent menu.
     */
    #define CUI_MENU_ITEM_INT_ACTION(_pItemDesc, _pFnIntercept) { \
                .pDesc=(_pItemDesc), \
                .itemType=CUI_MENU_ITEM_TYPE_INTERCEPT, \
                .interceptActive=false, \
                .item.pFnIntercept=(_pFnIntercept)},
    
    /*
     * Inserts a list action into the .menuItems[] of a parent menu.
     */
    #define CUI_MENU_ITEM_LIST_ACTION(_pItemDesc, _maxListItems, _pFnListAction) { \
                .pDesc=(_pItemDesc), \
                .itemType=CUI_MENU_ITEM_TYPE_LIST, \
                .interceptActive=false, \
                .item.pList=&((CUI_list_t){ \
                    .pFnListAction=(_pFnListAction), \
                    .maxListItems=_maxListItems, \
                    .currListIndex=0})},
    
    /*
     * Helper macros to add generic Help and Back screens to all menus
     * The CUI will use these for you. Do not use these in an application.
     */
    #define CUI_MENU_ITEM_HELP CUI_MENU_ITEM_INT_ACTION(CUI_MENU_ACTION_HELP_DESC, (CUI_pFnIntercept_t) CUI_menuActionHelp)
    #define CUI_MENU_ITEM_BACK CUI_MENU_ITEM_ACTION(CUI_MENU_ACTION_BACK_DESC, (CUI_pFnAction_t) CUI_menuActionBack)
    #define CUI_MAIN_MENU_END CUI_MENU_ITEM_HELP }};
    #define CUI_SUB_MENU_END CUI_MENU_ITEM_BACK }};
    #define CUI_MENU_ACTION_BACK_DESC  "<      BACK      >"
    #define CUI_MENU_ACTION_HELP_DESC  "<      HELP      >"
    #else
    
    #define CUI_MAIN_MENU(_menuSymbol, _pMenuTitle, _numItems, _pMenuUpdateFn) \
            CUI_menu_t _menuSymbol;
    
    #define CUI_SUB_MENU(_menuSymbol, _pMenuTitle, _numItems, _pUpperMenu) \
            CUI_menu_t _menuSymbol;
    
    #define CUI_MENU_ITEM_SUBMENU(_pSubMenu)
    
    #define CUI_MENU_ITEM_ACTION(_pItemDesc, _pFnAction)
    
    #define CUI_MENU_ITEM_INT_ACTION(_pItemDesc, _pFnIntercept)
    
    #define CUI_MENU_ITEM_LIST_ACTION(_pItemDesc, _maxListItems, _pFnListAction)
    
    #define CUI_MENU_ITEM_HELP
    #define CUI_MENU_ITEM_BACK
    #define CUI_MAIN_MENU_END
    #define CUI_SUB_MENU_END
    #define CUI_MENU_ACTION_BACK_DESC  "<      BACK      >"
    #define CUI_MENU_ACTION_HELP_DESC  "<      HELP      >"
    #endif
    
    #define CUI_IS_INPUT_NUM(_input)        ((_input >= '0') && (_input <= '9'))
    #define CUI_IS_INPUT_ALPHA(_input)      ((_input >= 'a') && (_input <= 'z'))
    #define CUI_IS_INPUT_ALPHA_NUM(_input)  ((CUI_IS_INPUT_ALPHA(_input)) && (CUI_IS_INPUT_NUM(_input)))
    #define CUI_IS_INPUT_HEX(_input)        ((CUI_IS_INPUT_NUM(_input)) || ((_input >= 'a') && (_input <= 'f')))
    #define CUI_IS_INPUT_BINARY(_input)     ((_input == '0') || (_input == '1'))
    
    
    
    /* Indication of previewing an interceptable item */
    #define CUI_ITEM_PREVIEW            0x00
    
    /* Indication item is now intercepting the uart */
    #define CUI_ITEM_INTERCEPT_START    0xFE
    
    /* Indication item is done intercepting the uart */
    #define CUI_ITEM_INTERCEPT_STOP     0xFF
    
    /* Indication item intercept should be canceled */
    #define CUI_ITEM_INTERCEPT_CANCEL   0xF9
    
    #define CUI_INPUT_UP                0xFA // Up Arrow
    #define CUI_INPUT_DOWN              0xFB // Down Arrow
    #define CUI_INPUT_RIGHT             0xFC // Right Arrow
    #define CUI_INPUT_LEFT              0xFD // Left Arrow
    #define CUI_INPUT_BACK              0x7F // Backspace Key
    #define CUI_INPUT_EXECUTE           0x0D // Enter Key
    #define CUI_INPUT_ESC               0x1B // ESC (escape) Key
    
    #define CUI_COLOR_RESET             "\033[0m"
    #define CUI_COLOR_RED               "\033[31m"
    #define CUI_COLOR_GREEN             "\033[32m"
    #define CUI_COLOR_YELLOW            "\033[33m"
    #define CUI_COLOR_BLUE              "\033[34m"
    #define CUI_COLOR_MAGENTA           "\033[35m"
    #define CUI_COLOR_CYAN              "\033[36m"
    #define CUI_COLOR_WHITE             "\033[37m"
    
    #define CUI_DEBUG_MSG_START         "\0337"
    #define CUI_DEBUG_MSG_END           "\0338"
    
    /******************************************************************************
     * TYPEDEFS
     */
    
    /*
     * [Return Types]
     */
    typedef enum CUI_retVal
    {
      CUI_SUCCESS,
      CUI_FAILURE,
      CUI_INVALID_CB,
      CUI_RESOURCE_ALREADY_ACQUIRED,
      CUI_RESOURCE_NOT_ACQUIRED,
      CUI_MODULE_UNINITIALIZED,
      CUI_INVALID_CLIENT_HANDLE,
      CUI_MAX_CLIENTS_REACHED,
      CUI_NO_ASYNC_LINES_RELEASED,
      CUI_INVALID_LINE_ID,
      CUI_UNKOWN_VALUE_TYPE,
      CUI_UART_FAILURE,
      CUI_INVALID_PARAM,
      CUI_MAX_MENUS_REACHED,
      CUI_PREV_WRITE_UNFINISHED,
      CUI_MISSING_UART_UPDATE_FN,
      CUI_NOT_MANAGING_UART
    } CUI_retVal_t;
    
    /*
     * [General CUI Types]
     */
    typedef uint32_t CUI_clientHandle_t;
    
    typedef struct {
        bool manageUart;
    } CUI_params_t;
    
    typedef struct {
        char clientName[MAX_CLIENT_NAME_LEN];
        uint8_t maxStatusLines;
    } CUI_clientParams_t;
    
    /*
     * [Menu Related Types]
     */
    typedef struct {
        int16_t row;
        int16_t col;
    } CUI_cursorInfo_t;
    
    typedef void (*CUI_pFnClientMenuUpdate_t)(void);
    
    /* Type definitions for action functions types */
    typedef void (*CUI_pFnAction_t)(const int32_t _itemEntry);
    typedef void (*CUI_pFnIntercept_t)(const char _input, char* _lines[3], CUI_cursorInfo_t * _curInfo);
    typedef void (*CUI_pFnListAction_t)(const uint32_t _listIndex, char* _lines[3], bool _selected);
    
    typedef struct CUI_menu_s CUI_menu_t;
    typedef struct CUI_list_s CUI_list_t;
    
    typedef enum CUI_menuItems{
        CUI_MENU_ITEM_TYPE_SUBMENU,
        CUI_MENU_ITEM_TYPE_ACTION,
        CUI_MENU_ITEM_TYPE_INTERCEPT,
        CUI_MENU_ITEM_TYPE_LIST
    }CUI_itemType_t;
    
    /* Type definition for a sub menu/action item entry */
    typedef struct {
        char* pDesc;                      /* action description. NULL for sub menu */
        CUI_itemType_t itemType;          /* What type of menu item is this */
        bool interceptActive;             /* Is item currently being intercepted */
        union {
            CUI_menu_t* pSubMenu;               /* Sub menu */
            CUI_pFnAction_t   pFnAction;        /* Function for action */
            CUI_pFnIntercept_t pFnIntercept;    /* Function for interceptable action */
            CUI_list_t* pList;                  /* List */
        } item;
    } CUI_menuItem_t;
    
    /* Type definition for a menu object */
    struct CUI_menu_s {
        CUI_pFnClientMenuUpdate_t uartUpdateFn;     /* Uart Update function */
        const char* pTitle;                         /* Title of this menu */
        uint8_t numItems;                           /* # of item entries */
        uint8_t gPrevMenuItemEntry;
        CUI_menu_t*  pUpper;                        /* upper menu */
        CUI_menuItem_t menuItems[];                 /* item entries */
    };
    
    /* Type definition for a list object */
    struct CUI_list_s {
        CUI_pFnListAction_t pFnListAction;
        uint16_t maxListItems;
        uint16_t currListIndex;
    };
    
    /******************************************************************************
     Function Prototypes
     *****************************************************************************/
    
    /******************************************************************************
     * General CUI APIs
     *****************************************************************************/
    /*********************************************************************
     * @fn          CUI_init
     *
     * @brief       Initialize the CUI module. This function must be called
     *                  before any other CUI functions.
     */
    CUI_retVal_t CUI_init(CUI_params_t* _pParams);
    
    /*********************************************************************
     * @fn          CUI_paramsInit
     *
     * @brief       Initialize a CUI_params_t struct to a known state.
     *                  The known state in this case setting each resource
     *                  management flag to true
     */
    void CUI_paramsInit(CUI_params_t* _pParams);
    
    /*********************************************************************
     * @fn          CUI_clientOpen
     *
     * @brief       Open a client with the CUI module. A client is required
     *                  to request/acquire resources
     */
    CUI_clientHandle_t CUI_clientOpen(CUI_clientParams_t* _pParams);
    
    /*********************************************************************
     * @fn          CUI_clientParamsInit
     *
     * @brief       Initialize a CUI_clientParams_t struct to a known state.
     */
    void CUI_clientParamsInit(CUI_clientParams_t* _pClientParams);
    
    /*********************************************************************
     * @fn          CUI_close
     *
     * @brief       Close the CUI module. Release all resources and memory.
     */
    CUI_retVal_t CUI_close();
    
    /******************************************************************************
     * Menu CUI APIs
     *****************************************************************************/
    /*********************************************************************
     * @fn          CUI_registerMenu
     *
     * @brief       Register a menu with the CUI module
     */
    CUI_retVal_t CUI_registerMenu(const CUI_clientHandle_t _clientHandle, CUI_menu_t* _pMenu);
    
    /*********************************************************************
     * @fn          CUI_deRegisterMenu
     *
     * @brief       De-registers a menu with the CUI module
     */
    CUI_retVal_t CUI_deRegisterMenu(const CUI_clientHandle_t _clientHandle, CUI_menu_t* _pMenu);
    
    /*********************************************************************
     * @fn          CUI_updateMultiMenuTitle
     *
     * @brief       Changes the default multi menu title
     */
    CUI_retVal_t CUI_updateMultiMenuTitle(const char* _pTitle);
    
    /*********************************************************************
     * @fn          CUI_menuNav
     *
     * @brief       Navigate to a specific entry of a menu that has already been
     *              registered
     */
    CUI_retVal_t CUI_menuNav(const CUI_clientHandle_t _clientHandle, CUI_menu_t* _pMenu, const uint32_t _itemIndex);
    
    /*********************************************************************
     * @fn          CUI_processMenuUpdate
     *
     * @brief       This function should be called whenever there is UART input
     *                  to be processed.
     */
    CUI_retVal_t CUI_processMenuUpdate(void);
    
    /******************************************************************************
     * Status Line CUI APIs
     *****************************************************************************/
    /*********************************************************************
     * @fn          CUI_statusLineResourceRequest
     *
     * @brief       Request access to a new status line
     */
    CUI_retVal_t CUI_statusLineResourceRequest(const CUI_clientHandle_t _clientHandle, const char _pLabel[MAX_STATUS_LINE_LABEL_LEN], const bool _refreshInd, uint32_t* _pLineId);
    
    /*********************************************************************
     * @fn          CUI_statusLinePrintf
     *
     * @brief        Update an acquired status line
     */
    CUI_retVal_t CUI_statusLinePrintf(const CUI_clientHandle_t _clientHandle, const uint32_t _lineId, const char *format, ...);
    
    void CUI_wrappedIncrement(size_t* _pValue, int32_t _incAmt, size_t _maxValue);
    /*********************************************************************
     * Assert Debug API
     ********************************************************************/
    /*********************************************************************
     *  @fn         CUI_assert
     *
     *  @brief      Without requiring a cuiHandle_t you may print an assert
     *                string and optionally spinLock while flashing the leds.
     *
     *              Note: If you choose to spinLock, this function will close
     *                all existing clients that have been opened and then
     *                enter an infinite loop that flashes the leds.
     */
    void CUI_assert(const char* _assertMsg, const bool _spinLock);
    #ifndef CUI_MIN_FOOTPRINT
    void CUI_menuActionBack(const int32_t _itemEntry);
    void CUI_menuActionHelp(const char _input, char* _pLines[3], CUI_cursorInfo_t* _pCurInfo);
    #endif
    #ifdef __cplusplus
    }
    #endif
    
    #endif /* CUI_H */