# 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` 則被做成函式庫看不到