ESP32使用WIFI STA搭建TCP服务端并使用FreeRTOS_CLI制作简单命令终端

ESP32使用WIFI STA搭建TCP服务端并使用FreeRTOS_CLI制作简单命令终端

本文简单讲了如何缝合几个示例代码和第三方库来实现简单的telnet服务端和CLI.

WiFi STA:WiFi客户端(连WiFi热点的那个设备)

1 搭建telnet(TCP)服务器

由于telnet实际就是TCP流,上面无任何协议栈,所以只要搭建个TCP服务器就能用telnet。

连接WIFI(WIFI STA)

需要这堆头文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <string.h>
#include <sys/param.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "esp_netif.h"

#include "lwip/err.h"
#include "lwip/sockets.h"
#include "lwip/sys.h"
#include <lwip/netdb.h>

然后,写连接wifi的代码。
注:

另外地:

ESP-NETIF 库有两个目的:
它为 TCP/IP 堆栈之上的应用程序提供了一个抽象层。这将允许应用程序将来在 IP 堆栈之间进行选择。
它提供的 API 是线程安全的,即使底层 TCP/IP 堆栈 API 不是。
ESP-IDF 目前仅针对 lwIP TCP/IP 堆栈实现 ESP-NETIF。但是,适配器本身与 TCP/IP 实现无关,并且可以使用不同的实现。
某些 ESP-NETIF API 函数旨在由应用程序代码调用,例如获取/设置接口 IP 地址、配置 DHCP。其他函数供网络驱动层内部 ESP-IDF 使用。
在很多情况下,应用不需要直接调用 ESP-NETIF API,因为它们是从默认的网络事件处理程序调用的。

以及:

事件循环库允许一个组件声明一个事件,该事件由其他组件注册事件处理程序,即这些事件发生时将执行的代码。这允许松散耦合的组件将所需的行为附加到其他组件来进行状态更改,而无需应用程序参与。例如,高级连接处理库可以直接订阅 Wi-Fi 子系统生成的事件并对这些事件进行操作。这通过将代码执行序列化和延迟到另一个上下文来简化事件处理。

代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
static const char *TAG = "example";

// wifi连接信息
#define EXAMPLE_ESP_WIFI_SSID "HONOR_9X" // wifi名
#define EXAMPLE_ESP_WIFI_PASS "1234567890" // wifi密码
#define EXAMPLE_ESP_MAXIMUM_RETRY 5 // 最大重新连接次数

// 加密方法
// #define ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_OPEN
// #define ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WEP
// #define ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WAPI_PSK
// #define ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WPA2_WPA3_PSK
// #define ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WPA3_PSK
// #define ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WPA_WPA2_PSK
#define ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WPA2_PSK
// #define ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WPA_PSK

/* FreeRTOS 事件组定义*/
static EventGroupHandle_t s_wifi_event_group;

// 事件的定义
#define WIFI_CONNECTED_BIT BIT0
#define WIFI_FAIL_BIT BIT1

//重连次数
static int s_retry_num = 0;

/**
* @brief 一个可选的wifi事件处理钩子(回调,callback,事件)函数,由事件循环调用,并向用户的代码发送事件组信息
* @param arg 用户参数(初始化的时候由用户设定)
* @param event_base 触发事件的循环(初始化的时候由用户设定)
* @param event_id 触发事件的id号(由触发函数设置)
* @param event_data 触发事件的数据(由触发函数设置)
*
*
*/
static void event_handler(void *arg, esp_event_base_t event_base,int32_t event_id, void *event_data);

//连接wifi的函数
void wifi_init_sta(void);

void app_main(){

//初始化nvs(注1)
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND)
{
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);
}

static void event_handler(void *arg, esp_event_base_t event_base,int32_t event_id, void *event_data)
{
//WIFI_EVENT事件循环的wifi sta启动事件发生
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START)
{
esp_wifi_connect();//开始连接wifi
}
//WIFI_EVENT事件循环的wifi sta断开连接事件发生
else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED)
{
//重连次数判断
if (s_retry_num < EXAMPLE_ESP_MAXIMUM_RETRY)
{
//小于最大重新连接次数
esp_wifi_connect();
s_retry_num++;
ESP_LOGI(TAG, "retry to connect to the AP");
}
else
{
//大于最大重新连接次数,向s_wifi_event_group这个事件组发送WIFI_FAIL_BIT这个事件
xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT);
}
ESP_LOGI(TAG, "connect to the AP fail");
}
//IP_EVENT事件循环的获取到ip事件发生
else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP)
{
//拿到获取的ip
ip_event_got_ip_t *event = (ip_event_got_ip_t *)event_data;
//打印
ESP_LOGI(TAG, "got ip:" IPSTR, IP2STR(&event->ip_info.ip));
//重置重连次数
s_retry_num = 0;
//向s_wifi_event_group这个事件组发送WIFI_CONNECTED_BIT这个事件
xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
}
}

