Skip to content

Commit 551de8c

Browse files
committed
feat(console): add support for running commands on separate tasks
Add asynchronous command execution with I/O redirection: - New esp_console_run_on_task() API to execute commands on FreeRTOS tasks - Support for custom stdin/stdout/stderr file pointers - Configurable stack size and priority per command - Task lifecycle management functions (wait, query status, cleanup) - Optional CONSOLE_COMMAND_ON_TASK config to enable feature This enables non-blocking command execution, pipelines, and better resource management for long-running console commands.
1 parent c13823d commit 551de8c

File tree

3 files changed

+296
-0
lines changed

3 files changed

+296
-0
lines changed

components/console/Kconfig

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,25 @@ menu "Console Library"
77
Instead of listing the commands in the order of registration, the help command lists
88
the available commands in sorted order, if this option is enabled.
99

10+
config CONSOLE_COMMAND_ON_TASK
11+
bool "Enable command on task"
12+
default n
13+
help
14+
In addition to run the command on current task, the console can also run commands on a dedicated
15+
task. This allows for longer running commands without blocking the console input, pipelines, and
16+
keeping the original task stack smaller.
17+
18+
config CONSOLE_COMMAND_DEFAULT_TASK_STACK_SIZE
19+
int "Console default task stack size"
20+
default 4096
21+
depends on CONSOLE_COMMAND_ON_TASK
22+
help
23+
Default stack size for the console command task, unless overridden.
24+
25+
config CONSOLE_COMMAND_DEFAULT_TASK_PRIORITY
26+
int "Console default task priority"
27+
default 5
28+
depends on CONSOLE_COMMAND_ON_TASK
29+
help
30+
Default priority for the console command task, unless overridden.
1031
endmenu

components/console/commands.c

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,12 @@
88
#include <string.h>
99
#include <stdlib.h>
1010
#include <sys/param.h>
11+
#include <unistd.h>
1112
#include "esp_heap_caps.h"
1213
#include "esp_log.h"
1314
#include "esp_console.h"
1415
#include "esp_system.h"
16+
#include "freertos/idf_additions.h"
1517
#include "linenoise/linenoise.h"
1618
#include "argtable3/argtable3.h"
1719
#include "sys/queue.h"
@@ -41,6 +43,8 @@ static const cmd_item_t *find_command_by_name(const char *name);
4143

4244
static esp_console_help_verbose_level_e s_verbose_level = ESP_CONSOLE_HELP_VERBOSE_LEVEL_1;
4345

46+
47+
4448
const esp_console_cmd_t *esp_console_get_by_name(const char *name)
4549
{
4650
const cmd_item_t *cmd = find_command_by_name(name);
@@ -262,6 +266,178 @@ esp_err_t esp_console_run(const char *cmdline, int *cmd_ret)
262266
return ESP_OK;
263267
}
264268

