Skip to content

Commit f666878

Browse files
committed
feat(esp_repl): Add esp_linenoise and esp_commands dependencies
1 parent 6553880 commit f666878

File tree

5 files changed

+146
-76
lines changed

5 files changed

+146
-76
lines changed

esp_repl/CMakeLists.txt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,4 @@ set(srcs "esp_repl.c")
44

55
idf_component_register(
66
SRCS ${srcs}
7-
INCLUDE_DIRS include
8-
PRIV_INCLUDE_DIRS private_include)
7+
INCLUDE_DIRS include)

esp_repl/esp_repl.c

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
12
/*
23
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
34
*
@@ -9,7 +10,8 @@
910
#include "freertos/semphr.h"
1011
#include "esp_repl.h"
1112
#include "esp_err.h"
12-
13+
#include "esp_commands.h"
14+
#include "esp_linenoise.h"
1315
typedef enum {
1416
ESP_REPL_STATE_RUNNING,
1517
ESP_REPL_STATE_STOPPED
@@ -34,8 +36,8 @@ typedef struct esp_repl_instance {
3436

3537
esp_err_t esp_repl_create(esp_repl_instance_handle_t *handle, const esp_repl_config_t *config)
3638
{
37-
if ((config->executor.func == NULL) ||
38-
(config->reader.func == NULL) ||
39+
if ((config->linenoise_handle == NULL) ||
40+
(config->command_set_handle == NULL) ||
3941
(config->max_cmd_line_size == 0)) {
4042
return ESP_ERR_INVALID_ARG;
4143
}
@@ -140,11 +142,22 @@ void esp_repl(esp_repl_instance_handle_t handle)
140142
* function is called. */
141143
xSemaphoreTake(state->mux, portMAX_DELAY);
142144