void wifi_init_sta(void)
{
s_wifi_event_group = xEventGroupCreate();//为wifi任务创建对应的事件组(注2)
ESP_ERROR_CHECK(esp_netif_init());//初始化netif(注3)
ESP_ERROR_CHECK(esp_event_loop_create_default());//初始化默认事件循环(注4)
esp_netif_create_default_wifi_sta();//使netif使用默认的wifi sta模式
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();//用默认参数初始化wifi配置结构体
ESP_ERROR_CHECK(esp_wifi_init(&cfg));//初始化WiFi为WiFi驱动程序分配资源,如WiFi控制结构,RX/TX缓冲区,WiFi NVS结构等。此 WiFi 也会启动 WiFi 任务。
//可选的事件处理(通过事件循环库)
esp_event_handler_instance_t instance_any_id;//系统事件循环已知的所有事件的句柄
esp_event_handler_instance_t instance_got_ip;//获取到了IP事件的句柄
//将事件处理程序的实例注册到WIFI_EVENT这个循环(这个循环的声明由wifi sdk导出)。
ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT, //注册到WIFI_EVENT这个循环
ESP_EVENT_ANY_ID,//系统事件循环已知的所有事件
&event_handler,//处理钩子(回调,callback,事件)函数
NULL,//处理钩子(回调,callback,事件)函数会得到的一个参数,这里不用
&instance_any_id));//返回的句柄(理论上可以为null,但是为了方便删除,保留)
//将事件处理程序的实例注册到IP_EVENT这个循环(这个循环的声明由wifi sdk导出)。
ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT,//注册到IP_EVENT这个循环
IP_EVENT_STA_GOT_IP,//wifi sta 获得了IP地址
&event_handler,//处理钩子(回调,callback,事件)函数
NULL,//处理钩子(回调,callback,事件)函数会得到的一个参数,这里不用
&instance_got_ip));//返回的句柄(理论上可以为null,但是为了方便删除,保留)
//wifi连接配置
wifi_config_t wifi_config = {
.sta = {//sta模式
.ssid = EXAMPLE_ESP_WIFI_SSID,//ssid(wif名字)
.password = EXAMPLE_ESP_WIFI_PASS,//password(wifi密码)
.threshold.authmode = ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD,//wifi认证模式
.sae_pwe_h2e = WPA3_SAE_PWE_BOTH,//是否启用了 SAE 哈希到元素
},
};
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));//设置模式为sta
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));//设置连接配置
ESP_ERROR_CHECK(esp_wifi_start());//开始连接wifi

ESP_LOGI(TAG, "wifi_init_sta finished.");

/* 等待连接建立 (WIFI_CONNECTED_BIT) 或连接失败,以获得最大重试次数 (WIFI_FAIL_BIT)。位由 event_handler() 设置(见上文,这个上文依赖了那些可选的事件处理) ,通过事件组来通知到任务*/
EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group,
WIFI_CONNECTED_BIT | WIFI_FAIL_BIT,
pdFALSE,
pdFALSE,
portMAX_DELAY);
/* xEventGroupWaitBits() 返回调用返回之前的位,因此我们可以测试实际发生的事件。*/

//连接成功
if (bits & WIFI_CONNECTED_BIT)
{
ESP_LOGI(TAG, "connected to ap SSID:%s password:%s",
EXAMPLE_ESP_WIFI_SSID, EXAMPLE_ESP_WIFI_PASS);
}
//连接失败
else if (bits & WIFI_FAIL_BIT)
{
ESP_LOGI(TAG, "Failed to connect to SSID:%s, password:%s",
EXAMPLE_ESP_WIFI_SSID, EXAMPLE_ESP_WIFI_PASS);
}
//未知事件
else
{
ESP_LOGE(TAG, "UNEXPECTED EVENT");
}
}

在WiFi基础上搭建TCP服务器

WiFi连接好后,WiFi物理层会连接到内置netif,netif来处理TCP/IP栈相关的东西,接下来只需要配置TCP相关的东西即可,而不用关心TCP层与IP、MAC层的通信(由TCP库自动配置)。

注:

