MTK
主要參考 amazon-freertos 裡頭 MiniCLI 裡的實作,分析其實作細節
MiniCLI 主要是 MTK 的一個小專案,用來提供使用者輸入 command 的介面
MiniCLI is a Command Line Interface engine implementation
參考 minicli/inc/cli.h 裡的結構定義,一共分成三個部份 cmd_t
, cli_history_t
及 cli_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_max
為 HISTORY_LINES
且輸入命令最長的長度 line_max
為 HISTORY_LINE_MAX
,同時 history
、 input
及 parse_token
都分別指到靜態變數 s_history_lines
、 s_history_input
及 s_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 架構,如下圖所示
接著開始分析怎麼去初始化 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
則被做成函式庫看不到