MiniCLI

tags: MTK

Introduction

主要參考 amazon-freertos 裡頭 MiniCLI 裡的實作,分析其實作細節

MiniCLI 主要是 MTK 的一個小專案,用來提供使用者輸入 command 的介面

MiniCLI is a Command Line Interface engine implementation

Data Structure

參考 minicli/inc/cli.h 裡的結構定義,一共分成三個部份 cmd_t, cli_history_tcli_s

首先分析結構 cmd_s ,以下為其定義

/**
 * @brief Provide a forward declaration for the cmd_t type
 *        to avoid issues with older compiler versions,
 *        see \link #cmd_s \endlink for more information.
 */
typedef struct cmd_s cmd_t;

/**
 * @brief   The MiniCLI command declaration structure.
 *          The strcut cmd_s is defined to aggregate function pointer, help
 *          messsage and sub-commands for MiniCLI engine.
 *
 * @note    Prefixing an ASCII value 01 to help message will effectively hide the 
 *          command from user.
 * @warning To reduce code size, duplicate commands are not detected by
 *          MiniCLI engine. It is developer's task to ensure there is no
 *          duplication in the command declaration.
 */
struct cmd_s {
    char                *cmd;       /**< Command string. */
    char                *help;      /**< Type '?' to retrieve help message. */
    cli_cmd_handler_t   fptr;       /**< The function pointer to call when an input
                                         string matches with the command in MiniCLI engine. */
    cmd_t               *sub;       /**< Sub-commands. */
};

結構 cmd_s 相對單純很多,其實就只是表示每個命令的結構而已,其中

  • cmd: 命令名稱
  • help: 該命令的說明,輸入 ? 可以列出所有可用的命令及命令說明
  • fptr: 為函式指標,用來執行輸入命令
  • sub: 表示 Sub-commands ,用來實作命令的其他選項,使用 linked list 表示

這裡給個實際的例子,參考 cli.c 裡的定義 (擷取部份)

static cmd_t root[] = {
	{ "a", "turn Wi-Fi on",                     _do_WIFI_On,               NULL },
	{ "b", "turn Wi-Fi off",                    _do_WIFI_Off,              NULL },
	{ "c", "connect to AP",                     _do_WIFI_ConnectAP,        NULL },
	{ "d", "disconnect from AP",                _do_WIFI_Disconnect,       NULL },
	{ "e", "reset Wi-Fi",                       _do_WIFI_Reset,            NULL },
	{ "f", "set mode (sta/ap/p2p (not N/A)",    _do_WIFI_SetMode,          NULL },
	...
}

root[0] 為例,這裡的命令名稱為 a ,命令說明為 turn Wi-Fi on ,該命令會執行函式 _do_WIFI_On 且沒有其他的選項

接著分析結構 cli_history_s 的設計,以下為其定義

/**
 * @brief Provide a forward declaration for the cli_history_t type
 * to avoid issues with older compiler versions,
 * see \link #cli_history_s \endlink for more information.
 */
typedef struct cli_history_s cli_history_t;

/**
 * @brief The MiniCLI control block definition.
 *
 * The strcut cli_history_s and its aliased type cli_history_t is defined to aggregate the
 * parameters for MiniCLI to work correctly.
 */
struct cli_history_s {
    char            **history;     /**< The pointers to <i>history_max</i> lines of buffer for input. */
    char            *input;        /**< used to save the current input before pressing "up" the first time */
    char            *parse_token;  /**< In cli.c, _cli_do_cmd() will result in the history command to be parsed
                                     *  into tokens, an additional array is required as an input to the #_cli_do_cmd() function 
									 *  to preserve the command history. */
    uint16_t        history_max;   /**< The maximum number of the lines in <i>history</i>. */
    uint16_t        line_max;      /**< The size of one history <i>line</i>. */
    uint16_t        index;         /**< When the user types a command and presses enter, index
                                     *  is the index that will be saved in. */
    uint16_t        position;      /**< When the user presses up/down, position is the history index
                                     *  displayed on the terminal. */
    uint8_t         full;          /**< Represents whether the number of history commands exceeded history_max
                                     *  if it is 0, history commands are not full, otherwise the commands are full. */
};