setsockopt: 设置与某个套接字关联的选项。选项可能存在于多层协议中,它们总会出现在最上面的套接字层。当操作套接字选项时,选项位于的层和选项的名称必须给出。为了操作套接字层的选项,应该将层的值指定为SOL_SOCKET。为了操作其它层的选项,控制选项的合适协议号必须给出。例如,为了表示一个选项由TCP协议解析,层应该设定为协议号TCP

确切地说,close() 用来关闭套接字,将套接字描述符(或句柄)从内存清除,之后再也不能使用该套接字,与C语言中的 fclose() 类似。应用程序关闭套接字后,与该套接字相关的连接和缓存也失去了意义,TCP协议会自动触发关闭连接的操作。

shutdown() 用来关闭连接,而不是套接字,不管调用多少次 shutdown(),套接字依然存在,直到调用 close() 将套接字从内存清除。

调用 close()关闭套接字时,或调用 shutdown() 关闭输出流时,都会向对方发送 FIN 包。FIN 包表示数据传输完毕,计算机收到 FIN 包就知道不会再有数据传送过来了。

默认情况下,close()会立即向网络中发送FIN包,不管输出缓冲区中是否还有数据,而shutdown() 会等输出缓冲区中的数据传输完毕再发送FIN包。也就意味着,调用 close()将丢失输出缓冲区中的数据,而调用 shutdown() 不会。

accept()函数返回了一个new socket,这个socket从listen socket而来,是server专为listen到的client准备的一个socket,可以认为是为这一对通路单独服务的server端socket。我们先把它叫做专属socket。

accept()过程结束,返回的socket用于接下来的recv()。

代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
// TCP参数
#define PORT 3333 // 端口
#define KEEPALIVE_IDLE 5 // CONFIG_EXAMPLE_KEEPALIVE_IDLE
#define KEEPALIVE_INTERVAL 5 // CONFIG_EXAMPLE_KEEPALIVE_INTERVAL
#define KEEPALIVE_COUNT 3 // CONFIG_EXAMPLE_KEEPALIVE_COUNT


//回显并打log
static void do_retransmit(const int sock)
{
int len; // 接收长度
char rx_buffer[128]; // 接收缓冲

// 循环:当不为错误或断开时:
do
{
// 接收,使用专属socket,最大长度为sizeof(rx_buffer) - 1,放到rx_buffer
len = recv(sock, rx_buffer, sizeof(rx_buffer) - 1, 0);
// 小于0,错误
if (len < 0)
{
ESP_LOGE(TAG, "Error occurred during receiving: errno %d", errno);
}
// 等于0,断开连接
else if (len == 0)
{
ESP_LOGW(TAG, "Connection closed");
}
// 大于0,正常数据
else
{
rx_buffer[len] = 0; // 方便打印,将最后一个字符换成\0
// 打印数据长度
ESP_LOGI(TAG, "Received %d bytes: %s", len, rx_buffer);
// 短数据显示16进制
if (len < 5)
{
printf("short hex data: ");
for (size_t i = 0; i < len; i++)
{
printf("%02x ", rx_buffer[i]);
}
printf("\r\n");
}

// send() can return less bytes than supplied length.
// Walk-around for robust implementation.
// 用send函数发回去(实现了分片)
int to_write = len;
while (to_write > 0)
{
// 发送,使用专属socket,发to_write长度,发rx_buffer + (len - to_write)数据,返回实际发了多少
int written = send(sock, rx_buffer + (len - to_write), to_write, 0);
// 分片逻辑
if (written < 0)
{
ESP_LOGE(TAG, "Error occurred during sending: errno %d", errno);
}
to_write -= written;
}
}
} while (len > 0);
}

//tcp服务端任务函数,在wifi连上之后vtaskcreate就行了
static void tcp_server_task(void *pvParameters)
{
char addr_str[128];//打印缓冲区
int addr_family = AF_INET;//协议族(IPV4,IPV6)
int ip_protocol = 0;//高层协议
int keepAlive = 1;//保持连接,是
int keepIdle = KEEPALIVE_IDLE;//发送心跳空闲周期
int keepInterval = KEEPALIVE_INTERVAL;//发送心跳间隔
int keepCount = KEEPALIVE_COUNT; // 心跳重发次数
struct sockaddr_storage dest_addr; //

struct sockaddr_in *dest_addr_ip4 = (struct sockaddr_in *)&dest_addr; // 地址赋值,用来导出配置
dest_addr_ip4->sin_addr.s_addr = htonl(INADDR_ANY); // 本机地址(设置为0。0.0.0表示每次发送都从ip层获取)
dest_addr_ip4->sin_family = AF_INET; // sock使用IPV4
dest_addr_ip4->sin_port = htons(PORT); // sock监听的端口
ip_protocol = IPPROTO_IP; // 使用默认协议(IP)

// 建立socket,使用IPV4,stream模式,默认协议(IP)
int listen_sock = socket(addr_family, SOCK_STREAM, ip_protocol);
// 错误处理
if (listen_sock < 0)
{
ESP_LOGE(TAG, "Unable to create socket: errno %d", errno);
vTaskDelete(NULL);
return;
}

int opt = 1;
setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));//让端口释放后立即就可以被再次使用(注1)

