feat(factory-cli): add Wi-Fi credential management commands

This commit is contained in:
chenjintao
2026-08-25 14:09:25 +08:00
parent 63df9f91b3
commit 140e90447c
8 changed files with 362 additions and 13 deletions
@@ -13,6 +13,7 @@
#include "control_plane_internal.h"
#include <ctype.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -29,7 +30,9 @@
#define FACTORY_CLI_DEVICE_ID_MAX_LEN 64
#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_RESET_REPLY_DELAY_MS 100
#ifndef CONFIG_TQ_FACTORY_CLI_TASK_STACK_BYTES
#define CONFIG_TQ_FACTORY_CLI_TASK_STACK_BYTES (10U * 1024U)
#endif
@@ -184,23 +187,56 @@ static void trim_text(char *value)
static size_t split_args(char *line, char *argv[], size_t max_args)
{
size_t argc = 0;
char *cursor = line;
while (*cursor != '\0' && argc < max_args) {
while (*cursor != '\0' && isspace((unsigned char)(*cursor))) {
cursor++;
char *read_cursor = line;
char *write_cursor = line;
while (*read_cursor != '\0') {
while (*read_cursor != '\0' && isspace((unsigned char)(*read_cursor))) {
read_cursor++;
}
if (*cursor == '\0') {
if (*read_cursor == '\0') {
break;
}
argv[argc++] = cursor;
while (*cursor != '\0' && !isspace((unsigned char)(*cursor))) {
cursor++;
if (argc >= max_args) {
return SIZE_MAX;
}
if (*cursor == '\0') {
break;
argv[argc++] = write_cursor;
char quote = '\0';
while (*read_cursor != '\0') {
char ch = *read_cursor;
if (quote != '\0') {
if (ch == quote) {
quote = '\0';
read_cursor++;
continue;
}
} else {
if (isspace((unsigned char)ch)) {
break;
}
if (ch == '\'' || ch == '"') {
quote = ch;
read_cursor++;
continue;
}
}
if (ch == '\\' && read_cursor[1] != '\0') {
read_cursor++;
ch = *read_cursor;
}
*write_cursor++ = ch;
read_cursor++;
}
*cursor = '\0';
cursor++;
if (quote != '\0') {
return SIZE_MAX;
}
while (*read_cursor != '\0' && isspace((unsigned char)(*read_cursor))) {
read_cursor++;
}
*write_cursor++ = '\0';
}
return argc;
}
@@ -226,6 +262,150 @@ static void cli_write_error(const char *message)
cli_write_line(line);
}
static bool json_append_text(char *out,
size_t out_len,
size_t *offset,
const char *text)
{
if (out == NULL || offset == NULL || text == NULL || *offset >= out_len) {
return false;
}
size_t text_len = strlen(text);
if (text_len >= out_len - *offset) {
return false;
}
memcpy(out + *offset, text, text_len);
*offset += text_len;
out[*offset] = '\0';
return true;
}
static bool json_append_escaped_string(char *out,
size_t out_len,
size_t *offset,
const char *value)
{
if (!json_append_text(out, out_len, offset, "\"")) {
return false;
}
for (const unsigned char *cursor = (const unsigned char *)value;
*cursor != '\0';
++cursor) {
char escape[8] = {0};
const char *encoded = escape;
switch (*cursor) {
case '"':
encoded = "\\\"";
break;
case '\\':
encoded = "\\\\";
break;
case '\b':
encoded = "\\b";
break;
case '\f':
encoded = "\\f";
break;
case '\n':
encoded = "\\n";
break;
case '\r':
encoded = "\\r";
break;
case '\t':
encoded = "\\t";
break;
default:
if (*cursor < 0x20U) {
snprintf(escape, sizeof(escape), "\\u%04x", (unsigned)*cursor);
} else {
escape[0] = (char)*cursor;
escape[1] = '\0';
}
break;
}
if (!json_append_text(out, out_len, offset, encoded)) {
return false;
}
}
return json_append_text(out, out_len, offset, "\"");
}
static void handle_get_wifi(void)
{
domain_wifi_saved_networks_t networks = {0};
esp_err_t err = domain_runtime_wifi_portal_get_saved_networks(&networks);
if (err != ESP_OK) {
cli_write_error("wifi_read_failed");
return;
}
char json[FACTORY_CLI_WIFI_JSON_MAX_LEN] = {0};
size_t offset = 0;
char prefix[48] = {0};
snprintf(prefix, sizeof(prefix), "{\"count\":%u,\"networks\":[", (unsigned)networks.count);
bool valid = json_append_text(json, sizeof(json), &offset, prefix);
for (size_t i = 0; valid && i < networks.count; ++i) {
char item_prefix[40] = {0};
snprintf(item_prefix,
sizeof(item_prefix),
"%s{\"priority\":%u,\"ssid\":",
i == 0U ? "" : ",",
(unsigned)i);
valid = json_append_text(json, sizeof(json), &offset, item_prefix) &&
json_append_escaped_string(json,
sizeof(json),
&offset,
networks.ssids[i]) &&
json_append_text(json, sizeof(json), &offset, "}");
}
valid = valid && json_append_text(json, sizeof(json), &offset, "]}");
if (!valid) {
cli_write_error("wifi_response_too_large");
return;
}
cli_write_line(json);
cli_write_ok();
}
static void handle_set_wifi(size_t argc, char *argv[])
{
if (argc < 2 || is_text_empty(argv[1])) {
cli_write_error("missing_wifi_ssid");
return;
}
if (argc > 3) {
cli_write_error("invalid_wifi_arguments");
return;
}
const char *password = argc >= 3 ? argv[2] : "";
esp_err_t err = domain_runtime_wifi_portal_save_credentials(argv[1], password);
if (err != ESP_OK) {
cli_write_error(err == ESP_ERR_INVALID_ARG ?
"invalid_wifi_credentials" :
"wifi_save_failed");
return;
}
cli_write_ok();
}
static void handle_reset_wifi(void)
{
esp_err_t err = domain_runtime_wifi_portal_clear_saved_credentials();
if (err != ESP_OK) {
cli_write_error("wifi_reset_failed");
return;
}
cli_write_ok();
vTaskDelay(pdMS_TO_TICKS(FACTORY_CLI_RESET_REPLY_DELAY_MS));
domain_runtime_restart();
}
static esp_err_t resolve_device_identity(char *device_id, size_t device_id_len)
{
char serial_number[FACTORY_CLI_SERIAL_MAX_LEN] = {0};
@@ -406,6 +586,10 @@ static void handle_command_line(char *line)
char *argv[FACTORY_CLI_MAX_ARGC] = {0};
size_t argc = split_args(line, argv, FACTORY_CLI_MAX_ARGC);
if (argc == SIZE_MAX) {
cli_write_error("invalid_command_syntax");
return;
}
if (argc == 0) {
return;
}
@@ -443,6 +627,12 @@ static void handle_command_line(char *line)
cli_write_ok();
} else if (strcmp(cmd, "save_config") == 0) {
handle_save_config();
} else if (strcmp(cmd, "get_wifi") == 0) {
handle_get_wifi();
} else if (strcmp(cmd, "set_wifi") == 0) {
handle_set_wifi(argc, argv);
} else if (strcmp(cmd, "reset_wifi") == 0) {
handle_reset_wifi();
} else if (strcmp(cmd, "get_auth_material") == 0 ||
strcmp(cmd, "auth_material") == 0 ||
strcmp(cmd, "get_device_auth_material") == 0 ||