由以上定義,可以發現結構 cli_history_s 其實是用來保存使用者輸入命令的歷史紀錄,同時可以搭配 up/down 鍵在 CLI 上顯示以前輸入的命令

  • history: 儲存所有輸入過的命令
  • input: 用來儲存輸入 up 鍵之前的當前輸入命令
  • parse_token: 將命令切割的結果
  • history_max: 最大可儲存的輸入命令數
  • line_max: 每個輸入命令最長的長度
  • index: 每當使用者輸入命令後,會儲存當前的索引
  • position: 為 history 的索引,當使用者按 up/down 鍵時,會顯示 position 索引的命令
  • full: 判斷 history 是否已經滿了,為 1 時表示滿了,為 0 時表示還沒滿

接著給個初始化的例子,參考 cli.c 裡的定義

static void vCliHistoryInitialize( cli_history_t *hist )
{
    static char s_history_lines[ HISTORY_LINES ][ HISTORY_LINE_MAX ];
    static char *s_history_ptrs[ HISTORY_LINES ];

    static char s_history_input[ HISTORY_LINE_MAX ];
    static char s_history_parse_token[ HISTORY_LINE_MAX ];

    hist->history = &s_history_ptrs[0];

    for ( int i = 0; i < HISTORY_LINES; i++ )
    {
        s_history_ptrs[i] = s_history_lines[i];
    }

    hist->input             = s_history_input;
    hist->parse_token       = s_history_parse_token;
    hist->history_max       = HISTORY_LINES;
    hist->line_max          = HISTORY_LINE_MAX;
    hist->index             = 0;
    hist->position          = 0;
    hist->full              = 0;
}

可以明顯看到最大儲存的輸入命令數 history_maxHISTORY_LINES 且輸入命令最長的長度 line_maxHISTORY_LINE_MAX ,同時 historyinputparse_token 都分別指到靜態變數 s_history_liness_history_inputs_history_parse_token

最後分析結構 cli_s ,定義如下所示

/**
 * @brief Provide a forward declaration for the cli_t type
 *        to avoid issues with older compiler versions,
 *        see \link #cli_t \endlink for more information.
 */
typedef struct cli_s cli_t;

/**
 * @brief The MiniCLI control block definition.
 *
 * The strcut cli_s and its aliased type cli_t is defined to aggregate the
 * parameters for MiniCLI to work correctly.
 */
struct cli_s {
    uint8_t             state;                 /**< Defines the login status. 0, if not logged in and 1, otherwise. */
    cmd_t               *cmd;                  /**< A pointer to the CLI commands. */
    char                echo;                  /**< Determines if user input should be printed on the screen or not. */
    getchar_fptr_t      get;                   /**< A function pointer to get a characeter from input source. */
    putchar_fptr_t      put;                   /**< A function pointer to write a characeter to output sink. */
    knock_fptr_t        knock;                 /**< An authentication function pointer. */
    char                *tok[CLI_MAX_TOKENS];  /**< The tokenized input command. Note that there is a limit of 20 tokens. */

#if !defined(CLI_DISABLE_LINE_EDIT) && !defined(CLI_DISABLE_HISTORY)
    cli_history_t       history;               /**< The command history record. */
#endif /* !CLI_DISABLE_LINE_EDIT && !CLI_DISABLE_HISTORY */
};

很明顯的,結構 cli_s 就是整個 MiniCLI 管理使用者輸入的結構,其中也包含了上述提到的兩個結構

  • state: 表示有無登入, 0 表示無而 1 表示有
  • cmd: 為指標且指到 CLI 命令
  • echo: 決定使用者的輸入是否要輸出到螢幕上
  • get: 為函式指標,用來從輸入(可能是透過 UART) 取得使用者的輸入命令
  • put: 為函式指標,用來將輸出送出 (可能是電腦的終端機)
  • knock: 用來驗證的函式指標
  • tok[CLI_MAX_TOKENS]: 將使用者輸入命令切割出來的小命令
  • history: 命令歷史紀錄

由這樣的結構我們可以簡單的畫出整個 MiniCLI 架構,如下圖所示







G



cli_s

**cli_s**

cmd