ESP_LOGI(TAG, "Socket created");

//绑定socket和监听的端口
int err = bind(listen_sock, (struct sockaddr *)&dest_addr, sizeof(dest_addr));
//错误处理
if (err != 0)
{
ESP_LOGE(TAG, "Socket unable to bind: errno %d", errno);
ESP_LOGE(TAG, "IPPROTO: %d", addr_family);
goto CLEAN_UP;
}
ESP_LOGI(TAG, "Socket bound, port %d", PORT);

//开始监听已经绑定的端口
err = listen(listen_sock, 1);
//错误处理
if (err != 0)
{
ESP_LOGE(TAG, "Error occurred during listen: errno %d", errno);
goto CLEAN_UP;
}

//主循环
while (1)
{

ESP_LOGI(TAG, "Socket listening");

struct sockaddr_storage source_addr; // Large enough for both IPv4 or IPv6
socklen_t addr_len = sizeof(source_addr);
//获得专属socket(注3)
int sock = accept(listen_sock, (struct sockaddr *)&source_addr, &addr_len);
//错误处理
if (sock < 0)
{
ESP_LOGE(TAG, "Unable to accept connection: errno %d", errno);
break;
}

// Set tcp keepalive option
setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, &keepAlive, sizeof(int));//保持连接,是(注1)
setsockopt(sock, IPPROTO_TCP, TCP_KEEPIDLE, &keepIdle, sizeof(int));//发送心跳空闲周期(注1)
setsockopt(sock, IPPROTO_TCP, TCP_KEEPINTVL, &keepInterval, sizeof(int));//发送心跳间隔(注1)
setsockopt(sock, IPPROTO_TCP, TCP_KEEPCNT, &keepCount, sizeof(int));//心跳重发次数(注1)

// 转换客户端的ip地址到字符串,并打印
if (source_addr.ss_family == PF_INET)
{
inet_ntoa_r(((struct sockaddr_in *)&source_addr)->sin_addr, addr_str, sizeof(addr_str) - 1);
}

ESP_LOGI(TAG, "Socket accepted ip address: %s", addr_str);

//上层处理函数
do_retransmit(sock);//回显并打log
//do_CLI(sock);//cli核心

//结束sock(注2)
shutdown(sock, 0);
//结束sock(注2)
close(sock);
}

CLEAN_UP:
//结束sock(注2)
close(listen_sock);
//rtos删任务
vTaskDelete(NULL);
}

TCP服务器搭建完成后,用telnet客户端连接ESP32获得的IP地址即可建立telnet连接

2 移植FreeRTOS_CLI

https://www.freertos.org/zh-cn-cmn-s/a00104.html下载freertos官方插件包,拿到代码。
注:ESP32进临界区会崩溃,暂时去掉临界区,要求注册新命令时不能运行cli核心。
打开FreeRTOS_CLI.c,做如下更改:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/* FreeRTOS includes. */
[-] #include "FreeRTOS.h"
[-] #include "task.h"

[+] #include "freertos/FreeRTOS.h"
[+] #include "freertos/task.h"

[+] #ifndef configCOMMAND_INT_MAX_OUTPUT_SIZE
[+] #define configCOMMAND_INT_MAX_OUTPUT_SIZE 512
#endif

[+] portMUX_TYPE mux; // for esp32

//去掉临界区
[-] taskENTER_CRITICAL();
[-] taskEXIT_CRITICAL();

然后作为组件加载给ESP-IDF就行了。

3 制作简单命令终端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
//带分片的tcp socket发送
static void sock_send_str(const int sock, size_t len, const char *str)
{
// send() can return less bytes than supplied length.
// Walk-around for robust implementation.
size_t to_write = len;
while (to_write > 0)
{
int written = send(sock, str + (len - to_write), to_write, 0);
if (written < 0)
{
ESP_LOGE(TAG, "Error occurred during sending: errno %d", errno);
}
to_write -= written;
}
}

