---
# System prepended metadata

title: MiniCLI
tags: [MTK]

---

# MiniCLI
###### tags: `MTK`

## Introduction
主要參考 [amazon-freertos](https://github.com/aws/amazon-freertos/) 裡頭 [MiniCLI](https://github.com/aws/amazon-freertos/tree/main/vendors/mediatek/sdk/middleware/MTK/minicli) 裡的實作，分析其實作細節

MiniCLI 主要是 MTK 的一個小專案，用來提供使用者輸入 command 的介面
> MiniCLI is a Command Line Interface engine implementation

## Data Structure
參考 [minicli/inc/cli.h](https://github.com/aws/amazon-freertos/blob/main/vendors/mediatek/sdk/middleware/MTK/minicli/inc/cli.h) 裡的結構定義，一共分成三個部份 `cmd_t`, `cli_history_t` 及 `cli_s`

首先分析結構 `cmd_s` ，以下為其定義
```c
/**
 * @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](https://github.com/aws/amazon-freertos/blob/main/vendors/mediatek/boards/mt7697hx-dev-kit/aws_demos/application_code/mediatek_code/source/cli.c) 裡的定義 (擷取部份)
```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` 的設計，以下為其定義
```c
/**
 * @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](https://github.com/aws/amazon-freertos/blob/main/vendors/mediatek/boards/mt7697hx-dev-kit/aws_demos/application_code/mediatek_code/source/cli.c) 裡的定義
```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_max` 為 `HISTORY_LINES` 且輸入命令最長的長度 `line_max` 為 `HISTORY_LINE_MAX` ，同時 `history` 、 `input` 及 `parse_token` 都分別指到靜態變數 `s_history_lines` 、 `s_history_input` 及 `s_history_parse_token`

最後分析結構 `cli_s` ，定義如下所示
```c
/**
 * @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 架構，如下圖所示
```graphviz
digraph G {
    rankdir = LR;
    splines = false;
    node[shape = "record"]
    cli_s[label = "**cli_s**|<cmd>cmd|history (cli_history_s)"]
	cmd_s1[label = "<name>**cmd_s**|<sub>sub"]
	cmd_s2[label = "<name>**cmd_s**|<sub>sub"]
	cmd_sn[label = "<name>**cmd_s**|sub"]
	inf[shape = "plain" label = "   ...  "]
	
	cli_s:cmd -> cmd_s1: name
	cmd_s1: sub -> cmd_s2: name
	cmd_s2: sub -> inf
	inf -> cmd_sn:name
}
```

## Initialize MiniCLI
接著開始分析怎麼去初始化 MiniCLI 系統，參考 [cli.c](https://github.com/aws/amazon-freertos/blob/main/vendors/mediatek/boards/mt7697hx-dev-kit/aws_demos/application_code/mediatek_code/source/cli.c) 裡的函式 `vCliTaskStartup`
```c=
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` 賦予初始值
```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;
}
```

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