history (cli_history_s)



cmd_s1

**cmd_s**

sub



cli_s:cmd->cmd_s1:name





cmd_s2

**cmd_s**

sub



cmd_s1:sub->cmd_s2:name





inf
   ...  



cmd_s2:sub->inf





cmd_sn

**cmd_s**

sub



inf->cmd_sn:name





Initialize MiniCLI

接著開始分析怎麼去初始化 MiniCLI 系統,參考 cli.c 裡的函式 vCliTaskStartup

void vCliTaskStartup(void) { static cmd_t root[] = { { "a", "turn Wi-Fi on", _do_WIFI_On, NULL }, { "b", "turn Wi-Fi off", _do_WIFI_Off, NULL }, { "c", "connect to AP", _do_WIFI_ConnectAP, NULL }, { "d", "disconnect from AP", _do_WIFI_Disconnect, NULL }, { "e", "reset Wi-Fi", _do_WIFI_Reset, NULL }, { "f", "set mode (sta/ap/p2p (not N/A)", _do_WIFI_SetMode, NULL }, { "g", "get mode", _do_WIFI_GetMode, NULL }, { "h", "add network", _do_WIFI_NetworkAdd, NULL }, { "i", "get network", _do_WIFI_NetworkGet, NULL }, { "j", "del network", _do_WIFI_NetworkDelete, NULL }, { "k", "ping <ip addr>", _do_WIFI_Ping, NULL }, { "l", "get ip", _do_WIFI_GetIP, NULL }, { "m", "get mac", _do_WIFI_GetMAC, NULL }, { "n", "get host ip addr", _do_WIFI_GetHostIP, NULL }, { "o", "scan and show result", _do_WIFI_Scan, NULL }, { "p", "start AP running", _do_WIFI_StartAP, NULL }, { "q", "stop AP running", _do_WIFI_StopAP, NULL }, { "r", "config AP", _do_WIFI_ConfigureAP, NULL }, { "s", "set power save mode <norm/lp/aon>", _do_WIFI_SetPMMode, NULL }, { "t", "get power save mode", _do_WIFI_GetPMMode, NULL }, { "z", "MQTT demo task", _do_MQTT_demo_task, NULL }, { NULL, NULL, NULL, NULL } }; static cli_t cb = { .state = 1, .echo = 0, .get = __io_getchar, .put = __io_putchar, .cmd = &root[0] }; vCliHistoryInitialize(&cb.history); cli_init(&cb); if (xTaskCreate(_cli_def_task, MINICLI_TASK_NAME, MINICLI_TASK_STACKSIZE / sizeof( portSTACK_TYPE ), NULL, MINICLI_TASK_PRIO, NULL ) != pdPASS) { configPRINTF(("CLI task create failed\n")); } }

函式 vCliTaskStartup 在第 3 行定義了所有可使用的命令,接著在第 28 行定義管理整個 MiniCLI 架構的變數 cb ,第 36 行初始化前面提到的 cli_history_s 結構,第 37 行則使用函式 cli_init 初始化 MiniCLI ,最後在第 39 行時使用 FreeRTOS 的函式 xTaskCreate 建立 CLI 的任務

第 36 行函式 vCliHistoryInitialize 如下所示,基本上就是給把結構 cli_history_s 賦予初始值

static void vCliHistoryInitialize( cli_history_t *hist )
{
    static char s_history_lines[ HISTORY_LINES ][ HISTORY_LINE_MAX ];
    static char *s_history_ptrs[ HISTORY_LINES ];

    static char s_history_input[ HISTORY_LINE_MAX ];
    static char s_history_parse_token[ HISTORY_LINE_MAX ];

    hist->history = &s_history_ptrs[0];

    for ( int i = 0; i < HISTORY_LINES; i++ )
    {
        s_history_ptrs[i] = s_history_lines[i];
    }

    hist->input             = s_history_input;
    hist->parse_token       = s_history_parse_token;
    hist->history_max       = HISTORY_LINES;
    hist->line_max          = HISTORY_LINE_MAX;
    hist->index             = 0;
    hist->position          = 0;
    hist->full              = 0;
}

而第 37 行函式 cli_init 則被做成函式庫看不到