static void do_CLI(const int sock)
{

// Peripheral_Descriptor_t xConsole;
uint8_t cRxedChar;//接收缓冲区
size_t cInputIndex = 0;//接收字节计数
BaseType_t xMoreDataToFollow;//命令回调执行结果
/* The input and output buffers are declared static to keep them off the stack. */
static char pcOutputString[MAX_OUTPUT_LENGTH], pcInputString[MAX_INPUT_LENGTH];//命令输入输出缓冲区

sock_send_str(sock, strlen(pcWelcomeMessage), pcWelcomeMessage);//显示欢迎信息

//接收长度
int len;

do
{
//接收1字节数据
len = recv(sock, (void *)&cRxedChar, 1, 0);
//错误处理
if (len < 0)
{
ESP_LOGE(TAG, "Error occurred during receiving: errno %d", errno);
}
else if (len == 0)
{
ESP_LOGW(TAG, "Connection closed");
}
//正常处理
else
{

/* This implementation reads a single character at a time. Wait in the
Blocked state until a character is received. */
// FreeRTOS_read(xConsole, &cRxedChar, sizeof(cRxedChar));
//将backspace(0x7f)转换为0x08(\b)
if (cRxedChar == 127)
{
cRxedChar = '\b';
}

//回车换行
if (cRxedChar == '\n')
{
/* A newline character was received, so the input command string is
complete and can be processed. Transmit a line separator, just to
make the output easier to read. */
// FreeRTOS_write( xConsole, "\r\n", strlen( "\r\n" );
//回闲一个回车换行
sock_send_str(sock, strlen("\r\n"), "\r\n");


ESP_LOGI(TAG, "input_str(len = %d): %s", strlen(pcInputString), pcInputString);

//debug打印
if (strlen(pcInputString) < 10)
{
for (size_t i = 0; i < strlen(pcInputString); i++)
{
printf("%02x ", pcInputString[i]);
}
printf("\r\n");
}

/* The command interpreter is called repeatedly until it returns
pdFALSE. See the "Implementing a command" documentation for an
exaplanation of why this is. */
do
{
/* Send the command string to the command interpreter. Any
output generated by the command interpreter will be placed in the
pcOutputString buffer. */
//命令处理
xMoreDataToFollow = FreeRTOS_CLIProcessCommand(
pcInputString, /* The command string.*/
pcOutputString, /* The output buffer. */
MAX_OUTPUT_LENGTH /* The size of the output buffer. */
);

/* Write the output generated by the command interpreter to the
console. */
// FreeRTOS_write(xConsole, pcOutputString, strlen(pcOutputString));
//回显命令执行的结果
sock_send_str(sock, strlen(pcOutputString), pcOutputString);

} while (xMoreDataToFollow != pdFALSE);

/* All the strings generated by the input command have been sent.
Processing of the command is complete. Clear the input string ready
to receive the next command. */
//清空输入缓冲区
cInputIndex = 0;
memset(pcInputString, 0x00, MAX_INPUT_LENGTH);
}
else
{
/* The if() clause performs the processing after a newline character
is received. This else clause performs the processing if any other
character is received. */
//忽略回车
if (cRxedChar == '\r')
{
/* Ignore carriage returns. */
}
//收到\b删除一个字符
else if (cRxedChar == '\b')
{
/* Backspace was pressed. Erase the last character in the input
buffer - if there are any. */
if (cInputIndex > 0)
{
cInputIndex--;
pcInputString[cInputIndex] = '\0';
}
}
//将接收到的字节加入输入缓冲区
else
{
/* A character was entered. It was not a new line, backspace
or carriage return, so it is accepted as part of the input and
placed into the input buffer. When a n is entered the complete
string will be passed to the command interpreter. */
if (cInputIndex < MAX_INPUT_LENGTH)
{
pcInputString[cInputIndex] = cRxedChar;
cInputIndex++;
}
}
}
}
} while (len > 0);
}



完整代码(tcp_server.c、FreeRTOS_CLI.c、FreeRTOS_CLI.h)请参见代码包LINK>>>

参考

ESP32使用WIFI STA搭建TCP服务端并使用FreeRTOS_CLI制作简单命令终端

https://wuxiproj.mzy7.cn/posts/dd3451c8.html

作者

【社区】UM4I

发布于

2023-04-15

更新于

2024-12-07

许可协议

CC BY-NC-SA 4.0

Your browser is out-of-date!

Update your browser to view this website correctly.&npsb;Update my browser now

×