269+
270+
#ifdef CONFIG_CONSOLE_COMMAND_ON_TASK
271+
272+
typedef struct esp_console_task_handle {
273+
const cmd_item_t *cmd; //!< Pointer to the command definition
274+
TaskHandle_t task_handle; //!< Handle of the created task (protected by lock)
275+
FILE *_stdin; //!< Pipe for command input
276+
FILE *_stdout; //!< Pipe for command output
277+
FILE *_stderr; //!< Pipe for command error output
278+
int exit_code; //!< Exit code of the command (protected by lock)
279+
_lock_t lock; //!< Lock to protect task_handle and exit_code
280+
size_t argc; //!< Number of command line arguments
281+
char *argv[0]; //!< Command line arguments (flexible array member)
282+
} esp_console_task_handle_t;
283+
284+
285+
static void task_cmd(void *arg) {
286+
esp_console_task_handle_t *task = (esp_console_task_handle_t *)arg;
287+
288+
289+
FILE *old_stdin = __getreent()->_stdin;
290+
FILE *old_stdout = __getreent()->_stdout;
291+
FILE *old_stderr = __getreent()->_stderr;
292+
293+
if (task->_stdin)
294+
__getreent()->_stdin = task->_stdin;
295+
if (task->_stdout)
296+
__getreent()->_stdout = task->_stdout;
297+
if (task->_stderr)
298+
__getreent()->_stderr = task->_stderr;
299+
300+
int exit_code = -1;
301+
if (task->cmd->def.func) {
302+
exit_code = task->cmd->def.func(task->argc, task->argv);
303+
}
304+
if (task->cmd->def.func_w_context) {
305+
exit_code = (*task->cmd->def.func_w_context)(task->cmd->def.context, task->argc, task->argv);
306+
}
307+
308+
// Close sockets only if not pointing to standard fds
309+
if (task->_stdin) {
310+
fclose(task->_stdin);
311+
__getreent()->_stdin = old_stdin;
312+
}
313+
if (task->_stdout) {
314+
fclose(task->_stdout);
315+
__getreent()->_stdout = old_stdout;
316+
}
317+
if (task->_stderr) {
318+
fclose(task->_stderr);
319+
__getreent()->_stderr = old_stderr;
320+
}
321+
322+
__lock_acquire(task->lock);
323+
task->exit_code = exit_code;
324+
task->task_handle = NULL;
325+
__lock_release(task->lock);
326+
327+
vTaskDelete(NULL);
328+
}
329+
330+
esp_err_t esp_console_run_on_task(const char *cmdline, FILE *_stdin, FILE *_stdout, FILE *_stderr, esp_console_task_handle_t **out_task)
331+
{
332+
const size_t cmd_len = strlen(cmdline);
333+
if (!cmd_len || cmd_len >= s_config.max_cmdline_length) {
334+
return ESP_ERR_INVALID_ARG;
335+
}
336+
337+
// Try to do all memory allocations in one go
338+
// Calculate the size of each component for clarity and maintainability
339+
const size_t task_struct_size = sizeof(esp_console_task_handle_t); // Size of the task struct
340+
const size_t argv_array_size = sizeof(char *) * s_config.max_cmdline_args; // Size of argv array
341+
const size_t cmdline_buf_size = cmd_len + 1; // Size of command line buffer (including null terminator)
342+
const size_t total_size = task_struct_size + argv_array_size + cmdline_buf_size;
343+
344+
esp_console_task_handle_t *task = (esp_console_task_handle_t *) heap_caps_calloc(1, total_size, s_config.heap_alloc_caps);
345+
if (task == NULL) {
346+
return ESP_ERR_NO_MEM;
347+
}
348+
349+
// The line buffer is placed after the struct and argv array
350+
char *line_buf = (char *) (((char *)task) + task_struct_size + argv_array_size);
351+
strlcpy(line_buf, cmdline, cmd_len+1);
352+
353+
task->argc = esp_console_split_argv(line_buf, task->argv,
354+
s_config.max_cmdline_args);
355+
if (task->argc == 0) {
356+
free(task);
357+
return ESP_ERR_INVALID_ARG;
358+
}
359+
360+
task->exit_code = -1;
361+
task->cmd = find_command_by_name(task->argv[0]);
362+
if (task->cmd == NULL) {
363+
free(task);
364+
return ESP_ERR_NOT_FOUND;
365+
}
366+
367+
task->_stdin = _stdin;
368+
task->_stdout = _stdout;
369+
task->_stderr = _stderr;
370+
__lock_init(task->lock);
371+
372+
uint32_t stack_size = task->cmd->def.stack_size ? task->cmd->def.stack_size : (CONFIG_CONSOLE_COMMAND_DEFAULT_TASK_STACK_SIZE);
373+
UBaseType_t priority = task->cmd->def.priority ? task->cmd->def.priority : (CONFIG_CONSOLE_COMMAND_DEFAULT_TASK_PRIORITY);
374+
BaseType_t handle = xTaskCreate(&task_cmd, task->argv[0], stack_size, task, priority, &task->task_handle);
375+
376+
if (handle != pdPASS) {
377+
__lock_close(task->lock);
378+
free(task);
379+
return ESP_ERR_NO_MEM;
380+
}
381+
*out_task = task;
382+
383+
return ESP_OK;
384+
}
385+
386+
void esp_console_task_free(esp_console_task_handle_t *task)
387+
{
388+
__lock_acquire(task->lock);
389+
TaskHandle_t handle = task->task_handle;
390+
__lock_release(task->lock);
391+
392+
if (handle) {
393+
// Wait for the task to finish before deleting
394+
esp_console_wait_task(task, NULL);
395+
vTaskDelete(handle);
396+
397+
__lock_acquire(task->lock);
398+
task->task_handle = NULL;
399+
__lock_release(task->lock);
400+
}
401+
__lock_close(task->lock);
402+
free(task);
403+
}
404+
405+
bool esp_console_task_is_running(esp_console_task_handle_t *task)
406+
{
407+
__lock_acquire(task->lock);
408+
TaskHandle_t handle = task->task_handle;
409+
__lock_release(task->lock);
410+
411+
if (handle) {
412+
return eTaskGetState(handle) != eDeleted;
413+
}
414+
return false;
415+
}
416+
417+
void esp_console_wait_task(esp_console_task_handle_t *task, int *cmd_ret)
418+
{
419+
TaskHandle_t handle;
420+
421+
__lock_acquire(task->lock);
422+
handle = task->task_handle;
423+
__lock_release(task->lock);
424+
425+
if (handle) {
426+
// Wait until task is done
427+
while (eTaskGetState(handle) != eDeleted) {
428+
vTaskDelay(10 / portTICK_PERIOD_MS);
429+
}
430+
}
431+
432+
if (cmd_ret) {
433+
__lock_acquire(task->lock);
434+
*cmd_ret = task->exit_code;
435+
__lock_release(task->lock);
436+
}
437+
}
438+
439+
#endif // CONFIG_CONSOLE_COMMAND_ON_TASK
440+
265441
static struct {
266442
struct arg_str *help_cmd;
267443
struct arg_int *verbose_level;

components/console/esp_console.h

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,18 @@ typedef struct {
190190
* If not set, the command will not be listed in 'help' output.
191191
*/
192192
const char *help;
193+
194+
#ifdef CONFIG_CONSOLE_COMMAND_ON_TASK
195+
/**
196+
* Stack size, if command is run on a separate task
197+
*/
198+
size_t stack_size;
199+
/**
200+
* Task priority, if command is run on a separate task
201+
*/
202+
UBaseType_t priority;
203+
#endif
204+
193205
/**
194206
* Hint text, usually lists possible arguments.
195207
* If set to NULL, and 'argtable' field is non-NULL, hint will be generated
@@ -491,6 +503,93 @@ esp_err_t esp_console_start_repl(esp_console_repl_t *repl);
491503
*/
492504
esp_err_t esp_console_stop_repl(esp_console_repl_t *repl);
493505

506+
507+
#ifdef CONFIG_CONSOLE_COMMAND_ON_TASK
508+
509+
/**
510+
* @brief Opaque handle for a console task
511+
*/
512+
typedef struct esp_console_task_handle esp_console_task_handle_t;
513+
514+
/**
515+
* @brief Run a console command on a separate FreeRTOS task
516+
*
517+
* This function executes a console command in a new FreeRTOS task, allowing
518+
* asynchronous execution with I/O redirection via file pointers.
519+
*
520+
* @param[in] cmdline Command line string (command name followed by arguments)
521+
* @param[in] _stdin FILE pointer for standard input (or NULL to use default)
522+
* @param[in] _stdout FILE pointer for standard output (or NULL to use default)
523+
* @param[in] _stderr FILE pointer for standard error (or NULL to use default)
524+
* @param[out] out_task Pointer to receive the task handle. Use this handle with
525+
* esp_console_task_is_running() and esp_console_wait_task().
526+
* Must be freed with esp_console_task_free() when done.
527+
*
528+
* @note The task will run with the stack size and priority specified in the
529+
* command's esp_console_cmd_t structure, or defaults if not specified.
530+
* @note The provided file pointers will be closed when the task completes,
531+
* allowing callers to be notified of task completion (e.g., via pipe EOF).
532+
* @note The caller is responsible for creating any pipes and managing file descriptors.
533+
*
534+
* @return
535+
* - ESP_OK on success (task created successfully)
536+
* - ESP_ERR_INVALID_STATE if esp_console_init wasn't called
537+
* - ESP_ERR_INVALID_ARG if the command line is empty or only whitespace
538+
* - ESP_ERR_NOT_FOUND if command with given name wasn't registered
539+
* - ESP_ERR_NO_MEM if out of memory
540+
*/
541+
esp_err_t esp_console_run_on_task(const char *cmdline, FILE *_stdin, FILE *_stdout, FILE *_stderr, esp_console_task_handle_t **out_task);
542+
543+
/**
544+
* @brief Free resources associated with a console task
545+
*
546+
* This function frees the memory allocated for the task handle and associated
547+
* resources. If the task is still running, it will be deleted.
548+
*
549+
* @param[in] task Task handle returned by esp_console_run_on_task()
550+
*
551+
* @note Always call this function after you're done with a task handle to
552+
* prevent memory leaks.
553+
* @note It's safe to call this on a task that has already finished.
554+
*/
555+
void esp_console_task_free(esp_console_task_handle_t *task);
556+
557+
/**
558+
* @brief Wait for a console task to complete
559+
*
560+
* This function blocks until the specified console task has finished execution.
561+
*
562+
* @param[in] task Task handle returned by esp_console_run_on_task()
563+
* @param[out] cmd_ret Pointer to store the command's return code. Can be NULL
564+
* if return code is not needed.
565+
*
566+
* @note This function will block until the task completes. Use
567+
* esp_console_task_is_running() for non-blocking status checks.
568+
* @note The task handle is not freed by this function, so call
569+
* esp_console_task_free() afterwards.
570+
*/
571+
void esp_console_wait_task(esp_console_task_handle_t *task, int *cmd_ret);
572+
573+
/**
574+
* @brief Check if a console task is still running
575+
*
576+
* This function provides a non-blocking way to check the status of a console task.
577+
*
578+
* @param[in] task Task handle returned by esp_console_run_on_task()
579+
*
580+
* @return
581+
* - true if the task is still running
582+
* - false if the task has completed or the handle is invalid
583+
*
584+
* @note This function does not block and returns immediately.
585+
* @note Use this in a loop with vTaskDelay() to poll for task completion, or
586+
* use esp_console_wait_task() for blocking wait.
587+
*/
588+
bool esp_console_task_is_running(esp_console_task_handle_t *task);
589+
590+
#endif // CONFIG_CONSOLE_COMMAND_ON_TASK
591+
592+
494593
#ifdef __cplusplus
495594
}
496595
#endif

0 commit comments

Comments
 (0)