From 6aed72e487b5c3a8ccb5ecadab558567e16fee9c Mon Sep 17 00:00:00 2001 From: Krzysztof Chruscinski Date: Thu, 9 Aug 2018 09:56:10 +0200 Subject: [PATCH] shell: Shell subsystem reimplementation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New shell support features like: - multi-instance - command tree - static and dynamic commands - multiline - help print function - smart tab (autocompletion) - meta-keys - history, wildcards etc. - generic transport (initially, uart present) Signed-off-by: Jakub Rzeszutko Signed-off-by: Krzysztof Chruscinski Signed-off-by: Piotr Zięcik --- include/linker/common-rom.ld | 7 + include/shell/shell.h | 538 ++++++++++++ include/shell/shell_fprintf.h | 85 ++ include/shell/shell_types.h | 54 ++ include/shell/shell_uart.h | 38 + scripts/sanitycheck | 2 +- subsys/CMakeLists.txt | 1 + subsys/shell/CMakeLists.txt | 12 +- subsys/shell/Kconfig | 86 ++ subsys/shell/shell.c | 1451 +++++++++++++++++++++++++++++++++ subsys/shell/shell_fprintf.c | 52 ++ subsys/shell/shell_ops.c | 264 ++++++ subsys/shell/shell_ops.h | 128 +++ subsys/shell/shell_uart.c | 89 ++ subsys/shell/shell_utils.c | 323 ++++++++ subsys/shell/shell_utils.h | 64 ++ subsys/shell/shell_vt100.h | 589 +++++++++++++ 17 files changed, 3781 insertions(+), 2 deletions(-) create mode 100644 include/shell/shell.h create mode 100644 include/shell/shell_fprintf.h create mode 100644 include/shell/shell_types.h create mode 100644 include/shell/shell_uart.h create mode 100644 subsys/shell/shell.c create mode 100644 subsys/shell/shell_fprintf.c create mode 100644 subsys/shell/shell_ops.c create mode 100644 subsys/shell/shell_ops.h create mode 100644 subsys/shell/shell_uart.c create mode 100644 subsys/shell/shell_utils.c create mode 100644 subsys/shell/shell_utils.h create mode 100644 subsys/shell/shell_vt100.h diff --git a/include/linker/common-rom.ld b/include/linker/common-rom.ld index 59bdd0ea54c..502d82533b5 100644 --- a/include/linker/common-rom.ld +++ b/include/linker/common-rom.ld @@ -73,3 +73,10 @@ KEEP(*(".log_backends")); __log_backends_end = .; } GROUP_LINK_IN(ROMABLE_REGION) + + SECTION_DATA_PROLOGUE(shell_root_cmds_sections, (OPTIONAL),) + { + __shell_root_cmds_start = .; + KEEP(*(SORT(.shell_root_cmd_*))); + __shell_root_cmds_end = .; + } GROUP_LINK_IN(ROMABLE_REGION) diff --git a/include/shell/shell.h b/include/shell/shell.h new file mode 100644 index 00000000000..0ea58c100a0 --- /dev/null +++ b/include/shell/shell.h @@ -0,0 +1,538 @@ +/* + * Copyright (c) 2018 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef SHELL_H__ +#define SHELL_H__ + +#include +#include +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define SHELL_RX_BUFF_SIZE 16 + +#ifndef CONFIG_SHELL_CMD_BUFF_SIZE +#define CONFIG_SHELL_CMD_BUFF_SIZE 0 +#endif + +#ifndef CONFIG_SHELL_PRINTF_BUFF_SIZE +#define CONFIG_SHELL_PRINTF_BUFF_SIZE 0 +#endif + +#define SHELL_CMD_ROOT_LVL (0u) + +/* + * @defgroup shell Shell + * @ingroup subsys + * + * @brief Module for providing shell. + * + * @{ + */ + +struct shell_static_entry; + +/* + * @brief Shell dynamic command descriptor. + * + * @details Function shall fill the received shell_static_entry structure + * with requested (idx) dynamic subcommand data. If there is more than + * one dynamic subcommand available, the function shall ensure that the + * returned commands: entry->syntax are sorted in alphabetical order. + * If idx exceeds the available dynamic subcommands, the function must + * write to entry->syntax NULL value. This will indicate to the shell + * module that there are no more dynamic commands to read. + */ +typedef void (*shell_dynamic_get)(size_t idx, + struct shell_static_entry *entry); + +/* + * @brief Shell command descriptor. + */ +struct shell_cmd_entry { + bool is_dynamic; + union { + /*!< Pointer to function returning dynamic commands.*/ + shell_dynamic_get dynamic_get; + + /*!< Pointer to array of static commands. */ + const struct shell_static_entry *entry; + } u; +}; + +struct shell; + +/* + * @brief Shell command handler prototype. + */ +typedef void (*shell_cmd_handler)(const struct shell *shell, + size_t argc, char **argv); + +/* + * @brief Shell static command descriptor. + */ +struct shell_static_entry { + const char *syntax; /*!< Command syntax strings. */ + const char *help; /*!< Command help string. */ + const struct shell_cmd_entry *subcmd; /*!< Pointer to subcommand. */ + shell_cmd_handler handler; /*!< Command handler. */ +}; + +#define SHELL_CMD_NAME(name) UTIL_CAT(shell_cmd_, name) +/* + * @brief Macro for defining and adding a root command (level 0). + * + * @note Each root command shall have unique syntax. + * + * @param[in] syntax Command syntax (for example: history). + * @param[in] subcmd Pointer to a subcommands array. + * @param[in] help Pointer to a command help string. + * @param[in] handler Pointer to a function handler. + */ +#define SHELL_CMD_REGISTER(syntax, subcmd, help, handler) \ + static const struct shell_static_entry UTIL_CAT(shell_, syntax) = \ + SHELL_CMD(syntax, subcmd, help, handler); \ + static const struct shell_cmd_entry UTIL_CAT(shell_cmd_, syntax) \ + __attribute__ ((section("." \ + STRINGIFY(UTIL_CAT(shell_root_cmd_, syntax))))) \ + __attribute__((used)) = { \ + .is_dynamic = false, \ + .u.entry = &UTIL_CAT(shell_, syntax) \ + } + +/* + * @brief Macro for creating a subcommand set. It must be used outside of any + * function body. + * + * @param[in] name Name of the subcommand set. + */ +#define SHELL_CREATE_STATIC_SUBCMD_SET(name) \ + static const struct shell_static_entry shell_##name[]; \ + static const struct shell_cmd_entry name = { \ + .is_dynamic = false, \ + .u.entry = shell_##name \ + }; \ + static const struct shell_static_entry shell_##name[] = + +/* + * @brief Define ending subcommands set. + * + */ +#define SHELL_SUBCMD_SET_END {NULL} + +/* + * @brief Macro for creating a dynamic entry. + * + * @param[in] name Name of the dynamic entry. + * @param[in] get Pointer to the function returning dynamic commands array + */ +#define SHELL_CREATE_DYNAMIC_CMD(name, get) \ + static const struct shell_cmd_entry name = { \ + .is_dynamic = true, \ + .u.dynamic_get = get \ + } + +/* + * @brief Initializes a shell command. + * + * @param[in] _syntax Command syntax (for example: history). + * @param[in] _subcmd Pointer to a subcommands array. + * @param[in] _help Pointer to a command help string. + * @param[in] _handler Pointer to a function handler. + */ +#define SHELL_CMD(_syntax, _subcmd, _help, _handler) { \ + .syntax = (const char *)STRINGIFY(_syntax), \ + .subcmd = _subcmd, \ + .help = (const char *)_help, \ + .handler = _handler \ +} + +/* + * @internal @brief Internal shell state in response to data received from the + * terminal. + */ +enum shell_receive_state { + SHELL_RECEIVE_DEFAULT, + SHELL_RECEIVE_ESC, + SHELL_RECEIVE_ESC_SEQ, + SHELL_RECEIVE_TILDE_EXP +}; + + +/* + * @internal @brief Internal shell state. + */ +enum shell_state { + SHELL_STATE_UNINITIALIZED, + SHELL_STATE_INITIALIZED, + SHELL_STATE_ACTIVE, + SHELL_STATE_PANIC_MODE_ACTIVE, /*!< Panic activated.*/ + SHELL_STATE_PANIC_MODE_INACTIVE /*!< Panic requested, not supported.*/ +}; + +/* @brief Shell transport event. */ +enum shell_transport_evt { + SHELL_TRANSPORT_EVT_RX_RDY, + SHELL_TRANSPORT_EVT_TX_RDY +}; + +typedef void (*shell_transport_handler_t)(enum shell_transport_evt evt, + void *context); + +struct shell_transport; + +/* + * @brief Unified shell transport interface. + */ +struct shell_transport_api { + /* + * @brief Function for initializing the shell transport interface. + * + * @param[in] transport Pointer to the transfer instance. + * @param[in] config Pointer to instance configuration. + * @param[in] evt_handler Event handler. + * @param[in] context Pointer to the context passed to event + * handler. + * + * @return Standard error code. + */ + int (*init)(const struct shell_transport *transport, + const void *config, + shell_transport_handler_t evt_handler, + void *context); + + /* + * @brief Function for uninitializing the shell transport interface. + * + * @param[in] transport Pointer to the transfer instance. + * + * @return Standard error code. + */ + int (*uninit)(const struct shell_transport *transport); + + /* + * @brief Function for reconfiguring the transport to work in blocking + * mode. + * + * @param transport Pointer to the transfer instance. + * @param blocking If true, the transport is enabled in blocking mode. + * + * @return NRF_SUCCESS on successful enabling, error otherwise (also if + * not supported). + */ + int (*enable)(const struct shell_transport *transport, bool blocking); + + /* + * @brief Function for writing data to the transport interface. + * + * @param[in] transport Pointer to the transfer instance. + * @param[in] data Pointer to the source buffer. + * @param[in] length Source buffer length. + * @param[in] cnt Pointer to the sent bytes counter. + * + * @return Standard error code. + */ + int (*write)(const struct shell_transport *transport, + const void *data, size_t length, size_t *cnt); + + /* + * @brief Function for reading data from the transport interface. + * + * @param[in] p_transport Pointer to the transfer instance. + * @param[in] p_data Pointer to the destination buffer. + * @param[in] length Destination buffer length. + * @param[in] cnt Pointer to the received bytes counter. + * + * @return Standard error code. + */ + int (*read)(const struct shell_transport *transport, + void *data, size_t length, size_t *cnt); + +}; + +struct shell_transport { + const struct shell_transport_api *api; + void *ctx; +}; + +/* + * @internal @brief Flags for internal shell usage. + */ +struct shell_flags { + u32_t insert_mode :1; /*!< Controls insert mode for text introduction.*/ + u32_t show_help :1; /*!< Shows help if -h or --help option present.*/ + u32_t use_colors :1; /*!< Controls colored syntax.*/ + u32_t echo :1; /*!< Controls shell echo.*/ + u32_t processing :1; /*!< Shell is executing process function.*/ + u32_t tx_rdy :1; + u32_t mode_delete :1; /*!< Operation mode of backspace key */ +}; + +_Static_assert(sizeof(struct shell_flags) == sizeof(u32_t), "Must fit in 32b."); + +/* + * @internal @brief Union for internal shell usage. + */ +union shell_internal { + u32_t value; + struct shell_flags flags; +}; + +enum shell_signal { + SHELL_SIGNAL_RXRDY, + SHELL_SIGNAL_TXDONE, + SHELL_SIGNAL_KILL, + SHELL_SIGNALS +}; + +/* + * @brief Shell instance context. + */ +struct shell_ctx { + enum shell_state state; /*!< Internal module state.*/ + enum shell_receive_state receive_state;/*!< Escape sequence indicator.*/ + + /*!< Currently executed command.*/ + struct shell_static_entry active_cmd; + + /*!< VT100 color and cursor position, terminal width.*/ + struct shell_vt100_ctx vt100_ctx; + + u16_t cmd_buff_len;/*!< Command length.*/ + u16_t cmd_buff_pos; /*!< Command buffer cursor position.*/ + + u16_t cmd_tmp_buff_len; /*!< Command length in tmp buffer.*/ + + /*!< Command input buffer.*/ + char cmd_buff[CONFIG_SHELL_CMD_BUFF_SIZE]; + + /*!< Command temporary buffer.*/ + char temp_buff[CONFIG_SHELL_CMD_BUFF_SIZE]; + + /*!< Printf buffer size.*/ + char printf_buff[CONFIG_SHELL_PRINTF_BUFF_SIZE]; + + volatile union shell_internal internal; /*!< Internal shell data.*/ + + struct k_poll_signal signals[SHELL_SIGNALS]; + struct k_poll_event events[SHELL_SIGNALS]; +}; + +extern const struct log_backend_api log_backend_shell_api; + +/* + * @brief Shell instance internals. + */ +struct shell { + const char *const name; /*!< Terminal name. */ + + const struct shell_transport *iface; /*!< Transport interface.*/ + struct shell_ctx *ctx; /*!< Internal context.*/ + + const struct shell_fprintf *fprintf_ctx; + + LOG_INSTANCE_PTR_DECLARE(log); + + /*!< New line character, only allowed values: \\n and \\r.*/ + const char newline_char; + + struct k_thread *thread; + k_thread_stack_t *stack; +}; + +/* + * @brief Macro for defining a shell instance. + * + * @param[in] _name Instance name. + * @param[in] shell_prefix Shell prefix string. + * @param[in] transport_iface Pointer to the transport interface. + * @param[in] newline_ch New line character - only allowed values are + * '\\n' or '\\r'. + * @param[in] log_queue_size Logger processing queue size. + */ +#define SHELL_DEFINE(_name, shell_prefix, transport_iface, \ + newline_ch, log_queue_size) \ + static const struct shell _name; \ + static struct shell_ctx UTIL_CAT(_name, _ctx); \ + static u8_t _name##_out_buffer[CONFIG_SHELL_PRINTF_BUFF_SIZE]; \ + SHELL_FPRINTF_DEFINE(_name## _fprintf, &_name, _name##_out_buffer, \ + CONFIG_SHELL_PRINTF_BUFF_SIZE, \ + true, shell_print_stream); \ + LOG_INSTANCE_REGISTER(shell, _name, CONFIG_SHELL_LOG_LEVEL); \ + static struct k_thread _name##_thread; \ + static K_THREAD_STACK_DEFINE(_name##_stack, CONFIG_SHELL_STACK_SIZE); \ + static const struct shell _name = { \ + .name = shell_prefix, \ + .iface = transport_iface, \ + .ctx = &UTIL_CAT(_name, _ctx), \ + .fprintf_ctx = &_name##_fprintf, \ + LOG_INSTANCE_PTR_INIT(log, shell, _name) \ + .newline_char = newline_ch, \ + .thread = &_name##_thread, \ + .stack = _name##_stack \ + } + +/* + * @brief Function for initializing a transport layer and internal shell state. + * + * @param[in] shell Pointer to shell instance. + * @param[in] transport_config Transport configuration during initialization. + * @param[in] use_colors Enables colored prints. + * @param[in] log_backend If true, the console will be used as logger + * backend. + * @param[in] init_log_level Default severity level for the logger. + * + * @return Standard error code. + */ +int shell_init(const struct shell *shell, const void *transport_config, + bool use_colors, bool log_backend, u32_t init_log_level); + +/* + * @brief Uninitializes the transport layer and the internal shell state. + * + * @param shell Pointer to shell instance. + * + * @return Standard error code. + */ +int shell_uninit(const struct shell *shell); + +/* + * @brief Function for starting shell processing. + * + * @param shell Pointer to the shell instance. + * + * @return Standard error code. + */ +int shell_start(const struct shell *shell); + +/* + * @brief Function for stopping shell processing. + * + * @param shell Pointer to shell instance. + * + * @return Standard error code. + */ +int shell_stop(const struct shell *shell); + +/* + * @brief Shell colors for nrf_shell_fprintf function. + */ +#define SHELL_NORMAL SHELL_VT100_COLOR_DEFAULT +#define SHELL_INFO SHELL_VT100_COLOR_GREEN +#define SHELL_OPTION SHELL_VT100_COLOR_CYAN +#define SHELL_WARNING SHELL_VT100_COLOR_YELLOW +#define SHELL_ERROR SHELL_VT100_COLOR_RED + +/* + * @brief Printf-like function which sends formatted data stream to the shell. + * This function shall not be used outside of the shell command context. + * + * @param[in] shell Pointer to the shell instance. + * @param[in] color Printf color. + * @param[in] p_fmt Format string. + * @param[in] ... List of parameters to print. + */ +void shell_fprintf(const struct shell *shell, enum shell_vt100_color color, + const char *p_fmt, ...); + +/* + * @brief Process function, which should be executed when data is ready in the + * transport interface. To be used if shell thread is disabled. + * + * @param[in] shell Pointer to the shell instance. + */ +void shell_process(const struct shell *shell); + +/* + * @brief Option descriptor. + */ +struct shell_getopt_option { + const char *optname; /*!< Option long name.*/ + const char *optname_short; /*!< Option short name.*/ + const char *optname_help; /*!< Option help string.*/ +}; + +/* + * @brief Option structure initializer. + * + * @param[in] _optname Option name long. + * @param[in] _shortname Option name short. + * @param[in] _help Option help string. + */ +#define SHELL_OPT(_optname, _shortname, _help) { \ + .optname = _optname, \ + .optname_short = _shortname, \ + .optname_help = _help, \ + } + +/* + * @brief Informs that a command has been called with -h or --help option. + * + * @param[in] shell Pointer to the shell instance. + * + * @return True if help has been requested. + */ +static inline bool shell_help_requested(const struct shell *shell) +{ + return shell->ctx->internal.flags.show_help; +} + +/* + * @brief Prints the current command help. + * + * Function will print a help string with: the currently entered command, its + * options,and subcommands (if they exist). + * + * @param[in] shell Pointer to the shell instance. + * @param[in] opt Pointer to the optional option array. + * @param[in] opt_len Option array size. + */ +void shell_help_print(const struct shell *shell, + const struct shell_getopt_option *opt, size_t opt_len); + +/* + * @brief Prints help if request and prints error message on wrong argument + * count. + * + * Optionally, printing help on wrong argument count can be enabled. + * + * @param[in] shell Pointer to the shell instance. + * @param[in] arg_cnt_ok Flag indicating valid number of arguments. + * @param[in] opt Pointer to the optional option array. + * @param[in] opt_len Option array size. + * + * @return True if check passed, false otherwise or help was requested. + */ +bool shell_cmd_precheck(const struct shell *shell, + bool arg_cnt_nok, + const struct shell_getopt_option *opt, + size_t opt_len); + +/* + * @internal @brief This function shall not be used directly, it is required by + * the fprintf module. + * + * @param[in] p_user_ctx Pointer to the context for the shell instance. + * @param[in] p_data Pointer to the data buffer. + * @param[in] data_len Data buffer size. + */ +void shell_print_stream(const void *user_ctx, const char *data, + size_t data_len); + +/* @} */ + +#ifdef __cplusplus +} +#endif + +#endif /* SHELL_H__ */ diff --git a/include/shell/shell_fprintf.h b/include/shell/shell_fprintf.h new file mode 100644 index 00000000000..a92c207e5a4 --- /dev/null +++ b/include/shell/shell_fprintf.h @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2018 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef SHELL_FPRINTF_H__ +#define SHELL_FPRINTF_H__ + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef void (*shell_fprintf_fwrite)(const void *user_ctx, + const char *data, + size_t length); + +struct shell_fprintf_control_block { + size_t buffer_cnt; + bool autoflush; +}; +/** + * @brief fprintf context + */ +struct shell_fprintf { + u8_t *buffer; + size_t buffer_size; + shell_fprintf_fwrite fwrite; + const void *user_ctx; + struct shell_fprintf_control_block *ctrl_blk; +}; + + +/** + * @brief Macro for defining shell_fprintf instance. + * + * @param _name Instance name. + * @param _user_ctx Pointer to user data. + * @param _buf Pointer to output buffer + * @param _size Size of output buffer. + * @param _autoflush Indicator if buffer shall be automatically flush. + * @param _fwrite Pointer to function sending data stream. + */ +#define SHELL_FPRINTF_DEFINE(_name, _user_ctx, _buf, _size, \ + _autoflush, _fwrite) \ + static struct shell_fprintf_control_block \ + _name##_shell_fprintf_ctx = { \ + .autoflush = _autoflush, \ + .buffer_cnt = 0 \ + }; \ + static const struct shell_fprintf _name = { \ + .buffer = _buf, \ + .buffer_size = _size, \ + .fwrite = _fwrite, \ + .user_ctx = _user_ctx, \ + .ctrl_blk = &_name##_shell_fprintf_ctx \ + } + +/** + * @brief fprintf like function which send formated data stream to output. + * + * @param sh_fprintf fprintf instance. + * @param fmt Format string. + * @param args List of parameters to print. + */ +void shell_fprintf_fmt(const struct shell_fprintf *sh_fprintf, + char const *fmt, va_list args); + +/** + * @brief function flushing data stored in io_buffer. + * + * @param sh_fprintf fprintf instance + */ +void shell_fprintf_buffer_flush(const struct shell_fprintf *sh_fprintf); + + +#ifdef __cplusplus +} +#endif + +#endif /* SHELL_FPRINTF_H__ */ diff --git a/include/shell/shell_types.h b/include/shell/shell_types.h new file mode 100644 index 00000000000..9a34c05c706 --- /dev/null +++ b/include/shell/shell_types.h @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2018 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef SHELL_TYPES_H__ +#define SHELL_TYPES_H__ + + +#ifdef __cplusplus +extern "C" { +#endif + +enum shell_vt100_color { + SHELL_VT100_COLOR_DEFAULT, + SHELL_VT100_COLOR_BLACK, + SHELL_VT100_COLOR_RED, + SHELL_VT100_COLOR_GREEN, + SHELL_VT100_COLOR_YELLOW, + SHELL_VT100_COLOR_BLUE, + SHELL_VT100_COLOR_MAGENTA, + SHELL_VT100_COLOR_CYAN, + SHELL_VT100_COLOR_WHITE, + + VT100_COLOR_END +}; + +struct shell_vt100_colors { + enum shell_vt100_color col; /* Text color. */ + enum shell_vt100_color bgcol; /* Background color. */ +}; + +struct shell_multiline_cons { + u16_t cur_x; /* horizontal cursor position in edited command line.*/ + u16_t cur_x_end; /* horizontal cursor position at the end of command.*/ + u16_t cur_y; /* vertical cursor position in edited command.*/ + u16_t cur_y_end; /* vertical cursor position at the end of command.*/ + u16_t terminal_hei; /* terminal screen height.*/ + u16_t terminal_wid; /* terminal screen width.*/ + u8_t name_len; /*! + +#ifdef __cplusplus +extern "C" { +#endif + +extern const struct shell_transport_api shell_uart_transport_api; + +struct shell_uart { + struct device *dev; + shell_transport_handler_t handler; + struct k_timer timer; + void *context; + u8_t rx[1]; + size_t rx_cnt; +}; + +#define SHELL_UART_DEFINE(_name) \ + static struct shell_uart _name##_shell_uart; \ + struct shell_transport _name = { \ + .api = &shell_uart_transport_api, \ + .ctx = (struct shell_uart *)&_name##_shell_uart \ + } + +#ifdef __cplusplus +} +#endif + +#endif /* SHELL_UART_H__ */ diff --git a/scripts/sanitycheck b/scripts/sanitycheck index 522c5c9b65f..fc47ab6d1c3 100755 --- a/scripts/sanitycheck +++ b/scripts/sanitycheck @@ -654,7 +654,7 @@ class SizeCalculator: "kobject_data", "mmu_tables", "app_pad", "priv_stacks", "ccm_data", "usb_descriptor", "usb_data", "usb_bos_desc", 'log_backends_sections', 'log_dynamic_sections', - 'log_const_sections',"app_smem"] + 'log_const_sections',"app_smem", 'shell_root_cmds_sections'] # These get copied into RAM only on non-XIP ro_sections = ["text", "ctors", "init_array", "reset", "object_access", "rodata", "devconfig", "net_l2", "vector", "_bt_settings_area"] diff --git a/subsys/CMakeLists.txt b/subsys/CMakeLists.txt index 50b74b66975..de436f0a0ff 100644 --- a/subsys/CMakeLists.txt +++ b/subsys/CMakeLists.txt @@ -4,6 +4,7 @@ add_subdirectory(logging) add_subdirectory_ifdef(CONFIG_BT bluetooth) add_subdirectory_ifdef(CONFIG_CONSOLE_SUBSYS console) add_subdirectory_ifdef(CONFIG_CONSOLE_SHELL shell) +add_subdirectory_ifdef(CONFIG_SHELL shell) add_subdirectory_ifdef(CONFIG_CPLUSPLUS cpp) add_subdirectory_ifdef(CONFIG_DISK_ACCESS disk) add_subdirectory(fs) diff --git a/subsys/shell/CMakeLists.txt b/subsys/shell/CMakeLists.txt index 4137203e52a..63c4f3bbee3 100644 --- a/subsys/shell/CMakeLists.txt +++ b/subsys/shell/CMakeLists.txt @@ -2,9 +2,19 @@ zephyr_include_directories_ifdef(CONFIG_CONSOLE_SHELL ${ZEPHYR_BASE}/include/drivers ) -zephyr_sources( +zephyr_sources_ifdef( + CONFIG_CONSOLE_SHELL shell_service.c legacy_shell.c ) add_subdirectory(modules) + +zephyr_sources_ifdef( + CONFIG_SHELL + shell.c + shell_fprintf.c + shell_utils.c + shell_ops.c + shell_uart.c + ) \ No newline at end of file diff --git a/subsys/shell/Kconfig b/subsys/shell/Kconfig index 05ca22db93c..fbb56e41c46 100644 --- a/subsys/shell/Kconfig +++ b/subsys/shell/Kconfig @@ -33,3 +33,89 @@ config CONSOLE_SHELL_MAX_CMD_QUEUED source "subsys/shell/modules/Kconfig" endif + +config SHELL + bool "Enable shell" + select LOG_RUNTIME_FILTERING + select POLL + +if SHELL + +module = SHELL +module-str = Shell +source "subsys/logging/Kconfig.template.log_config" + +config SHELL_STACK_SIZE + int "Shell thread stack size" + default 1024 if MULTITHREADING + default 0 if !MULTITHREADING + help + Stack size for thread created for each instance. + +config SHELL_THREAD_PRIO + int "Shell thread priority" + depends on MULTITHREADING + default -2 + help + Shell thread priority. + +config SHELL_BACKSPACE_MODE_DELETE + bool "Default escape code for backspace is DELETE (0x7F)" + default y + help + Terminals have different escape code settings for backspace button. + Some terminals send code: 0x08 (backspace) other 0x7F (delete). When + this option is set shell will expect 0x7F for backspace key. + +config SHELL_CMD_BUFF_SIZE + int "Shell command buffer size" + default 256 + help + Maximum command size. + +config SHELL_PRINTF_BUFF_SIZE + int "Shell print buffer size" + default 30 + help + Maximum text buffer size for fprintf function. + It is working like stdio buffering in Linux systems + to limit number of peripheral access calls. + +config SHELL_ARGC_MAX + int "Maximum arguments in shell command" + default 12 + help + Maximum number of arguments that can build a command. + If command is composed of more than defined, argument SHELL_ARGC_MAX + and following are passed as one argument in the string. + +config SHELL_ECHO_STATUS + bool "Enable echo on shell" + default y + help + If enabled shell prints back every input byte. + +config SHELL_VT100_COLORS + bool "Enable colors in shell" + default y + help + If enabled VT100 colors are used in shell (e.g. print errors in red). + +config SHELL_METAKEYS + bool "Enable metakeys" + default y + help + Enables shell metakeys: Home, End, ctrl+a, ctrl+c, ctrl+e, ctrl+l, + ctrl+u, ctrl+w + +config SHELL_HELP + bool "Enable help message" + default y + help + Enables formatting help message when requested with '-h' or '--help'. + +config SHELL_HELP_ON_WRONG_ARGUMENT_COUNT + bool "Enable printing help on wrong argument count" + default y + +endif #SHELL diff --git a/subsys/shell/shell.c b/subsys/shell/shell.c new file mode 100644 index 00000000000..284fd110e50 --- /dev/null +++ b/subsys/shell/shell.c @@ -0,0 +1,1451 @@ +/* + * Copyright (c) 2018 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include "shell_utils.h" +#include "shell_ops.h" +#include "shell_vt100.h" +#include +#include + +/* 2 == 1 char for cmd + 1 char for '\0' */ +#if (CONFIG_SHELL_CMD_BUFF_SIZE < 2) + #error too small CONFIG_SHELL_CMD_BUFF_SIZE +#endif + +#if (CONFIG_SHELL_PRINTF_BUFF_SIZE < 1) + #error too small SHELL_PRINTF_BUFF_SIZE +#endif + +#define SHELL_MSG_COMMAND_NOT_FOUND ": command not found" +#define SHELL_MSG_TAB_OVERFLOWED \ + "Tab function: commands counter overflowed.\r\n" + +#define SHELL_INIT_OPTION_PRINTER (NULL) + +/* Initial cursor position is: (1, 1). */ +#define SHELL_INITIAL_CURS_POS (1u) + +static void shell_execute(const struct shell *shell); + +extern const struct shell_cmd_entry __shell_root_cmds_start[0]; +extern const struct shell_cmd_entry __shell_root_cmds_end[0]; + +static inline const struct shell_cmd_entry *shell_root_cmd_get(u32_t id) +{ + return &__shell_root_cmds_start[id]; +} + +static inline u32_t shell_root_cmd_count(void) +{ + return ((void *)__shell_root_cmds_end - + (void *)__shell_root_cmds_start)/ + sizeof(struct shell_cmd_entry); +} + +static inline void transport_buffer_flush(const struct shell *shell) +{ + shell_fprintf_buffer_flush(shell->fprintf_ctx); +} + +static inline void help_flag_set(const struct shell *shell) +{ + shell->ctx->internal.flags.show_help = 1; +} +static inline void help_flag_clear(const struct shell *shell) +{ + shell->ctx->internal.flags.show_help = 0; +} + +/* Function returns true if delete escape code shall be interpreted as + * backspace. + */ +static inline bool flag_delete_mode_set(const struct shell *shell) +{ + return shell->ctx->internal.flags.mode_delete == 1 ? true : false; +} + +static inline bool flag_processing_is_set(const struct shell *shell) +{ + return shell->ctx->internal.flags.processing == 1 ? true : false; +} + +static inline void receive_state_change(const struct shell *shell, + enum shell_receive_state state) +{ + shell->ctx->receive_state = state; +} + +static void shell_cmd_buffer_clear(const struct shell *shell) +{ + shell->ctx->cmd_buff[0] = '\0'; /* clear command buffer */ + shell->ctx->cmd_buff_pos = 0; + shell->ctx->cmd_buff_len = 0; +} + +/* Function sends data stream to the shell instance. Each time before the + * shell_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(shell) + */ +static void shell_write(const struct shell *shell, const void *data, + size_t length) +{ + assert(shell && data); + + size_t offset = 0; + size_t tmp_cnt; + + while (length) { + int err = shell->iface->api->write(shell->iface, + &((const u8_t *) data)[offset], length, + &tmp_cnt); + (void)err; + assert(err == 0); + assert(length >= tmp_cnt); + offset += tmp_cnt; + length -= tmp_cnt; + if (tmp_cnt == 0 && + (shell->ctx->state != SHELL_STATE_PANIC_MODE_ACTIVE)) { + /* todo semaphore pend*/ + if (IS_ENABLED(CONFIG_MULTITHREADING)) { + k_poll(&shell->ctx->events[SHELL_SIGNAL_TXDONE], + 1, K_FOREVER); + } else { + /* Blocking wait in case of bare metal. */ + while (!shell->ctx->internal.flags.tx_rdy) { + + } + shell->ctx->internal.flags.tx_rdy = 0; + } + } + } +} + +/* @brief Function shall be used to search commands. + * + * It moves the pointer entry to command of static command structure. If the + * command cannot be found, the function will set entry to NULL. + * + * @param command Pointer to command which will be processed (no matter + * the root command). + * @param lvl Level of the requested command. + * @param idx Index of the requested command. + * @param entry Pointer which points to subcommand[idx] after function + * execution. + * @param st_entry Pointer to the structure where dynamic entry data can be + * stored. + */ +static void cmd_get(const struct shell_cmd_entry *command, size_t lvl, + size_t idx, const struct shell_static_entry **entry, + struct shell_static_entry *d_entry) +{ + assert(entry != NULL); + assert(command != NULL); + assert(d_entry != NULL); + + if (lvl == SHELL_CMD_ROOT_LVL) { + if (idx < shell_root_cmd_count()) { + const struct shell_cmd_entry *cmd; + + cmd = shell_root_cmd_get(idx); + *entry = cmd->u.entry; + } else { + *entry = NULL; + } + return; + } + + if (command == NULL) { + *entry = NULL; + return; + } + + if (command->is_dynamic) { + command->u.dynamic_get(idx, d_entry); + *entry = (d_entry->syntax != NULL) ? d_entry : NULL; + } else { + *entry = (command->u.entry[idx].syntax != NULL) ? + &command->u.entry[idx] : NULL; + } +} + +static void vt100_color_set(const struct shell *shell, + enum shell_vt100_color color) +{ + + if (shell->ctx->vt100_ctx.col.col == color) { + return; + } + + shell->ctx->vt100_ctx.col.col = color; + + if (color != SHELL_NORMAL) { + + u8_t cmd[] = SHELL_VT100_COLOR(color - 1); + + shell_raw_fprintf(shell->fprintf_ctx, "%s", cmd); + } else { + static const u8_t cmd[] = SHELL_VT100_MODESOFF; + + shell_raw_fprintf(shell->fprintf_ctx, "%s", cmd); + } +} + +static void vt100_bgcolor_set(const struct shell *shell, + enum shell_vt100_color bgcolor) +{ + if ((bgcolor == SHELL_NORMAL) || + (shell->ctx->vt100_ctx.col.bgcol == bgcolor)) { + return; + } + + /* -1 because default value is first in enum */ + u8_t cmd[] = SHELL_VT100_BGCOLOR(bgcolor - 1); + + shell->ctx->vt100_ctx.col.bgcol = bgcolor; + shell_raw_fprintf(shell->fprintf_ctx, "%s", cmd); + +} + +static inline void vt100_colors_store(const struct shell *shell, + struct shell_vt100_colors *color) +{ + memcpy(color, &shell->ctx->vt100_ctx.col, sizeof(*color)); +} + +static void vt100_colors_restore(const struct shell *shell, + const struct shell_vt100_colors *color) +{ + vt100_color_set(shell, color->col); + vt100_bgcolor_set(shell, color->bgcol); +} + +static void shell_state_set(const struct shell *shell, enum shell_state state) +{ + shell->ctx->state = state; + + if (state == SHELL_STATE_ACTIVE) { + shell_cmd_buffer_clear(shell); + shell_fprintf(shell, SHELL_INFO, "%s", shell->name); + } +} + +static void tab_item_print(const struct shell *shell, const char *option, + u16_t longest_option) +{ + static const char *tab = " "; + u16_t columns; + u16_t diff; + + /* Function initialization has been requested. */ + if (option == NULL) { + shell->ctx->vt100_ctx.printed_cmd = 0; + return; + } + + longest_option += shell_strlen(tab); + + columns = (shell->ctx->vt100_ctx.cons.terminal_wid + - shell_strlen(tab)) / longest_option; + diff = longest_option - shell_strlen(option); + + if (shell->ctx->vt100_ctx.printed_cmd++ % columns == 0) { + shell_fprintf(shell, SHELL_OPTION, "\r\n%s%s", tab, option); + } else { + shell_fprintf(shell, SHELL_OPTION, "%s", option); + } + + shell_op_cursor_horiz_move(shell, diff); +} + +static const struct shell_static_entry *find_cmd( + const struct shell_cmd_entry *cmd, + size_t lvl, + char *cmd_str, + struct shell_static_entry *d_entry) +{ + const struct shell_static_entry *entry = NULL; + size_t idx = 0; + + do { + cmd_get(cmd, lvl, idx++, &entry, d_entry); + if (entry && (strcmp(cmd_str, entry->syntax) == 0)) { + return entry; + } + } while (entry); + + return entry; +} + +/** @brief Function for getting last valid command in list of arguments. */ +static const struct shell_static_entry *get_last_command( + const struct shell *shell, + size_t argc, + char *argv[], + size_t *match_arg, + struct shell_static_entry *d_entry) +{ + const struct shell_static_entry *prev_entry = NULL; + const struct shell_cmd_entry *prev_cmd = NULL; + const struct shell_static_entry *entry = NULL; + *match_arg = SHELL_CMD_ROOT_LVL; + + while (*match_arg < argc) { + entry = find_cmd(prev_cmd, *match_arg, argv[*match_arg], + d_entry); + if (entry) { + prev_cmd = entry->subcmd; + prev_entry = entry; + (*match_arg)++; + } else { + entry = NULL; + break; + } + } + + return entry; +} + +static inline u16_t completion_space_get(const struct shell *shell) +{ + u16_t space = (CONFIG_SHELL_CMD_BUFF_SIZE - 1) - + shell->ctx->cmd_buff_len; + return space; +} + +/* Prepare arguments and return number of space available for completion. */ +static bool shell_tab_prepare(const struct shell *shell, + const struct shell_static_entry **cmd, + char **argv, size_t *argc, + size_t *complete_arg_idx, + struct shell_static_entry *d_entry) +{ + u16_t compl_space = completion_space_get(shell); + size_t search_argc; + + if (compl_space == 0) { + return false; + } + + /* Copy command from its beginning to cursor position. */ + memcpy(shell->ctx->temp_buff, shell->ctx->cmd_buff, + shell->ctx->cmd_buff_pos); + shell->ctx->temp_buff[shell->ctx->cmd_buff_pos] = '\0'; + + /* Create argument list. */ + (void)shell_make_argv(argc, argv, shell->ctx->temp_buff, + CONFIG_SHELL_ARGC_MAX); + + /* If last command is not completed (followed by space) it is treated + * as uncompleted one. + */ + int space = isspace((int)shell->ctx->cmd_buff[ + shell->ctx->cmd_buff_pos - 1]); + + /* root command completion */ + if ((*argc == 0) || ((space == 0) && (*argc == 1))) { + *complete_arg_idx = SHELL_CMD_ROOT_LVL; + *cmd = NULL; + return true; + } + + search_argc = space ? *argc : *argc - 1; + + *cmd = get_last_command(shell, search_argc, argv, complete_arg_idx, + d_entry); + + /* if search_argc == 0 (empty command line) get_last_command will return + * NULL tab is allowed, otherwise not. + */ + if ((*cmd == NULL) && (search_argc != 0)) { + return false; + } + + return true; +} + +static inline bool is_completion_candidate(const char *candidate, + const char *str, size_t len) +{ + return (strncmp(candidate, str, len) == 0) ? true : false; +} + +static void find_completion_candidates(const struct shell_static_entry *cmd, + const char *incompl_cmd, + size_t *first_idx, size_t *cnt, + u16_t *longest) +{ + size_t incompl_cmd_len = shell_strlen(incompl_cmd); + const struct shell_static_entry *candidate; + struct shell_static_entry dynamic_entry; + bool found = false; + size_t idx = 0; + + *longest = 0; + *cnt = 0; + + while (true) { + cmd_get(cmd ? cmd->subcmd : NULL, cmd ? 1 : 0, + idx, &candidate, &dynamic_entry); + + if (!candidate) { + break; + } + + if (is_completion_candidate(candidate->syntax, incompl_cmd, + incompl_cmd_len)) { + size_t slen = strlen(candidate->syntax); + + *longest = (slen > *longest) ? slen : *longest; + (*cnt)++; + + if (!found) { + *first_idx = idx; + } + + found = true; + } else { + if (found) { + break; + } + } + idx++; + } +} + +static void autocomplete(const struct shell *shell, + const struct shell_static_entry *cmd, + const char *arg, + size_t subcmd_idx) +{ + const struct shell_static_entry *match; + size_t arg_len = shell_strlen(arg); + size_t cmd_len; + + /* shell->ctx->active_cmd can be safely used outside of command context + * to save stack + */ + cmd_get(cmd ? cmd->subcmd : NULL, cmd ? 1 : 0, + subcmd_idx, &match, &shell->ctx->active_cmd); + cmd_len = shell_strlen(match->syntax); + + /* no exact match found */ + if (cmd_len != arg_len) { + shell_op_completion_insert(shell, + match->syntax + arg_len, + cmd_len - arg_len); + } + + /* Next character in the buffer is not 'space'. */ + if (!isspace((int) shell->ctx->cmd_buff[ + shell->ctx->cmd_buff_pos])) { + if (shell->ctx->internal.flags.insert_mode) { + shell->ctx->internal.flags.insert_mode = 0; + shell_op_char_insert(shell, ' '); + shell->ctx->internal.flags.insert_mode = 1; + } else { + shell_op_char_insert(shell, ' '); + } + } else { + /* case: + * | | -> cursor + * cons_name $: valid_cmd valid_sub_cmd| |argument + */ + shell_op_cursor_move(shell, 1); + /* result: + * cons_name $: valid_cmd valid_sub_cmd |a|rgument + */ + } +} + +static size_t shell_str_common(const char *s1, const char *s2, size_t n) +{ + size_t common = 0; + + while ((n > 0) && (*s1 == *s2) && (*s1 != '\0')) { + s1++; + s2++; + n--; + common++; + } + + return common; +} + +static void tab_options_print(const struct shell *shell, + const struct shell_static_entry *cmd, + size_t first, size_t cnt, u16_t longest) +{ + const struct shell_static_entry *match; + size_t idx = first; + + /* Printing all matching commands (options). */ + tab_item_print(shell, SHELL_INIT_OPTION_PRINTER, longest); + + while (cnt) { + /* shell->ctx->active_cmd can be safely used outside of command + * context to save stack + */ + cmd_get(cmd ? cmd->subcmd : NULL, cmd ? 1 : 0, + idx, &match, &shell->ctx->active_cmd); + tab_item_print(shell, match->syntax, longest); + cnt--; + idx++; + } + + shell_fprintf(shell, SHELL_INFO, "\r\n%s", shell->name); + shell_fprintf(shell, SHELL_NORMAL, "%s", shell->ctx->cmd_buff); + + shell_op_cursor_position_synchronize(shell); +} + +static u16_t common_beginning_find(const struct shell_static_entry *cmd, + const char **str, + size_t first, size_t cnt) +{ + struct shell_static_entry dynamic_entry; + const struct shell_static_entry *match; + u16_t common = UINT16_MAX; + + + cmd_get(cmd ? cmd->subcmd : NULL, cmd ? 1 : 0, + first, &match, &dynamic_entry); + + *str = match->syntax; + + for (size_t idx = first + 1; idx < first + cnt; idx++) { + struct shell_static_entry dynamic_entry2; + const struct shell_static_entry *match2; + int curr_common; + + cmd_get(cmd ? cmd->subcmd : NULL, cmd ? 1 : 0, + idx, &match2, &dynamic_entry2); + + curr_common = shell_str_common(match->syntax, match2->syntax, + UINT16_MAX); + common = (curr_common < common) ? curr_common : common; + } + + return common; +} + +static void partial_autocomplete(const struct shell *shell, + const struct shell_static_entry *cmd, + const char *arg, + size_t first, size_t cnt) +{ + const char *completion; + u16_t common = common_beginning_find(cmd, &completion, first, cnt); + int arg_len = shell_strlen(arg); + + if (common) { + shell_op_completion_insert(shell, &completion[arg_len], + common - arg_len); + } +} + +static void shell_tab_handle(const struct shell *shell) +{ + /* +1 reserved for NULL in function shell_make_argv */ + char *argv[CONFIG_SHELL_ARGC_MAX + 1]; + /* d_entry - placeholder for dynamic command */ + struct shell_static_entry d_entry; + const struct shell_static_entry *cmd; + size_t arg_idx; + u16_t longest; + size_t first; + size_t argc; + size_t cnt; + + + bool tab_possible = shell_tab_prepare(shell, &cmd, argv, &argc, + &arg_idx, &d_entry); + + if (tab_possible == false) { + return; + } + + find_completion_candidates(cmd, argv[arg_idx], &first, &cnt, &longest); + + if (!cnt) { + /* No candidates to propose. */ + return; + } else if (cnt == 1) { + /* Autocompletion.*/ + autocomplete(shell, cmd, argv[arg_idx], first); + } else { + tab_options_print(shell, cmd, first, cnt, longest); + partial_autocomplete(shell, cmd, argv[arg_idx], first, cnt); + } +} + +#define SHELL_ASCII_MAX_CHAR (127u) +static inline int ascii_filter(const char data) +{ + return (u8_t) data > SHELL_ASCII_MAX_CHAR ? + -EINVAL : 0; +} + +static void metakeys_handle(const struct shell *shell, char data) +{ + /* Optional feature */ + if (!IS_ENABLED(CONFIG_SHELL_METAKEYS)) { + return; + } + + switch (data) { + case SHELL_VT100_ASCII_CTRL_A: /* CTRL + A */ + shell_op_cursor_home_move(shell); + break; + + case SHELL_VT100_ASCII_CTRL_C: /* CTRL + C */ + shell_op_cursor_end_move(shell); + if (!shell_cursor_in_empty_line(shell)) { + cursor_next_line_move(shell); + } + shell_state_set(shell, SHELL_STATE_ACTIVE); + break; + + case SHELL_VT100_ASCII_CTRL_E: /* CTRL + E */ + shell_op_cursor_end_move(shell); + break; + + case SHELL_VT100_ASCII_CTRL_L: /* CTRL + L */ + SHELL_VT100_CMD(shell, SHELL_VT100_CURSORHOME); + SHELL_VT100_CMD(shell, SHELL_VT100_CLEARSCREEN); + shell_fprintf(shell, SHELL_INFO, "%s", shell->name); + if (flag_echo_is_set(shell)) { + shell_fprintf(shell, SHELL_NORMAL, "%s", + shell->ctx->cmd_buff); + shell_op_cursor_position_synchronize(shell); + } + break; + + case SHELL_VT100_ASCII_CTRL_U: /* CTRL + U */ + shell_op_cursor_home_move(shell); + shell_cmd_buffer_clear(shell); + clear_eos(shell); + break; + + case SHELL_VT100_ASCII_CTRL_W: /* CTRL + W */ + shell_op_word_remove(shell); + break; + + default: + break; + } +} + +static void shell_state_collect(const struct shell *shell) +{ + size_t count = 0; + char data; + + while (true) { + (void)shell->iface->api->read(shell->iface, &data, + sizeof(data), &count); + if (count == 0) { + return; + } + + if (ascii_filter(data) != 0) { + continue; + } + + /* todo pwr_mgmt_feed();*/ + + switch (shell->ctx->receive_state) { + case SHELL_RECEIVE_DEFAULT: + if (data == shell->newline_char) { + if (!shell->ctx->cmd_buff_len) { + cursor_next_line_move(shell); + } else { + /* Command execution */ + shell_execute(shell); + } + shell_state_set(shell, SHELL_STATE_ACTIVE); + return; + } + switch (data) { + case SHELL_VT100_ASCII_ESC: /* ESCAPE */ + receive_state_change(shell, SHELL_RECEIVE_ESC); + break; + + case '\0': + break; + + case '\t': /* TAB */ + if (flag_echo_is_set(shell)) { + shell_tab_handle(shell); + } + break; + + case SHELL_VT100_ASCII_BSPACE: /* BACKSPACE */ + if (flag_echo_is_set(shell)) { + shell_op_char_backspace(shell); + } + break; + + case SHELL_VT100_ASCII_DEL: /* DELETE */ + if (flag_echo_is_set(shell)) { + if (flag_delete_mode_set(shell)) { + shell_op_char_backspace(shell); + + } else { + shell_op_char_delete(shell); + } + } + break; + + default: + if (isprint((int) data)) { + shell_op_char_insert(shell, data); + } else { + metakeys_handle(shell, data); + } + break; + } + break; + + case SHELL_RECEIVE_ESC: + if (data == '[') { + receive_state_change(shell, + SHELL_RECEIVE_ESC_SEQ); + } else { + receive_state_change(shell, + SHELL_RECEIVE_DEFAULT); + } + break; + + case SHELL_RECEIVE_ESC_SEQ: + receive_state_change(shell, SHELL_RECEIVE_DEFAULT); + + if (!flag_echo_is_set(shell)) { + return; + } + + switch (data) { + case 'C': /* RIGHT arrow */ + shell_op_right_arrow(shell); + break; + + case 'D': /* LEFT arrow */ + shell_op_left_arrow(shell); + break; + + case '4': /* END Button in ESC[n~ mode */ + receive_state_change(shell, + SHELL_RECEIVE_TILDE_EXP); + /* fall through */ + /* no break */ + case 'F': /* END Button in VT100 mode */ + shell_op_cursor_end_move(shell); + break; + + case '1': /* HOME Button in ESC[n~ mode */ + receive_state_change(shell, + SHELL_RECEIVE_TILDE_EXP); + /* fall through */ + /* no break */ + case 'H': /* HOME Button in VT100 mode */ + shell_op_cursor_home_move(shell); + break; + + case '2': /* INSERT Button in ESC[n~ mode */ + receive_state_change(shell, + SHELL_RECEIVE_TILDE_EXP); + /* fall through */ + /* no break */ + case 'L': /* INSERT Button in VT100 mode */ + shell->ctx->internal.flags.insert_mode ^= 1; + break; + + case '3':/* DELETE Button in ESC[n~ mode */ + receive_state_change(shell, + SHELL_RECEIVE_TILDE_EXP); + if (flag_echo_is_set(shell)) { + shell_op_char_delete(shell); + } + break; + + default: + break; + } + break; + + case SHELL_RECEIVE_TILDE_EXP: + receive_state_change(shell, SHELL_RECEIVE_DEFAULT); + break; + + default: + receive_state_change(shell, SHELL_RECEIVE_DEFAULT); + break; + } + } +} + +static void cmd_trim(const struct shell *shell) +{ + shell_buffer_trim(shell->ctx->cmd_buff, &shell->ctx->cmd_buff_len); + shell->ctx->cmd_buff_pos = shell->ctx->cmd_buff_len; +} + +/* Function returning pointer to root command matching requested syntax. */ +static const struct shell_cmd_entry *root_cmd_find(const char *syntax) +{ + const size_t cmd_count = shell_root_cmd_count(); + const struct shell_cmd_entry *cmd; + + for (size_t cmd_idx = 0; cmd_idx < cmd_count; ++cmd_idx) { + cmd = shell_root_cmd_get(cmd_idx); + if (strcmp(syntax, cmd->u.entry->syntax) == 0) { + return cmd; + } + } + + return NULL; +} + +/* 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 shell_execute(const struct shell *shell) +{ + struct shell_static_entry d_entry; /* Memory for dynamic commands. */ + char *argv[CONFIG_SHELL_ARGC_MAX + 1]; /* +1 reserved for NULL */ + const struct shell_static_entry *p_static_entry = NULL; + const struct shell_cmd_entry *p_cmd = NULL; + size_t cmd_lvl = SHELL_CMD_ROOT_LVL; + size_t cmd_with_handler_lvl = 0; + size_t cmd_idx; + size_t argc; + char quote; + + shell_op_cursor_end_move(shell); + if (!shell_cursor_in_empty_line(shell)) { + cursor_next_line_move(shell); + } + + memset(&shell->ctx->active_cmd, 0, sizeof(shell->ctx->active_cmd)); + + cmd_trim(shell); + + /* create argument list */ + quote = shell_make_argv(&argc, &argv[0], shell->ctx->cmd_buff, + CONFIG_SHELL_ARGC_MAX); + + if (!argc) { + return; + } + + if (quote != 0) { + shell_fprintf(shell, SHELL_ERROR, "not terminated: %c\r\n", + quote); + return; + } + + /* Searching for a matching root command. */ + p_cmd = root_cmd_find(argv[0]); + if (p_cmd == NULL) { + shell_fprintf(shell, SHELL_ERROR, "%s%s\r\n", argv[0], + SHELL_MSG_COMMAND_NOT_FOUND); + return; + } + + /* Root command shall be always static. */ + assert(p_cmd->is_dynamic == false); + + /* checking if root command has a handler */ + shell->ctx->active_cmd = *p_cmd->u.entry; + + p_cmd = p_cmd->u.entry->subcmd; + cmd_lvl++; + cmd_idx = 0; + + /* Below loop is analyzing subcommands of found root command. */ + while (true) { + 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. + */ + help_flag_set(shell); + break; + } + + cmd_get(p_cmd, cmd_lvl, cmd_idx++, &p_static_entry, &d_entry); + + if ((cmd_idx == 0) || (p_static_entry == NULL)) { + break; + } + + if (strcmp(argv[cmd_lvl], p_static_entry->syntax) == 0) { + /* checking if command has a handler */ + if (p_static_entry->handler != NULL) { + shell->ctx->active_cmd = *p_static_entry; + cmd_with_handler_lvl = cmd_lvl; + } + + cmd_lvl++; + cmd_idx = 0; + p_cmd = p_static_entry->subcmd; + } + } + + /* Executing the deepest found handler. */ + if (shell->ctx->active_cmd.handler == NULL) { + if (shell->ctx->active_cmd.help) { + shell_help_print(shell, NULL, 0); + } else { + shell_fprintf(shell, SHELL_ERROR, + SHELL_MSG_SPECIFY_SUBCOMMAND); + } + } else { + shell->ctx->active_cmd.handler(shell, + argc - cmd_with_handler_lvl, + &argv[cmd_with_handler_lvl]); + } + + help_flag_clear(shell); +} + +static void shell_transport_evt_handler(enum shell_transport_evt evt_type, + void *context) +{ + struct shell *shell = (struct shell *)context; + struct k_poll_signal *signal; + + signal = (evt_type == SHELL_TRANSPORT_EVT_RX_RDY) ? + &shell->ctx->signals[SHELL_SIGNAL_RXRDY] : + &shell->ctx->signals[SHELL_SIGNAL_TXDONE]; + k_poll_signal(signal, 0); +} + +static int shell_instance_init(const struct shell *shell, const void *p_config, + bool use_colors) +{ + assert(shell); + assert(shell->ctx && shell->iface && shell->name); + assert((shell->newline_char == '\n') || (shell->newline_char == '\r')); + + int err; + + err = shell->iface->api->init(shell->iface, p_config, + shell_transport_evt_handler, + (void *) shell); + if (err != 0) { + return err; + } + + memset(shell->ctx, 0, sizeof(*shell->ctx)); + + if (IS_ENABLED(CONFIG_SHELL_BACKSPACE_MODE_DELETE)) { + shell->ctx->internal.flags.mode_delete = 1; + } + + shell->ctx->internal.flags.tx_rdy = 1; + shell->ctx->internal.flags.echo = CONFIG_SHELL_ECHO_STATUS; + shell->ctx->state = SHELL_STATE_INITIALIZED; + shell->ctx->vt100_ctx.cons.terminal_wid = SHELL_DEFAULT_TERMINAL_WIDTH; + shell->ctx->vt100_ctx.cons.terminal_hei = SHELL_DEFAULT_TERMINAL_HEIGHT; + shell->ctx->vt100_ctx.cons.name_len = shell_strlen(shell->name); + shell->ctx->internal.flags.use_colors = + IS_ENABLED(CONFIG_SHELL_VT100_COLORS); + + return 0; +} + +static int shell_instance_uninit(const struct shell *shell); + +void shell_thread(void *shell_handle, void *dummy1, void *dummy2) +{ + struct shell *shell = (struct shell *)shell_handle; + int err; + int i; + + for (i = 0; i < SHELL_SIGNALS; i++) { + k_poll_signal_init(&shell->ctx->signals[i]); + k_poll_event_init(&shell->ctx->events[i], + K_POLL_TYPE_SIGNAL, + K_POLL_MODE_NOTIFY_ONLY, + &shell->ctx->signals[i]); + } + + err = shell_start(shell); + if (err != 0) { + return; + } + + while (true) { + int signaled; + int result; + + err = k_poll(shell->ctx->events, SHELL_SIGNALS, K_FOREVER); + (void)err; + + k_poll_signal_check(&shell->ctx->signals[SHELL_SIGNAL_KILL], + &signaled, &result); + + if (signaled) { + k_poll_signal_reset( + &shell->ctx->signals[SHELL_SIGNAL_KILL]); + (void)shell_instance_uninit(shell); + + k_thread_abort(k_current_get()); + } else { + /* Other signals handled together.*/ + k_poll_signal_reset( + &shell->ctx->signals[SHELL_SIGNAL_RXRDY]); + k_poll_signal_reset( + &shell->ctx->signals[SHELL_SIGNAL_TXDONE]); + shell_process(shell); + } + } +} + +int shell_init(const struct shell *shell, const void *transport_config, + bool use_colors, bool log_backend, u32_t init_log_level) +{ + assert(shell); + int err; + + err = shell_instance_init(shell, transport_config, use_colors); + if (err != 0) { + return err; + } + + (void)k_thread_create(shell->thread, + shell->stack, CONFIG_SHELL_STACK_SIZE, + shell_thread, (void *)shell, NULL, NULL, + CONFIG_SHELL_THREAD_PRIO, 0, K_NO_WAIT); + + return 0; +} + +static int shell_instance_uninit(const struct shell *shell) +{ + assert(shell); + assert(shell->ctx && shell->iface && shell->name); + int err; + + if (flag_processing_is_set(shell)) { + return -EBUSY; + } + + err = shell->iface->api->uninit(shell->iface); + if (err != 0) { + return err; + } + + shell->ctx->state = SHELL_STATE_UNINITIALIZED; + + return 0; +} + +int shell_uninit(const struct shell *shell) +{ + if (IS_ENABLED(CONFIG_MULTITHREADING)) { + /* signal kill message */ + (void)k_poll_signal(&shell->ctx->signals[SHELL_SIGNAL_KILL], 0); + + return 0; + } else { + return shell_instance_uninit(shell); + } +} + +int shell_start(const struct shell *shell) +{ + assert(shell); + assert(shell->ctx && shell->iface && shell->name); + int err; + + if (shell->ctx->state != SHELL_STATE_INITIALIZED) { + return -ENOTSUP; + } + + err = shell->iface->api->enable(shell->iface, false); + if (err != 0) { + return err; + } + + if (IS_ENABLED(CONFIG_SHELL_VT100_COLORS_ENABLED)) { + vt100_color_set(shell, SHELL_NORMAL); + } + + shell_raw_fprintf(shell->fprintf_ctx, "\r\n\n"); + + shell_state_set(shell, SHELL_STATE_ACTIVE); + + return 0; +} + +int shell_stop(const struct shell *shell) +{ + assert(shell); + + if ((shell->ctx->state == SHELL_STATE_INITIALIZED) || + (shell->ctx->state == SHELL_STATE_UNINITIALIZED)) { + return -ENOTSUP; + } + + shell_state_set(shell, SHELL_STATE_INITIALIZED); + + return 0; +} + +void shell_process(const struct shell *shell) +{ + assert(shell); + + union shell_internal internal; + + internal.value = 0; + internal.flags.processing = 1; + + (void)atomic_or((atomic_t *)&shell->ctx->internal.value, + internal.value); + + switch (shell->ctx->state) { + case SHELL_STATE_UNINITIALIZED: + case SHELL_STATE_INITIALIZED: + /* Console initialized but not started. */ + break; + + case SHELL_STATE_ACTIVE: + shell_state_collect(shell); + break; + + default: + break; + } + + transport_buffer_flush(shell); + + internal.value = 0xFFFFFFFF; + internal.flags.processing = 0; + (void)atomic_and((atomic_t *)&shell->ctx->internal.value, + internal.value); +} + +/* Function shall be only used by the fprintf module. */ +void shell_print_stream(const void *user_ctx, const char *data, + size_t data_len) +{ + shell_write((const struct shell *) user_ctx, data, data_len); +} + +void shell_fprintf(const struct shell *shell, enum shell_vt100_color color, + const char *p_fmt, ...) +{ + assert(shell); + + va_list args = { 0 }; + + va_start(args, p_fmt); + + if (IS_ENABLED(CONFIG_SHELL_VT100_COLORS) && + shell->ctx->internal.flags.use_colors && + (color != shell->ctx->vt100_ctx.col.col)) { + struct shell_vt100_colors col; + + vt100_colors_store(shell, &col); + vt100_color_set(shell, color); + + shell_fprintf_fmt(shell->fprintf_ctx, p_fmt, args); + + vt100_colors_restore(shell, &col); + } else { + shell_fprintf_fmt(shell->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. + * shell Pointer to shell 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 formatted_text_print(const struct shell *shell, const char *str, + size_t terminal_offset, bool offset_first_line) +{ + size_t offset = 0; + size_t length; + + if (str == NULL) { + return; + } + + if (offset_first_line) { + shell_op_cursor_horiz_move(shell, terminal_offset); + } + + + /* Skipping whitespace. */ + while (isspace((int) *(str + offset))) { + ++offset; + } + + while (true) { + size_t idx = 0; + + length = shell_strlen(str) - offset; + + if (length <= + shell->ctx->vt100_ctx.cons.terminal_wid - terminal_offset) { + for (idx = 0; idx < length; idx++) { + if (*(str + offset + idx) == '\n') { + transport_buffer_flush(shell); + shell_write(shell, str + offset, idx); + offset += idx + 1; + cursor_next_line_move(shell); + shell_op_cursor_horiz_move(shell, + terminal_offset); + break; + } + } + + /* String will fit in one line. */ + shell_raw_fprintf(shell->fprintf_ctx, str + offset); + + break; + } + + /* String is longer than terminal line so text needs to + * divide in the way to not divide words. + */ + length = shell->ctx->vt100_ctx.cons.terminal_wid + - terminal_offset; + + while (true) { + /* Determining line break. */ + if (isspace((int) (*(str + offset + idx)))) { + length = idx; + if (*(str + offset + idx) == '\n') { + break; + } + } + + if ((idx + terminal_offset) >= + shell->ctx->vt100_ctx.cons.terminal_wid) { + /* End of line reached. */ + break; + } + + ++idx; + } + + /* Writing one line, fprintf IO buffer must be flushed + * before calling shell_write. + */ + transport_buffer_flush(shell); + shell_write(shell, str + offset, length); + offset += length; + + /* Calculating text offset to ensure that next line will + * not begin with a space. + */ + while (isspace((int) (*(str + offset)))) { + ++offset; + } + + cursor_next_line_move(shell); + shell_op_cursor_horiz_move(shell, terminal_offset); + + } + cursor_next_line_move(shell); +} + +static void help_cmd_print(const struct shell *shell) +{ + static const char cmd_sep[] = " - "; /* commands separator */ + + u16_t field_width = shell_strlen(shell->ctx->active_cmd.syntax) + + shell_strlen(cmd_sep); + + shell_fprintf(shell, SHELL_NORMAL, "%s%s", + shell->ctx->active_cmd.syntax, cmd_sep); + + formatted_text_print(shell, shell->ctx->active_cmd.help, + field_width, false); +} + +static void help_item_print(const struct shell *shell, const char *item_name, + u16_t item_name_width, const char *item_help) +{ + static const u8_t tabulator[] = " "; + const u16_t offset = 2 * strlen(tabulator) + item_name_width + 1; + + if (item_name == NULL) { + return; + } + + /* print option name */ + shell_fprintf(shell, SHELL_NORMAL, "%s%-*s%s:", + tabulator, + item_name_width, item_name, + tabulator); + + if (item_help == NULL) { + cursor_next_line_move(shell); + return; + } + /* print option help */ + formatted_text_print(shell, item_help, offset, false); +} + +static void help_options_print(const struct shell *shell, + const struct shell_getopt_option *opt, + size_t opt_cnt) +{ + static const char opt_sep[] = ", "; /* options separator */ + static const char help_opt[] = "-h, --help"; + u16_t longest_name = shell_strlen(help_opt); + + shell_fprintf(shell, SHELL_NORMAL, "Options:\r\n"); + + if ((opt == NULL) || (opt_cnt == 0)) { + help_item_print(shell, help_opt, longest_name, + "Show command help."); + return; + } + + /* Looking for the longest option string. */ + for (size_t i = 0; i < opt_cnt; ++i) { + u16_t len = shell_strlen(opt[i].optname_short) + + shell_strlen(opt[i].optname) + + shell_strlen(opt_sep); + + longest_name = len > longest_name ? len : longest_name; + } + + /* help option is printed first */ + help_item_print(shell, help_opt, longest_name, "Show command help."); + + /* prepare a buffer to compose a string: + * option name short - option separator - option name + */ + memset(shell->ctx->temp_buff, 0, longest_name + 1); + + /* Formating and printing all available options (except -h, --help). */ + for (size_t i = 0; i < opt_cnt; ++i) { + if (opt[i].optname_short) { + strcpy(shell->ctx->temp_buff, opt[i].optname_short); + } + if (opt[i].optname) { + if (*shell->ctx->temp_buff) { + strcat(shell->ctx->temp_buff, opt_sep); + strcat(shell->ctx->temp_buff, opt[i].optname); + } else { + strcpy(shell->ctx->temp_buff, opt[i].optname); + } + } + help_item_print(shell, shell->ctx->temp_buff, longest_name, + opt[i].optname_help); + } +} + +/* Function is printing command help, its subcommands name and subcommands + * help string. + */ +static void help_subcmd_print(const struct shell *shell) +{ + const struct shell_static_entry *entry = NULL; + struct shell_static_entry static_entry; + u16_t longest_syntax = 0; + size_t cmd_idx = 0; + + /* Checking if there are any subcommands available. */ + if (!shell->ctx->active_cmd.subcmd) { + return; + } + + /* Searching for the longest subcommand to print. */ + do { + cmd_get(shell->ctx->active_cmd.subcmd, !SHELL_CMD_ROOT_LVL, + cmd_idx++, &entry, &static_entry); + + if (!entry) { + break; + } + + u16_t len = shell_strlen(entry->syntax); + + longest_syntax = longest_syntax > len ? longest_syntax : len; + } while (cmd_idx != 0); /* too many commands */ + + if (cmd_idx == 1) { + return; + } + + shell_fprintf(shell, SHELL_NORMAL, "Subcommands:\r\n"); + + /* Printing subcommands and help string (if exists). */ + cmd_idx = 0; + + while (true) { + cmd_get(shell->ctx->active_cmd.subcmd, !SHELL_CMD_ROOT_LVL, + cmd_idx++, &entry, &static_entry); + + if (entry == NULL) { + break; + } + + help_item_print(shell, entry->syntax, longest_syntax, + entry->help); + } +} + +void shell_help_print(const struct shell *shell, + const struct shell_getopt_option *opt, size_t opt_len) +{ + assert(shell); + + if (!IS_ENABLED(CONFIG_SHELL_HELP)) { + return; + } + + help_cmd_print(shell); + help_options_print(shell, opt, opt_len); + help_subcmd_print(shell); +} + +bool shell_cmd_precheck(const struct shell *shell, + bool arg_cnt_ok, + const struct shell_getopt_option *opt, + size_t opt_len) +{ + if (shell_help_requested(shell)) { + shell_help_print(shell, opt, opt_len); + return false; + } + + if (!arg_cnt_ok) { + shell_fprintf(shell, SHELL_ERROR, + "%s: wrong parameter count\r\n", + shell->ctx->active_cmd.syntax); + + if (IS_ENABLED(SHELL_HELP_ON_WRONG_ARGUMENT_COUNT)) { + shell_help_print(shell, opt, opt_len); + } + + return false; + } + + return true; +} diff --git a/subsys/shell/shell_fprintf.c b/subsys/shell/shell_fprintf.c new file mode 100644 index 00000000000..178eafc6152 --- /dev/null +++ b/subsys/shell/shell_fprintf.c @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2018 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#ifdef CONFIG_NEWLIB_LIBC +typedef int (*out_func_t)(int c, void *ctx); +extern void _vprintk(out_func_t out, void *ctx, const char *fmt, va_list ap); +#else +extern int _prf(int (*func)(), void *dest, char *format, va_list vargs); +#endif + +static int out_func(int c, void *ctx) +{ + const struct shell_fprintf *sh_fprintf; + + sh_fprintf = (const struct shell_fprintf *)ctx; + + sh_fprintf->buffer[sh_fprintf->ctrl_blk->buffer_cnt] = (u8_t)c; + sh_fprintf->ctrl_blk->buffer_cnt++; + + if (sh_fprintf->ctrl_blk->buffer_cnt == sh_fprintf->buffer_size) { + shell_fprintf_buffer_flush(sh_fprintf); + } + + return 0; +} + +void shell_fprintf_fmt(const struct shell_fprintf *sh_fprintf, + const char *fmt, va_list args) +{ +#ifndef CONFIG_NEWLIB_LIBC + (void)_prf(out_func, (void *)sh_fprintf, (char *)fmt, args); +#else + _vprintk(out_func, (void *)sh_fprintf, fmt, args); +#endif + + if (sh_fprintf->ctrl_blk->autoflush) { + shell_fprintf_buffer_flush(sh_fprintf); + } +} + + +void shell_fprintf_buffer_flush(const struct shell_fprintf *sh_fprintf) +{ + sh_fprintf->fwrite(sh_fprintf->user_ctx, sh_fprintf->buffer, + sh_fprintf->ctrl_blk->buffer_cnt); + sh_fprintf->ctrl_blk->buffer_cnt = 0; +} diff --git a/subsys/shell/shell_ops.c b/subsys/shell/shell_ops.c new file mode 100644 index 00000000000..561e20e8b62 --- /dev/null +++ b/subsys/shell/shell_ops.c @@ -0,0 +1,264 @@ +/* + * Copyright (c) 2018 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "shell_ops.h" + +void shell_op_cursor_vert_move(const struct shell *shell, s32_t delta) +{ + if (delta != 0) { + shell_raw_fprintf(shell->fprintf_ctx, "\033[%d%c", + delta > 0 ? delta : -delta, + delta > 0 ? 'A' : 'B'); + } +} + +void shell_op_cursor_horiz_move(const struct shell *shell, s32_t delta) +{ + if (delta != 0) { + shell_raw_fprintf(shell->fprintf_ctx, "\033[%d%c", + delta > 0 ? delta : -delta, + delta > 0 ? 'C' : 'D'); + } +} + +/* Function returns true if command length is equal to multiplicity of terminal + * width. + */ +static inline bool full_line_cmd(const struct shell *shell) +{ + return ((shell->ctx->cmd_buff_len + shell_strlen(shell->name)) + % shell->ctx->vt100_ctx.cons.terminal_wid == 0); +} + +/* Function returns true if cursor is at beginning of an empty line. */ +bool shell_cursor_in_empty_line(const struct shell *shell) +{ + return ((shell->ctx->cmd_buff_pos + shell_strlen(shell->name)) + % shell->ctx->vt100_ctx.cons.terminal_wid == 0); +} + +void shell_op_cond_next_line(const struct shell *shell) +{ + if (shell_cursor_in_empty_line(shell) || full_line_cmd(shell)) { + cursor_next_line_move(shell); + } +} + +void shell_op_cursor_position_synchronize(const struct shell *shell) +{ + struct shell_multiline_cons *cons = &shell->ctx->vt100_ctx.cons; + bool last_line; + + shell_multiline_data_calc(cons, shell->ctx->cmd_buff_pos, + shell->ctx->cmd_buff_len); + last_line = (cons->cur_y == cons->cur_y_end); + + /* In case cursor reaches the bottom line of a terminal, it will + * be moved to the next line. + */ + if (full_line_cmd(shell)) { + cursor_next_line_move(shell); + } + + if (last_line) { + shell_op_cursor_horiz_move(shell, cons->cur_x - + cons->cur_x_end); + } else { + shell_op_cursor_vert_move(shell, cons->cur_y_end - cons->cur_y); + shell_op_cursor_horiz_move(shell, cons->cur_x - + cons->cur_x_end); + } +} + +void shell_op_cursor_move(const struct shell *shell, s16_t val) +{ + struct shell_multiline_cons *cons = &shell->ctx->vt100_ctx.cons; + u16_t new_pos = shell->ctx->cmd_buff_pos + val; + s32_t row_span; + s32_t col_span; + + shell_multiline_data_calc(cons, shell->ctx->cmd_buff_pos, + shell->ctx->cmd_buff_len); + + /* Calculate the new cursor. */ + row_span = row_span_with_buffer_offsets_get(&shell->ctx->vt100_ctx.cons, + shell->ctx->cmd_buff_pos, + new_pos); + col_span = column_span_with_buffer_offsets_get( + &shell->ctx->vt100_ctx.cons, + shell->ctx->cmd_buff_pos, + new_pos); + + shell_op_cursor_vert_move(shell, -row_span); + shell_op_cursor_horiz_move(shell, col_span); + shell->ctx->cmd_buff_pos = new_pos; +} + +void shell_op_word_remove(const struct shell *shell) +{ + char *str = &shell->ctx->cmd_buff[shell->ctx->cmd_buff_pos - 1]; + char *str_start = &shell->ctx->cmd_buff[0]; + u16_t chars_to_delete; + + /* Line must not be empty and cursor must not be at 0 to continue. */ + if ((shell->ctx->cmd_buff_len == 0) || + (shell->ctx->cmd_buff_pos == 0)) { + return; + } + + /* Start at the current position. */ + chars_to_delete = 0; + + /* Look back for all spaces then for non-spaces. */ + while ((str >= str_start) && (*str == ' ')) { + ++chars_to_delete; + --str; + } + + while ((str >= str_start) && (*str != ' ')) { + ++chars_to_delete; + --str; + } + + /* Manage the buffer. */ + memmove(str + 1, str + 1 + chars_to_delete, + shell->ctx->cmd_buff_len - chars_to_delete); + shell->ctx->cmd_buff_len -= chars_to_delete; + shell->ctx->cmd_buff[shell->ctx->cmd_buff_len] = '\0'; + + /* Update display. */ + shell_op_cursor_move(shell, -chars_to_delete); + cursor_save(shell); + shell_fprintf(shell, SHELL_NORMAL, "%s", str + 1); + clear_eos(shell); + cursor_restore(shell); +} + +void shell_op_cursor_home_move(const struct shell *shell) +{ + shell_op_cursor_move(shell, -shell->ctx->cmd_buff_pos); +} + +void shell_op_cursor_end_move(const struct shell *shell) +{ + shell_op_cursor_move(shell, shell->ctx->cmd_buff_len - + shell->ctx->cmd_buff_pos); +} + + +void shell_op_left_arrow(const struct shell *shell) +{ + if (shell->ctx->cmd_buff_pos > 0) { + shell_op_cursor_move(shell, -1); + } +} + +void shell_op_right_arrow(const struct shell *shell) +{ + if (shell->ctx->cmd_buff_pos < shell->ctx->cmd_buff_len) { + shell_op_cursor_move(shell, 1); + } +} + +static void reprint_from_cursor(const struct shell *shell, u16_t diff, + bool data_removed) +{ + /* Clear eos is needed only when newly printed command is shorter than + * previously printed command. This can happen when delete or backspace + * was called. + * + * Such condition is useful for Bluetooth devices to save number of + * bytes transmitted between terminal and device. + */ + if (data_removed) { + clear_eos(shell); + } + + shell_fprintf(shell, SHELL_NORMAL, "%s", + &shell->ctx->cmd_buff[shell->ctx->cmd_buff_pos]); + shell->ctx->cmd_buff_pos = shell->ctx->cmd_buff_len; + + if (full_line_cmd(shell)) { + if (((data_removed) && (diff > 0)) || (!data_removed)) { + cursor_next_line_move(shell); + } + } + + shell_op_cursor_move(shell, -diff); +} + +static void data_insert(const struct shell *shell, const char *data, u16_t len) +{ + u16_t after = shell->ctx->cmd_buff_len - shell->ctx->cmd_buff_pos; + char *curr_pos = &shell->ctx->cmd_buff[shell->ctx->cmd_buff_pos]; + + if ((shell->ctx->cmd_buff_len + len) >= CONFIG_SHELL_CMD_BUFF_SIZE) { + return; + } + + memmove(curr_pos + len, curr_pos, after); + memcpy(curr_pos, data, len); + shell->ctx->cmd_buff_len += len; + shell->ctx->cmd_buff[shell->ctx->cmd_buff_len] = '\0'; + + if (!flag_echo_is_set(shell)) { + shell->ctx->cmd_buff_pos += len; + return; + } + + reprint_from_cursor(shell, after, false); +} + +void char_replace(const struct shell *shell, char data) +{ + shell->ctx->cmd_buff[shell->ctx->cmd_buff_pos++] = data; + shell_raw_fprintf(shell->fprintf_ctx, "%c", data); + if (shell_cursor_in_empty_line(shell)) { + cursor_next_line_move(shell); + } +} + +void shell_op_char_insert(const struct shell *shell, char data) +{ + if (shell->ctx->internal.flags.insert_mode && + (shell->ctx->cmd_buff_len != shell->ctx->cmd_buff_pos)) { + char_replace(shell, data); + } else { + data_insert(shell, &data, 1); + } +} + +void shell_op_char_backspace(const struct shell *shell) +{ + if ((shell->ctx->cmd_buff_len == 0) || + (shell->ctx->cmd_buff_pos == 0)) { + return; + } + + shell_op_cursor_move(shell, -1); + shell_op_char_delete(shell); +} + +void shell_op_char_delete(const struct shell *shell) +{ + u16_t diff = shell->ctx->cmd_buff_len - shell->ctx->cmd_buff_pos; + char *str = &shell->ctx->cmd_buff[shell->ctx->cmd_buff_pos]; + + if (diff == 0) { + return; + } + + memmove(str, str + 1, diff); + --shell->ctx->cmd_buff_len; + reprint_from_cursor(shell, --diff, true); +} + +void shell_op_completion_insert(const struct shell *shell, + const char *compl, + u16_t compl_len) +{ + data_insert(shell, compl, compl_len); +} diff --git a/subsys/shell/shell_ops.h b/subsys/shell/shell_ops.h new file mode 100644 index 00000000000..c87db7ab8fa --- /dev/null +++ b/subsys/shell/shell_ops.h @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2018 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef SHELL_OPS_H__ +#define SHELL_OPS_H__ + +#include +#include +#include "shell_vt100.h" +#include "shell_utils.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define SHELL_DEFAULT_TERMINAL_WIDTH (80u) /* Default PuTTY width. */ +#define SHELL_DEFAULT_TERMINAL_HEIGHT (24u) /* Default PuTTY height. */ + +static inline void shell_raw_fprintf(const struct shell_fprintf *const ctx, + const char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + shell_fprintf_fmt(ctx, fmt, args); + va_end(args); +} + +/* Macro to send VT100 commands. */ +#define SHELL_VT100_CMD(_shell_, _cmd_) \ + do { \ + static const char cmd[] = _cmd_; \ + shell_raw_fprintf(_shell_->fprintf_ctx, "%s", cmd); \ + } while (0) + +/* Function sends VT100 command to clear the screen from cursor position to + * end of the screen. + */ +static inline void clear_eos(const struct shell *shell) +{ + SHELL_VT100_CMD(shell, SHELL_VT100_CLEAREOS); +} + +/* Function sends VT100 command to save cursor position. */ +static inline void cursor_save(const struct shell *shell) +{ + SHELL_VT100_CMD(shell, SHELL_VT100_SAVECURSOR); +} + +/* Function sends VT100 command to restore saved cursor position. */ +static inline void cursor_restore(const struct shell *shell) +{ + SHELL_VT100_CMD(shell, SHELL_VT100_RESTORECURSOR); +} + +/* Function forcing new line - cannot be replaced with function + * cursor_down_move. + */ +static inline void cursor_next_line_move(const struct shell *shell) +{ + shell_raw_fprintf(shell->fprintf_ctx, "\r\n"); +} + +/* Function sends 1 character to the shell instance. */ +static inline void shell_putc(const struct shell *shell, char ch) +{ + shell_raw_fprintf(shell->fprintf_ctx, "%c", ch); +} + +static inline bool flag_echo_is_set(const struct shell *shell) +{ + return shell->ctx->internal.flags.echo == 1 ? true : false; +} + +void shell_op_cursor_vert_move(const struct shell *shell, s32_t delta); +void shell_op_cursor_horiz_move(const struct shell *shell, s32_t delta); + +void shell_op_cond_next_line(const struct shell *shell); + +/* 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. + */ +void shell_op_cursor_position_synchronize(const struct shell *shell); + +void shell_op_cursor_move(const struct shell *shell, s16_t val); + +void shell_op_left_arrow(const struct shell *shell); + +void shell_op_right_arrow(const struct shell *shell); + +/* + * Removes the "word" to the left of the cursor: + * - if there are spaces at the cursor position, remove all spaces to the left + * - remove the non-spaces (word) until a space is found or a beginning of + * buffer + */ +void shell_op_word_remove(const struct shell *shell); + +/* Function moves cursor to begin of command position, just after console + * name. + */ +void shell_op_cursor_home_move(const struct shell *shell); + +/* Function moves cursor to end of command. */ +void shell_op_cursor_end_move(const struct shell *shell); + +void char_replace(const struct shell *shell, char data); + +void shell_op_char_insert(const struct shell *shell, char data); + +void shell_op_char_backspace(const struct shell *shell); + +void shell_op_char_delete(const struct shell *shell); + +void shell_op_completion_insert(const struct shell *shell, + const char *compl, + u16_t compl_len); + +bool shell_cursor_in_empty_line(const struct shell *shell); +#ifdef __cplusplus +} +#endif + +#endif /* SHELL_OPS_H__ */ diff --git a/subsys/shell/shell_uart.c b/subsys/shell/shell_uart.c new file mode 100644 index 00000000000..adbb33895f8 --- /dev/null +++ b/subsys/shell/shell_uart.c @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2018 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +static void timer_handler(struct k_timer *timer) +{ + struct shell_uart *sh_uart = + CONTAINER_OF(timer, struct shell_uart, timer); + + if (uart_poll_in(sh_uart->dev, sh_uart->rx) == 0) { + sh_uart->rx_cnt = 1; + sh_uart->handler(SHELL_TRANSPORT_EVT_RX_RDY, sh_uart->context); + } +} + + +static int init(const struct shell_transport *transport, + const void *config, + shell_transport_handler_t evt_handler, + void *context) +{ + struct shell_uart *sh_uart = (struct shell_uart *)transport->ctx; + + sh_uart->dev = device_get_binding(CONFIG_UART_CONSOLE_ON_DEV_NAME); + sh_uart->handler = evt_handler; + sh_uart->context = context; + + k_timer_init(&sh_uart->timer, timer_handler, NULL); + + k_timer_start(&sh_uart->timer, 20, 20); + + return 0; +} + +static int uninit(const struct shell_transport *transport) +{ + return 0; +} + +static int enable(const struct shell_transport *transport, bool blocking) +{ + return 0; +} + +static int write(const struct shell_transport *transport, + const void *data, size_t length, size_t *cnt) +{ + struct shell_uart *sh_uart = (struct shell_uart *)transport->ctx; + const u8_t *data8 = (const u8_t *)data; + + for (size_t i = 0; i < length; i++) { + uart_poll_out(sh_uart->dev, data8[i]); + } + + *cnt = length; + + sh_uart->handler(SHELL_TRANSPORT_EVT_TX_RDY, sh_uart->context); + + return 0; +} + +static int read(const struct shell_transport *transport, + void *data, size_t length, size_t *cnt) +{ + struct shell_uart *sh_uart = (struct shell_uart *)transport->ctx; + + if (sh_uart->rx_cnt) { + memcpy(data, sh_uart->rx, 1); + sh_uart->rx_cnt = 0; + *cnt = 1; + } else { + *cnt = 0; + } + + return 0; +} + +const struct shell_transport_api shell_uart_transport_api = { + .init = init, + .uninit = uninit, + .enable = enable, + .write = write, + .read = read +}; diff --git a/subsys/shell/shell_utils.c b/subsys/shell/shell_utils.c new file mode 100644 index 00000000000..cb83a9d54aa --- /dev/null +++ b/subsys/shell/shell_utils.c @@ -0,0 +1,323 @@ +/* + * Copyright (c) 2018 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include "shell_utils.h" +#include + +/* Calculates relative line number of given position in buffer */ +static u32_t line_num_with_buffer_offset_get(struct shell_multiline_cons *cons, + u16_t buffer_pos) +{ + return ((buffer_pos + cons->name_len) / cons->terminal_wid); +} + +/* Calculates column number of given position in buffer */ +static u32_t col_num_with_buffer_offset_get(struct shell_multiline_cons *cons, + u16_t buffer_pos) +{ + /* columns are counted from 1 */ + return (1 + ((buffer_pos + cons->name_len) % cons->terminal_wid)); +} + +s32_t column_span_with_buffer_offsets_get(struct shell_multiline_cons *cons, + u16_t offset1, + u16_t offset2) +{ + return col_num_with_buffer_offset_get(cons, offset2) + - col_num_with_buffer_offset_get(cons, offset1); +} + +s32_t row_span_with_buffer_offsets_get(struct shell_multiline_cons *cons, + u16_t offset1, + u16_t offset2) +{ + return line_num_with_buffer_offset_get(cons, offset2) + - line_num_with_buffer_offset_get(cons, offset1); +} + +void shell_multiline_data_calc(struct shell_multiline_cons *cons, + u16_t buff_pos, u16_t buff_len) +{ + /* Current cursor position in command. + * +1 -> because home position is (1, 1) + */ + cons->cur_x = (buff_pos + cons->name_len) % cons->terminal_wid + 1; + cons->cur_y = (buff_pos + cons->name_len) / cons->terminal_wid + 1; + + /* Extreme position when cursor is at the end of command. */ + cons->cur_y_end = (buff_len + cons->name_len) / cons->terminal_wid + 1; + cons->cur_x_end = (buff_len + cons->name_len) % cons->terminal_wid + 1; +} + +static void make_argv(char **ppcmd, u8_t c, u8_t quote) +{ + char *cmd = *ppcmd; + + while (1) { + c = *cmd; + + if (c == '\0') { + break; + } + + if (!quote) { + switch (c) { + case '\\': + memmove(cmd, cmd + 1, + shell_strlen(cmd)); + cmd += 1; + continue; + + case '\'': + case '\"': + memmove(cmd, cmd + 1, + shell_strlen(cmd)); + quote = c; + continue; + default: + break; + } + } + + if (quote == c) { + memmove(cmd, cmd + 1, shell_strlen(cmd)); + quote = 0; + continue; + } + + if (quote && c == '\\') { + char t = *(cmd + 1); + + if (t == quote) { + memmove(cmd, cmd + 1, + shell_strlen(cmd)); + cmd += 1; + continue; + } + + if (t == '0') { + u8_t i; + u8_t v = 0; + + for (i = 2; i < (2 + 3); i++) { + t = *(cmd + i); + + if (t >= '0' && t <= '7') { + v = (v << 3) | (t - '0'); + } else { + break; + } + } + + if (i > 2) { + memmove(cmd, cmd + (i - 1), + shell_strlen(cmd) - (i - 2)); + *cmd++ = v; + continue; + } + } + + if (t == 'x') { + u8_t i; + u8_t v = 0; + + for (i = 2; i < (2 + 2); i++) { + t = *(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(cmd, cmd + (i - 1), + shell_strlen(cmd) - (i - 2)); + *cmd++ = v; + continue; + } + } + } + + if (!quote && isspace((int) c)) { + break; + } + + cmd += 1; + } + *ppcmd = cmd; +} + + +char shell_make_argv(size_t *argc, char **argv, char *cmd, u8_t max_argc) +{ + char quote = 0; + char c; + + *argc = 0; + do { + c = *cmd; + if (c == '\0') { + break; + } + + if (isspace((int) c)) { + *cmd++ = '\0'; + continue; + } + + argv[(*argc)++] = cmd; + quote = 0; + + make_argv(&cmd, c, quote); + } while (*argc < max_argc); + + argv[*argc] = 0; + + return quote; +} + +void shell_pattern_remove(char *buff, u16_t *buff_len, const char *pattern) +{ + char *pattern_addr = strstr(buff, pattern); + u16_t pattern_len = shell_strlen(pattern); + size_t shift; + + if (!pattern_addr) { + return; + } + + if (pattern_addr > buff) { + if (*(pattern_addr - 1) == ' ') { + pattern_len++; /* space needs to be removed as well */ + pattern_addr--; /* set pointer to space */ + } + } + + shift = shell_strlen(pattern_addr) - pattern_len + 1; /* +1 for EOS */ + *buff_len -= pattern_len; + + memmove(pattern_addr, pattern_addr + pattern_len, shift); +} + +int shell_command_add(char *buff, u16_t *buff_len, + const char *new_cmd, const char *pattern) +{ + u16_t cmd_len = shell_strlen(new_cmd); + char *cmd_source_addr; + u16_t shift; + + /* +1 for space */ + if ((*buff_len + cmd_len + 1) > CONFIG_SHELL_CMD_BUFF_SIZE) { + return -ENOMEM; + } + + cmd_source_addr = strstr(buff, pattern); + + if (!cmd_source_addr) { + return -EINVAL; + } + + shift = shell_strlen(cmd_source_addr); + + /* make place for new command: + 1 for space + 1 for EOS */ + memmove(cmd_source_addr + cmd_len + 1, cmd_source_addr, shift + 1); + memcpy(cmd_source_addr, new_cmd, cmd_len); + cmd_source_addr[cmd_len] = ' '; + + *buff_len += cmd_len + 1; /* + 1 for space */ + + return 0; +} + +void shell_spaces_trim(char *str) +{ + u16_t len = shell_strlen(str); + u16_t shift = 0; + + if (!str) { + return; + } + + for (u16_t i = 0; i < len - 1; i++) { + if (isspace((int)str[i])) { + for (u16_t j = i + 1; j < len; j++) { + if (isspace((int)str[j])) { + shift++; + continue; + } + + if (shift > 0) { + /* +1 for EOS */ + memmove(&str[i + 1], + &str[j], + len - shift + 1); + len -= shift; + shift = 0; + } + + break; + } + } + } +} + +void shell_buffer_trim(char *buff, u16_t *buff_len) +{ + u16_t i = 0; + + /* no command in the buffer */ + if (buff[0] == '\0') { + return; + } + + while (isspace((int) buff[*buff_len - 1])) { + *buff_len -= 1; + if (*buff_len == 0) { + buff[0] = '\0'; + return; + } + } + buff[*buff_len] = '\0'; + + /* Counting whitespace characters starting from beginning of the + * command. + */ + while (isspace((int) buff[i++])) { + if (i == 0) { + buff[0] = '\0'; + return; + } + } + + /* Removing counted whitespace characters. */ + if (--i > 0) { + memmove(buff, buff + i, (*buff_len + 1) - i); /* +1 for '\0' */ + *buff_len = *buff_len - i; + } +} + +u16_t shell_str_similarity_check(const char *str_a, const char *str_b) +{ + u16_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; +} diff --git a/subsys/shell/shell_utils.h b/subsys/shell/shell_utils.h new file mode 100644 index 00000000000..04fae207adf --- /dev/null +++ b/subsys/shell/shell_utils.h @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2018 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef SHELL_UTILS_H__ +#define SHELL_UTILS_H__ + +#include +#include +#ifdef __cplusplus +extern "C" { +#endif + +#define SHELL_MSG_SPECIFY_SUBCOMMAND "Please specify a subcommand.\r\n" + +#define SHELL_DEFAULT_TERMINAL_WIDTH (80u) /* Default PuTTY width. */ +#define SHELL_DEFAULT_TERMINAL_HEIGHT (24u) /* Default PuTTY height. */ + + + +s32_t row_span_with_buffer_offsets_get(struct shell_multiline_cons *cons, + u16_t offset1, + u16_t offset2); + +s32_t column_span_with_buffer_offsets_get(struct shell_multiline_cons *cons, + u16_t offset1, + u16_t offset2); + +void shell_multiline_data_calc(struct shell_multiline_cons *cons, + u16_t buff_pos, u16_t buff_len); + +static inline size_t shell_strlen(const char *str) +{ + return str == NULL ? 0 : strlen(str); +} + +char shell_make_argv(size_t *argc, char **argv, char *cmd, uint8_t max_argc); + +/** @brief Removes pattern and following space + * + */ +void shell_pattern_remove(char *buff, u16_t *buff_len, const char *pattern); + +int shell_command_add(char *buff, u16_t *buff_len, + const char *new_cmd, const char *pattern); + +void shell_spaces_trim(char *str); + +/** @brief Remove white chars from beginning and end of command buffer. + * + */ +void shell_buffer_trim(char *buff, u16_t *buff_len); + +/* Function checks how many identical characters have two strings starting + * from the first character. + */ +u16_t shell_str_similarity_check(const char *str_a, const char *str_b); + +#ifdef __cplusplus +} +#endif + +#endif /* SHELL_UTILS_H__ */ diff --git a/subsys/shell/shell_vt100.h b/subsys/shell/shell_vt100.h new file mode 100644 index 00000000000..8e4a6c6f6c6 --- /dev/null +++ b/subsys/shell/shell_vt100.h @@ -0,0 +1,589 @@ +/* + * Copyright (c) 2018 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef SHELL_VT100_H__ +#define SHELL_VT100_H__ + +#define SHELL_VT100_ASCII_ESC (0x1b) +#define SHELL_VT100_ASCII_DEL (0x7F) +#define SHELL_VT100_ASCII_BSPACE (0x08) +#define SHELL_VT100_ASCII_CTRL_A (0x01) +#define SHELL_VT100_ASCII_CTRL_C (0x03) +#define SHELL_VT100_ASCII_CTRL_E (0x05) +#define SHELL_VT100_ASCII_CTRL_L (0x0C) +#define SHELL_VT100_ASCII_CTRL_U (0x15) +#define SHELL_VT100_ASCII_CTRL_W (0x17) + +#define SHELL_VT100_SETNL \ +{ \ + SHELL_VT100_ASCII_ESC, '[', '2', '0', 'h', '\0' \ +} /* Set new line mode */ +#define SHELL_VT100_SETAPPL \ +{ \ + SHELL_VT100_ASCII_ESC, '[', '?', '1', 'h', '\0' \ +} /* Set cursor key to application */ +#define SHELL_VT100_SETCOL_132 \ +{ \ + SHELL_VT100_ASCII_ESC, '[', '?', '3', 'h', '\0' \ +} /* Set number of columns to 132 */ +#define SHELL_VT100_SETSMOOTH \ +{ \ + SHELL_VT100_ASCII_ESC, '[', '?', '4', 'h', '\0' \ +} /* Set smooth scrolling */ +#define SHELL_VT100_SETREVSCRN \ +{ \ + SHELL_VT100_ASCII_ESC, '[', '?', '5', 'h', '\0' \ +} /* Set reverse video on screen */ +#define SHELL_VT100_SETORGREL \ +{ \ + SHELL_VT100_ASCII_ESC, '[', '?', '6', 'h', '\0' \ +} /* Set origin to relative */ +#define SHELL_VT100_SETWRAP_ON \ +{ \ + SHELL_VT100_ASCII_ESC, '[', '?', '7', 'h', '\0' \ +} /* Set auto-wrap mode */ +#define SHELL_VT100_SETWRAP_OFF \ +{ \ + SHELL_VT100_ASCII_ESC, '[', '?', '7', 'l', '\0' \ +} /* Set auto-wrap mode */ + +#define SHELL_VT100_SETREP \ +{ \ + SHELL_VT100_ASCII_ESC, '[', '?', '8', 'h', '\0' \ +} /* Set auto-repeat mode */ +#define SHELL_VT100_SETINTER \ +{ \ + SHELL_VT100_ASCII_ESC, '[', '?', '9', 'h', '\0' \ +} /* Set interlacing mode */ + +#define SHELL_VT100_SETLF \ +{ \ + SHELL_VT100_ASCII_ESC, '[', '2', '0', 'l', '\0' \ +} /* Set line feed mode */ +#define SHELL_VT100_SETCURSOR \ +{ \ + SHELL_VT100_ASCII_ESC, '[', '?', '1', 'l', '\0' \ +} /* Set cursor key to cursor */ +#define SHELL_VT100_SETVT52 \ +{ \ + SHELL_VT100_ASCII_ESC, '[', '?', '2', 'l', '\0' \ +} /* Set VT52 (versus ANSI) */ +#define SHELL_VT100_SETCOL_80 \ +{ \ + SHELL_VT100_ASCII_ESC, '[', '?', '3', 'l', '\0' \ +} /* Set number of columns to 80 */ +#define SHELL_VT100_SETJUMP \ +{ \ + SHELL_VT100_ASCII_ESC, '[', '?', '4', 'l', '\0' \ +} /* Set jump scrolling */ +#define SHELL_VT100_SETNORMSCRN \ +{ \ + SHELL_VT100_ASCII_ESC, '[', '?', '5', 'l', '\0' \ +} /* Set normal video on screen */ +#define SHELL_VT100_SETORGABS \ +{ \ + SHELL_VT100_ASCII_ESC, '[', '?', '6', 'l', '\0' \ +} /* Set origin to absolute */ +#define SHELL_VT100_RESETWRAP \ +{ \ + SHELL_VT100_ASCII_ESC, '[', '?', '7', 'l', '\0' \ +} /* Reset auto-wrap mode */ +#define SHELL_VT100_RESETREP \ +{ \ + SHELL_VT100_ASCII_ESC, '[', '?', '8', 'l', '\0' \ +} /* Reset auto-repeat mode */ +#define SHELL_VT100_RESETINTER \ +{ \ + SHELL_VT100_ASCII_ESC, '[', '?', '9', 'l', '\0' \ +} /* Reset interlacing mode */ + +#define SHELL_VT100_ALTKEYPAD \ +{ \ + SHELL_VT100_ASCII_ESC, '=', '\0' \ +} /* Set alternate keypad mode */ +#define SHELL_VT100_NUMKEYPAD \ +{ \ + SHELL_VT100_ASCII_ESC, '>', '\0' \ +} /* Set numeric keypad mode */ + +#define SHELL_VT100_SETUKG0 \ +{ \ + SHELL_VT100_ASCII_ESC, '(', 'A', '\0' \ +} /* Set United Kingdom G0 character set */ +#define SHELL_VT100_SETUKG1 \ +{ \ + SHELL_VT100_ASCII_ESC, ')', 'A', '\0' \ +} /* Set United Kingdom G1 character set */ +#define SHELL_VT100_SETUSG0 \ +{ \ + SHELL_VT100_ASCII_ESC, '(', 'B', '\0' \ +} /* Set United States G0 character set */ +#define SHELL_VT100_SETUSG1 \ +{ \ + SHELL_VT100_ASCII_ESC, ')', 'B', '\0' \ +} /* Set United States G1 character set */ +#define SHELL_VT100_SETSPECG0 \ +{ \ + SHELL_VT100_ASCII_ESC, '(', '0', '\0' \ +} /* Set G0 special chars. & line set */ +#define SHELL_VT100_SETSPECG1 \ +{ \ + SHELL_VT100_ASCII_ESC, ')', '0', '\0' \ +} /* Set G1 special chars. & line set */ +#define SHELL_VT100_SETALTG0 \ +{ \ + SHELL_VT100_ASCII_ESC, '(', '1', '\0' \ +} /* Set G0 alternate character ROM */ +#define SHELL_VT100_SETALTG1 \ +{ \ + SHELL_VT100_ASCII_ESC, ')', '1', '\0' \ +} /* Set G1 alternate character ROM */ +#define SHELL_VT100_SETALTSPECG0 \ +{ \ + SHELL_VT100_ASCII_ESC, '(', '2', '\0' \ +} /* Set G0 alt char ROM and spec. graphics */ +#define SHELL_VT100_SETALTSPECG1 \ +{ \ + SHELL_VT100_ASCII_ESC, ')', '2', '\0' \ +} /* Set G1 alt char ROM and spec. graphics */ + +#define SHELL_VT100_SETSS2 \ +{ \ + SHELL_VT100_ASCII_ESC, 'N', '\0' \ +} /* Set single shift 2 */ +#define SHELL_VT100_SETSS3 \ +{ \ + SHELL_VT100_ASCII_ESC, 'O', '\0' \ +} /* Set single shift 3 */ + +#define SHELL_VT100_MODESOFF \ +{ \ + SHELL_VT100_ASCII_ESC, '[', 'm', '\0' \ +} /* Turn off character attributes */ +#define SHELL_VT100_MODESOFF_ \ +{ \ + SHELL_VT100_ASCII_ESC, '[', '0', 'm', '\0' \ +} /* Turn off character attributes */ +#define SHELL_VT100_BOLD \ +{ \ + SHELL_VT100_ASCII_ESC, '[', '1', 'm', '\0' \ +} /* Turn bold mode on */ +#define SHELL_VT100_LOWINT \ +{ \ + SHELL_VT100_ASCII_ESC, '[', '2', 'm', '\0' \ +} /* Turn low intensity mode on */ +#define SHELL_VT100_UNDERLINE \ +{ \ + SHELL_VT100_ASCII_ESC, '[', '4', 'm', '\0' \ +} /* Turn underline mode on */ +#define SHELL_VT100_BLINK \ +{ \ + SHELL_VT100_ASCII_ESC, '[', '5', 'm', '\0' \ +} /* Turn blinking mode on */ +#define SHELL_VT100_REVERSE \ +{ \ + SHELL_VT100_ASCII_ESC, '[', '7', 'm', '\0' \ +} /* Turn reverse video on */ +#define SHELL_VT100_INVISIBLE \ +{ \ + SHELL_VT100_ASCII_ESC, '[', '8', 'm', '\0' \ +} /* Turn invisible text mode on */ + +#define SHELL_VT100_SETWIN(t, b) \ +{ \ + SHELL_VT100_ASCII_ESC, '[', (t), ';', (b), 'r', '\0' \ +} /* Set top and bottom line#s of a window */ + +#define SHELL_VT100_CURSORUP(n) \ +{ \ + SHELL_VT100_ASCII_ESC, '[', (n), 'A', '\0' \ +} /* Move cursor up n lines */ +#define SHELL_VT100_CURSORDN(n) \ +{ \ + SHELL_VT100_ASCII_ESC, '[', (n), 'B', '\0' \ +} /* Move cursor down n lines */ +#define SHELL_VT100_CURSORRT(n) \ +{ \ + SHELL_VT100_ASCII_ESC, '[', (n), 'C', '\0' \ +} /* Move cursor right n lines */ +#define SHELL_VT100_CURSORLF(n) \ +{ \ + SHELL_VT100_ASCII_ESC, '[', (n), 'D', '\0' \ +} /* Move cursor left n lines */ +#define SHELL_VT100_CURSORHOME \ +{ \ + SHELL_VT100_ASCII_ESC, '[', 'H', '\0' \ +} /* Move cursor to upper left corner */ +#define SHELL_VT100_CURSORHOME_ \ +{ \ + SHELL_VT100_ASCII_ESC, '[', ';', 'H', '\0' \ +} /* Move cursor to upper left corner */ +#define SHELL_VT100_CURSORPOS(v, h) \ +{ \ + SHELL_VT100_ASCII_ESC, '[', (v), ';', (h), 'H', '\0' \ +} /* Move cursor to screen location v,h */ + +#define SHELL_VT100_HVHOME \ +{ \ + SHELL_VT100_ASCII_ESC, '[', 'f', '\0' \ +} /* Move cursor to upper left corner */ +#define SHELL_VT100_HVHOME_ \ +{ \ + SHELL_VT100_ASCII_ESC, '[', ';', 'f', '\0' \ +} /* Move cursor to upper left corner */ +#define SHELL_VT100_HVPOS(v, h) \ +{ \ + SHELL_VT100_ASCII_ESC, '[', (v), ';', (h), 'f', '\0' \ +} /* Move cursor to screen location v,h */ +#define SHELL_VT100_INDEX \ +{ \ + SHELL_VT100_ASCII_ESC, 'D', '\0' \ +} /* Move/scroll window up one line */ +#define SHELL_VT100_REVINDEX \ +{ \ + SHELL_VT100_ASCII_ESC, 'M', '\0' \ +} /* Move/scroll window down one line */ +#define SHELL_VT100_NEXTLINE \ +{ \ + SHELL_VT100_ASCII_ESC, 'E', '\0' \ +} /* Move to next line */ +#define SHELL_VT100_SAVECURSOR \ +{ \ + SHELL_VT100_ASCII_ESC, '7', '\0' \ +} /* Save cursor position and attributes */ +#define SHELL_VT100_RESTORECURSOR \ +{ \ + SHELL_VT100_ASCII_ESC, '8', '\0' \ +} /* Restore cursor position and attribute */ + +#define SHELL_VT100_TABSET \ +{ \ + SHELL_VT100_ASCII_ESC, 'H', '\0' \ +} /* Set a tab at the current column */ +#define SHELL_VT100_TABCLR \ +{ \ + SHELL_VT100_ASCII_ESC, '[', 'g', '\0' \ +} /* Clear a tab at the current column */ +#define SHELL_VT100_TABCLR_ \ +{ \ + SHELL_VT100_ASCII_ESC, '[', '0', 'g', '\0' \ +} /* Clear a tab at the current column */ +#define SHELL_VT100_TABCLRALL \ +{ \ + SHELL_VT100_ASCII_ESC, '[', '3', 'g', '\0' \ +} /* Clear all tabs */ + +#define SHELL_VT100_DHTOP \ +{ \ + SHELL_VT100_ASCII_ESC, '#', '3', '\0' \ +} /* Double-height letters, top half */ +#define SHELL_VT100_DHBOT \ +{ \ + SHELL_VT100_ASCII_ESC, '#', '4', '\0' \ +} /* Double-height letters, bottom hal */ +#define SHELL_VT100_SWSH \ +{ \ + SHELL_VT100_ASCII_ESC, '#', '5', '\0' \ +} /* Single width, single height letters */ +#define SHELL_VT100_DWSH \ +{ \ + SHELL_VT100_ASCII_ESC, '#', '6', '\0' \ +} /* Double width, single height letters */ + +#define SHELL_VT100_CLEAREOL \ +{ \ + SHELL_VT100_ASCII_ESC, '[', 'K', '\0' \ +} /* Clear line from cursor right */ +#define SHELL_VT100_CLEAREOL_ \ +{ \ + SHELL_VT100_ASCII_ESC, '[', '0', 'K', '\0' \ +} /* Clear line from cursor right */ +#define SHELL_VT100_CLEARBOL \ +{ \ + SHELL_VT100_ASCII_ESC, '[', '1', 'K', '\0' \ +} /* Clear line from cursor left */ +#define SHELL_VT100_CLEARLINE \ +{ \ + SHELL_VT100_ASCII_ESC, '[', '2', 'K', '\0' \ +} /* Clear entire line */ + +#define SHELL_VT100_CLEAREOS \ +{ \ + SHELL_VT100_ASCII_ESC, '[', 'J', '\0' \ +} /* Clear screen from cursor down */ +#define SHELL_VT100_CLEAREOS_ \ +{ \ + SHELL_VT100_ASCII_ESC, '[', '0', 'J', '\0' \ +} /* Clear screen from cursor down */ +#define SHELL_VT100_CLEARBOS \ +{ \ + SHELL_VT100_ASCII_ESC, '[', '1', 'J', '\0' \ +} /* Clear screen from cursor up */ +#define SHELL_VT100_CLEARSCREEN \ +{ \ + SHELL_VT100_ASCII_ESC, '[', '2', 'J', '\0' \ +} /* Clear entire screen */ + +#define SHELL_VT100_DEVSTAT \ +{ \ + SHELL_VT100_ASCII_ESC, '5', 'n', '\0' \ +} /* Device status report */ +#define SHELL_VT100_TERMOK \ +{ \ + SHELL_VT100_ASCII_ESC, '0', 'n', '\0' \ +} /* Response: terminal is OK */ +#define SHELL_VT100_TERMNOK \ +{ \ + SHELL_VT100_ASCII_ESC, '3', 'n', '\0' \ +} /* Response: terminal is not OK */ + +#define SHELL_VT100_GETCURSOR \ +{ \ + SHELL_VT100_ASCII_ESC, '[', '6', 'n', '\0' \ +} /* Get cursor position */ +#define SHELL_VT100_CURSORPOSAT \ +{ \ + SHELL_VT100_ASCII_ESC, (v), ';', (h), 'R', '\0' \ +} /* Response: cursor is at v,h */ + +#define SHELL_VT100_IDENT \ +{ \ + SHELL_VT100_ASCII_ESC, '[', 'c', '\0' \ +} /* Identify what terminal type */ +#define SHELL_VT100_IDENT_ \ +{ \ + SHELL_VT100_ASCII_ESC, '[', '0', 'c', '\0' \ +} /* Identify what terminal type */ +#define SHELL_VT100_GETTYPE \ +{ \ + SHELL_VT100_ASCII_ESC, '[', '?', '1', ';', (n), '0', 'c', '\0' \ +} /* Response: terminal type code n */ + +#define SHELL_VT100_RESET \ +{ \ + SHELL_VT100_ASCII_ESC, 'c', '\0' \ +} /*Reset terminal to initial state */ + +#define SHELL_VT100_ALIGN \ +{ \ + SHELL_VT100_ASCII_ESC, '#', '8', '\0' \ +} /* Screen alignment display */ +#define SHELL_VT100_TESTPU \ +{ \ + SHELL_VT100_ASCII_ESC, '[', '2', ';', '1', 'y', '\0' \ +} /* Confidence power up test */ +#define SHELL_VT100_TESTLB \ +{ \ + SHELL_VT100_ASCII_ESC, '[', '2', ';', '2', 'y', '\0' \ +} /* Confidence loopback test */ +#define SHELL_VT100_TESTPUREP \ +{ \ + SHELL_VT100_ASCII_ESC, '[', '2', ';', '9', 'y', '\0' \ +} /* Repeat power up test */ +#define SHELL_VT100_TESTLBREP \ +{ \ + SHELL_VT100_ASCII_ESC, '[', '2', ';', '1', '0', 'y', '\0' \ +} /* Repeat loopback test */ + +#define SHELL_VT100_LEDSOFF \ +{ \ +SHELL_VT100_ASCII_ESC, '[', '0', 'q', '\0' \ +} /* Turn off all four leds */ +#define SHELL_VT100_LED1 \ +{ \ +SHELL_VT100_ASCII_ESC, '[', '1', 'q', '\0' \ +} /* Turn on LED #1 */ +#define SHELL_VT100_LED2 \ +{ \ +SHELL_VT100_ASCII_ESC, '[', '2', 'q', '\0' \ +} /* Turn on LED #2 */ +#define SHELL_VT100_LED3 \ +{ \ +SHELL_VT100_ASCII_ESC, '[', '3', 'q', '\0' \ +} /* Turn on LED #3 */ +#define SHELL_VT100_LED4 \ +{ \ +SHELL_VT100_ASCII_ESC, '[', '4', 'q', '\0' \ +} /* Turn on LED #4 */ + +/* Function Keys */ + +#define SHELL_VT100_PF1 \ +{ \ +SHELL_VT100_ASCII_ESC, 'O', 'P', '\0' \ +} +#define SHELL_VT100_PF2 \ +{ \ +SHELL_VT100_ASCII_ESC, 'O', 'Q', '\0' \ +} + +#define SHELL_VT100_PF3 \ +{ \ + SHELL_VT100_ASCII_ESC, 'O', 'R', '\0' \ +} +#define SHELL_VT100_PF4 \ +{ \ + SHELL_VT100_ASCII_ESC, 'O', 'S', '\0' \ +} + +/* Arrow keys */ + +#define SHELL_VT100_UP_RESET \ +{ \ + SHELL_VT100_ASCII_ESC, 'A', '\0' \ +} +#define SHELL_VT100_UP_SET \ +{ \ + SHELL_VT100_ASCII_ESC, 'O', 'A', '\0' \ +} +#define SHELL_VT100_DOWN_RESET \ +{ \ + SHELL_VT100_ASCII_ESC, 'B', '\0' \ +} +#define SHELL_VT100_DOWN_SET \ +{ \ + SHELL_VT100_ASCII_ESC, 'O', 'B', '\0' \ +} +#define SHELL_VT100_RIGHT_RESET \ +{ \ + SHELL_VT100_ASCII_ESC, 'C', '\0' \ +} +#define SHELL_VT100_RIGHT_SET \ +{ \ + SHELL_VT100_ASCII_ESC, 'O', 'C', '\0' \ +} +#define SHELL_VT100_LEFT_RESET \ +{ \ + SHELL_VT100_ASCII_ESC, 'D', '\0' \ +} +#define SHELL_VT100_LEFT_SET \ +{ \ + SHELL_VT100_ASCII_ESC, 'O', 'D', '\0' \ +} + +/* Numeric Keypad Keys */ +#define SHELL_VT100_NUMERIC_0 \ +{ \ + '0', '\0' \ +} +#define SHELL_VT100_ALT_0 \ +{ \ + SHELL_VT100_ASCII_ESC, 'O', 'p', '\0' \ +} +#define SHELL_VT100_NUMERIC_1 \ +{ \ + '1', '\0' \ +} +#define SHELL_VT100_ALT_1 \ +{ \ + SHELL_VT100_ASCII_ESC, 'O', 'q', '\0' \ +} +#define SHELL_VT100_NUMERIC_2 \ +{ \ + '2', '\0' \ +} +#define SHELL_VT100_ALT_2 \ +{ \ + SHELL_VT100_ASCII_ESC, 'O', 'r', '\0' \ +} +#define SHELL_VT100_NUMERIC_3 \ +{ \ + '3', '\0' \ +} +#define SHELL_VT100_ALT_3 \ +{ \ + SHELL_VT100_ASCII_ESC, 'O', 's', '\0' \ +} +#define SHELL_VT100_NUMERIC_4 \ +{ \ + '4', '\0' \ +} +#define SHELL_VT100_ALT_4 \ +{ \ + SHELL_VT100_ASCII_ESC, 'O', 't', '\0' \ +} +#define SHELL_VT100_NUMERIC_5 \ +{ \ + '5', '\0' \ +} +#define SHELL_VT100_ALT_5 \ +{ \ + SHELL_VT100_ASCII_ESC, 'O', 'u', '\0' \ +} +#define SHELL_VT100_NUMERIC_6 \ +{ \ + '6', '\0' \ +} +#define SHELL_VT100_ALT_6 \ +{ \ + SHELL_VT100_ASCII_ESC, 'O', 'v', '\0' \ +} +#define SHELL_VT100_NUMERIC_7 \ +{ \ + '7', '\0' \ +} +#define SHELL_VT100_ALT_7 \ +{ \ + SHELL_VT100_ASCII_ESC, 'O', 'w', '\0' \ +} +#define SHELL_VT100_NUMERIC_8 \ +{ \ + '8', '\0' \ +} +#define SHELL_VT100_ALT_8 \ +{ \ + SHELL_VT100_ASCII_ESC, 'O', 'x', '\0' \ +} +#define SHELL_VT100_NUMERIC_9 \ +{ \ + '9', '\0' \ +} +#define SHELL_VT100_ALT_9 \ +{ \ + SHELL_VT100_ASCII_ESC, 'O', 'y' \ +} +#define SHELL_VT100_NUMERIC_MINUS \ +{ \ + '-', '\0' \ +} +#define SHELL_VT100_ALT_MINUS \ +{ \ + SHELL_VT100_ASCII_ESC, 'O', 'm', '\0' \ +} +#define SHELL_VT100_NUMERIC_COMMA \ +{ \ + ',', '\0' \ +} +#define SHELL_VT100_ALT_COMMA \ +{ \ + SHELL_VT100_ASCII_ESC, 'O', 'l', '\0' \ +} +#define SHELL_VT100_NUMERIC_ENTER \ +{ \ + ASCII_CR \ +} +#define SHELL_VT100_NUMERIC_PERIOD \ +{ \ + '.', '\0' \ +} +#define SHELL_VT100_ALT_PERIOD \ +{ \ + SHELL_VT100_ASCII_ESC, 'O', 'n', '\0' \ +} +#define SHELL_VT100_ALT_ENTER \ +{ \ + SHELL_VT100_ASCII_ESC, 'O', 'M', '\0' \ +} +#define SHELL_VT100_BGCOLOR(__col) \ +{ \ + SHELL_VT100_ASCII_ESC, '[', '4', '0' + (__col), 'm', '\0' \ +} +#define SHELL_VT100_COLOR(__col) \ +{ \ + SHELL_VT100_ASCII_ESC, '[', '1', ';', '3', '0' + (__col), 'm', '\0' \ +} + +#endif /* SHELL_VT100_H__ */