145+
esp_linenoise_handle_t l_hdl = config->linenoise_handle;
146+
esp_command_set_handle_t c_set = config->command_set_handle;
147+
143148
/* REPL loop */
144149
while (state->state == ESP_REPL_STATE_RUNNING) {
145150

146151
/* try to read a command line */
147-
const esp_err_t read_ret = config->reader.func(config->reader.ctx, cmd_line, cmd_line_size);
152+
const esp_err_t read_ret = esp_linenoise_get_line(l_hdl, cmd_line, cmd_line_size);
153+
154+
/* Add the command to the history */
155+
esp_linenoise_history_add(l_hdl, cmd_line);
156+
157+
/* Save command history to filesystem */
158+
if (config->history_save_path) {
159+
esp_linenoise_history_save(l_hdl, config->history_save_path);
160+
}
148161

149162
/* forward the raw command line to the pre executor callback (e.g., save in history).
150163
* this callback is not necessary for the user to register, continue if it isn't */
@@ -159,7 +172,7 @@ void esp_repl(esp_repl_instance_handle_t handle)
159172

160173
/* try to run the command */
161174
int cmd_func_ret;
162-
const esp_err_t exec_ret = config->executor.func(config->executor.ctx, cmd_line, &cmd_func_ret);
175+
const esp_err_t exec_ret = esp_commands_execute(c_set, cmd_line, &cmd_func_ret);
163176

164177
/* forward the raw command line to the post executor callback (e.g., save in history).
165178
* this callback is not necessary for the user to register, continue if it isn't */

esp_repl/idf_component.yml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,12 @@ version: "1.0.0"
22
description: "esp_repl - Read Eval Print Loop component"
33
url: https://github.com/espressif/idf-extra-components/tree/master/esp_repl
44
dependencies:
5-
idf: ">=6.0"
5+
SoucheSouche/test_esp_linenoise:
6+
version: "*"
7+
registry_url: https://components-staging.espressif.com
8+
SoucheSouche/test_esp_commands:
9+
version: "*"
10+
registry_url: https://components-staging.espressif.com
611
sbom:
712
manifests:
813
- path: sbom_esp_repl.yml

esp_repl/include/esp_repl.h

Lines changed: 9 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -11,31 +11,13 @@ extern "C" {
1111

1212
#include <stdbool.h>
1313
#include "esp_err.h"
14+
#include "esp_linenoise.h"
1415

1516
/**
1617
* @brief Handle to a REPL instance.
1718
*/
1819
typedef struct esp_repl_instance *esp_repl_instance_handle_t;
1920

20-
/**
21-
* @brief Function prototype for reading input for the REPL.
22-
*
23-
* @param ctx User-defined context pointer.
24-
* @param buf Buffer to store the read data.
25-
* @param buf_size Size of the buffer in bytes.
26-
*
27-
* @return ESP_OK on success, error code otherwise.
28-
*/
29-
typedef esp_err_t (*esp_repl_reader_fn)(void *ctx, char *buf, size_t buf_size);
30-
31-
/**
32-
* @brief Reader configuration structure for the REPL.
33-
*/
34-
typedef struct esp_repl_reader {
35-
esp_repl_reader_fn func; /**!< Function to read input */
36-
void *ctx; /**!< Context passed to the reader function */
37-
} esp_repl_reader_t;
38-
3921
/**
4022
* @brief Function prototype called before executing a command.
4123
*
@@ -55,25 +37,6 @@ typedef struct esp_repl_pre_executor {
5537
void *ctx; /**!< Context passed to the pre-executor function */
5638
} esp_repl_pre_executor_t;
5739

58-
/**
59-
* @brief Function prototype to execute a REPL command.
60-
*
61-
* @param ctx User-defined context pointer.
62-
* @param buf Null-terminated command string.
63-
* @param ret_val Pointer to store the command return value.
64-
*
65-
* @return ESP_OK on success, error code otherwise.
66-
*/
67-
typedef esp_err_t (*esp_repl_executor_fn)(void *ctx, const char *buf, int *ret_val);
68-
69-
/**
70-
* @brief Executor configuration structure for the REPL.
71-
*/
72-
typedef struct esp_repl_executor {
73-
esp_repl_executor_fn func; /**!< Function to execute commands */
74-
void *ctx; /**!< Context passed to the executor function */
75-
} esp_repl_executor_t;
76-
7740
/**
7841
* @brief Function prototype called after executing a command.
7942
*
@@ -133,13 +96,14 @@ typedef struct esp_repl_on_exit {
13396
* @brief Configuration structure to initialize a REPL instance.
13497
*/
13598
typedef struct esp_repl_config {
136-
size_t max_cmd_line_size; /**!< Maximum allowed command line size */
137-
esp_repl_reader_t reader; /**!< Reader callback and context */
138-
esp_repl_pre_executor_t pre_executor; /**!< Pre-executor callback and context */
139-
esp_repl_executor_t executor; /**!< Executor callback and context */
140-
esp_repl_post_executor_t post_executor; /**!< Post-executor callback and context */
141-
esp_repl_on_stop_t on_stop; /**!< Stop callback and context */
142-
esp_repl_on_exit_t on_exit; /**!< Exit callback and context */
99+
esp_linenoise_handle_t linenoise_handle; /**!< Handle to the esp_linenoise instance */
100+
esp_command_set_handle_t command_set_handle; /**!< Handle to a set of commands */
101+
size_t max_cmd_line_size; /**!< Maximum allowed command line size */
102+
const char *history_save_path; /**!< Path to file to save the history */
103+
esp_repl_pre_executor_t pre_executor; /**!< Pre-executor callback and context */
104+
esp_repl_post_executor_t post_executor; /**!< Post-executor callback and context */
105+
esp_repl_on_stop_t on_stop; /**!< Stop callback and context */
106+
esp_repl_on_exit_t on_exit; /**!< Exit callback and context */
143107
} esp_repl_config_t;
144108

145109
/**

esp_repl/test_apps/main/test_esp_repl.c

Lines changed: 112 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -6,70 +6,159 @@
66

77
#include <string.h>
88
#include <stdio.h>
9+
#include <sys/socket.h>
910
#include "freertos/FreeRTOS.h"
1011
#include "freertos/task.h"
1112
#include "freertos/semphr.h"
1213
#include "unity.h"
1314
#include "esp_repl.h"
15+
#include "esp_linenoise.h"
16+
#include "esp_commands.h"
1417

15-
typedef struct esp_linenoise_dummy {
16-
size_t value;
17-
} esp_linenoise_dummy_t;
18-
typedef struct esp_linenoise_dummy *esp_linenoise_handle_t;
1918

20-
typedef struct esp_commands_dummy {
21-
size_t value;
22-
} esp_commands_dummy_t;
23-
typedef struct esp_commands_dummy *esp_commands_handle_t;
19+
static int s_socket_fd[2];
20+
static size_t s_pre_executor_nb_of_calls = 0;
21+
static size_t s_post_executor_nb_of_calls = 0;
22+
static size_t s_on_stop_nb_of_calls = 0;
23+
static size_t s_on_exit_nb_of_calls = 0;
2424

25-
esp_err_t test_reader_non_blocking(esp_linenoise_handle_t handle, char *buf, size_t buf_size)
25+
static void test_socket_setup(int socket_fd[2])
2626
{
27-
return ESP_OK;
27+
// 2 fd are generated, simulating the full-duplex
28+
// communication between linenoise and the terminal
29+
TEST_ASSERT_EQUAL(0, socketpair(AF_UNIX, SOCK_STREAM, 0, socket_fd));
30+
31+
// assure that the read will be blocking
32+
int flags = fcntl(socket_fd[0], F_GETFL, 0);
33+
flags &= ~O_NONBLOCK;
34+
fcntl(socket_fd[0], F_SETFL, flags);
35+
36+
flags = fcntl(socket_fd[1], F_GETFL, 0);
37+
flags &= ~O_NONBLOCK;
38+
fcntl(socket_fd[0], F_SETFL, flags);
2839
}
2940

30-
esp_err_t test_pre_executor(void *ctx, char *buf, const esp_err_t reader_ret_val)
41+
static void test_socket_teardown(int socket_fd[2])
3142
{
32-
return ESP_OK;
43+
close(socket_fd[0]);
44+
close(socket_fd[1]);
3345
}
3446

35-
esp_err_t test_executor(esp_commands_handle_t handle, const char *buf, int *ret_val)
47+
static void test_send_characters(int socket_fd, const char *msg)
3648
{
49+
// wait to simulate that the user is doing the input
50+
// and prevent linenoise to detect the incoming character(s)
51+
// as pasted
52+
wait_ms(100);
53+
54+
const size_t msg_len = strlen(msg);
55+
const int nwrite = write(socket_fd, msg, msg_len);
56+
TEST_ASSERT_EQUAL(msg_len, nwrite);
57+
}
58+
59+
esp_err_t test_pre_executor(void *ctx, char *buf, const esp_err_t reader_ret_val)
60+
{
61+
s_pre_executor_nb_of_calls++;
3762
return ESP_OK;
3863
}
3964

4065
esp_err_t test_post_executor(void *ctx, const char *buf, const esp_err_t executor_ret_val, const int cmd_ret_val)
4166
{
67+
s_post_executor_nb_of_calls++;
4268
return ESP_OK;
4369
}
4470

4571
void test_on_stop(void *ctx, esp_repl_instance_handle_t handle)
4672
{
73+
s_on_stop_nb_of_calls++;
4774
return;
4875
}
4976

5077
void test_on_exit(void *ctx, esp_repl_instance_handle_t handle)
5178
{
79+
s_on_exit_nb_of_calls++;
5280
return;
5381
}
5482

55-
TEST_CASE("esp_repl() called after successful init, with non blocking reader", "[esp_repl]")
83+
TEST_CASE("esp_repl() loop calls all callbacks and exit on call to esp_repl_stop", "[esp_repl]")
5684
{
57-
esp_commands_dummy_t dummy_esp_linenoise = {.value = 0x01 };
58-
esp_commands_dummy_t dummy_esp_commands = {.value = 0x02 };
59-
esp_repl_config_t config = {
85+
esp_linenoise_config_t linenoise_config;
86+
esp_linenoise_get_instance_config_default(&linenoise_config);
87+
test_socket_setup(s_socket_fd);
88+
esp_linenoise_handle_t esp_linenoise_hdl = esp_linenoise_create_instance(&linenoise_config);
89+
90+
/* wait for a bit so esp_linenoise has time to properly initialize */
91+
vTaskDelay(pdMS_TO_TICKS(100));
92+
93+
esp_commands_dummy_t dummy_esp_commands = { .value = 0x02 };
94+
esp_repl_config_t repl_config = {
95+
.linenoise_handle = esp_linenoise_hdl,
96+
.command_set_handle = NULL,
6097
.max_cmd_line_size = 256,
61-
.reader = { .func = (esp_repl_reader_fn)test_reader_non_blocking, .ctx = &dummy_esp_linenoise },
98+
.history_save_path = NULL,
6299
.pre_executor = { .func = test_pre_executor, .ctx = NULL },
63-
.executor = { .func = (esp_repl_executor_fn)test_executor, .ctx = &dummy_esp_commands },
64100
.post_executor = { .func = test_post_executor, .ctx = NULL },
65101
.on_stop = { .func = test_on_stop, .ctx = NULL },
66102
.on_exit = { .func = test_on_exit, .ctx = NULL }
67103
};
68104

69-
esp_repl_instance_handle_t handle = NULL;
70-
TEST_ASSERT_EQUAL(ESP_OK, esp_repl_create(&handle, &config));
71-
TEST_ASSERT_NOT_NULL(handle);
105+
esp_repl_instance_handle_t repl_handle = NULL;
106+
TEST_ASSERT_EQUAL(ESP_OK, esp_repl_create(&repl_handle, &repl_config));
107+
TEST_ASSERT_NOT_NULL(repl_handle);
72108

73109
xTaskCreate(esp_apptrace_send_uart_tx_task, "app_trace_uart_tx_task", 2500, hw_data, uart_prio, NULL);
74110

75-
}
111+
/* should fail before repl is started */
112+
TEST_ASSERT_NOT_EQUAL(ESP_OK, esp_repl_stop(repl_handle));
113+
114+
/* start repl */
115+
TEST_ASSERT_NOT_EQUAL(ESP_OK, esp_repl_start(NULL));
116+
TEST_ASSERT_EQUAL(ESP_OK, esp_repl_start(repl_handle));
117+
118+
/* wait for a bit so linenoise_get_line is reached in the esp_repl while loop */
119+
vTaskDelay(pdMS_TO_TICKS(100));
120+
121+
/* send a dummy string new line terminated to trigger linenoise to return */
122+
const char *input_line = "dummy_message\n";
123+
test_send_characters(s_socket_fd[1], input_line);
124+
125+
/* wait for a bit so so esp_repl() has time to loop back into esp_linenoise_get_line */
126+
vTaskDelay(pdMS_TO_TICKS(100));
127+
128+
/* check that pre-executor, post-executor callbacks are called */
129+
TEST_ASSERT(1, s_pre_executor_nb_of_calls);
130+
TEST_ASSERT(1, s_post_executor_nb_of_calls);
131+
132+
/* here, esp_repl() should be back in esp_linenoise_get_line, call
133+
* esp_repl_stop() and check that both the on_stop and on_exit callbacks
134+
* are being called */
135+
TEST_ASSERT_NOT_EQUAL(ESP_OK, esp_repl_stop(NULL));
136+
TEST_ASSERT_EQUAL(ESP_OK, esp_repl_stop(repl_handle));
137+
138+
vTaskDelay(pdMS_TO_TICKS(100));
139+
140+
TEST_ASSERT_EQUAL(1, s_on_stop_nb_of_calls);
141+
TEST_ASSERT_EQUAL(1, s_on_exit_nb_of_calls);
142+
TEST_ASSERT_EQUAL(2, s_pre_executor_nb_of_calls);
143+
TEST_ASSERT_EQUAL(2, s_post_executor_nb_of_calls);
144+
145+
/* make sure calling stop fails because the repl is no longer running */
146+
TEST_ASSERT_NOT_EQUAL(ESP_OK, esp_repl_stop(repl_handle));
147+
148+
/* reset the static variables */
149+
s_on_stop_nb_of_calls = 0;
150+
s_on_exit_nb_of_calls = 0;
151+
s_pre_executor_nb_of_calls = 0;
152+
s_post_executor_nb_of_calls = 0;
153+
154+
/* destroy the repl instance */
155+
TEST_ASSERT_NOT_EQUAL(ESP_OK, esp_repl_destroy(NULL));
156+
TEST_ASSERT_EQUAL(ESP_OK, esp_repl_destroy(repl_handle));
157+
158+
/* should fail after the instance is destroy */
159+
TEST_ASSERT_NOT_EQUAL(ESP_OK, esp_repl_start(repl_handle));
160+
TEST_ASSERT_NOT_EQUAL(ESP_OK, esp_repl_stop(repl_handle));
161+
TEST_ASSERT_NOT_EQUAL(ESP_OK, esp_repl_destroy(repl_handle));
162+
163+
test_socket_teardown(s_socket_fd);
164+
}

0 commit comments

Comments
 (0)