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