diff options
Diffstat (limited to 'thirdparty/nRF5_SDK_15.0.0_a53641a/components/libraries/cli/nrf_cli.c')
-rw-r--r-- | thirdparty/nRF5_SDK_15.0.0_a53641a/components/libraries/cli/nrf_cli.c | 3500 |
1 files changed, 3500 insertions, 0 deletions
diff --git a/thirdparty/nRF5_SDK_15.0.0_a53641a/components/libraries/cli/nrf_cli.c b/thirdparty/nRF5_SDK_15.0.0_a53641a/components/libraries/cli/nrf_cli.c new file mode 100644 index 0000000..1e4eba5 --- /dev/null +++ b/thirdparty/nRF5_SDK_15.0.0_a53641a/components/libraries/cli/nrf_cli.c @@ -0,0 +1,3500 @@ +/** + * Copyright (c) 2016 - 2018, Nordic Semiconductor ASA + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form, except as embedded into a Nordic + * Semiconductor ASA integrated circuit in a product or a software update for + * such product, must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of Nordic Semiconductor ASA nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * 4. This software, with or without modification, must only be used with a + * Nordic Semiconductor ASA integrated circuit. + * + * 5. Any software provided in binary form under this license must not be reverse + * engineered, decompiled, modified and/or disassembled. + * + * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ +#include "sdk_common.h" +#if NRF_MODULE_ENABLED(NRF_CLI) +#include <ctype.h> +#include <stdarg.h> +#include <stdlib.h> +#include "nrf_cli.h" +#include "nrf_cli_vt100.h" +#include "app_error.h" +#include "nrf_assert.h" +#include "nrf_delay.h" +#include "nrf_pwr_mgmt.h" +#include "nrf_atomic.h" + +#if NRF_MODULE_ENABLED(NRF_CLI_WILDCARD) +#include "fnmatch.h" +#endif + +#if NRF_MODULE_ENABLED(NRF_CLI_HISTORY) + #if NRF_CLI_HISTORY_ELEMENT_SIZE * NRF_CLI_HISTORY_ELEMENT_COUNT == 0 + #error Not proper memory size allocated for NRF_CLI_HISTORY + #endif +#endif + +/* 2 == 1 char for cmd + 1 char for '\0' */ +#if NRF_CLI_CMD_BUFF_SIZE < 2 + #error too small NRF_CLI_CMD_BUFF_SIZE +#endif + +#if NRF_CLI_PRINTF_BUFF_SIZE < 1 + #error too small NRF_CLI_PRINTF_BUFF_SIZE +#endif + +#define NRF_CLI_HELP_CLEAR "Clear screen." +#define NRF_CLI_HELP_COLORS "Toggle colored syntax." +#define NRF_CLI_HELP_COLORS_OFF "Disable colored syntax." +#define NRF_CLI_HELP_COLORS_ON "Enable colored syntax." +#define NRF_CLI_HELP_STATISTICS "CLI statistics." +#define NRF_CLI_HELP_STATISTICS_SHOW "Get CLI statistics for the Logger module." +#define NRF_CLI_HELP_STATISTICS_RESET "Reset CLI statistics for the Logger module." +#define NRF_CLI_HELP_RESIZE "Console gets terminal screen size or assumes 80 in case " \ + "the readout fails. It must be executed after each terminal " \ + "width change to ensure correct text display." +#define NRF_CLI_HELP_RESIZE_DEFAULT "Assume 80 chars screen width and send this setting " \ + "to the terminal." +#define NRF_CLI_HELP_HISTORY "Command history." +#define NRF_CLI_HELP_ECHO "Toggle CLI echo." +#define NRF_CLI_HELP_ECHO_ON "Enable CLI echo." +#define NRF_CLI_HELP_ECHO_OFF "Disable CLI echo. Arrows and buttons: Backspace, " \ + "Delete, End, Home, Insert are not handled." +#define NRF_CLI_HELP_CLI "Useful, not Unix-like CLI commands." + +#define NRF_CLI_MSG_SPECIFY_SUBCOMMAND "Please specify a subcommand.\r\n" +#define NRF_CLI_MSG_UNKNOWN_PARAMETER " unknown parameter: " +#define NRF_CLI_MSG_COMMAND_NOT_FOUND ": command not found" +#define NRF_CLI_MSG_TAB_OVERFLOWED "Tab function: commands counter overflowed.\r\n" + +/*lint -save -esym(526,cli_command*) -esym(526,cli_sorted_cmd_ptrs*)*/ +NRF_SECTION_DEF(cli_command, nrf_cli_cmd_entry_t); +#define CLI_DATA_SECTION_ITEM_GET(i) NRF_SECTION_ITEM_GET(cli_command, nrf_cli_cmd_entry_t, (i)) +#define CLI_DATA_SECTION_ITEM_COUNT NRF_SECTION_ITEM_COUNT(cli_command, nrf_cli_cmd_entry_t) + +NRF_SECTION_DEF(cli_sorted_cmd_ptrs, const char *); +/*lint -restore*/ +#define CLI_SORTED_CMD_PTRS_ITEM_GET(i) NRF_SECTION_ITEM_GET(cli_sorted_cmd_ptrs, const char *, (i)) +#define CLI_SORTED_CMD_PTRS_START_ADDR_GET NRF_SECTION_START_ADDR(cli_sorted_cmd_ptrs) + +#if defined(NRF_CLI_LOG_BACKEND) && NRF_CLI_LOG_BACKEND +#include "nrf_log_str_formatter.h" +#include "nrf_log_internal.h" +#endif + +#define NRF_CLI_INIT_OPTION_PRINTER (NULL) + +#define NRF_CLI_MAX_TERMINAL_SIZE (250u) +#define NRF_CLI_CURSOR_POSITION_BUFFER (10u) /* 10 == {esc, [, 2, 5, 0, ;, 2, 5, 0, '\0'} */ +#define NRF_CLI_DEFAULT_TERMINAL_WIDTH (80u) /* Default PuTTY width. */ +#define NRF_CLI_DEFAULT_TERMINAL_HEIGHT (24u) /* Default PuTTY height. */ +#define NRF_CLI_INITIAL_CURS_POS (1u) /* Initial cursor position is: (1, 1). */ + +#define NRF_CLI_CMD_ROOT_LVL (0u) + +/* Macro to send VT100 commands. */ +#define NRF_CLI_VT100_CMD(_p_cli_, _cmd_) { \ + ASSERT(_p_cli_); \ + ASSERT(_p_cli_->p_fprintf_ctx); \ + static char const cmd[] = _cmd_; \ + nrf_fprintf(_p_cli_->p_fprintf_ctx, "%s", cmd); \ +} + +#if NRF_MODULE_ENABLED(NRF_CLI_WILDCARD) +typedef enum +{ + WILDCARD_CMD_ADDED, + WILDCARD_CMD_ADDED_MISSING_SPACE, + WILDCARD_CMD_NO_MATCH_FOUND +} wildcard_cmd_status_t; +#endif + + +static bool cli_log_entry_process(nrf_cli_t const * p_cli, bool skip); +static void cli_execute(nrf_cli_t const * p_cli); + + +static inline void transport_buffer_flush(nrf_cli_t const * p_cli) +{ + nrf_fprintf_buffer_flush(p_cli->p_fprintf_ctx); +} + +static inline void cli_flag_help_set(nrf_cli_t const * p_cli) +{ + p_cli->p_ctx->internal.flag.show_help = 1; +} +static inline void cli_flag_help_clear(nrf_cli_t const * p_cli) +{ + p_cli->p_ctx->internal.flag.show_help = 0; +} + +static inline void cli_flag_echo_set(nrf_cli_t const * p_cli) +{ + p_cli->p_ctx->internal.flag.echo = 1; +} + +static inline void cli_flag_echo_clear(nrf_cli_t const * p_cli) +{ + p_cli->p_ctx->internal.flag.echo = 0; +} + +static inline bool cli_flag_echo_is_set(nrf_cli_t const * p_cli) +{ + return p_cli->p_ctx->internal.flag.echo == 1 ? true : false; +} + +static inline bool cli_flag_processing_is_set(nrf_cli_t const * p_cli) +{ + return p_cli->p_ctx->internal.flag.processing == 1 ? true : false; +} + +static inline void recieve_state_change(nrf_cli_t const * p_cli, nrf_cli_receive_t state) +{ + p_cli->p_ctx->receive_state = state; +} + +static inline size_t cli_strlen(char const * str) +{ + return str == NULL ? 0 : strlen(str); +} + +/* Function returns true if cursor is at beginning of an empty line. */ +static inline bool cursor_in_empty_line(nrf_cli_t const * p_cli) +{ + return ( (p_cli->p_ctx->cmd_buff_pos + cli_strlen(p_cli->p_name)) % + p_cli->p_ctx->vt100_ctx.cons.terminal_wid == 0); +} + +/* Function returns true if command length is equal to multiplicity of terminal width. */ +static inline bool full_line_cmd(nrf_cli_t const * p_cli) +{ + return ((p_cli->p_ctx->cmd_buff_len + cli_strlen(p_cli->p_name)) % + p_cli->p_ctx->vt100_ctx.cons.terminal_wid == 0); +} + +#if NRF_MODULE_ENABLED(NRF_CLI_WILDCARD) +/* Function returns true if string contains wildcard character: '?' or '*'. */ +static bool wildcard_character_exist(char * p_str) +{ + size_t str_len = cli_strlen(p_str); + for (size_t i = 0; i < str_len; i++) + { + if ((p_str[i] == '?') || (p_str[i] == '*')) + { + return true; + } + } + return false; +} +#endif + +/* Function sends data stream to the CLI instance. Each time before the cli_write function is called, + * it must be ensured that IO buffer of fprintf is flushed to avoid synchronization issues. + * For that purpose, use function transport_buffer_flush(p_cli) */ +static void cli_write(nrf_cli_t const * p_cli, + void const * p_data, + size_t length, + size_t * p_cnt) +{ + ASSERT(p_cli && p_data); + ASSERT(p_cli->p_iface->p_api); + size_t offset = 0; + size_t cnt; + while (length) + { + ret_code_t ret = p_cli->p_iface->p_api->write(p_cli->p_iface, + &((uint8_t const *)p_data)[offset], + length, + &cnt); + UNUSED_VARIABLE(ret); + ASSERT(ret == NRF_SUCCESS); + ASSERT(length >= cnt); + offset += cnt; + length -= cnt; + if (cnt == 0 && (p_cli->p_ctx->state != NRF_CLI_STATE_PANIC_MODE_ACTIVE)) + { +#if NRF_MODULE_ENABLED(NRF_CLI_USES_TASK_MANAGER) + (void)task_events_wait(NRF_CLI_TRANSPORT_TX_RDY_TASK_EVT); +#else + while (p_cli->p_ctx->internal.flag.tx_rdy == 0) + { + ; + } + p_cli->p_ctx->internal.flag.tx_rdy = 0; +#endif + } + } + + if (p_cnt) + { + *p_cnt = cnt; + } +} + +/* Function sends 1 character to the CLI instance. */ +static inline void cli_putc(nrf_cli_t const * p_cli, char ch) +{ + nrf_fprintf(p_cli->p_fprintf_ctx, "%c", ch); +} + +/* Function reads data from the CLI instance. */ +static void cli_read(nrf_cli_t const * p_cli, + void * p_data, + size_t length, + size_t * p_cnt) +{ + ASSERT(p_cli && p_data); + ASSERT(p_cli->p_iface); + + ret_code_t ret = p_cli->p_iface->p_api->read(p_cli->p_iface, p_data, length, p_cnt); + UNUSED_VARIABLE(ret); +} + +/* Function cmd_get shall be used to search commands. It moves the pointer pp_entry to command + * of static command structure. If the command cannot be found, the function will set pp_entry to NULL. + * p_command Pointer to command which will be processed (no matter the root command). + * lvl Level of the requested command. + * idx Index of the requested command. + * pp_entry Pointer which points to subcommand[idx] after function execution. + * p_st_entry Pointer to the structure where dynamic entry data can be stored. + */ +static void cmd_get(nrf_cli_cmd_entry_t const * p_command, + size_t lvl, + size_t idx, + nrf_cli_static_entry_t const ** pp_entry, + nrf_cli_static_entry_t * p_st_entry) +{ + ASSERT (pp_entry != NULL); + ASSERT (p_st_entry != NULL); + + if (lvl == NRF_CLI_CMD_ROOT_LVL) + { + if (idx < CLI_DATA_SECTION_ITEM_COUNT) + { + nrf_cli_cmd_entry_t const * p_cmd = NULL; + char const * * pp_sorted_cmds = (char const * *)CLI_SORTED_CMD_PTRS_START_ADDR_GET; + for (size_t i = 0; i < CLI_DATA_SECTION_ITEM_COUNT; i++) + { + p_cmd = CLI_DATA_SECTION_ITEM_GET(i); + if (!strcmp(pp_sorted_cmds[idx], p_cmd->u.p_static->p_syntax)) + { + *pp_entry = p_cmd->u.p_static; + return; + } + } + } + *pp_entry = NULL; + return; + } + + if (p_command == NULL) + { + *pp_entry = NULL; + return; + } + + if (p_command->is_dynamic) + { + p_command->u.p_dynamic_get(idx, p_st_entry); + if (p_st_entry->p_syntax == NULL) + { + *pp_entry = NULL; + } + else + { + *pp_entry = p_st_entry; + } + } + else + { + if (p_command->u.p_static[idx].p_syntax != NULL) + { + *pp_entry = &p_command->u.p_static[idx]; + } + else + { + *pp_entry = NULL; + } + } +} + +/* Function multiline_console_data_check checks the current cursor position (x, y) on terminal screen + * based on: command length, console name length, and terminal width. + * Example 1: + * || - cursor + * ---------------------------- + * |console_name $: || | + * ---------------------------- + * => coordinates are: cur_x = 17, cur_x_end = 17, + * cur_y = 1, cur_y_end = 1 + * Example 2: + * ---------------------------- + * |console_name $: test command| + * |showing |e|xample | + * ---------------------------- + * => coordinates are: cur_x = 9, cur_x_end = 18 (cursor can be one column after 'e') + * => cur_y = 2, cur_y_end = 2 + * Example 3: + * ---------------------------- + * |console_name $: test command| + * |showing e|x|ample with more | + * |parameters | + * ---------------------------- + * => coordinates are: cur_x = 10, cur_x_end = 11 (cursor can be one column after 's') + * => cur_y = 2, cur_y_end = 3 + */ +static nrf_cli_multiline_cons_t const * multiline_console_data_check(nrf_cli_t const * p_cli) +{ + nrf_cli_ctx_t * p_ctx = p_cli->p_ctx; + nrf_cli_multiline_cons_t * p_cons = &p_cli->p_ctx->vt100_ctx.cons; + + p_cons->name_len = cli_strlen(p_cli->p_name); + + /* Current cursor position in command. + * +1 -> because home position is (1, 1) */ + p_cons->cur_x = (p_ctx->cmd_buff_pos + p_cons->name_len) % p_cons->terminal_wid + 1; + p_cons->cur_y = (p_ctx->cmd_buff_pos + p_cons->name_len) / p_cons->terminal_wid + 1; + + /* Extreme position when cursor is at the end of command. */ + p_cons->cur_y_end = (p_ctx->cmd_buff_len + p_cons->name_len) / p_cons->terminal_wid + 1; + p_cons->cur_x_end = (p_ctx->cmd_buff_len + p_cons->name_len) % p_cons->terminal_wid + 1; + + return p_cons; +} + +/* Function sends VT100 command to clear the screen from cursor position to end of the screen. */ +static inline void cli_clear_eos(nrf_cli_t const * p_cli) +{ + NRF_CLI_VT100_CMD(p_cli, NRF_CLI_VT100_CLEAREOS); +} + +/* Function sends VT100 command to save cursor position. */ +static inline void cli_cursor_save(nrf_cli_t const * p_cli) +{ + NRF_CLI_VT100_CMD(p_cli, NRF_CLI_VT100_SAVECURSOR); +} + +/* Function sends VT100 command to restore saved cursor position. */ +static inline void cli_cursor_restore(nrf_cli_t const * p_cli) +{ + NRF_CLI_VT100_CMD(p_cli, NRF_CLI_VT100_RESTORECURSOR); +} + +/* Function forcing new line - cannot be replaced with function cursor_down_move. */ +static inline void cursor_next_line_move(nrf_cli_t const * p_cli) +{ + NRF_CLI_VT100_CMD(p_cli, NRF_CLI_VT100_NEXTLINE); +} + +/* Function moves cursor left by n positions. */ +static inline void cursor_left_move(nrf_cli_t const * p_cli, nrf_cli_cmd_len_t n) +{ + if (n > 0) + { + nrf_fprintf(p_cli->p_fprintf_ctx, "\033[%dD", n); + } +} + +/* Function moves cursor right by n positions. */ +static inline void cursor_right_move(nrf_cli_t const * p_cli, nrf_cli_cmd_len_t n) +{ + if (n > 0) + { + nrf_fprintf(p_cli->p_fprintf_ctx, "\033[%dC", n); + } +} + +/* Function moves cursor up by n positions. */ +static inline void cursor_up_move(nrf_cli_t const * p_cli, nrf_cli_cmd_len_t n) +{ + if (n > 0) + { + nrf_fprintf(p_cli->p_fprintf_ctx, "\033[%dA", n); + } +} + +/* Function moves cursor down by n positions but it will bring no effect if cursor is in the last + * line of terminal screen. In such case, the cursor_next_line_move function shall be invoked. */ +static inline void cursor_down_move(nrf_cli_t const * p_cli, nrf_cli_cmd_len_t n) +{ + if (n > 0) + { + nrf_fprintf(p_cli->p_fprintf_ctx, "\033[%dB", n); + } +} + +/* Function increments cursor position (if possible) and moves cursor to new line if necessary. */ +static void cursor_position_increment(nrf_cli_t const * p_cli) +{ + if (p_cli->p_ctx->cmd_buff_pos >= p_cli->p_ctx->cmd_buff_len) + { + return; /* incrementation not possible */ + } + + nrf_cli_multiline_cons_t const * p_cons = multiline_console_data_check(p_cli); + nrf_cli_cmd_len_t cur_y = p_cons->cur_y; + ++p_cli->p_ctx->cmd_buff_pos; + p_cons = multiline_console_data_check(p_cli); + + if (cur_y == p_cons->cur_y) + { + cursor_right_move(p_cli, 1); + } + else + { + cursor_next_line_move(p_cli); + } +} + +/* Function will move cursor back to position == cmd_buff_pos. Example usage is when cursor needs + * to be moved back after printing some text. This function cannot be used to move cursor to new + * location by manual change of cmd_buff_pos.*/ +static void cursor_position_synchronize(nrf_cli_t const * p_cli) +{ + nrf_cli_multiline_cons_t const * p_cons = multiline_console_data_check(p_cli); + bool last_line = p_cons->cur_y == p_cons->cur_y_end ? true : false; + + /* In case cursor reaches the bottom line of a terminal, it will be moved to the next line. */ + if (cursor_in_empty_line(p_cli) || full_line_cmd(p_cli)) + { + cursor_next_line_move(p_cli); + } + + if (last_line) + { + cursor_left_move(p_cli, p_cons->cur_x_end - p_cons->cur_x); + } + else + { + cursor_up_move(p_cli, p_cons->cur_y_end - p_cons->cur_y); + if (p_cons->cur_x > p_cons->cur_x_end) + { + cursor_right_move(p_cli, p_cons->cur_x - p_cons->cur_x_end); + } + else + { + cursor_left_move(p_cli, p_cons->cur_x_end - p_cons->cur_x); + } + } +} + +/* Function moves cursor to begin of command position, just after console name. */ +static void cursor_home_position_move(nrf_cli_t const * p_cli) +{ + nrf_cli_multiline_cons_t const * p_cons = multiline_console_data_check(p_cli); + + if ((p_cons->cur_x == p_cons->name_len + NRF_CLI_INITIAL_CURS_POS) && + (p_cons->cur_y == NRF_CLI_INITIAL_CURS_POS)) + { + return; /* nothing to handle because cursor is in start position */ + } + + if (p_cons->cur_y > NRF_CLI_INITIAL_CURS_POS) + { + cursor_up_move(p_cli, p_cons->cur_y - NRF_CLI_INITIAL_CURS_POS); + } + + if (p_cons->cur_x > p_cons->name_len) + { + cursor_left_move(p_cli, p_cons->cur_x - NRF_CLI_INITIAL_CURS_POS - p_cons->name_len); + } + else + { + cursor_right_move(p_cli, p_cons->name_len + NRF_CLI_INITIAL_CURS_POS - p_cons->cur_x); + } + /* align data buffer pointer with cursor position */ + p_cli->p_ctx->cmd_buff_pos = 0; +} + +/* Function moves cursor to end of command. */ +static void cursor_end_position_move(nrf_cli_t const * p_cli) +{ + nrf_cli_multiline_cons_t const * p_cons = multiline_console_data_check(p_cli); + + if ((p_cons->cur_x == p_cons->cur_x_end) && (p_cons->cur_y == p_cons->cur_y_end)) + { + return; /* nothing to handle because cursor is in end position */ + } + + if (p_cons->cur_y_end > p_cons->cur_y) + { + cursor_down_move(p_cli, p_cons->cur_y_end - p_cons->cur_y); + } + + if (p_cons->cur_x > p_cons->cur_x_end) + { + cursor_left_move(p_cli, p_cons->cur_x - p_cons->cur_x_end); + } + else + { + cursor_right_move(p_cli, p_cons->cur_x_end - p_cons->cur_x); + } + /* align data buffer pointer with cursor position */ + p_cli->p_ctx->cmd_buff_pos = p_cli->p_ctx->cmd_buff_len; +} + +#if NRF_MODULE_ENABLED(NRF_CLI_BUILD_IN_CMDS) +/* Function reads cursor position from terminal. */ +static ret_code_t cursor_position_get(nrf_cli_t const * p_cli) +{ + size_t cnt; + uint16_t x = 0; /* horizontal position */ + uint16_t y = 0; /* vertical position */ + char c = 0; + + nrf_cli_cmd_len_t buff_idx = 0; + + /* clear temp buffer */ + memset(p_cli->p_ctx->temp_buff, 0, sizeof(p_cli->p_ctx->temp_buff)); + + /* escape code asking terminal about its size */ + static char const cmd_get_terminal_size[] = "\033[6n"; + + nrf_fprintf(p_cli->p_fprintf_ctx, cmd_get_terminal_size); + /* fprintf buffer needs to be flushed to start sending prepared escape code to the terminal */ + transport_buffer_flush(p_cli); + + /* timeout for terminal response = ~1s */ + for (uint16_t i = 0; i < 1000; i++) + { + do + { + cli_read(p_cli, &c, sizeof(c), &cnt); + if (cnt == 0) + { + nrf_delay_us(999); + continue; + } + if ((c != NRF_CLI_VT100_ASCII_ESC) && + (p_cli->p_ctx->temp_buff[0] != NRF_CLI_VT100_ASCII_ESC)) + { + continue; + } + + if (c == 'R') /* end of response from the terminal */ + { + p_cli->p_ctx->temp_buff[buff_idx] = '\0'; + if (p_cli->p_ctx->temp_buff[1] != '[') + { + p_cli->p_ctx->temp_buff[0] = 0; + return NRF_ERROR_INVALID_DATA; + } + buff_idx = 2; /* index start position in the buffer where 'y' is stored */ + while (p_cli->p_ctx->temp_buff[buff_idx] != ';') + { + y = y * 10 + (p_cli->p_ctx->temp_buff[buff_idx++] - '0'); + if (buff_idx >= NRF_CLI_CMD_BUFF_SIZE) + { + return NRF_ERROR_DATA_SIZE; + } + } + if (++buff_idx >= NRF_CLI_CMD_BUFF_SIZE) + { + return NRF_ERROR_DATA_SIZE; + } + while (p_cli->p_ctx->temp_buff[buff_idx] != '\0') + { + x = x * 10 + (p_cli->p_ctx->temp_buff[buff_idx++] - '0'); + if (buff_idx >= NRF_CLI_CMD_BUFF_SIZE) + { + return NRF_ERROR_DATA_SIZE; + } + } + /* horizontal cursor position */ + if (x > NRF_CLI_MAX_TERMINAL_SIZE) + { + p_cli->p_ctx->vt100_ctx.cons.cur_x = NRF_CLI_MAX_TERMINAL_SIZE; + } + else + { + p_cli->p_ctx->vt100_ctx.cons.cur_x = (nrf_cli_cmd_len_t)x; + } + /* vertical cursor position */ + if (y > NRF_CLI_MAX_TERMINAL_SIZE) + { + p_cli->p_ctx->vt100_ctx.cons.cur_y = NRF_CLI_MAX_TERMINAL_SIZE; + } + else + { + p_cli->p_ctx->vt100_ctx.cons.cur_y = (nrf_cli_cmd_len_t)y; + } + p_cli->p_ctx->temp_buff[0] = 0; + return NRF_SUCCESS; + } + else + { + p_cli->p_ctx->temp_buff[buff_idx] = c; + } + + if (++buff_idx > NRF_CLI_CURSOR_POSITION_BUFFER - 1) + { + p_cli->p_ctx->temp_buff[0] = 0; + /* data_buf[NRF_CLI_CURSOR_POSITION_BUFFER - 1] is reserved for '\0' */ + return NRF_ERROR_NO_MEM; + } + + } while (cnt > 0); + } + return NRF_ERROR_TIMEOUT; +} + +/* Function gets terminal width and height. */ +static ret_code_t terminal_size_get(nrf_cli_t const * p_cli, + nrf_cli_cmd_len_t * p_length, + nrf_cli_cmd_len_t * p_height) +{ + ASSERT(p_length); + ASSERT(p_height); + + uint16_t x; + uint16_t y; + + if (cursor_position_get(p_cli) == NRF_SUCCESS) + { + x = p_cli->p_ctx->vt100_ctx.cons.cur_x; + y = p_cli->p_ctx->vt100_ctx.cons.cur_y; + /* assumption: terminal widht and height < 999 */ + cursor_right_move(p_cli, NRF_CLI_MAX_TERMINAL_SIZE); /* move to last column */ + cursor_down_move(p_cli, NRF_CLI_MAX_TERMINAL_SIZE); /* move to last row */ + } + else + { + return NRF_ERROR_NOT_SUPPORTED; + } + + if (cursor_position_get(p_cli) == NRF_SUCCESS) + { + *p_length = p_cli->p_ctx->vt100_ctx.cons.cur_x; + *p_height = p_cli->p_ctx->vt100_ctx.cons.cur_y; + cursor_left_move(p_cli, *p_length - x); + cursor_up_move(p_cli, *p_height - y); + + return NRF_SUCCESS; + } + return NRF_ERROR_NOT_SUPPORTED; +} +#endif // NRF_MODULE_ENABLED(NRF_CLI_BUILD_IN_CMDS) + +#if NRF_MODULE_ENABLED(NRF_CLI_VT100_COLORS) +static void vt100_color_set(nrf_cli_t const * p_cli, nrf_cli_vt100_color_t color) +{ + if (color != NRF_CLI_DEFAULT) + { + if (p_cli->p_ctx->vt100_ctx.col.col == color) + { + return; + } + + uint8_t cmd[] = NRF_CLI_VT100_COLOR(color - 1); + + p_cli->p_ctx->vt100_ctx.col.col = color; + nrf_fprintf(p_cli->p_fprintf_ctx, "%s", cmd); + } + else + { + static uint8_t const cmd[] = NRF_CLI_VT100_MODESOFF; + + p_cli->p_ctx->vt100_ctx.col.col = color; + nrf_fprintf(p_cli->p_fprintf_ctx, "%s", cmd); + } +} + +static void vt100_bgcolor_set(nrf_cli_t const * p_cli, nrf_cli_vt100_color_t bgcolor) +{ + if (bgcolor != NRF_CLI_DEFAULT) + { + if (p_cli->p_ctx->vt100_ctx.col.bgcol == bgcolor) + { + return; + } + /* -1 because default value is first in enum */ + uint8_t cmd[] = NRF_CLI_VT100_BGCOLOR(bgcolor - 1); + + p_cli->p_ctx->vt100_ctx.col.bgcol = bgcolor; + nrf_fprintf(p_cli->p_fprintf_ctx, "%s", cmd); + } +} + +static inline void vt100_colors_store(nrf_cli_t const * p_cli, + nrf_cli_vt100_colors_t * p_color) +{ + memcpy(p_color, &p_cli->p_ctx->vt100_ctx.col, sizeof(nrf_cli_vt100_colors_t)); +} + +static void vt100_colors_restore(nrf_cli_t const * p_cli, + nrf_cli_vt100_colors_t const * p_color) +{ + vt100_color_set(p_cli, p_color->col); + vt100_bgcolor_set(p_cli, p_color->bgcol); +} +#endif // NRF_MODULE_ENABLED(NRF_CLI_VT100_COLORS) + +static void left_arrow_handle(nrf_cli_t const * p_cli) +{ + nrf_cli_multiline_cons_t const * p_cons = multiline_console_data_check(p_cli); + + if ((p_cons->cur_x == p_cons->name_len + NRF_CLI_INITIAL_CURS_POS) && + (p_cons->cur_y == NRF_CLI_INITIAL_CURS_POS)) + { + return; /* nothing to handle because cursor is in start position */ + } + + if (p_cons->cur_x == NRF_CLI_INITIAL_CURS_POS) + { /* go to previous line */ + cursor_up_move(p_cli, 1); + cursor_right_move(p_cli, p_cons->terminal_wid); + --p_cli->p_ctx->cmd_buff_pos; + } + else + { + cursor_left_move(p_cli, 1); + --p_cli->p_ctx->cmd_buff_pos; + } +} + +static void right_arrow_handle(nrf_cli_t const * p_cli) +{ + nrf_cli_multiline_cons_t const * p_cons = multiline_console_data_check(p_cli); + + if ((p_cons->cur_x == p_cons->cur_x_end) && + (p_cons->cur_y == p_cons->cur_y_end)) + { + return; /* nothing to handle because cursor is in start position */ + } + + if (p_cons->cur_x == p_cons->terminal_wid) /* go to next line */ + { + cursor_down_move(p_cli, 1); + cursor_left_move(p_cli, p_cons->terminal_wid); + ++p_cli->p_ctx->cmd_buff_pos; + } + else + { + cursor_right_move(p_cli, 1); + ++p_cli->p_ctx->cmd_buff_pos; + } +} + +static inline void char_insert_echo_off(nrf_cli_t const * p_cli, char data) +{ + if (p_cli->p_ctx->cmd_buff_len >= (NRF_CLI_CMD_BUFF_SIZE - 1)) + { + return; + } + + p_cli->p_ctx->cmd_buff[p_cli->p_ctx->cmd_buff_pos++] = data; + p_cli->p_ctx->cmd_buff[p_cli->p_ctx->cmd_buff_pos] = '\0'; + ++p_cli->p_ctx->cmd_buff_len; +} + +static void char_insert(nrf_cli_t const * p_cli, char data) +{ + nrf_cli_cmd_len_t diff; + bool ins_mode = (bool)p_cli->p_ctx->internal.flag.insert_mode; + + diff = p_cli->p_ctx->cmd_buff_len - p_cli->p_ctx->cmd_buff_pos; + + if (!ins_mode) + { + if (p_cli->p_ctx->cmd_buff_len >= (NRF_CLI_CMD_BUFF_SIZE - 1)) + { + return; + } + if (diff > 0) + { + memmove(&p_cli->p_ctx->cmd_buff[p_cli->p_ctx->cmd_buff_pos + 1], + &p_cli->p_ctx->cmd_buff[p_cli->p_ctx->cmd_buff_pos], + diff); + } + } + else + { + if ((p_cli->p_ctx->cmd_buff_len >= (NRF_CLI_CMD_BUFF_SIZE - 1)) && + (diff == 0)) + { + /* If cmd buffer is full, it is possible to replace chars but adding new + is not allowed. */ + return; + } + } + + p_cli->p_ctx->cmd_buff[p_cli->p_ctx->cmd_buff_pos] = data; + + if (!ins_mode) + { + p_cli->p_ctx->cmd_buff[++p_cli->p_ctx->cmd_buff_len] = '\0'; + } + + if (diff > 0) + { + nrf_cli_multiline_cons_t const * p_cons = multiline_console_data_check(p_cli); + bool last_line = p_cons->cur_y == p_cons->cur_y_end ? true : false; + + /* Below if-else statement is to minimize esc codes transmission. */ + if (last_line) + { + nrf_cli_fprintf(p_cli, + NRF_CLI_NORMAL, + "%s", + &p_cli->p_ctx->cmd_buff[p_cli->p_ctx->cmd_buff_pos]); + /* Move cursor one position left less in case of insert mode. */ + cursor_left_move(p_cli, diff - ins_mode); + } + else + { + /* Save the current cursor position in order to get back after fprintf function. */ + cli_cursor_save(p_cli); + nrf_cli_fprintf(p_cli, + NRF_CLI_NORMAL, + "%s", + &p_cli->p_ctx->cmd_buff[p_cli->p_ctx->cmd_buff_pos]); + cli_cursor_restore(p_cli); + /* Move cursor right by one position to edit the next character. */ + cursor_right_move(p_cli, 1); + } + } + else + { + /* New char appended at the end of buffer. */ + if (ins_mode) + { + p_cli->p_ctx->cmd_buff[++p_cli->p_ctx->cmd_buff_len] = '\0'; + } + cli_putc(p_cli, data); + } + + /* Incrementation needs to be executed before invoking function: cursor_in_empty_line. */ + ++p_cli->p_ctx->cmd_buff_pos; + + /* Forcing terminal to switch to a new line if the command is too long. */ + if (cursor_in_empty_line(p_cli)) + { + cursor_next_line_move(p_cli); + return; + } + + if (full_line_cmd(p_cli)) + { + nrf_cli_multiline_cons_t const * p_cons = multiline_console_data_check(p_cli); + + /* The code below will force the terminal to scroll one line down when the currently entered command + * reaches lower right corner of the terminal screen. */ + cursor_down_move(p_cli, p_cons->cur_y_end - p_cons->cur_y - 1); + cursor_next_line_move(p_cli); + cursor_up_move(p_cli, p_cons->cur_y_end - p_cons->cur_y); + cursor_right_move(p_cli, p_cons->cur_x - 1); + return; + } +} + +static void char_backspace(nrf_cli_t const * p_cli) +{ + nrf_cli_cmd_len_t diff; + + if ((p_cli->p_ctx->cmd_buff_len == 0) || (p_cli->p_ctx->cmd_buff_pos == 0)) + { + return; + } + + diff = p_cli->p_ctx->cmd_buff_len - p_cli->p_ctx->cmd_buff_pos; + + memmove(&p_cli->p_ctx->cmd_buff[p_cli->p_ctx->cmd_buff_pos - 1], + &p_cli->p_ctx->cmd_buff[p_cli->p_ctx->cmd_buff_pos], + diff + 1); + + --p_cli->p_ctx->cmd_buff_pos; + --p_cli->p_ctx->cmd_buff_len; + + if (diff > 0) + { + cli_putc(p_cli, NRF_CLI_VT100_ASCII_BSPACE); + + nrf_cli_multiline_cons_t const * p_cons = multiline_console_data_check(p_cli); + bool last_line = p_cons->cur_y == p_cons->cur_y_end ? true : false; + + if (last_line) + { + nrf_cli_fprintf(p_cli, + NRF_CLI_NORMAL, + "%s", + &p_cli->p_ctx->cmd_buff[p_cli->p_ctx->cmd_buff_pos]); + cli_clear_eos(p_cli); + cursor_left_move(p_cli, diff); + } + else + { + /* If cursor is not in last cmd line, its position needs to be saved by + * VT100 command. */ + cli_cursor_save(p_cli); + cli_clear_eos(p_cli); + nrf_cli_fprintf(p_cli, + NRF_CLI_NORMAL, + "%s", + &p_cli->p_ctx->cmd_buff[p_cli->p_ctx->cmd_buff_pos]); + cli_cursor_restore(p_cli); + } + } + else + { + static char const cmd_bspace[] = { + NRF_CLI_VT100_ASCII_BSPACE, ' ', NRF_CLI_VT100_ASCII_BSPACE, '\0'}; + nrf_fprintf(p_cli->p_fprintf_ctx, "%s", cmd_bspace); + } +} + +static void char_delete(nrf_cli_t const * p_cli) +{ + nrf_cli_cmd_len_t diff; + + diff = p_cli->p_ctx->cmd_buff_len - p_cli->p_ctx->cmd_buff_pos; + + if (diff == 0) + { + return; + } + + memmove(&p_cli->p_ctx->cmd_buff[p_cli->p_ctx->cmd_buff_pos], + &p_cli->p_ctx->cmd_buff[p_cli->p_ctx->cmd_buff_pos + 1], + diff); + + --p_cli->p_ctx->cmd_buff_len; + + nrf_cli_multiline_cons_t const * p_cons = multiline_console_data_check(p_cli); + bool last_line = p_cons->cur_y == p_cons->cur_y_end ? true : false; + + /* If last line of command is edited, there is no need for saving cursor position. */ + if (last_line) + { + nrf_cli_fprintf(p_cli, + NRF_CLI_NORMAL, + "%s", + &p_cli->p_ctx->cmd_buff[p_cli->p_ctx->cmd_buff_pos]); + NRF_CLI_VT100_CMD(p_cli, NRF_CLI_VT100_CLEAREOL); + cursor_left_move(p_cli, --diff); + } + else + { + cli_cursor_save(p_cli); + cli_clear_eos(p_cli); + nrf_cli_fprintf(p_cli, + NRF_CLI_NORMAL, + "%s", + &p_cli->p_ctx->cmd_buff[p_cli->p_ctx->cmd_buff_pos]); + cli_cursor_restore(p_cli); + } +} + +static char make_argv(size_t * p_argc, char ** pp_argv, char * p_cmd, uint8_t max_argc) +{ + char c; + char quote = 0; + + *p_argc = 0; + do + { + c = *p_cmd; + if (c == '\0') + { + break; + } + + if (isspace((int)c)) + { + *p_cmd++ = '\0'; + continue; + } + + pp_argv[(*p_argc)++] = p_cmd; + quote = 0; + + while (1) + { + c = *p_cmd; + + if (c == '\0') + { + break; + } + + if (!quote) + { + switch (c) + { + case '\\': + memmove(p_cmd, p_cmd + 1, cli_strlen(p_cmd)); + p_cmd += 1; + continue; + + case '\'': + case '\"': + memmove(p_cmd, p_cmd + 1, cli_strlen(p_cmd)); + quote = c; + continue; + default: + break; + } + } + + if (quote == c) + { + memmove(p_cmd, p_cmd + 1, cli_strlen(p_cmd)); + quote = 0; + continue; + } + + if (quote && c == '\\') + { + char t = *(p_cmd + 1); + + if (t == quote) + { + memmove(p_cmd, p_cmd + 1, cli_strlen(p_cmd)); + p_cmd += 1; + continue; + } + + if (t == '0') + { + uint8_t i; + uint8_t v = 0; + + for (i = 2; i < (2 + 3); i++) + { + t = *(p_cmd + i); + + if (t >= '0' && t <= '7') + { + v = (v << 3) | (t - '0'); + } + else + { + break; + } + } + + if (i > 2) + { + memmove(p_cmd, p_cmd + (i - 1), cli_strlen(p_cmd) - (i - 2)); + *p_cmd++ = v; + continue; + } + } + + if (t == 'x') + { + uint8_t i; + uint8_t v = 0; + + for (i = 2; i < (2 + 2); i++) + { + t = *(p_cmd + i); + + if (t >= '0' && t <= '9') + { + v = (v << 4) | (t - '0'); + } + else if (t >= 'a' && t <= 'f') + { + v = (v << 4) | (t - 'a' + 10); + } + else if (t >= 'A' && t <= 'F') + { + v = (v << 4) | (t - 'A' + 10); + } + else + { + break; + } + } + + if (i > 2) + { + memmove(p_cmd, p_cmd + (i - 1), cli_strlen(p_cmd) - (i - 2)); + *p_cmd++ = v; + continue; + } + } + } + if (!quote && isspace((int)c)) + { + break; + } + + p_cmd += 1; + } + } while (*p_argc < max_argc); + + ASSERT(*p_argc <= NRF_CLI_ARGC_MAX); + pp_argv[*p_argc] = 0; + + return quote; +} + +static void cli_state_set(nrf_cli_t const * p_cli, nrf_cli_state_t state) +{ + p_cli->p_ctx->state = state; + + if (state == NRF_CLI_STATE_ACTIVE) + { + p_cli->p_ctx->cmd_buff[0] = '\0'; /* clear command buffer */ + p_cli->p_ctx->cmd_buff_pos = 0; + p_cli->p_ctx->cmd_buff_len = 0; + nrf_cli_fprintf(p_cli, NRF_CLI_INFO, "%s", p_cli->p_name); + } +} + +#if NRF_MODULE_ENABLED(NRF_CLI_HISTORY) +static inline void history_mode_exit(nrf_cli_t const * p_cli) +{ + p_cli->p_ctx->p_cmd_list_element = NULL; +} + +static void history_handle(nrf_cli_t const * p_cli, bool up) +{ + nrf_cli_memobj_header_t header = { + .p_prev = NULL, + .p_next = NULL, + .cmd_len = 0 + }; + nrf_cli_cmd_len_t current_cmd_len; + bool skip = false; + + if (!up) /* button down */ + { + if (p_cli->p_ctx->p_cmd_list_element == NULL) + { + return; + } + cursor_home_position_move(p_cli); + + nrf_memobj_read(p_cli->p_ctx->p_cmd_list_element, + &header, + NRF_CLI_HISTORY_HEADER_SIZE, + (uint32_t)0); + + p_cli->p_ctx->p_cmd_list_element = header.p_next; + current_cmd_len = p_cli->p_ctx->cmd_buff_len; + + if (p_cli->p_ctx->p_cmd_list_element == NULL) + { + if (cli_strlen(p_cli->p_ctx->temp_buff) > 0) + { + strcpy(p_cli->p_ctx->cmd_buff, p_cli->p_ctx->temp_buff); + } + else + { + p_cli->p_ctx->cmd_buff[0] = '\0'; + } + header.cmd_len = cli_strlen(p_cli->p_ctx->cmd_buff); + skip = true; + } + } + else /* button up */ + { + if ((p_cli->p_ctx->p_cmd_list_element == p_cli->p_ctx->p_cmd_list_tail) || + (p_cli->p_ctx->p_cmd_list_head == NULL)) + { + /* Nothing to display. */ + return; + } + cursor_home_position_move(p_cli); + + if (p_cli->p_ctx->p_cmd_list_element == NULL) + { + current_cmd_len = cli_strlen(p_cli->p_ctx->cmd_buff); + + p_cli->p_ctx->p_cmd_list_element = p_cli->p_ctx->p_cmd_list_head; + /* Save the currently entered and not executed command. */ + if (current_cmd_len > 0) + { + strcpy(p_cli->p_ctx->temp_buff, p_cli->p_ctx->cmd_buff); + } + else + { + p_cli->p_ctx->temp_buff[0] = '\0'; + } + } + else + { + nrf_memobj_read(p_cli->p_ctx->p_cmd_list_element, + &header, + NRF_CLI_HISTORY_HEADER_SIZE, + (uint32_t)0); + current_cmd_len = header.cmd_len; + p_cli->p_ctx->p_cmd_list_element = header.p_prev; + } + } + if (!skip) + { + nrf_memobj_read(p_cli->p_ctx->p_cmd_list_element, + &header, + NRF_CLI_HISTORY_HEADER_SIZE, + (uint32_t)0); + + nrf_memobj_read(p_cli->p_ctx->p_cmd_list_element, + p_cli->p_ctx->cmd_buff, + header.cmd_len + 1, /* +1 for '\0' */ + (uint32_t)NRF_CLI_HISTORY_HEADER_SIZE); + } + + p_cli->p_ctx->cmd_buff_pos = header.cmd_len; + p_cli->p_ctx->cmd_buff_len = header.cmd_len; + + if (current_cmd_len > header.cmd_len) + { + cli_clear_eos(p_cli); + } + + nrf_cli_fprintf(p_cli, NRF_CLI_NORMAL, "%s", p_cli->p_ctx->cmd_buff); + if (cursor_in_empty_line(p_cli) || full_line_cmd(p_cli)) + { + cursor_next_line_move(p_cli); + } +} + +static void history_list_element_add(nrf_cli_t const * p_cli, nrf_memobj_t * p_memobj) +{ + ASSERT(p_memobj != NULL); + + nrf_cli_memobj_header_t header; + + if (p_cli->p_ctx->p_cmd_list_head == NULL) + { + p_cli->p_ctx->p_cmd_list_head = p_memobj; + p_cli->p_ctx->p_cmd_list_tail = p_memobj; + header.p_prev = NULL; + header.p_next = NULL; + header.cmd_len = p_cli->p_ctx->cmd_buff_len; + } + else + { + nrf_memobj_read(p_cli->p_ctx->p_cmd_list_head, + &header, + NRF_CLI_HISTORY_HEADER_SIZE, + (uint32_t)0); + + header.p_next = p_memobj; + + nrf_memobj_write(p_cli->p_ctx->p_cmd_list_head, + &header, + NRF_CLI_HISTORY_HEADER_SIZE, + (uint32_t)0); + + header.p_next = NULL; + header.p_prev = p_cli->p_ctx->p_cmd_list_head; + header.cmd_len = p_cli->p_ctx->cmd_buff_len; + + p_cli->p_ctx->p_cmd_list_head = p_memobj; + } + + nrf_memobj_write(p_memobj, + &header, + NRF_CLI_HISTORY_HEADER_SIZE, + (uint32_t)0); + + nrf_memobj_write(p_memobj, + p_cli->p_ctx->cmd_buff, + p_cli->p_ctx->cmd_buff_len + 1, /* +1 for '\0' */ + (uint32_t)NRF_CLI_HISTORY_HEADER_SIZE); +} + +static void history_list_element_oldest_remove(nrf_cli_t const * p_cli) +{ + if (p_cli->p_ctx->p_cmd_list_tail == NULL) + { + return; // nothing to do + } + + nrf_cli_memobj_header_t header; + nrf_memobj_t * p_memobj = p_cli->p_ctx->p_cmd_list_tail; + + nrf_memobj_read(p_memobj, + &header, + NRF_CLI_HISTORY_HEADER_SIZE, + (uint32_t)0); + + p_cli->p_ctx->p_cmd_list_tail = header.p_next; + memset(&header, 0, sizeof(nrf_cli_memobj_header_t)); + nrf_memobj_write(p_memobj, &header, NRF_CLI_HISTORY_HEADER_SIZE, 0); + nrf_memobj_free(p_memobj); + + /* Checking if memory objects list is empty. */ + if (p_cli->p_ctx->p_cmd_list_tail == NULL) + { + p_cli->p_ctx->p_cmd_list_head = NULL; + return; + } + + nrf_memobj_read(p_cli->p_ctx->p_cmd_list_tail, + &header, + NRF_CLI_HISTORY_HEADER_SIZE, + (uint32_t)0); + + header.p_prev = NULL; + nrf_memobj_write(p_cli->p_ctx->p_cmd_list_tail, &header, NRF_CLI_HISTORY_HEADER_SIZE, 0); +} + +static void history_list_free_memory(nrf_cli_t const * p_cli) +{ + while (p_cli->p_ctx->p_cmd_list_tail != NULL) + { + history_list_element_oldest_remove(p_cli); + } +} + +static void history_save(nrf_cli_t const * p_cli) +{ + nrf_cli_cmd_len_t cmd_new_len = cli_strlen(p_cli->p_ctx->cmd_buff); + + history_mode_exit(p_cli); + + if (cmd_new_len == 0) + { + return; + } + + /* Checking if newly entered command is not duplicated with previous one. */ + if (p_cli->p_ctx->p_cmd_list_head != NULL) + { + nrf_cli_memobj_header_t header; + + nrf_memobj_read(p_cli->p_ctx->p_cmd_list_head, + &header, + NRF_CLI_HISTORY_HEADER_SIZE, + (uint32_t)0); + if (cmd_new_len == header.cmd_len) + { + nrf_memobj_read(p_cli->p_ctx->p_cmd_list_head, + p_cli->p_ctx->temp_buff, + header.cmd_len + 1, /* +1 for '\0' */ + (uint32_t)NRF_CLI_HISTORY_HEADER_SIZE); + + if (strcmp(p_cli->p_ctx->cmd_buff, p_cli->p_ctx->temp_buff) == 0) + { + /* Duplicated command, nothing to save. */ + p_cli->p_ctx->temp_buff[0] = '\0'; + return; + } + p_cli->p_ctx->temp_buff[0] = '\0'; + } + } + + for (size_t idx = 0; idx < NRF_CLI_HISTORY_ELEMENT_COUNT; idx++) + { + nrf_memobj_t * p_memobj; + + p_memobj = nrf_memobj_alloc(p_cli->p_cmd_hist_mempool, + ((size_t)NRF_CLI_HISTORY_HEADER_SIZE + cmd_new_len + 1)); + if (p_memobj != NULL) + { + history_list_element_add(p_cli, p_memobj); + return; + } + else + { + history_list_element_oldest_remove(p_cli); + } + } + return; +} +#endif // NRF_MODULE_ENABLED(NRF_CLI_HISTORY) + +/* Function checks how many identical characters have two strings starting from the first character. */ +static nrf_cli_cmd_len_t str_similarity_check(char const * str_a, char const * str_b) +{ + nrf_cli_cmd_len_t cnt = 0; + + while (str_a[cnt] != '\0') + { + if (str_a[cnt] != str_b[cnt]) + { + return cnt; + } + + if (++cnt == 0) + { + return --cnt; /* too long strings */ + } + } + return cnt; +} + +static void completion_insert(nrf_cli_t const * p_cli, + char const * p_compl, + nrf_cli_cmd_len_t compl_len) +{ + ASSERT (p_compl); + + nrf_cli_cmd_len_t diff = p_cli->p_ctx->cmd_buff_len - p_cli->p_ctx->cmd_buff_pos; + + if ((p_cli->p_ctx->cmd_buff_len + compl_len > NRF_CLI_CMD_BUFF_SIZE - 1) || + (compl_len == 0)) + { + return; + } + + /* Make space for completion. */ + memmove(&p_cli->p_ctx->cmd_buff[p_cli->p_ctx->cmd_buff_pos + compl_len], + &p_cli->p_ctx->cmd_buff[p_cli->p_ctx->cmd_buff_pos], + diff + 1); /* + 1 for '\0' */ + + /* Insert completion. */ + memmove(&p_cli->p_ctx->cmd_buff[p_cli->p_ctx->cmd_buff_pos], + p_compl, + compl_len); + + p_cli->p_ctx->cmd_buff_len = cli_strlen(p_cli->p_ctx->cmd_buff); + nrf_cli_fprintf(p_cli, + NRF_CLI_NORMAL, + "%s", + &p_cli->p_ctx->cmd_buff[p_cli->p_ctx->cmd_buff_pos]); + p_cli->p_ctx->cmd_buff_pos += compl_len; + + if (cursor_in_empty_line(p_cli) || full_line_cmd(p_cli)) + { + cursor_next_line_move(p_cli); + } + + if (diff > 0) + { + cursor_position_synchronize(p_cli); + } +} + +static void option_print(nrf_cli_t const * p_cli, + char const * p_option, + nrf_cli_cmd_len_t longest_option) +{ + static char const * tab = " "; + + /* Function initialization has been requested. */ + if (p_option == NULL) + { + p_cli->p_ctx->vt100_ctx.printed_cmd = 0; + return; + } + longest_option += cli_strlen(tab); + + nrf_cli_cmd_len_t columns = + (p_cli->p_ctx->vt100_ctx.cons.terminal_wid - cli_strlen(tab)) / longest_option; + nrf_cli_cmd_len_t diff = longest_option - cli_strlen(p_option); + + if (p_cli->p_ctx->vt100_ctx.printed_cmd++ % columns == 0) + { + nrf_cli_fprintf(p_cli, NRF_CLI_OPTION, "\r\n%s%s", tab, p_option); + } + else + { + nrf_cli_fprintf(p_cli, NRF_CLI_OPTION, "%s", p_option); + } + cursor_right_move(p_cli, diff); +} + +static void cli_tab_handle(nrf_cli_t const * p_cli) +{ + size_t cmd_idx; + size_t cmd_last = 0; + size_t cmd_first = 0; + + size_t argc; + char * argv[NRF_CLI_ARGC_MAX + 1]; /* +1 reserved for NULL in function make_argv */ + + nrf_cli_cmd_len_t cmd_lvl = NRF_CLI_CMD_ROOT_LVL; + nrf_cli_cmd_len_t cmd_longest = 0; /* longest matching command */ + + /* Calculating the longest possible completion length. -1 for '\0'. */ + nrf_cli_cmd_len_t compl_len = (NRF_CLI_CMD_BUFF_SIZE - 1) - p_cli->p_ctx->cmd_buff_len; + + if (compl_len == 0) + { + return; + } + + /* Copy command from its beginning to cursor position. */ + memcpy(p_cli->p_ctx->temp_buff, + p_cli->p_ctx->cmd_buff, + p_cli->p_ctx->cmd_buff_pos); + + p_cli->p_ctx->temp_buff[p_cli->p_ctx->cmd_buff_pos] = '\0'; + + /* Check if the current cursor position points to the 'space' character. */ + bool space = isspace((int)p_cli->p_ctx->cmd_buff[p_cli->p_ctx->cmd_buff_pos - 1]); + +#if NRF_MODULE_ENABLED(NRF_CLI_HISTORY) + /* If the Tab key is pressed, "history mode" must be terminated because tab and history handlers + are sharing the same array: temp_buff. */ + history_mode_exit(p_cli); +#endif + + /* Create argument list. */ + (void)make_argv(&argc, + &argv[0], + p_cli->p_ctx->temp_buff, + NRF_CLI_ARGC_MAX); + + nrf_cli_cmd_len_t arg_len = cli_strlen(argv[cmd_lvl]); + + /* Variable 'static_entry' is needed to handle dynamic commands. */ + nrf_cli_static_entry_t static_entry; + + nrf_cli_cmd_entry_t const * p_cmd = NULL; + nrf_cli_static_entry_t const * p_st_cmd = NULL; + nrf_cli_static_entry_t const * p_st_cmd_last = NULL; + + do + { + if ((argc == 0) || + (cmd_lvl >= argc - 1 + space)) + { + if (space) + { + arg_len = 0; + } + else + { + arg_len = cli_strlen(argv[cmd_lvl]); + } + + cmd_idx = 0; + + while (1) + { + cmd_get(p_cmd, cmd_lvl, cmd_idx++, &p_st_cmd, &static_entry); + + if (p_st_cmd == NULL) + { + /* No more commands available. */ + break; + } + + if (strncmp(argv[cmd_lvl], p_st_cmd->p_syntax, arg_len) != 0) + { + if (p_st_cmd_last != NULL) + { + /* No more matches will be found as commands are sorted alphabetically. */ + break; + } + continue; + } + if (p_st_cmd_last == NULL) + { + cmd_first = cmd_idx - 1; + cmd_longest = cli_strlen(p_st_cmd->p_syntax); + if (compl_len > (cmd_longest - arg_len)) + { + compl_len = cmd_longest - arg_len; + } + } + else + { + nrf_cli_cmd_len_t len = cli_strlen(p_st_cmd->p_syntax); + if (len > cmd_longest) + { + cmd_longest = len; + } + + if (compl_len > 0) /* Checking if partial completion is possible */ + { + nrf_cli_static_entry_t last_entry; + cmd_get(p_cmd, cmd_lvl, cmd_last, &p_st_cmd_last, &last_entry); + + len = str_similarity_check(p_st_cmd->p_syntax + arg_len, + p_st_cmd_last->p_syntax + arg_len); + if (compl_len > len) + { + /* Determining the longest possible completion. */ + compl_len = len; + } + } + } + cmd_last = cmd_idx - 1; + p_st_cmd_last = p_st_cmd; + + if (cmd_idx == 0) /* Too many possibilities */ + { + nrf_cli_fprintf(p_cli, NRF_CLI_WARNING, NRF_CLI_MSG_TAB_OVERFLOWED); + break; + } + } + } + else + { + cmd_idx = 0; + + while (1) + { + cmd_get(p_cmd, cmd_lvl, cmd_idx++, &p_st_cmd, &static_entry); + + if (cmd_idx == 0) + { + /* No match found and commands counter overflowed. */ + nrf_cli_fprintf(p_cli, NRF_CLI_WARNING, NRF_CLI_MSG_TAB_OVERFLOWED); + return; + } + + if (p_st_cmd == NULL) /* No more commands available */ + { + return; + } + +#if NRF_MODULE_ENABLED(NRF_CLI_WILDCARD) + /* Ignore wildcard character arguments if they are "standalone". Example: + 1. log enable info b*<tab> -> "b*" is treated as a command so no match found + 2. log enable info b* <tab> -> "b* " is ignored, <tab> will prompt all available + commands. */ + if (wildcard_character_exist(argv[cmd_lvl])) + { + break; + } +#endif + /* Fuction "strcmp" is used because an exact match is required. */ + if (strcmp(argv[cmd_lvl], p_st_cmd->p_syntax) == 0) + { + p_cmd = p_st_cmd->p_subcmd; + break; + } + } + } + + if ((p_cmd == NULL) || (p_st_cmd == NULL)) + { + break; + } + + } while (++cmd_lvl < argc + space); + + if (p_st_cmd_last == NULL) + { + /* No match found. */ + return; + } + + if (cmd_first == cmd_last) /* only one match found */ + { + if (p_cmd->is_dynamic) + { + /* In case of dynamic entry, function cmd_get shall be called again for matching + * command index (cmd_last). It is because static_entry is most likely appended by + * not valid data. + */ + cmd_get(p_cmd, cmd_lvl, cmd_last, &p_st_cmd_last, &static_entry); + } + if (cli_strlen(p_st_cmd_last->p_syntax) != arg_len) /* no exact match found */ + { + completion_insert(p_cli, p_st_cmd_last->p_syntax + arg_len, compl_len); + } + + /* Next character in the buffer is not 'space'. */ + if (!isspace((int)p_cli->p_ctx->cmd_buff[p_cli->p_ctx->cmd_buff_pos])) + { + if (p_cli->p_ctx->internal.flag.insert_mode) + { + p_cli->p_ctx->internal.flag.insert_mode = 0; + char_insert(p_cli, ' '); + p_cli->p_ctx->internal.flag.insert_mode = 1; + } + else + { + char_insert(p_cli, ' '); + } + } + else + { + /* case: + | | -> cursor + cons_name $: valid_cmd valid_sub_cmd| |argument <tab> + */ + cursor_position_increment(p_cli); + /* result: + cons_name $: valid_cmd valid_sub_cmd |a|rgument + */ + } + return; + } + + /* Printing all matching commands (options). */ + option_print(p_cli, NRF_CLI_INIT_OPTION_PRINTER, cmd_longest); + for (cmd_idx = cmd_first; cmd_idx <= cmd_last; cmd_idx++) + { + cmd_get(p_cmd, cmd_lvl, cmd_idx, &p_st_cmd, &static_entry); + option_print(p_cli, p_st_cmd->p_syntax, cmd_longest); + } + + nrf_cli_fprintf(p_cli, NRF_CLI_INFO, "\r\n%s", p_cli->p_name); + nrf_cli_fprintf(p_cli, NRF_CLI_NORMAL, "%s", p_cli->p_ctx->cmd_buff); + + cursor_position_synchronize(p_cli); + completion_insert(p_cli, p_st_cmd_last->p_syntax + arg_len, compl_len); +} + +#define NRF_CLI_ASCII_MAX_CHAR (127u) +static inline ret_code_t ascii_filter(char const data) +{ + return (uint8_t)data > NRF_CLI_ASCII_MAX_CHAR ? NRF_ERROR_INVALID_DATA : NRF_SUCCESS; +} + +static void cli_state_collect(nrf_cli_t const * p_cli) +{ + size_t count = 0; + char data; + + while (1) + { + cli_read(p_cli, &data, sizeof(data), &count); + if (count == 0) + { + return; + } + + if (ascii_filter(data) != NRF_SUCCESS) + { + continue; + } + +#if NRF_MODULE_ENABLED(NRF_PWR_MGMT) + nrf_pwr_mgmt_feed(); +#endif + + switch (p_cli->p_ctx->receive_state) + { + case NRF_CLI_RECEIVE_DEFAULT: + if (data == p_cli->newline_char) + { + if (p_cli->p_ctx->cmd_buff_len == 0) + { + cursor_next_line_move(p_cli); + } + else + { + /* Command execution */ + cli_execute(p_cli); + } + cli_state_set(p_cli, NRF_CLI_STATE_ACTIVE); + return; + } + switch (data) + { + case NRF_CLI_VT100_ASCII_ESC: /* ESCAPE */ + recieve_state_change(p_cli, NRF_CLI_RECEIVE_ESC); + break; + case '\0': + break; + case '\t': /* TAB */ + if (cli_flag_echo_is_set(p_cli)) + { + cli_tab_handle(p_cli); + } + break; + case NRF_CLI_VT100_ASCII_BSPACE: /* BACKSPACE */ + if (cli_flag_echo_is_set(p_cli)) + { + char_backspace(p_cli); + } + break; + case NRF_CLI_VT100_ASCII_DEL: /* DELETE */ + if (cli_flag_echo_is_set(p_cli)) + { + char_delete(p_cli); + } + break; + default: + if (isprint((int)data)) + { + if (cli_flag_echo_is_set(p_cli)) + { + char_insert(p_cli, data); + } + else + { + char_insert_echo_off(p_cli, data); + } + } + break; + } + break; + case NRF_CLI_RECEIVE_ESC: + if (data == '[') + { + recieve_state_change(p_cli, NRF_CLI_RECEIVE_ESC_SEQ); + } + else + { + recieve_state_change(p_cli, NRF_CLI_RECEIVE_DEFAULT); + } + break; + case NRF_CLI_RECEIVE_ESC_SEQ: + recieve_state_change(p_cli, NRF_CLI_RECEIVE_DEFAULT); + + if (!cli_flag_echo_is_set(p_cli)) + { + return; + } + + switch (data) + { +#if NRF_MODULE_ENABLED(NRF_CLI_HISTORY) + case 'A': /* UP arrow */ + history_handle(p_cli, true); + break; + case 'B': /* DOWN arrow */ + history_handle(p_cli, false); + break; +#endif + case 'C': /* RIGHT arrow */ + right_arrow_handle(p_cli); + break; + case 'D': /* LEFT arrow */ + left_arrow_handle(p_cli); + break; + case 'F': /* END Button */ + cursor_end_position_move(p_cli); + break; + case 'H': /* HOME Button */ + cursor_home_position_move(p_cli); + break; + case 'L': /* INSERT Button */ + p_cli->p_ctx->internal.flag.insert_mode ^= 1; + break; + default: + break; + } + break; + default: + recieve_state_change(p_cli, NRF_CLI_RECEIVE_DEFAULT); + break; + } + } +} + +/* Function remove white chars from beginning and end of command buffer. */ +static void cmd_trim(nrf_cli_t const * p_cli) +{ + nrf_cli_cmd_len_t i = 0; + + if (p_cli->p_ctx->cmd_buff[0] == '\0') /* no command in the buffer */ + { + return; + } + + /* Counting white characters starting from beginning of the command. */ + while (isspace((int)p_cli->p_ctx->cmd_buff[i++])) + { + if (i == 0) + { + p_cli->p_ctx->cmd_buff[0] = '\0'; + return; + } + } + + /* Removing counted white characters. */ + if (--i > 0) + { + memmove(p_cli->p_ctx->cmd_buff, + p_cli->p_ctx->cmd_buff + i, + (p_cli->p_ctx->cmd_buff_len + 1) - i); /* +1 for '\0' */ + p_cli->p_ctx->cmd_buff_len = p_cli->p_ctx->cmd_buff_len - i; + p_cli->p_ctx->cmd_buff_pos = p_cli->p_ctx->cmd_buff_len; + } + + /* Counting white characters starting from end of command. */ + char * p_end = &p_cli->p_ctx->cmd_buff[p_cli->p_ctx->cmd_buff_len - 1]; + i = 0; + while (isspace((int)*p_end)) + { + ++i; + --p_end; + } + + /* Removing counted white characters. */ + if (p_end != &p_cli->p_ctx->cmd_buff[p_cli->p_ctx->cmd_buff_len - 1]) + { + p_cli->p_ctx->cmd_buff[p_cli->p_ctx->cmd_buff_len - i] = '\0'; + p_cli->p_ctx->cmd_buff_len = p_cli->p_ctx->cmd_buff_len - i; + p_cli->p_ctx->cmd_buff_pos = p_cli->p_ctx->cmd_buff_len; + } +} + +#if NRF_MODULE_ENABLED(NRF_CLI_WILDCARD) +static void spaces_trim(char * p_char) +{ + nrf_cli_cmd_len_t shift = 0; + nrf_cli_cmd_len_t len = cli_strlen(p_char); + + if (p_char == NULL) + { + return; + } + + for (nrf_cli_cmd_len_t i = 0; i < len - 1; i++) + { + if (isspace((int)p_char[i])) + { + for (nrf_cli_cmd_len_t j = i + 1; j < len; j++) + { + if (isspace((int)p_char[j])) + { + shift++; + continue; + } + if (shift > 0) + { + memmove(&p_char[i + 1], &p_char[j], len - shift + 1); // +1 for EOS + len -= shift; + shift = 0; + } + break; + } + } + } +} + +/* Adds new command and one space just before pattern */ +static bool command_to_tmp_buffer_add(nrf_cli_t const * p_cli, + char const * p_new_cmd, + char const * p_pattern) +{ + nrf_cli_cmd_len_t cmd_len = cli_strlen(p_new_cmd); + nrf_cli_cmd_len_t shift; + char * p_cmd_source_addr; + + /* +1 for space */ + if (((size_t)p_cli->p_ctx->cmd_tmp_buff_len + cmd_len + 1) > NRF_CLI_CMD_BUFF_SIZE) + { + nrf_cli_fprintf(p_cli, + NRF_CLI_WARNING, + "Command buffer is too short to expand all commands matching " + "wildcard pattern\r\n"); + return false; + } + + p_cmd_source_addr = strstr(p_cli->p_ctx->temp_buff, p_pattern); + + if (p_cmd_source_addr == NULL) + { + return false; + } + + shift = cli_strlen(p_cmd_source_addr); + + /* make place for new command: + 1 for space + 1 for EOS */ + memmove(p_cmd_source_addr + cmd_len + 1, p_cmd_source_addr, shift + 1); + memcpy(p_cmd_source_addr, p_new_cmd, cmd_len); + p_cmd_source_addr[cmd_len] = ' '; + + p_cli->p_ctx->cmd_tmp_buff_len += cmd_len + 1; // + 1 for space + + return true; +} + +/* removes pattern and following space */ +static void pattern_from_tmp_buffer_remove(nrf_cli_t const * p_cli, + char const * p_pattern) +{ + size_t shift; + char * p_pattern_addr = strstr(p_cli->p_ctx->temp_buff, p_pattern); + + nrf_cli_cmd_len_t pattern_len = cli_strlen(p_pattern); + + if (p_pattern_addr == NULL) + { + return; + } + + if (p_pattern_addr > p_cli->p_ctx->temp_buff) + { + if (*(p_pattern_addr - 1) == ' ') + { + pattern_len++; /* space needs to be removed as well */ + p_pattern_addr--; /* set pointer to space */ + } + } + + shift = cli_strlen(p_pattern_addr) - pattern_len + 1; /* +1 for EOS */ + p_cli->p_ctx->cmd_tmp_buff_len -= pattern_len; + + memmove(p_pattern_addr, p_pattern_addr + pattern_len, shift); +} + +/** + * @internal @brief Function for searching and adding commands matching to wildcard pattern. + * + * This function is internal to nrf_cli module and shall be not called directly. + * + * @param[in/out] p_cli Pointer to the CLI instance. + * @param[in] p_cmd Pointer to command which will be processed + * @param[in] cmd_lvl Command level in the command tree. + * @param[in] p_pattern Pointer to wildcard pattern. + * @param[out] p_counter Number of found and added commands. + * + * @retval WILDCARD_CMD_ADDED All matching commands added to the buffer. + * @retval WILDCARD_CMD_ADDED_MISSING_SPACE Not all matching commands added because + * NRF_CLI_CMD_BUFF_SIZE is too small. + * @retval WILDCARD_CMD_NO_MATCH_FOUND No matching command found. + */ +static wildcard_cmd_status_t commands_expand(nrf_cli_t const * p_cli, + nrf_cli_cmd_entry_t const * p_cmd, + size_t cmd_lvl, + char * p_pattern, + size_t * p_counter) +{ + size_t cmd_idx = 0; + size_t counter = 0; + bool success = false; + + nrf_cli_static_entry_t static_entry; + nrf_cli_static_entry_t const * p_static_entry = NULL; + wildcard_cmd_status_t ret_val = WILDCARD_CMD_NO_MATCH_FOUND; + + do + { + cmd_get(p_cmd, + cmd_lvl, + cmd_idx++, + &p_static_entry, + &static_entry); + + if (p_static_entry == NULL) + { + break; + } + + if (0 == fnmatch(p_pattern, p_static_entry->p_syntax, 0)) + { + success = command_to_tmp_buffer_add(p_cli, + p_static_entry->p_syntax, + p_pattern); + if (!success) + { + break; + } + counter++; + } + + } while(cmd_idx != 0); + + if (counter > 0) + { + *p_counter = counter; + pattern_from_tmp_buffer_remove(p_cli, p_pattern); + + if (success) + { + ret_val = WILDCARD_CMD_ADDED; + } + else + { + ret_val = WILDCARD_CMD_ADDED_MISSING_SPACE; + } + } + + return ret_val; +} +#endif // NRF_MODULE_ENABLED(NRF_CLI_WILDCARD) + +/* Function is analyzing the command buffer to find matching commands. Next, it invokes the last recognized + * command which has a handler and passes the rest of command buffer as arguments. */ +static void cli_execute(nrf_cli_t const * p_cli) +{ + char quote; + size_t argc; + char * argv[NRF_CLI_ARGC_MAX + 1]; /* +1 reserved for NULL added by function make_argv */ + + size_t cmd_idx; /* currently analyzed command in cmd_level */ + size_t cmd_lvl = NRF_CLI_CMD_ROOT_LVL; /* currently analyzed command level */ + size_t cmd_handler_lvl = 0; /* last command level for which a handler has been found */ + size_t cmd_handler_idx = 0; /* last command index for which a handler has been found */ + + nrf_cli_cmd_entry_t const * p_cmd = NULL; + + cmd_trim(p_cli); + +#if NRF_MODULE_ENABLED(NRF_CLI_HISTORY) + history_save(p_cli); +#endif + +#if NRF_MODULE_ENABLED(NRF_CLI_WILDCARD) +/* Wildcard can be correctly handled under following conditions: + - wildcard command does not have a handler + - wildcard command is on the deepest commands level + - other commands on the same level as wildcard command shall also not have a handler + + Algorithm: + 1. Command buffer is copied to Temp buffer. + 2. Algorithm goes through Command buffer to find handlers and subcommands. + 3. If algorithm will find a wildcard character it switches to Temp buffer. + 4. In the Temp buffer command with found wildcard character is changed into matching command(s). + 5. Algorithm switch back to Command buffer and analyzes next command. + 6. When all arguments are analyzed from Command buffer, Temp buffer is copied to Command buffer. + 7. Last found handler is executed with all arguments in the Command buffer. +*/ + size_t commands_expanded = 0; + + memset(p_cli->p_ctx->temp_buff, 0, sizeof(p_cli->p_ctx->temp_buff)); + memcpy(p_cli->p_ctx->temp_buff, + p_cli->p_ctx->cmd_buff, + p_cli->p_ctx->cmd_buff_len); + + /* Function spaces_trim must be used instead of make_argv. At this point it is important to keep + temp_buff as a one string. It will allow to find wildcard commands easly with strstr + function. */ + spaces_trim(p_cli->p_ctx->temp_buff); + p_cli->p_ctx->cmd_tmp_buff_len = cli_strlen(p_cli->p_ctx->temp_buff) + 1; // +1 for EOS +#endif + + cursor_end_position_move(p_cli); + if (!cursor_in_empty_line(p_cli)) + { + cursor_next_line_move(p_cli); + } + + /* create argument list */ + quote = make_argv(&argc, + &argv[0], + p_cli->p_ctx->cmd_buff, + NRF_CLI_ARGC_MAX); + + if (!argc) + { + cursor_next_line_move(p_cli); + return; + } + + if (quote != 0) + { + nrf_cli_fprintf(p_cli, NRF_CLI_ERROR, "not terminated: %c\r\n", quote); + return; + } + + /* Searching for a matching root command. */ + for (cmd_idx = 0; cmd_idx <= CLI_DATA_SECTION_ITEM_COUNT; ++cmd_idx) + { + if (cmd_idx >= CLI_DATA_SECTION_ITEM_COUNT) + { + nrf_cli_fprintf(p_cli, + NRF_CLI_ERROR, + "%s%s\r\n", + argv[0], + NRF_CLI_MSG_COMMAND_NOT_FOUND); + return; + } + + p_cmd = CLI_DATA_SECTION_ITEM_GET(cmd_idx); + if (strcmp(argv[cmd_lvl], p_cmd->u.p_static->p_syntax) != 0) + { + continue; + } + break; + } + + /* Root command shall be always static. */ + ASSERT(p_cmd->is_dynamic == false); + + /* Pointer to the deepest command level with a handler. */ + nrf_cli_cmd_entry_t const * p_cmd_low_level_entry = NULL; + + /* Memory reserved for dynamic commands. */ + nrf_cli_static_entry_t static_entry; + nrf_cli_static_entry_t const * p_static_entry = NULL; + + nrf_cli_cmd_handler handler_cmd_lvl_0 = p_cmd->u.p_static->handler; + if (handler_cmd_lvl_0 != NULL) + { + p_cli->p_ctx->p_current_stcmd = p_cmd->u.p_static; + } + + p_cmd = p_cmd->u.p_static->p_subcmd; + cmd_lvl++; + cmd_idx = 0; + + while (1) + { + if (cmd_lvl >= argc) + { + break; + } + + if (!strcmp(argv[cmd_lvl], "-h") || !strcmp(argv[cmd_lvl], "--help")) + { + /* Command called with help option so it makes no sense to search deeper commands. */ + cli_flag_help_set(p_cli); + break; + } + +#if NRF_MODULE_ENABLED(NRF_CLI_WILDCARD) + /* Wildcard character is found */ + if (wildcard_character_exist(argv[cmd_lvl])) + { + size_t counter = 0; + wildcard_cmd_status_t status; + + /* Function will search commands tree for commands matching wildcard pattern stored in + argv[cmd_lvl]. If match is found wildcard pattern will be replaced by matching + commands in temp_buffer. If there is no space to add all matching commands function + will add as many as possible. Next it will continue to search for next wildcard + pattern and it will try to add matching commands. */ + status = commands_expand(p_cli, p_cmd, cmd_lvl, argv[cmd_lvl], &counter); + if (WILDCARD_CMD_NO_MATCH_FOUND == status) + { + break; + } + commands_expanded += counter; + cmd_lvl++; + continue; + } +#endif + + cmd_get(p_cmd, + cmd_lvl, + cmd_idx++, + &p_static_entry, + &static_entry); + + if ((cmd_idx == 0) || (p_static_entry == NULL)) + { + break; + } + + if (strcmp(argv[cmd_lvl], p_static_entry->p_syntax) == 0) + { + /* checking if command has a handler */ + if (p_static_entry->handler != NULL) + { +#if NRF_MODULE_ENABLED(NRF_CLI_WILDCARD) + if (commands_expanded > 0) + { + cursor_end_position_move(p_cli); + if (!cursor_in_empty_line(p_cli)) + { + cursor_next_line_move(p_cli); + } + /* An error occured, fnmatch argument cannot be followed by argument + * with a handler to avoid multiple function calls. */ + nrf_cli_fprintf(p_cli, + NRF_CLI_ERROR, + "Error: requested multiple function executions\r\n"); + cli_flag_help_clear(p_cli); + return; + } +#endif + /* Storing p_st_cmd->handler is not feasible for dynamic commands. Data will be + * invalid with the next loop iteration. */ + cmd_handler_lvl = cmd_lvl; + cmd_handler_idx = cmd_idx - 1; + p_cmd_low_level_entry = p_cmd; + } + cmd_lvl++; + cmd_idx = 0; + p_cmd = p_static_entry->p_subcmd; + } + } +#if NRF_MODULE_ENABLED(NRF_CLI_WILDCARD) + if (commands_expanded > 0) + { + /* Copy temp_buff to cmd_buff */ + memcpy(p_cli->p_ctx->cmd_buff, + p_cli->p_ctx->temp_buff, + p_cli->p_ctx->cmd_tmp_buff_len); + p_cli->p_ctx->cmd_buff_len = p_cli->p_ctx->cmd_tmp_buff_len; + + /* calling make_arg function again because cmd_buffer has additional commads */ + (void)make_argv(&argc, + &argv[0], + p_cli->p_ctx->cmd_buff, + NRF_CLI_ARGC_MAX); + } + #endif + + /* Executing the deepest found handler. */ + if (p_cmd_low_level_entry != NULL) + { + cmd_get(p_cmd_low_level_entry, + cmd_handler_lvl, + cmd_handler_idx, + &p_static_entry, + &static_entry); + + p_cli->p_ctx->p_current_stcmd = p_static_entry; + + p_cli->p_ctx->p_current_stcmd->handler(p_cli, + argc - cmd_handler_lvl, + &argv[cmd_handler_lvl]); + } + else if (handler_cmd_lvl_0 != NULL) + { + handler_cmd_lvl_0(p_cli, argc, &argv[0]); + } + else + { + nrf_cli_fprintf(p_cli, NRF_CLI_ERROR, NRF_CLI_MSG_SPECIFY_SUBCOMMAND); + } + cli_flag_help_clear(p_cli); +} + +/* Function required by qsort. */ +static int string_cmp(void const * pp_a, void const * pp_b) +{ + ASSERT(pp_a); + ASSERT(pp_b); + + char const ** pp_str_a = (char const **)pp_a; + char const ** pp_str_b = (char const **)pp_b; + + return strcmp(*pp_str_a, *pp_str_b); +} + +static void cli_transport_evt_handler(nrf_cli_transport_evt_t evt_type, void * p_context) +{ + nrf_cli_t * p_cli = (nrf_cli_t *)p_context; + ASSERT(p_cli); +#if NRF_MODULE_ENABLED(NRF_CLI_USES_TASK_MANAGER) + task_events_set(p_cli->p_ctx->task_id, evt_type == NRF_CLI_TRANSPORT_EVT_RX_RDY ? + NRF_CLI_TRANSPORT_RX_RDY_TASK_EVT : NRF_CLI_TRANSPORT_TX_RDY_TASK_EVT); +#else + + if (evt_type == NRF_CLI_TRANSPORT_EVT_RX_RDY) + { + + } + else + { + /* wr done evt */ + p_cli->p_ctx->internal.flag.tx_rdy = 1; + } +#endif +} + +static ret_code_t nrf_cli_instance_init(nrf_cli_t const * p_cli, + void const * p_config, + bool use_colors) +{ + ASSERT(p_cli); + ASSERT(p_cli->p_ctx && p_cli->p_iface && p_cli->p_name); + ASSERT((p_cli->newline_char == '\n') || (p_cli->newline_char == '\r')); + +#if defined(NRF_CLI_LOG_BACKEND) && NRF_CLI_LOG_BACKEND + p_cli->p_log_backend->p_cli = p_cli; +#endif + ret_code_t ret = p_cli->p_iface->p_api->init(p_cli->p_iface, + p_config, + cli_transport_evt_handler, + (void *)p_cli); + if (ret != NRF_SUCCESS) + { + return ret; + } + +#if NRF_MODULE_ENABLED(NRF_CLI_HISTORY) + ASSERT(p_cli->p_cmd_hist_mempool); + ret = nrf_memobj_pool_init(p_cli->p_cmd_hist_mempool); + if (ret != NRF_SUCCESS) + { + return ret; + } + p_cli->p_ctx->p_cmd_list_head = NULL; + p_cli->p_ctx->p_cmd_list_tail = NULL; +#endif + + memset(p_cli->p_ctx, 0, sizeof(nrf_cli_ctx_t)); + p_cli->p_ctx->internal.flag.tx_rdy = 1; + +#if NRF_MODULE_ENABLED(NRF_CLI_VT100_COLORS) + p_cli->p_ctx->internal.flag.use_colors = use_colors; +#endif + p_cli->p_ctx->internal.flag.echo = NRF_CLI_ECHO_STATUS; + p_cli->p_ctx->state = NRF_CLI_STATE_INITIALIZED; + p_cli->p_ctx->vt100_ctx.cons.terminal_wid = NRF_CLI_DEFAULT_TERMINAL_WIDTH; + p_cli->p_ctx->vt100_ctx.cons.terminal_hei = NRF_CLI_DEFAULT_TERMINAL_HEIGHT; + + const char * * pp_sorted_cmds = (const char * *)CLI_SORTED_CMD_PTRS_START_ADDR_GET; + for (size_t i = 0; i < CLI_DATA_SECTION_ITEM_COUNT; i++) + { + const nrf_cli_cmd_entry_t * cmd; + cmd = CLI_DATA_SECTION_ITEM_GET(i); + + /* NULL syntax commands not allowed. */ + ASSERT(cmd); + ASSERT(cmd->u.p_static->p_syntax); + + pp_sorted_cmds[i] = cmd->u.p_static->p_syntax; + } + + if (CLI_DATA_SECTION_ITEM_COUNT > 0) + { + qsort(pp_sorted_cmds, + CLI_DATA_SECTION_ITEM_COUNT, + sizeof (char *), + string_cmp); + } + + return NRF_SUCCESS; +} + +#if NRF_MODULE_ENABLED(NRF_CLI_USES_TASK_MANAGER) +static ret_code_t nrf_cli_instance_uninit(nrf_cli_t const * p_cli); +void console_task(void * p_context) +{ + nrf_cli_t * p_cli = (nrf_cli_t *)p_context; + + ret_code_t ret = nrf_cli_start(p_cli); + APP_ERROR_CHECK(ret); + + while (1) + { + uint32_t evts = task_events_wait(NRF_CLI_TASK_EVTS); + + if (evts & NRF_CLI_KILL_TASK_EVT) + { + (void)nrf_cli_instance_uninit(p_cli); + task_exit(); + } + else + { + nrf_cli_process(p_cli); + } + } +} +#endif + +ret_code_t nrf_cli_init(nrf_cli_t const * p_cli, + void const * p_config, + bool use_colors, + bool log_backend, + nrf_log_severity_t init_lvl) +{ + ASSERT(p_cli); + + ret_code_t err_code = nrf_cli_instance_init(p_cli, p_config, use_colors); + +#if NRF_CLI_LOG_BACKEND && NRF_MODULE_ENABLED(NRF_LOG) + if ((err_code == NRF_SUCCESS) && log_backend && NRF_CLI_LOG_BACKEND) + { + int32_t id = nrf_log_backend_add(&p_cli->p_log_backend->backend, init_lvl); + if (id < 0) + { + return NRF_ERROR_NO_MEM; + } + + nrf_log_backend_enable(&p_cli->p_log_backend->backend); + } +#endif + return err_code; +} + +ret_code_t nrf_cli_task_create(nrf_cli_t const * p_cli) +{ +#if NRF_MODULE_ENABLED(NRF_CLI_USES_TASK_MANAGER) + p_cli->p_ctx->task_id = task_create(console_task, p_cli->p_name,(void *)p_cli); + if (p_cli->p_ctx->task_id == TASK_ID_INVALID) + { + return NRF_ERROR_NO_MEM; + } + else + { + return NRF_SUCCESS; + } +#else + return NRF_ERROR_NOT_SUPPORTED; +#endif +} + +static ret_code_t nrf_cli_instance_uninit(nrf_cli_t const * p_cli) +{ + ASSERT(p_cli); + ASSERT(p_cli->p_ctx && p_cli->p_iface && p_cli->p_name); + + if (cli_flag_processing_is_set(p_cli)) + { + return NRF_ERROR_BUSY; + } + +#if NRF_CLI_LOG_BACKEND && NRF_MODULE_ENABLED(NRF_LOG) + if (p_cli->p_log_backend != NULL) + { + nrf_log_backend_disable(&p_cli->p_log_backend->backend); + nrf_log_backend_remove(&p_cli->p_log_backend->backend); + } +#endif + + ret_code_t ret = p_cli->p_iface->p_api->uninit(p_cli->p_iface); + if (ret != NRF_SUCCESS) + { + return ret; + } + +#if NRF_MODULE_ENABLED(NRF_CLI_HISTORY) + history_list_free_memory(p_cli); +#endif + + memset(p_cli->p_ctx, 0, sizeof(nrf_cli_ctx_t)); + p_cli->p_ctx->state = NRF_CLI_STATE_UNINITIALIZED; + + return NRF_SUCCESS; +} + +ret_code_t nrf_cli_uninit(nrf_cli_t const * p_cli) +{ +#if NRF_MODULE_ENABLED(NRF_CLI_USES_TASK_MANAGER) + if (cli_flag_processing_is_set(p_cli)) + { + return NRF_ERROR_BUSY; + } + task_events_set(p_cli->p_ctx->task_id, NRF_CLI_KILL_TASK_EVT); + return NRF_SUCCESS; +#else + return nrf_cli_instance_uninit(p_cli); +#endif +} + +ret_code_t nrf_cli_start(nrf_cli_t const * p_cli) +{ + ASSERT(p_cli); + ASSERT(p_cli->p_ctx && p_cli->p_iface && p_cli->p_name); + + if (p_cli->p_ctx->state != NRF_CLI_STATE_INITIALIZED) + { + return NRF_ERROR_INVALID_STATE; + } + +#if NRF_MODULE_ENABLED(NRF_CLI_USES_TASK_MANAGER) + void * p_context = (void *)((uint32_t)task_id_get()); + p_cli->p_log_backend->p_context = p_context; +#endif + + ret_code_t err_code = p_cli->p_iface->p_api->enable(p_cli->p_iface, false); + +#if NRF_MODULE_ENABLED(NRF_CLI_USES_TASK_MANAGER) + task_events_set(task_id_get(), NRF_CLI_TRANSPORT_RX_RDY_TASK_EVT); +#endif + + if (err_code == NRF_SUCCESS) + { +#if NRF_CLI_VT100_COLORS_ENABLED + vt100_color_set(p_cli, NRF_CLI_NORMAL); + vt100_bgcolor_set(p_cli, NRF_CLI_VT100_COLOR_BLACK); +#endif + nrf_fprintf(p_cli->p_fprintf_ctx, "\r\n\n"); + cli_state_set(p_cli, NRF_CLI_STATE_ACTIVE); + } + + return err_code; +} + +ret_code_t nrf_cli_stop(nrf_cli_t const * p_cli) +{ + ASSERT(p_cli); + ASSERT(p_cli->p_ctx && p_cli->p_iface && p_cli->p_name); + + if (p_cli->p_ctx->state == NRF_CLI_STATE_INITIALIZED || + p_cli->p_ctx->state == NRF_CLI_STATE_UNINITIALIZED) + { + return NRF_ERROR_INVALID_STATE; + } + + cli_state_set(p_cli, NRF_CLI_STATE_INITIALIZED); + return NRF_SUCCESS; +} + +void nrf_cli_process(nrf_cli_t const * p_cli) +{ + ASSERT(p_cli); + ASSERT(p_cli->p_ctx && p_cli->p_iface && p_cli->p_name); + + nrf_cli_internal_t internal; + internal.value = 0; + internal.flag.processing = 1; + (void)nrf_atomic_u32_or((nrf_atomic_u32_t *)&p_cli->p_ctx->internal.value, + internal.value); + + switch (p_cli->p_ctx->state) + { + case NRF_CLI_STATE_UNINITIALIZED: + case NRF_CLI_STATE_INITIALIZED: + /* Console initialized but not started. */ + break; + case NRF_CLI_STATE_ACTIVE: + { + cli_state_collect(p_cli); + bool log_processed = cli_log_entry_process(p_cli, false); + if (log_processed) + { + nrf_cli_fprintf(p_cli, NRF_CLI_INFO, "%s", p_cli->p_name); + if (cli_flag_echo_is_set(p_cli)) + { + nrf_cli_fprintf(p_cli, NRF_CLI_NORMAL, "%s", p_cli->p_ctx->cmd_buff); + cursor_position_synchronize(p_cli); + } + } + break; + } + default: + break; + } + transport_buffer_flush(p_cli); + internal.value = (uint32_t)0xFFFFFFFF; + internal.flag.processing = 0; + (void)nrf_atomic_u32_and((nrf_atomic_u32_t *)&p_cli->p_ctx->internal.value, + internal.value); +} + +/* Function shall be only used by the nrf_fprintf module. */ +void nrf_cli_print_stream(void const * p_user_ctx, char const * p_data, size_t data_len) +{ + cli_write((nrf_cli_t const *)p_user_ctx, + p_data, + data_len, + NULL); +} + +void nrf_cli_fprintf(nrf_cli_t const * p_cli, + nrf_cli_vt100_color_t color, + char const * p_fmt, + ...) +{ + ASSERT(p_fmt); + ASSERT(p_cli); + ASSERT(p_cli->p_ctx && p_cli->p_iface && p_cli->p_name); + + va_list args = {0}; + va_start(args, p_fmt); + +#if NRF_MODULE_ENABLED(NRF_CLI_VT100_COLORS) + if ((p_cli->p_ctx->internal.flag.use_colors) && + (color != p_cli->p_ctx->vt100_ctx.col.col)) + { + nrf_cli_vt100_colors_t col; + + vt100_colors_store(p_cli, &col); + vt100_color_set(p_cli, color); + + nrf_fprintf_fmt(p_cli->p_fprintf_ctx, p_fmt, &args); + + vt100_colors_restore(p_cli, &col); + } + else +#endif // NRF_MODULE_ENABLED(NRF_CLI_VT100_COLORS) + { + nrf_fprintf_fmt(p_cli->p_fprintf_ctx, p_fmt, &args); + } + + va_end(args); +} + +/* Function prints a string on terminal screen with requested margin. + * It takes care to not divide words. + * p_cli Pointer to CLI instance. + * p_str Pointer to string to be printed. + * terminal_offset Requested left margin. + * offset_first_line Add margin to the first printed line. + */ +static void format_offset_string_print(nrf_cli_t const * p_cli, + char const * p_str, + size_t terminal_offset, + bool offset_first_line) +{ + if (p_str == NULL) + { + return; + } + + if (offset_first_line) + { + cursor_right_move(p_cli, terminal_offset); + } + + size_t length; + size_t offset = 0; + + /* Skipping whitespace. */ + while (isspace((int)*(p_str + offset))) + { + ++offset; + } + + while (1) + { + size_t idx = 0; + length = cli_strlen(p_str) - offset; + + if (length <= p_cli->p_ctx->vt100_ctx.cons.terminal_wid - terminal_offset) + { + for (idx = 0; idx < length; idx++) + { + if (*(p_str + offset + idx) == '\n') + { + transport_buffer_flush(p_cli); + cli_write(p_cli, p_str + offset, idx, NULL); + offset += idx + 1; + cursor_next_line_move(p_cli); + cursor_right_move(p_cli, terminal_offset); + break; + } + } + /* String will fit in one line. */ + nrf_fprintf(p_cli->p_fprintf_ctx, p_str + offset); + break; + } + else + { + /* String is longer than terminal line so text needs to divide in the way + to not divide words. */ + length = p_cli->p_ctx->vt100_ctx.cons.terminal_wid - terminal_offset; + + while (1) + { + /* Determining line break. */ + if (isspace((int)(*(p_str + offset + idx)))) + { + length = idx; + if (*(p_str + offset + idx) == '\n') + { + break; + } + } + if ((idx + terminal_offset) >= p_cli->p_ctx->vt100_ctx.cons.terminal_wid) + { + /* End of line reached. */ + break; + } + ++idx; + } + + /* Writing one line, fprintf IO buffer must be flushed before calling cli_write. */ + transport_buffer_flush(p_cli); + cli_write(p_cli, p_str + offset, length, NULL); + offset += length; + + /* Calculating text offset to ensure that next line will not begin with a space. */ + while (isspace((int)(*(p_str + offset)))) + { + ++offset; + } + cursor_next_line_move(p_cli); + cursor_right_move(p_cli, terminal_offset); + } + } + cursor_next_line_move(p_cli); +} + +void nrf_cli_help_print(nrf_cli_t const * p_cli, + nrf_cli_getopt_option_t const * p_opt, + size_t opt_len) +{ + ASSERT(p_cli); + ASSERT(p_cli->p_ctx && p_cli->p_iface && p_cli->p_name); + + static uint8_t const tab_len = 2; + static char const opt_sep[] =", "; /* options separator */ + static char const help[] = "-h, --help"; + static char const cmd_sep[] = " - "; /* command separator */ + uint16_t field_width = 0; + uint16_t longest_string = cli_strlen(help) - cli_strlen(opt_sep); + + /* Printing help string for command. */ + nrf_cli_fprintf(p_cli, + NRF_CLI_NORMAL, + "%s%s", + p_cli->p_ctx->p_current_stcmd->p_syntax, + cmd_sep); + + field_width = cli_strlen(p_cli->p_ctx->p_current_stcmd->p_syntax) + cli_strlen(cmd_sep); + format_offset_string_print(p_cli, p_cli->p_ctx->p_current_stcmd->p_help, field_width, false); + + nrf_cli_fprintf(p_cli, NRF_CLI_NORMAL, "Options:\r\n"); + + /* Looking for the longest option string. */ + if ((opt_len > 0) && (p_opt != NULL)) + { + for (size_t i = 0; i < opt_len; ++i) + { + if (cli_strlen(p_opt[i].p_optname_short) + cli_strlen(p_opt[i].p_optname) + > longest_string) + { + longest_string = cli_strlen(p_opt[i].p_optname_short) + + cli_strlen(p_opt[i].p_optname); + } + } + } + longest_string += cli_strlen(opt_sep) + tab_len; + + nrf_cli_fprintf(p_cli, + NRF_CLI_NORMAL, + " %-*s:", + longest_string, + help); + + /* Print help string for options (only -h and --help). */ + field_width = longest_string + tab_len + 1; /* tab_len + 1 == " " and ':' from: " %-*s:" */ + format_offset_string_print(p_cli, "Show command help.", field_width, false); + + /* Formating and printing all available options (except -h, --help). */ + if (p_opt != NULL) + { + for (size_t i = 0; i < opt_len; ++i) + { + if ((p_opt[i].p_optname_short != NULL) && (p_opt[i].p_optname != NULL)) + { + nrf_cli_fprintf(p_cli, + NRF_CLI_NORMAL, + " %s%s%s", + p_opt[i].p_optname_short, + opt_sep, + p_opt[i].p_optname); + field_width = longest_string + tab_len; + cursor_right_move(p_cli, + field_width - ( cli_strlen(p_opt[i].p_optname_short) + + cli_strlen(p_opt[i].p_optname) + + tab_len + + cli_strlen(opt_sep))); + cli_putc(p_cli, ':'); + ++field_width; /* incrementing because char ':' was already printed above */ + } + else if (p_opt[i].p_optname_short != NULL) + { + nrf_cli_fprintf(p_cli, + NRF_CLI_NORMAL, + " %-*s:", + longest_string, + p_opt[i].p_optname_short); + /* tab_len + 1 == " " and ':' from: " %-*s:" */ + field_width = longest_string + tab_len + 1; + } + else if (p_opt[i].p_optname != NULL) + { + nrf_cli_fprintf(p_cli, + NRF_CLI_NORMAL, + " %-*s:", + longest_string, + p_opt[i].p_optname); + /* tab_len + 1 == " " and ':' from: " %-*s:" */ + field_width = longest_string + tab_len + 1; + } + else + { + /* Do nothing. */ + } + + if (p_opt[i].p_optname_help != NULL) + { + format_offset_string_print(p_cli, p_opt[i].p_optname_help, field_width, false); + } + else + { + cursor_next_line_move(p_cli); + } + } + } + + /* Checking if there are any subcommands avilable. */ + if (p_cli->p_ctx->p_current_stcmd->p_subcmd == NULL) + { + return; + } + + /* Printing formatted help of one level deeper subcommands. */ + nrf_cli_static_entry_t static_entry; + nrf_cli_cmd_entry_t const * p_cmd = p_cli->p_ctx->p_current_stcmd->p_subcmd; + nrf_cli_static_entry_t const * p_st_cmd = NULL; + + field_width = 0; + longest_string = 0; + + size_t cmd_idx = 0; + + /* Searching for the longest subcommand to print. */ + while (1) + { + cmd_get(p_cmd, !NRF_CLI_CMD_ROOT_LVL, cmd_idx++, &p_st_cmd, &static_entry); + + if (p_st_cmd == NULL) + { + break; + } + if (cli_strlen(p_st_cmd->p_syntax) > longest_string) + { + longest_string = cli_strlen(p_st_cmd->p_syntax); + } + } + + /* Checking if there are dynamic subcommands. */ + if (cmd_idx == 1) + { + /* No dynamic subcommands available. */ + return; + } + + nrf_cli_fprintf(p_cli, NRF_CLI_NORMAL, "Subcommands:\r\n"); + + /* Printing subcommands and help string (if exists). */ + cmd_idx = 0; + while (1) + { + cmd_get(p_cmd, !NRF_CLI_CMD_ROOT_LVL, cmd_idx++, &p_st_cmd, &static_entry); + + if (p_st_cmd == NULL) + { + break; + } + + field_width = longest_string + tab_len; + nrf_cli_fprintf(p_cli, NRF_CLI_NORMAL," %-*s:", field_width, p_st_cmd->p_syntax); + field_width += tab_len + 1; /* tab_len + 1 == " " and ':' from: " %-*s:" */ + + if (p_st_cmd->p_help != NULL) + { + format_offset_string_print(p_cli, p_st_cmd->p_help, field_width, false); + } + else + { + cursor_next_line_move(p_cli); + } + } +} + +#if NRF_CLI_LOG_BACKEND && NRF_MODULE_ENABLED(NRF_LOG) + +#define NRF_CLI_LOG_MSG_OVERFLOW_MSK ((uint32_t)7) +static bool cli_log_entry_process(nrf_cli_t const * p_cli, bool skip) +{ + nrf_log_entry_t entry; + +#if NRF_MODULE_ENABLED(NRF_CLI_STATISTICS) + bool print_msg = false; +#endif + + if (nrf_queue_pop(p_cli->p_log_backend->p_queue, &entry) != NRF_SUCCESS) + { + return false; + } + + if (skip) + { + nrf_memobj_put(entry); +#if NRF_MODULE_ENABLED(NRF_CLI_STATISTICS) + ++p_cli->p_ctx->statistics.log_lost_cnt; + if ((p_cli->p_ctx->statistics.log_lost_cnt & NRF_CLI_LOG_MSG_OVERFLOW_MSK) == 1) + { + /* Set flag to print a message after clearing the currently entered command. */ + print_msg = true; + } + else +#endif + { + return true; + } + } + { + /* Erasing the currently displayed command and console name. */ + nrf_cli_multiline_cons_t const * p_cons = multiline_console_data_check(p_cli); + + if (p_cons->cur_y > NRF_CLI_INITIAL_CURS_POS) + { + cursor_up_move(p_cli, p_cons->cur_y - NRF_CLI_INITIAL_CURS_POS); + } + + if (p_cons->cur_x > NRF_CLI_INITIAL_CURS_POS) + { + cursor_left_move(p_cli, p_cons->cur_x - NRF_CLI_INITIAL_CURS_POS); + } + cli_clear_eos(p_cli); + } + +#if NRF_MODULE_ENABLED(NRF_CLI_STATISTICS) + if (print_msg) + { + /* Print the requested string and exit function. */ + nrf_cli_fprintf(p_cli, NRF_CLI_ERROR, "Lost logs - increase log backend queue size.\r\n"); + + return true; + } +#endif + + /* Printing logs from the queue. */ + do + { + nrf_log_header_t header; + uint32_t memobj_offset = 0; + nrf_log_str_formatter_entry_params_t params; + + nrf_memobj_read(entry, &header, HEADER_SIZE*sizeof(uint32_t), memobj_offset); + memobj_offset = HEADER_SIZE * sizeof(uint32_t); + + params.timestamp = header.timestamp; + params.module_id = header.module_id; + params.dropped = header.dropped; + params.use_colors = NRF_LOG_USES_COLORS; /* Color will be provided by the console application. */ + + if (header.base.generic.type == HEADER_TYPE_STD) + { + char const * p_log_str = (char const *)((uint32_t)header.base.std.addr); + params.severity = (nrf_log_severity_t)header.base.std.severity; + uint32_t nargs = header.base.std.nargs; + uint32_t args[6]; + + nrf_memobj_read(entry, args, nargs*sizeof(uint32_t), memobj_offset); + nrf_log_std_entry_process(p_log_str, + args, + nargs, + ¶ms, + p_cli->p_fprintf_ctx); + } + else if (header.base.generic.type == HEADER_TYPE_HEXDUMP) + { + uint32_t data_len; + uint8_t data_buf[8]; + uint32_t chunk_len; + + data_len = header.base.hexdump.len; + params.severity = (nrf_log_severity_t)header.base.hexdump.severity; + + do + { + chunk_len = sizeof(data_buf) > data_len ? data_len : sizeof(data_buf); + nrf_memobj_read(entry, data_buf, chunk_len, memobj_offset); + memobj_offset += chunk_len; + data_len -= chunk_len; + nrf_log_hexdump_entry_process(data_buf, chunk_len, ¶ms, p_cli->p_fprintf_ctx); + } while (data_len > 0); + } + + nrf_memobj_put(entry); + } while (nrf_queue_pop(p_cli->p_log_backend->p_queue, &entry) == NRF_SUCCESS); + return true; +} + +static void nrf_log_backend_cli_put(nrf_log_backend_t const * p_backend, nrf_log_entry_t * p_msg) +{ + nrf_cli_log_backend_t * p_backend_cli = CONTAINER_OF(p_backend, nrf_cli_log_backend_t, backend); + nrf_cli_t const * p_cli = p_backend_cli->p_cli; + + //If panic mode cannot be handled, stop handling new requests. + if (p_cli->p_ctx->state != NRF_CLI_STATE_PANIC_MODE_INACTIVE) + { + bool panic_mode = (p_cli->p_ctx->state == NRF_CLI_STATE_PANIC_MODE_ACTIVE); + //If there is no place for a new log entry, remove the oldest one. + ret_code_t err_code = nrf_queue_push(p_backend_cli->p_queue, &p_msg); + while (err_code != NRF_SUCCESS) + { + (void)cli_log_entry_process(p_cli, panic_mode ? false : true); + + err_code = nrf_queue_push(p_backend_cli->p_queue, &p_msg); + } + nrf_memobj_get(p_msg); + + if (panic_mode) + { + (void)cli_log_entry_process(p_cli, false); + } +#if NRF_MODULE_ENABLED(NRF_CLI_USES_TASK_MANAGER) + else + { + task_events_set((task_id_t)((uint32_t)p_backend_cli->p_context & 0x000000FF), + NRF_CLI_LOG_PENDING_TASK_EVT); + } +#endif + } +} + +static void nrf_log_backend_cli_flush(nrf_log_backend_t const * p_backend) +{ + nrf_cli_log_backend_t * p_backend_cli; + nrf_cli_t const * p_cli; + nrf_log_entry_t * p_msg; + + p_backend_cli = CONTAINER_OF(p_backend, nrf_cli_log_backend_t, backend); + p_cli = p_backend_cli->p_cli; + + if (nrf_queue_pop(p_backend_cli->p_queue, &p_msg) == NRF_SUCCESS) + { + (void)cli_log_entry_process(p_cli, false); + } + UNUSED_PARAMETER(p_backend); +} + +static void nrf_log_backend_cli_panic_set(nrf_log_backend_t const * p_backend) +{ + nrf_cli_log_backend_t * p_backend_cli = CONTAINER_OF(p_backend, nrf_cli_log_backend_t, backend); + nrf_cli_t const * p_cli = p_backend_cli->p_cli; + + if (p_cli->p_iface->p_api->enable(p_cli->p_iface, true) == NRF_SUCCESS) + { + p_cli->p_ctx->state = NRF_CLI_STATE_PANIC_MODE_ACTIVE; + } + else + { + p_cli->p_ctx->state = NRF_CLI_STATE_PANIC_MODE_INACTIVE; + } +} + +const nrf_log_backend_api_t nrf_log_backend_cli_api = { + .put = nrf_log_backend_cli_put, + .flush = nrf_log_backend_cli_flush, + .panic_set = nrf_log_backend_cli_panic_set, +}; +#else +static bool cli_log_entry_process(nrf_cli_t const * p_cli, bool skip) +{ + UNUSED_PARAMETER(p_cli); + UNUSED_PARAMETER(skip); + return false; +} +#endif // NRF_CLI_LOG_BACKEND + +/* ============ built-in commands ============ */ +#if NRF_MODULE_ENABLED(NRF_CLI_BUILD_IN_CMDS) + +static bool nrf_cli_build_in_cmd_common_executed(nrf_cli_t const * p_cli, + bool arg_cnt_nok, + nrf_cli_getopt_option_t const * p_opt, + size_t opt_len) +{ + if (nrf_cli_help_requested(p_cli)) + { + nrf_cli_help_print(p_cli, p_opt, opt_len); + return true; + } + + if (arg_cnt_nok) + { + nrf_cli_fprintf(p_cli, + NRF_CLI_ERROR, + "%s: wrong parameter count\r\n", + p_cli->p_ctx->p_current_stcmd->p_syntax); + return true; + } + + return false; +} + +static void nrf_cli_cmd_clear(nrf_cli_t const * p_cli, size_t argc, char **argv) +{ + ASSERT(p_cli); + ASSERT(p_cli->p_ctx && p_cli->p_iface && p_cli->p_name); + UNUSED_PARAMETER(argv); + + if ((argc == 2) && (nrf_cli_help_requested(p_cli))) + { + nrf_cli_help_print(p_cli, NULL, 0); + return; + } + NRF_CLI_VT100_CMD(p_cli, NRF_CLI_VT100_CURSORHOME); + NRF_CLI_VT100_CMD(p_cli, NRF_CLI_VT100_CLEARSCREEN); +} + +static void nrf_cli_cmd_cli(nrf_cli_t const * p_cli, size_t argc, char **argv) +{ + ASSERT(p_cli); + ASSERT(p_cli->p_ctx && p_cli->p_iface && p_cli->p_name); + UNUSED_PARAMETER(argv); + + if ((argc == 1) || ((argc == 2) && nrf_cli_help_requested(p_cli)) ) + { + nrf_cli_help_print(p_cli, NULL, 0); + return; + } + nrf_cli_fprintf(p_cli, NRF_CLI_ERROR, NRF_CLI_MSG_SPECIFY_SUBCOMMAND); +} + +#if NRF_MODULE_ENABLED(NRF_CLI_VT100_COLORS) +static void nrf_cli_cmd_colors_off(nrf_cli_t const * p_cli, size_t argc, char **argv) +{ + if (nrf_cli_build_in_cmd_common_executed(p_cli, (argc != 1), NULL, 0)) + { + return; + } + p_cli->p_ctx->internal.flag.use_colors = 0; +} + +static void nrf_cli_cmd_colors_on(nrf_cli_t const * p_cli, size_t argc, char **argv) +{ + if (nrf_cli_build_in_cmd_common_executed(p_cli, (argc != 1), NULL, 0)) + { + return; + } + p_cli->p_ctx->internal.flag.use_colors = 1; +} + +static void nrf_cli_cmd_colors(nrf_cli_t const * p_cli, size_t argc, char **argv) +{ + ASSERT(p_cli); + ASSERT(p_cli->p_ctx && p_cli->p_iface && p_cli->p_name); + + if (argc == 1) + { + nrf_cli_help_print(p_cli, NULL, 0); + return; + } + + if (nrf_cli_build_in_cmd_common_executed(p_cli, (argc != 2), NULL, 0)) + { + return; + } + + nrf_cli_fprintf(p_cli, + NRF_CLI_ERROR, + "%s:%s%s\r\n", + argv[0], + NRF_CLI_MSG_UNKNOWN_PARAMETER, + argv[1]); +} +#endif // NRF_MODULE_ENABLED(NRF_CLI_VT100_COLORS) + +static void nrf_cli_cmd_echo(nrf_cli_t const * p_cli, size_t argc, char **argv) +{ + if (nrf_cli_build_in_cmd_common_executed(p_cli, (argc > 2), NULL, 0)) + { + return; + } + + if (argc == 2) + { + nrf_cli_fprintf(p_cli, + NRF_CLI_ERROR, + "%s:%s%s\r\n", + argv[0], + NRF_CLI_MSG_UNKNOWN_PARAMETER, + argv[1]); + return; + } + + nrf_cli_fprintf(p_cli, + NRF_CLI_NORMAL, + "Echo status: %s\r\n", + cli_flag_echo_is_set(p_cli) ? "on" : "off"); +} + +static void nrf_cli_cmd_echo_off(nrf_cli_t const * p_cli, size_t argc, char **argv) +{ + if (nrf_cli_build_in_cmd_common_executed(p_cli, (argc != 1), NULL, 0)) + { + return; + } + + cli_flag_echo_clear(p_cli); +} + +static void nrf_cli_cmd_echo_on(nrf_cli_t const * p_cli, size_t argc, char **argv) +{ + if (nrf_cli_build_in_cmd_common_executed(p_cli, (argc != 1), NULL, 0)) + { + return; + } + + cli_flag_echo_set(p_cli); +} + +#if NRF_MODULE_ENABLED(NRF_CLI_HISTORY) +static void nrf_cli_cmd_history(nrf_cli_t const * p_cli, size_t argc, char **argv) +{ + ASSERT(p_cli); + ASSERT(p_cli->p_ctx && p_cli->p_iface && p_cli->p_name); + + if (nrf_cli_build_in_cmd_common_executed(p_cli, (argc != 1), NULL, 0)) + { + return; + } + + size_t i = 0; + nrf_memobj_t const * p_cmd_list = p_cli->p_ctx->p_cmd_list_tail; + nrf_cli_memobj_header_t header; + + while (1) + { + if ((p_cmd_list == NULL) || (i >= NRF_CLI_HISTORY_ELEMENT_COUNT)) + { + break; + } + nrf_memobj_read((nrf_memobj_t * )p_cmd_list, + &header, + NRF_CLI_HISTORY_HEADER_SIZE, + (uint32_t)0); + nrf_memobj_read((nrf_memobj_t * )p_cmd_list, + p_cli->p_ctx->temp_buff, + header.cmd_len + 1, + (uint32_t)NRF_CLI_HISTORY_HEADER_SIZE); + p_cmd_list = header.p_next; + nrf_cli_fprintf(p_cli, NRF_CLI_NORMAL, "[%3d] %s\r\n", i++, p_cli->p_ctx->temp_buff); + } + p_cli->p_ctx->temp_buff[0] = '\0'; +} +#endif // NRF_MODULE_ENABLED(NRF_CLI_HISTORY) + +#if NRF_MODULE_ENABLED(NRF_CLI_STATISTICS) +void nrf_cli_cmd_cli_stats(nrf_cli_t const * p_cli, size_t argc, char **argv) +{ + if (argc == 1) + { + nrf_cli_help_print(p_cli, NULL, 0); + return; + } + + if (argc == 2) + { + nrf_cli_fprintf(p_cli, + NRF_CLI_ERROR, + "%s:%s%s\r\n", + argv[0], + NRF_CLI_MSG_UNKNOWN_PARAMETER, + argv[1]); + return; + } + + UNUSED_RETURN_VALUE(nrf_cli_build_in_cmd_common_executed(p_cli, (argc > 2), NULL, 0)); +} + +void nrf_cli_cmd_cli_stats_show(nrf_cli_t const * p_cli, size_t argc, char **argv) +{ + if (nrf_cli_build_in_cmd_common_executed(p_cli, (argc != 1), NULL, 0)) + { + return; + } + + uint8_t max_util = nrf_queue_max_utilization_get(p_cli->p_log_backend->p_queue); + uint8_t utilization = (uint8_t)(max_util * 100ul / p_cli->p_log_backend->p_queue->size); + + nrf_cli_fprintf(p_cli, + NRF_CLI_NORMAL, + "Lost logs: %u\r\n" + "Max log queue utilization: %u%% [%u/%u]\r\n", + p_cli->p_ctx->statistics.log_lost_cnt, + utilization, + max_util, + p_cli->p_log_backend->p_queue->size); +} + +void nrf_cli_cmd_cli_stats_reset(nrf_cli_t const * p_cli, size_t argc, char **argv) +{ + if (nrf_cli_build_in_cmd_common_executed(p_cli, (argc != 1), NULL, 0)) + { + return; + } + + p_cli->p_ctx->statistics.log_lost_cnt = 0; + nrf_queue_max_utilization_reset(p_cli->p_log_backend->p_queue); +} +#endif // NRF_MODULE_ENABLED(NRF_CLI_STATISTICS) + +static void nrf_cli_cmd_resize_default(nrf_cli_t const * p_cli, size_t argc, char **argv) +{ + if (nrf_cli_build_in_cmd_common_executed(p_cli, (argc != 1), NULL, 0)) + { + return; + } + + NRF_CLI_VT100_CMD(p_cli, NRF_CLI_VT100_SETCOL_80); + p_cli->p_ctx->vt100_ctx.cons.terminal_wid = NRF_CLI_DEFAULT_TERMINAL_WIDTH; + p_cli->p_ctx->vt100_ctx.cons.terminal_hei = NRF_CLI_DEFAULT_TERMINAL_HEIGHT; +} + +static void nrf_cli_cmd_resize(nrf_cli_t const * p_cli, size_t argc, char **argv) +{ + ASSERT(p_cli); + ASSERT(p_cli->p_ctx && p_cli->p_iface && p_cli->p_name); + + if (argc == 1) + { + if (terminal_size_get(p_cli, + &p_cli->p_ctx->vt100_ctx.cons.terminal_wid, + &p_cli->p_ctx->vt100_ctx.cons.terminal_hei) != NRF_SUCCESS) + { + p_cli->p_ctx->vt100_ctx.cons.terminal_wid = NRF_CLI_DEFAULT_TERMINAL_WIDTH; + p_cli->p_ctx->vt100_ctx.cons.terminal_hei = NRF_CLI_DEFAULT_TERMINAL_HEIGHT; + nrf_cli_fprintf(p_cli, + NRF_CLI_WARNING, + "No response from the terminal, assumed 80x24 screen size\r\n"); + } + return; + } + + if (nrf_cli_build_in_cmd_common_executed(p_cli, (argc > 2), NULL, 0)) + { + return; + } + + nrf_cli_fprintf(p_cli, + NRF_CLI_ERROR, + "%s:%s%s\r\n", + argv[0], + NRF_CLI_MSG_UNKNOWN_PARAMETER, + argv[1]); +} + +#if NRF_MODULE_ENABLED(NRF_CLI_VT100_COLORS) +NRF_CLI_CREATE_STATIC_SUBCMD_SET(m_sub_colors) +{ + NRF_CLI_CMD(off, NULL, NRF_CLI_HELP_COLORS_OFF, nrf_cli_cmd_colors_off), + NRF_CLI_CMD(on, NULL, NRF_CLI_HELP_COLORS_ON, nrf_cli_cmd_colors_on), + NRF_CLI_SUBCMD_SET_END +}; +#endif + +NRF_CLI_CREATE_STATIC_SUBCMD_SET(m_sub_echo) +{ + NRF_CLI_CMD(off, NULL, NRF_CLI_HELP_ECHO_OFF, nrf_cli_cmd_echo_off), + NRF_CLI_CMD(on, NULL, NRF_CLI_HELP_ECHO_ON, nrf_cli_cmd_echo_on), + NRF_CLI_SUBCMD_SET_END +}; + +#if NRF_MODULE_ENABLED(NRF_CLI_STATISTICS) +NRF_CLI_CREATE_STATIC_SUBCMD_SET(m_sub_cli_stats) +{ + NRF_CLI_CMD(reset, NULL, NRF_CLI_HELP_STATISTICS_RESET, nrf_cli_cmd_cli_stats_reset), + NRF_CLI_CMD(show, NULL, NRF_CLI_HELP_STATISTICS_SHOW, nrf_cli_cmd_cli_stats_show), + NRF_CLI_SUBCMD_SET_END +}; +#endif // NRF_MODULE_ENABLED(NRF_CLI_STATISTICS) + +NRF_CLI_CREATE_STATIC_SUBCMD_SET(m_sub_cli) +{ +#if NRF_MODULE_ENABLED(NRF_CLI_VT100_COLORS) + NRF_CLI_CMD(colors, &m_sub_colors, NRF_CLI_HELP_COLORS, nrf_cli_cmd_colors), +#endif + NRF_CLI_CMD(echo, &m_sub_echo, NRF_CLI_HELP_ECHO, nrf_cli_cmd_echo), +#if NRF_MODULE_ENABLED(NRF_CLI_STATISTICS) + NRF_CLI_CMD(stats, &m_sub_cli_stats, NRF_CLI_HELP_STATISTICS, nrf_cli_cmd_cli_stats), +#endif + NRF_CLI_SUBCMD_SET_END +}; + +NRF_CLI_CREATE_STATIC_SUBCMD_SET(m_sub_resize) +{ + NRF_CLI_CMD(default, NULL, NRF_CLI_HELP_RESIZE_DEFAULT, nrf_cli_cmd_resize_default), + NRF_CLI_SUBCMD_SET_END +}; + +NRF_CLI_CMD_REGISTER(clear, NULL, NRF_CLI_HELP_CLEAR, nrf_cli_cmd_clear); +NRF_CLI_CMD_REGISTER(cli, &m_sub_cli, NRF_CLI_HELP_CLI, nrf_cli_cmd_cli); +#if NRF_MODULE_ENABLED(NRF_CLI_HISTORY) +NRF_CLI_CMD_REGISTER(history, NULL, NRF_CLI_HELP_HISTORY, nrf_cli_cmd_history); +#endif +NRF_CLI_CMD_REGISTER(resize, &m_sub_resize, NRF_CLI_HELP_RESIZE, nrf_cli_cmd_resize); + +#endif // NRF_MODULE_ENABLED(NRF_CLI_BUILD_IN_CMDS) + +#endif // NRF_MODULE_ENABLED(NRF_CLI) + |