feat(factory): support USB CDC alongside UART CLI
This commit is contained in:
@@ -4,8 +4,8 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* Factory provisioning serial CLI. Owns command line parsing, provisioning
|
||||
* command handlers, and UART replies in the control-plane layer. Reads
|
||||
* `\r\n`-delimited commands from the console UART through the domain console
|
||||
* command handlers, and per-port replies in the control-plane layer. Reads
|
||||
* `\r\n`-delimited commands from UART0 and USB Serial/JTAG CDC through the domain console
|
||||
* bridge and replies with `OK`, `ERROR <reason>`, or one JSON line.
|
||||
*
|
||||
* 工厂串口 CLI 拥有行缓冲、命令解析和应答格式,但设备身份/Wi-Fi 的持久化仍经 domain 完成。
|
||||
@@ -36,7 +36,8 @@
|
||||
#define FACTORY_CLI_SERIAL_MAX_LEN 64
|
||||
#define FACTORY_CLI_JSON_MAX_LEN 640
|
||||
#define FACTORY_CLI_WIFI_JSON_MAX_LEN 3072
|
||||
#define FACTORY_CLI_READ_TIMEOUT_MS 100
|
||||
#define FACTORY_CLI_INPUT_BURST_BYTES 64
|
||||
#define FACTORY_CLI_PARTIAL_TIMEOUT_MS 2000
|
||||
#define FACTORY_CLI_RESET_REPLY_DELAY_MS 100
|
||||
#ifndef CONFIG_TQ_FACTORY_CLI_TASK_STACK_BYTES
|
||||
#define CONFIG_TQ_FACTORY_CLI_TASK_STACK_BYTES (7U * 1024U)
|
||||
@@ -71,9 +72,22 @@ static bool s_worker_starting;
|
||||
static portMUX_TYPE s_worker_lock = portMUX_INITIALIZER_UNLOCKED;
|
||||
|
||||
/* Touched only by the single worker task; no extra lock required. */
|
||||
static bool s_factory_mode_enabled;
|
||||
static char s_pending_device_id[FACTORY_CLI_DEVICE_ID_MAX_LEN];
|
||||
static char s_pending_serial_number[FACTORY_CLI_SERIAL_MAX_LEN];
|
||||
typedef struct {
|
||||
char line[FACTORY_CLI_MAX_LINE_LEN];
|
||||
size_t line_len;
|
||||
bool discard_until_eol;
|
||||
TickType_t last_rx_tick;
|
||||
bool factory_mode_enabled;
|
||||
char pending_device_id[FACTORY_CLI_DEVICE_ID_MAX_LEN];
|
||||
char pending_serial_number[FACTORY_CLI_SERIAL_MAX_LEN];
|
||||
} factory_cli_session_t;
|
||||
|
||||
/* 两路行缓冲及身份暂存也隔离,不能把UART的ID和CDC的序列号拼成一次保存。
|
||||
* 只有单个CLI worker访问;回复路由只在同步执行命令期间有效。 */
|
||||
static factory_cli_session_t s_uart_session;
|
||||
static factory_cli_session_t s_cdc_session;
|
||||
static factory_cli_session_t *s_current_session = &s_uart_session;
|
||||
static bool s_reply_over_cdc;
|
||||
|
||||
static TaskHandle_t factory_cli_get_worker_task(void)
|
||||
{
|
||||
@@ -250,7 +264,11 @@ static size_t split_args(char *line, char *argv[], size_t max_args)
|
||||
|
||||
static void cli_write_line(const char *line)
|
||||
{
|
||||
domain_runtime_console_write_line(line);
|
||||
if (s_reply_over_cdc) {
|
||||
domain_runtime_console_write_cdc_line(line);
|
||||
} else {
|
||||
domain_runtime_console_write_line(line);
|
||||
}
|
||||
}
|
||||
|
||||
static void cli_write_ok(void)
|
||||
@@ -443,7 +461,7 @@ static void handle_get_device_info(void)
|
||||
char json[FACTORY_CLI_JSON_MAX_LEN] = {0};
|
||||
snprintf(json, sizeof(json),
|
||||
"{\"factory_mode\":%s,\"device_id\":\"%s\",\"serial_number\":\"%s\"}",
|
||||
s_factory_mode_enabled ? "true" : "false",
|
||||
s_current_session->factory_mode_enabled ? "true" : "false",
|
||||
device_id,
|
||||
serial_number);
|
||||
cli_write_line(json);
|
||||
@@ -457,8 +475,8 @@ static void handle_save_config(void)
|
||||
domain_runtime_read_device_identity(current_id, sizeof(current_id),
|
||||
current_sn, sizeof(current_sn));
|
||||
|
||||
const char *id_source = s_pending_device_id[0] != '\0' ? s_pending_device_id : current_id;
|
||||
const char *sn_source = s_pending_serial_number[0] != '\0' ? s_pending_serial_number : current_sn;
|
||||
const char *id_source = s_current_session->pending_device_id[0] != '\0' ? s_current_session->pending_device_id : current_id;
|
||||
const char *sn_source = s_current_session->pending_serial_number[0] != '\0' ? s_current_session->pending_serial_number : current_sn;
|
||||
if (is_text_empty(id_source) || is_text_empty(sn_source)) {
|
||||
cli_write_error("identity_not_ready");
|
||||
return;
|
||||
@@ -481,8 +499,8 @@ static void handle_save_config(void)
|
||||
return;
|
||||
}
|
||||
|
||||
s_pending_device_id[0] = '\0';
|
||||
s_pending_serial_number[0] = '\0';
|
||||
s_current_session->pending_device_id[0] = '\0';
|
||||
s_current_session->pending_serial_number[0] = '\0';
|
||||
/* Force a fresh challenge/response against the new identity on next use. */
|
||||
control_plane_multimodal_device_auth_invalidate(0);
|
||||
/* 仅发布配置更新;Wi-Fi worker 在自己的上下文恢复配网,不阻塞串口应答。 */
|
||||
@@ -597,7 +615,7 @@ static void handle_command_line(char *line)
|
||||
const char *cmd = argv[0];
|
||||
|
||||
if (strcmp(cmd, "factory_mode") == 0) {
|
||||
s_factory_mode_enabled = true;
|
||||
s_current_session->factory_mode_enabled = true;
|
||||
cli_write_ok();
|
||||
} else if (strcmp(cmd, "get_device_info") == 0) {
|
||||
handle_get_device_info();
|
||||
@@ -606,9 +624,9 @@ static void handle_command_line(char *line)
|
||||
cli_write_error("missing_device_id");
|
||||
return;
|
||||
}
|
||||
strlcpy(s_pending_device_id, argv[1], sizeof(s_pending_device_id));
|
||||
trim_text(s_pending_device_id);
|
||||
if (s_pending_device_id[0] == '\0') {
|
||||
strlcpy(s_current_session->pending_device_id, argv[1], sizeof(s_current_session->pending_device_id));
|
||||
trim_text(s_current_session->pending_device_id);
|
||||
if (s_current_session->pending_device_id[0] == '\0') {
|
||||
cli_write_error("invalid_device_id");
|
||||
return;
|
||||
}
|
||||
@@ -618,9 +636,9 @@ static void handle_command_line(char *line)
|
||||
cli_write_error("missing_serial_number");
|
||||
return;
|
||||
}
|
||||
strlcpy(s_pending_serial_number, argv[1], sizeof(s_pending_serial_number));
|
||||
trim_text(s_pending_serial_number);
|
||||
if (s_pending_serial_number[0] == '\0') {
|
||||
strlcpy(s_current_session->pending_serial_number, argv[1], sizeof(s_current_session->pending_serial_number));
|
||||
trim_text(s_current_session->pending_serial_number);
|
||||
if (s_current_session->pending_serial_number[0] == '\0') {
|
||||
cli_write_error("invalid_serial_number");
|
||||
return;
|
||||
}
|
||||
@@ -684,6 +702,53 @@ static void handle_command_line(char *line)
|
||||
}
|
||||
}
|
||||
|
||||
static void factory_cli_poll_input(factory_cli_session_t *session, bool over_cdc)
|
||||
{
|
||||
/* 每轮每路最多64字节,避免一端持续发送饿死另一端;所有命令仍串行执行。 */
|
||||
if (session->line_len > 0U &&
|
||||
(TickType_t)(xTaskGetTickCount() - session->last_rx_tick) >=
|
||||
pdMS_TO_TICKS(FACTORY_CLI_PARTIAL_TIMEOUT_MS)) {
|
||||
session->line_len = 0U;
|
||||
session->discard_until_eol = true;
|
||||
}
|
||||
for (unsigned i = 0; i < FACTORY_CLI_INPUT_BURST_BYTES; ++i) {
|
||||
uint8_t ch = 0;
|
||||
int got = over_cdc ? domain_runtime_console_read_cdc_bytes(&ch, 1U, 0)
|
||||
: domain_runtime_console_read_bytes(&ch, 1U, 0);
|
||||
if (got <= 0) {
|
||||
if (got < 0) {
|
||||
session->line_len = 0U;
|
||||
session->discard_until_eol = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
session->last_rx_tick = xTaskGetTickCount();
|
||||
if (ch == '\r' || ch == '\n') {
|
||||
if (session->discard_until_eol) {
|
||||
session->discard_until_eol = false;
|
||||
session->line_len = 0U;
|
||||
} else if (session->line_len > 0U) {
|
||||
session->line[session->line_len] = '\0';
|
||||
s_current_session = session;
|
||||
s_reply_over_cdc = over_cdc;
|
||||
handle_command_line(session->line);
|
||||
session->line_len = 0U;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (session->discard_until_eol || ch < 0x20 || ch == 0x7F) {
|
||||
continue;
|
||||
}
|
||||
if (session->line_len < sizeof(session->line) - 1U) {
|
||||
session->line[session->line_len++] = (char)ch;
|
||||
} else {
|
||||
/* 整行丢弃到换行,不能把超长命令尾部误当成save_config等新命令。 */
|
||||
session->line_len = 0U;
|
||||
session->discard_until_eol = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void factory_cli_worker(void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
@@ -696,34 +761,20 @@ static void factory_cli_worker(void *arg)
|
||||
vTaskDelete(NULL);
|
||||
return;
|
||||
}
|
||||
ESP_LOGI(TAG, "Factory serial CLI ready on console UART");
|
||||
|
||||
char line[FACTORY_CLI_MAX_LINE_LEN];
|
||||
size_t line_len = 0;
|
||||
ESP_LOGI(TAG, "Factory serial CLI ready (UART0 and enabled USB Serial/JTAG CDC)");
|
||||
memset(&s_uart_session, 0, sizeof(s_uart_session));
|
||||
memset(&s_cdc_session, 0, sizeof(s_cdc_session));
|
||||
s_current_session = &s_uart_session;
|
||||
s_reply_over_cdc = false;
|
||||
|
||||
while (!factory_cli_get_stop_requested()) {
|
||||
uint8_t ch = 0;
|
||||
int got = domain_runtime_console_read_bytes(&ch, 1, FACTORY_CLI_READ_TIMEOUT_MS);
|
||||
if (got <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ch == '\r' || ch == '\n') {
|
||||
if (line_len > 0) {
|
||||
line[line_len] = '\0';
|
||||
handle_command_line(line);
|
||||
line_len = 0;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (ch < 0x20 || ch == 0x7F) {
|
||||
continue;
|
||||
}
|
||||
if (line_len < sizeof(line) - 1U) {
|
||||
line[line_len++] = (char)ch;
|
||||
} else {
|
||||
line_len = 0; /* overlong line: drop and resync */
|
||||
factory_cli_poll_input(&s_uart_session, false);
|
||||
if (factory_cli_get_stop_requested()) {
|
||||
break;
|
||||
}
|
||||
factory_cli_poll_input(&s_cdc_session, true);
|
||||
/* 驱动读取均为非阻塞;空闲时至少让出一个tick,不创建第二个CLI任务。 */
|
||||
vTaskDelay(1);
|
||||
}
|
||||
|
||||
factory_cli_finish_worker_exit();
|
||||
|
||||
@@ -613,12 +613,20 @@ void domain_runtime_restart(void);
|
||||
*
|
||||
* `read_bytes` returns bytes read (0 on timeout, <0 on error). A zero timeout
|
||||
* polls once; negative timeouts are invalid and return <0. Used by the factory
|
||||
* provisioning CLI.
|
||||
* provisioning CLI. install幂等,安装锁最多等待100ms;至少一路可用即成功。
|
||||
* 原有read/write_line操作UART0,CDC使用下方独立入口。
|
||||
*/
|
||||
esp_err_t domain_runtime_console_install(void);
|
||||
int domain_runtime_console_read_bytes(uint8_t *buf, size_t len, int timeout_ms);
|
||||
void domain_runtime_console_write_line(const char *line);
|
||||
|
||||
/** 独立USB Serial/JTAG CDC端点:read语义同UART,禁用/不可用返回负数。
|
||||
* write为尽力发送(共250ms入队预算,超时可能已发送部分),不切换或广播到UART。
|
||||
* 两路输入必须由同一个CLI任务消费,并各自维护行缓冲。驱动常驻,不随CLI stop卸载。
|
||||
*/
|
||||
int domain_runtime_console_read_cdc_bytes(uint8_t *buf, size_t len, int timeout_ms);
|
||||
void domain_runtime_console_write_cdc_line(const char *line);
|
||||
|
||||
void domain_runtime_behavior_set_keep_awake(bool keep_awake);
|
||||
esp_err_t domain_runtime_behavior_play_prompt(domain_wifi_prompt_id_t prompt);
|
||||
esp_err_t domain_runtime_behavior_play_prompt_file(const char *path);
|
||||
|
||||
@@ -1213,6 +1213,16 @@ void domain_runtime_console_write_line(const char *line)
|
||||
platform_console_write_line(line);
|
||||
}
|
||||
|
||||
int domain_runtime_console_read_cdc_bytes(uint8_t *buf, size_t len, int timeout_ms)
|
||||
{
|
||||
return platform_console_read_cdc_bytes(buf, len, timeout_ms);
|
||||
}
|
||||
|
||||
void domain_runtime_console_write_cdc_line(const char *line)
|
||||
{
|
||||
platform_console_write_cdc_line(line);
|
||||
}
|
||||
|
||||
esp_err_t domain_runtime_stepper_rotate_angle_with_accel_timeout(float angle,
|
||||
int target_delay_us,
|
||||
int timeout_ms)
|
||||
|
||||
@@ -77,6 +77,10 @@ set(platform_priv_requires
|
||||
nvs_flash
|
||||
)
|
||||
|
||||
if(CONFIG_TQ_FACTORY_CLI_USB_CDC_ENABLE)
|
||||
list(APPEND platform_priv_requires esp_driver_usb_serial_jtag)
|
||||
endif()
|
||||
|
||||
if(CONFIG_TQ_NETWORK_DIAGNOSTICS_ENABLE)
|
||||
list(APPEND platform_priv_requires lwip)
|
||||
endif()
|
||||
|
||||
@@ -243,7 +243,8 @@ esp_err_t platform_write_device_identity(const char *device_id, const char *seri
|
||||
* @brief 工厂 CLI 使用的串口行传输原语。
|
||||
* Console (UART) line-transport primitives for the factory CLI.
|
||||
*
|
||||
* 这些接口不保存命令状态。install 可重复调用;read 返回读取字节数,超时返回0,
|
||||
* 这些接口不保存命令状态。install 可重复调用,安装锁最多等待100ms;至少一路可用即成功。
|
||||
* 原有read/write_line操作UART0;read 返回读取字节数,超时返回0,
|
||||
* 错误返回负数。零超时表示轮询一次,负超时无效。
|
||||
* Stateless primitives over the console UART. `platform_console_install()` is
|
||||
* idempotent (a pre-existing console driver is treated as success).
|
||||
@@ -254,6 +255,13 @@ esp_err_t platform_console_install(void);
|
||||
int platform_console_read_bytes(uint8_t *buf, size_t len, int timeout_ms);
|
||||
void platform_console_write_line(const char *line);
|
||||
|
||||
/** 独立USB Serial/JTAG CDC端点:read语义同UART,禁用/不可用返回负数。
|
||||
* write为尽力发送(共250ms入队预算,超时可能已发送部分),不切换或广播到UART。
|
||||
* 两路输入必须由同一个CLI任务消费,并各自维护行缓冲。驱动常驻,不随CLI stop卸载。
|
||||
*/
|
||||
int platform_console_read_cdc_bytes(uint8_t *buf, size_t len, int timeout_ms);
|
||||
void platform_console_write_cdc_line(const char *line);
|
||||
|
||||
/**
|
||||
* @brief 基于 NVS 的通用持久化键值存储原语。
|
||||
* Generic persistent key/value blob storage (NVS-backed).
|
||||
|
||||
@@ -3,15 +3,24 @@
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* Console UART line-transport primitives for the factory CLI. Line
|
||||
* UART and USB Serial/JTAG CDC line-transport primitives for the factory CLI. Line
|
||||
* accumulation and the worker loop live in the control-plane CLI owner.
|
||||
*/
|
||||
|
||||
#include "platform.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "driver/uart.h"
|
||||
#include "sdkconfig.h"
|
||||
#if CONFIG_TQ_FACTORY_CLI_USB_CDC_ENABLE
|
||||
#include "driver/usb_serial_jtag.h"
|
||||
#if CONFIG_VFS_SUPPORT_IO
|
||||
#include "driver/usb_serial_jtag_vfs.h"
|
||||
#endif
|
||||
#endif
|
||||
#include "freertos/semphr.h"
|
||||
#include "esp_log.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
@@ -22,30 +31,67 @@
|
||||
|
||||
static const char *TAG = "platform_console";
|
||||
|
||||
/* UART0 驱动安装具有幂等性,已由其他启动路径安装也视为可用。 */
|
||||
/* 两个驱动是平台常驻资源,CLI stop只停止消费者,不卸载共享控制台驱动。
|
||||
* 初始化串行化且可重入;USB安装失败仍允许UART工作,下一次install会重试USB。 */
|
||||
static StaticSemaphore_t s_install_lock_buffer;
|
||||
static SemaphoreHandle_t s_install_lock;
|
||||
static portMUX_TYPE s_install_lock_guard = portMUX_INITIALIZER_UNLOCKED;
|
||||
#if CONFIG_TQ_FACTORY_CLI_USB_CDC_ENABLE && CONFIG_VFS_SUPPORT_IO
|
||||
static bool s_usb_vfs_attached;
|
||||
#endif
|
||||
|
||||
esp_err_t platform_console_install(void)
|
||||
{
|
||||
esp_err_t err = uart_driver_install(PLATFORM_CONSOLE_UART_PORT,
|
||||
PLATFORM_CONSOLE_RX_BUFFER,
|
||||
0,
|
||||
0,
|
||||
NULL,
|
||||
0);
|
||||
/* The console may have already installed the UART driver; treat that as success. */
|
||||
if (err == ESP_ERR_INVALID_STATE) {
|
||||
return ESP_OK;
|
||||
portENTER_CRITICAL(&s_install_lock_guard);
|
||||
if (s_install_lock == NULL) {
|
||||
s_install_lock = xSemaphoreCreateMutexStatic(&s_install_lock_buffer);
|
||||
}
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to install UART%d driver: %s",
|
||||
(int)PLATFORM_CONSOLE_UART_PORT, esp_err_to_name(err));
|
||||
portEXIT_CRITICAL(&s_install_lock_guard);
|
||||
if (s_install_lock == NULL ||
|
||||
xSemaphoreTake(s_install_lock, pdMS_TO_TICKS(100)) != pdTRUE) {
|
||||
return ESP_ERR_TIMEOUT;
|
||||
}
|
||||
return err;
|
||||
esp_err_t uart_err = ESP_OK;
|
||||
if (!uart_is_driver_installed(PLATFORM_CONSOLE_UART_PORT)) {
|
||||
uart_err = uart_driver_install(PLATFORM_CONSOLE_UART_PORT,
|
||||
PLATFORM_CONSOLE_RX_BUFFER, 0, 0, NULL, 0);
|
||||
}
|
||||
if (uart_err != ESP_OK) {
|
||||
ESP_LOGW(TAG, "Factory UART0 unavailable: %s", esp_err_to_name(uart_err));
|
||||
}
|
||||
bool ready = uart_err == ESP_OK;
|
||||
#if CONFIG_TQ_FACTORY_CLI_USB_CDC_ENABLE
|
||||
esp_err_t usb_err = ESP_OK;
|
||||
if (!usb_serial_jtag_is_driver_installed()) {
|
||||
usb_serial_jtag_driver_config_t config = {
|
||||
.rx_buffer_size = PLATFORM_CONSOLE_RX_BUFFER,
|
||||
.tx_buffer_size = 1024,
|
||||
};
|
||||
usb_err = usb_serial_jtag_driver_install(&config);
|
||||
}
|
||||
if (usb_err != ESP_OK) {
|
||||
ESP_LOGW(TAG, "Factory USB Serial/JTAG CDC unavailable: %s", esp_err_to_name(usb_err));
|
||||
} else {
|
||||
#if CONFIG_VFS_SUPPORT_IO
|
||||
if (!s_usb_vfs_attached) {
|
||||
/* 日志的USB第二输出也走同一驱动队列,避免VFS直接写FIFO与ISR竞争。 */
|
||||
usb_serial_jtag_vfs_use_driver();
|
||||
s_usb_vfs_attached = true;
|
||||
}
|
||||
#endif
|
||||
ESP_LOGI(TAG, "Factory USB Serial/JTAG CDC ready (GPIO19 D-, GPIO20 D+)");
|
||||
ready = true;
|
||||
}
|
||||
#endif
|
||||
(void)xSemaphoreGive(s_install_lock);
|
||||
return ready ? ESP_OK : uart_err;
|
||||
}
|
||||
|
||||
int platform_console_read_bytes(uint8_t *buf, size_t len, int timeout_ms)
|
||||
{
|
||||
/* 0 表示单次轮询,负数是无效参数;正数转换为 FreeRTOS 有界等待。 */
|
||||
if (buf == NULL || len == 0U || timeout_ms < 0) {
|
||||
if (buf == NULL || len == 0U || timeout_ms < 0 ||
|
||||
!uart_is_driver_installed(PLATFORM_CONSOLE_UART_PORT)) {
|
||||
return -1;
|
||||
}
|
||||
TickType_t ticks = pdMS_TO_TICKS(timeout_ms);
|
||||
@@ -55,9 +101,63 @@ int platform_console_read_bytes(uint8_t *buf, size_t len, int timeout_ms)
|
||||
void platform_console_write_line(const char *line)
|
||||
{
|
||||
/* 工厂协议按行发送,统一补充 CRLF,避免调用方重复处理行结束符。 */
|
||||
if (line == NULL) {
|
||||
if (line == NULL || !uart_is_driver_installed(PLATFORM_CONSOLE_UART_PORT)) {
|
||||
return;
|
||||
}
|
||||
uart_write_bytes(PLATFORM_CONSOLE_UART_PORT, line, strlen(line));
|
||||
uart_write_bytes(PLATFORM_CONSOLE_UART_PORT, "\r\n", 2);
|
||||
}
|
||||
|
||||
int platform_console_read_cdc_bytes(uint8_t *buf, size_t len, int timeout_ms)
|
||||
{
|
||||
#if CONFIG_TQ_FACTORY_CLI_USB_CDC_ENABLE
|
||||
if (buf == NULL || len == 0U || len > UINT32_MAX || timeout_ms < 0 ||
|
||||
!usb_serial_jtag_is_driver_installed()) {
|
||||
return -1;
|
||||
}
|
||||
return usb_serial_jtag_read_bytes(buf, (uint32_t)len, pdMS_TO_TICKS(timeout_ms));
|
||||
#else
|
||||
(void)buf;
|
||||
(void)len;
|
||||
(void)timeout_ms;
|
||||
return -1;
|
||||
#endif
|
||||
}
|
||||
|
||||
void platform_console_write_cdc_line(const char *line)
|
||||
{
|
||||
#if CONFIG_TQ_FACTORY_CLI_USB_CDC_ENABLE
|
||||
if (line == NULL || !usb_serial_jtag_is_driver_installed()) {
|
||||
return;
|
||||
}
|
||||
/* USB未打开/拔出时不能让CLI卡住。使用零等待的小块写入和一条应答共250ms预算;
|
||||
* 超时停止入队,已经交给驱动的字节不可撤销,主机按完整JSON/OK判定成功。 */
|
||||
TickType_t started = xTaskGetTickCount();
|
||||
TickType_t budget = pdMS_TO_TICKS(250);
|
||||
/* 普通ESP_LOG使用stdout;按行有界获取同一锁,避免JSON被并发日志插入。
|
||||
* 不使用无界flockfile,未能获得锁也必须让CLI继续处理停止/另一端输入。 */
|
||||
while (ftrylockfile(stdout) != 0) {
|
||||
if ((TickType_t)(xTaskGetTickCount() - started) >= budget) {
|
||||
return;
|
||||
}
|
||||
vTaskDelay(1);
|
||||
}
|
||||
size_t length = strlen(line);
|
||||
size_t sent = 0U;
|
||||
while (sent < length + 2U &&
|
||||
(TickType_t)(xTaskGetTickCount() - started) < budget) {
|
||||
const char *data = sent < length ? line + sent : &"\r\n"[sent - length];
|
||||
size_t remaining = sent < length ? length - sent : length + 2U - sent;
|
||||
size_t chunk = remaining > 64U ? 64U : remaining;
|
||||
int written = usb_serial_jtag_write_bytes(data, chunk, 0);
|
||||
if (written > 0) {
|
||||
sent += (size_t)written;
|
||||
} else {
|
||||
vTaskDelay(1);
|
||||
}
|
||||
}
|
||||
funlockfile(stdout);
|
||||
#else
|
||||
(void)line;
|
||||
#endif
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user