Merge pull request #4 from chenkui164/feat-cli-wifi

feat(cli/wifi): USB CDC console and whole-radio Wi-Fi switch
This commit is contained in:
cuicuiV5 committed 2026-09-12 15:11:36 +08:00
commit b339f3525d
14 files changed
+242 -42

No files matched your search

+120 -19
View File
@@ -1,20 +1,118 @@
#include "cli_manager.h"
#include "version.h"
#include "wifi/wifi_manager.h"
#include "log/app_log.h"
#include "ble/ble_remote_client.h"
#include "audio/audio_pipeline.h"
#include "keymap/key_state_machine.h"
#include <Arduino.h>
#include <ArduinoJson.h>
#include <USBCDC.h>
#if !ARDUINO_USB_CDC_ON_BOOT
extern USBCDC USBSerial;
#endif
extern key_mapper_engine_t g_key_engine;
static String s_input_buffer = "";
static void handle_command(const String& line);
static void cli_write_line(const String& line) {
Serial.println(line);
#if !ARDUINO_USB_CDC_ON_BOOT
if (USBSerial) {
USBSerial.println(line);
}
#endif
}
static void cli_feed_char(char c) {
if (c == '\r' || c == '\n') {
if (s_input_buffer.length() > 0) {
handle_command(s_input_buffer);
s_input_buffer = "";
}
} else {
s_input_buffer += c;
if (s_input_buffer.length() > 250) {
s_input_buffer = "";
}
}
}
static void handle_wifi_command(const String& arg) {
bool want_on = arg.equalsIgnoreCase("on");
bool want_off = arg.equalsIgnoreCase("off");
if (want_on || want_off) {
if (wifi_manager_get_enabled() == want_on) {
cli_write_line(want_on
? "{\"status\":\"nochange\",\"wifi_enabled\":true}"
: "{\"status\":\"nochange\",\"wifi_enabled\":false}");
return;
}
wifi_manager_set_enabled(want_on);
cli_write_line(want_on
? "{\"status\":\"ok\",\"wifi_enabled\":true,\"rebooting\":true}"
: "{\"status\":\"ok\",\"wifi_enabled\":false,\"rebooting\":true}");
cli_write_line(want_on
? "Wi-Fi 将在重启后开启(AP 热点 RemoteMapper-AP 恢复)..."
: "Wi-Fi 将在重启后关闭(后台改用 USB CDC/UART 的 'wifi on' 恢复)...");
delay(300);
ESP.restart();
} else {
JsonDocument doc;
doc["wifi_enabled"] = wifi_manager_get_enabled();
doc["ap_running"] = wifi_manager_is_ap_running();
doc["ap_ip"] = wifi_manager_get_ap_ip();
doc["sta_connected"] = wifi_manager_is_sta_connected();
doc["sta_ip"] = wifi_manager_get_sta_ip();
String out;
serializeJson(doc, out);
cli_write_line(out);
}
}
static void handle_log_command(const String& arg) {
if (arg.equalsIgnoreCase("on")) {
app_log_set_cdc_enabled(true);
cli_write_line("{\"status\":\"ok\",\"cdc_log_enabled\":true}");
cli_write_line("CDC 日志镜像已开启(注意:语音会话期间日志较密集)");
} else if (arg.equalsIgnoreCase("off")) {
app_log_set_cdc_enabled(false);
cli_write_line("{\"status\":\"ok\",\"cdc_log_enabled\":false}");
cli_write_line("CDC 日志镜像已关闭(UART 日志不受影响)");
} else {
JsonDocument doc;
doc["cdc_log_enabled"] = app_log_get_cdc_enabled();
String out;
serializeJson(doc, out);
cli_write_line(out);
}
}
static void handle_command(const String& line) {
String cmd = line;
cmd.trim();
if (cmd.length() == 0) return;
// Split into "head [tail]" so wifi on/off/status can share one dispatcher
int sp = cmd.indexOf(' ');
String head = (sp < 0) ? cmd : cmd.substring(0, sp);
String tail = (sp < 0) ? String("") : cmd.substring(sp + 1);
tail.trim();
if (head.equalsIgnoreCase("wifi")) {
handle_wifi_command(tail);
return;
}
if (head.equalsIgnoreCase("log")) {
handle_log_command(tail);
return;
}
if (cmd.equalsIgnoreCase("status") || cmd.equalsIgnoreCase("info")) {
JsonDocument doc;
doc["firmware"] = FIRMWARE_NAME;
@@ -26,28 +124,33 @@ static void handle_command(const String& line) {
doc["samples_pushed"] = g_audio_pipeline.total_samples_pushed;
doc["free_heap"] = ESP.getFreeHeap();
doc["free_psram"] = ESP.getFreePsram();
doc["wifi_enabled"] = wifi_manager_get_enabled();
String out;
serializeJson(doc, out);
Serial.println(out);
cli_write_line(out);
}
else if (cmd.equalsIgnoreCase("reconnect")) {
Serial.println("{\"status\":\"reconnecting\"}");
cli_write_line("{\"status\":\"reconnecting\"}");
ble_remote_trigger_reconnect();
}
else if (cmd.equalsIgnoreCase("reset_keys")) {
key_engine_load_defaults(&g_key_engine);
Serial.println("{\"status\":\"keymap_reset_to_defaults\"}");
cli_write_line("{\"status\":\"keymap_reset_to_defaults\"}");
}
else if (cmd.equalsIgnoreCase("help")) {
Serial.println("Commands:");
Serial.println(" status - Display system info & runtime statistics (JSON)");
Serial.println(" reconnect - Trigger BLE remote re-scan");
Serial.println(" reset_keys - Reset key bindings to factory defaults");
Serial.println(" help - Show available commands");
cli_write_line("Commands:");
cli_write_line(" status - Display system info & runtime statistics (JSON)");
cli_write_line(" reconnect - Trigger BLE remote re-scan");
cli_write_line(" reset_keys - Reset key bindings to factory defaults");
cli_write_line(" wifi on|off - Enable/disable the whole Wi-Fi radio (persisted)");
cli_write_line(" wifi status - Show Wi-Fi radio & connection status (JSON)");
cli_write_line(" log on|off - Mirror full logs to USB CDC (default: off)");
cli_write_line(" log status - Show CDC log mirror state (JSON)");
cli_write_line(" help - Show available commands");
}
else {
Serial.println("{\"error\":\"unknown_command\",\"hint\":\"type help\"}");
cli_write_line("{\"error\":\"unknown_command\",\"hint\":\"type help\"}");
}
}
@@ -60,18 +163,16 @@ void cli_manager_init(void) {
void cli_manager_task(void) {
while (Serial.available() > 0) {
char c = (char)Serial.read();
if (c == '\r' || c == '\n') {
if (s_input_buffer.length() > 0) {
handle_command(s_input_buffer);
s_input_buffer = "";
}
} else {
s_input_buffer += c;
if (s_input_buffer.length() > 250) {
s_input_buffer = "";
}
cli_feed_char(c);
}
#if !ARDUINO_USB_CDC_ON_BOOT
if (USBSerial) {
while (USBSerial.available() > 0) {
char c = (char)USBSerial.read();
cli_feed_char(c);
}
}
#endif
}
} // extern "C"
+20
View File
@@ -1,9 +1,16 @@
#include "app_log.h"
#include <ArduinoJson.h>
#include <USBCDC.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#if !ARDUINO_USB_CDC_ON_BOOT
extern USBCDC USBSerial;
#endif
static bool s_cdc_log_enabled = false;
#define STATIC_LOG_LINES 250
#define LOG_LINE_MAX_LEN 160
@@ -36,6 +43,11 @@ void app_log(const char* tag, const char* format, ...) {
if (Serial) {
Serial.println(full_line);
}
#if !ARDUINO_USB_CDC_ON_BOOT
if (s_cdc_log_enabled && USBSerial) {
USBSerial.println(full_line);
}
#endif
// 2. Store to circular buffer with spinlock protection
taskENTER_CRITICAL(&s_log_mux);
@@ -71,3 +83,11 @@ void app_log_clear(void) {
s_log_count = 0;
taskEXIT_CRITICAL(&s_log_mux);
}
void app_log_set_cdc_enabled(bool enabled) {
s_cdc_log_enabled = enabled;
}
bool app_log_get_cdc_enabled(void) {
return s_cdc_log_enabled;
}
+2
View File
@@ -12,6 +12,8 @@ void app_log_init(void);
void app_log(const char* tag, const char* format, ...);
String app_log_get_json(void);
void app_log_clear(void);
void app_log_set_cdc_enabled(bool enabled);
bool app_log_get_cdc_enabled(void);
#ifdef __cplusplus
}
+17 -5
View File
@@ -69,8 +69,14 @@ void setup() {
// 6. Initialize Wi-Fi AP + STA & Captive Portal
wifi_manager_init();
// 7. Initialize Embedded Web Server & REST APIs
web_server_init();
// 7. Initialize Embedded Web Server & REST APIs (Wi-Fi only).
// lwIP/esp_netif are initialized inside the Wi-Fi library; starting a TCP
// server while Wi-Fi is disabled triggers a lwIP assert and reboots the chip.
if (wifi_manager_get_enabled()) {
web_server_init();
} else {
app_log("SYSTEM", "Wi-Fi disabled: web server not started");
}
// 8. Launch BLE Central Task pinned to Core 0
xTaskCreatePinnedToCore(
@@ -83,7 +89,11 @@ void setup() {
TASK_CORE_BLE
);
app_log("SYSTEM", "System initialization complete. Web available at http://192.168.4.1 or http://remotemapper.local");
if (wifi_manager_get_enabled()) {
app_log("SYSTEM", "System initialization complete. Web available at http://192.168.4.1 or http://remotemapper.local");
} else {
app_log("SYSTEM", "System initialization complete. Wi-Fi radio off (use CDC/UART: 'wifi on')");
}
}
void loop() {
@@ -95,8 +105,10 @@ void loop() {
// 2. Service Wi-Fi & DNS tasks
wifi_manager_task();
// 3. Service HTTP Web Server
web_server_task();
// 3. Service HTTP Web Server (only when Wi-Fi is enabled)
if (wifi_manager_get_enabled()) {
web_server_task();
}
// 4. Service Serial / WebSerial CLI commands
cli_manager_task();
+1
View File
@@ -116,6 +116,7 @@ String nvs_manager_dump_json(void) {
if (prefs.isKey("sta_pass")) wifiObj["sta_pass"] = prefs.getString("sta_pass", "");
if (prefs.isKey("ap_ssid")) wifiObj["ap_ssid"] = prefs.getString("ap_ssid", "");
if (prefs.isKey("ap_pass")) wifiObj["ap_pass"] = prefs.getString("ap_pass", "");
if (prefs.isKey("wifi_enabled")) wifiObj["wifi_enabled"] = prefs.getBool("wifi_enabled", true);
prefs.end();
}
if (prefs.begin("ble_conf", true)) {
+1
View File
@@ -33,6 +33,7 @@ static void handle_status() {
doc["sta_ip"] = wifi_manager_get_sta_ip();
doc["sta_connected"] = wifi_manager_is_sta_connected();
doc["ap_ssid"] = AP_SSID;
doc["wifi_enabled"] = wifi_manager_get_enabled();
String ap_pass = wifi_manager_get_ap_pass();
doc["ap_secured"] = (ap_pass.length() >= 8);
doc["ap_pass"] = ap_pass;
+78 -17
View File
@@ -16,17 +16,15 @@ static IPAddress s_ap_netmask(255, 255, 255, 0);
static bool s_sta_configured = false;
static uint32_t s_last_sta_check = 0;
static bool s_wifi_enabled = true;
static bool s_ap_running = false;
static bool s_mdns_started = false;
void wifi_manager_init(void) {
s_prefs.begin("wifi_conf", false);
String sta_ssid = s_prefs.getString("ssid", "");
String sta_pass = s_prefs.getString("pass", "");
String ap_pass = s_prefs.getString("ap_pass", "");
// Set Wi-Fi Mode
WiFi.mode(WIFI_AP_STA);
// 1. Configure and start AP Mode
static void wifi_start_ap(const String& ap_pass) {
if (s_ap_running) {
s_dns_server.stop();
WiFi.softAPdisconnect(false);
}
WiFi.softAPConfig(s_ap_ip, s_ap_ip, s_ap_netmask);
if (ap_pass.length() >= 8) {
WiFi.softAP(AP_SSID, ap_pass.c_str());
@@ -35,12 +33,23 @@ void wifi_manager_init(void) {
WiFi.softAP(AP_SSID, "");
app_log("WIFI", "AP Started: %s (Open Network, IP: 192.168.4.1)", AP_SSID);
}
// 2. Start Captive Portal DNS
s_dns_server.setErrorReplyCode(DNSReplyCode::NoError);
s_dns_server.start(DNS_PORT, "*", s_ap_ip);
s_ap_running = true;
}
// 3. Connect to Home Wi-Fi if saved
static void wifi_apply_config(void) {
String sta_ssid = s_prefs.getString("ssid", "");
String sta_pass = s_prefs.getString("pass", "");
String ap_pass = s_prefs.getString("ap_pass", "");
// Set Wi-Fi Mode (AP + STA)
WiFi.mode(WIFI_AP_STA);
// 1. Configure and start AP Mode
wifi_start_ap(ap_pass);
// 2. Connect to Home Wi-Fi if saved
if (sta_ssid.length() > 0) {
s_sta_configured = true;
app_log("WIFI", "Connecting to Home Wi-Fi: %s...", sta_ssid.c_str());
@@ -49,15 +58,59 @@ void wifi_manager_init(void) {
app_log("WIFI", "No Home Wi-Fi configured, running in AP Setup mode");
}
// 4. Start mDNS
if (MDNS.begin(MDNS_HOSTNAME)) {
// 3. Start mDNS (once per boot)
if (!s_mdns_started && MDNS.begin(MDNS_HOSTNAME)) {
MDNS.addService("http", "tcp", 80);
app_log("WIFI", "mDNS responder started: http://%s.local", MDNS_HOSTNAME);
s_mdns_started = true;
}
}
void wifi_manager_init(void) {
s_prefs.begin("wifi_conf", false);
s_wifi_enabled = s_prefs.getBool("wifi_enabled", true);
if (!s_wifi_enabled) {
// Never touch the WiFi stack when disabled: de-initializing WiFi before BLE
// starts (or tearing it down at runtime) breaks 2.4GHz coexistence and can
// stop the BLE link from coming up. The radio simply stays uninitialized.
app_log("WIFI", "Wi-Fi disabled by config; radio left uninitialized (USB CDC/UART: 'wifi on')");
return;
}
// 1. Start AP + STA + mDNS
wifi_apply_config();
}
bool wifi_manager_get_enabled(void) {
return s_wifi_enabled;
}
bool wifi_manager_is_ap_running(void) {
return s_ap_running;
}
bool wifi_manager_set_enabled(bool enabled) {
if (s_wifi_enabled == enabled) {
return true;
}
s_prefs.putBool("wifi_enabled", enabled);
s_wifi_enabled = enabled;
// Applying the change requires a reboot: the caller (CLI/web) must restart.
// Switching the WiFi stack on/off at runtime is unsafe while BLE is running.
app_log("WIFI", "Wi-Fi %s saved; reboot required to apply", enabled ? "enable" : "disable");
return true;
}
void wifi_manager_task(void) {
s_dns_server.processNextRequest();
if (!s_wifi_enabled) {
return;
}
if (s_ap_running) {
s_dns_server.processNextRequest();
}
uint32_t now = millis();
if (s_sta_configured && (now - s_last_sta_check > 5000)) {
@@ -73,10 +126,12 @@ void wifi_manager_task(void) {
}
String wifi_manager_get_ap_ip(void) {
if (!s_wifi_enabled) return "Disabled";
return WiFi.softAPIP().toString();
}
String wifi_manager_get_sta_ip(void) {
if (!s_wifi_enabled) return "Disabled";
if (WiFi.status() == WL_CONNECTED) {
return WiFi.localIP().toString();
}
@@ -84,10 +139,12 @@ String wifi_manager_get_sta_ip(void) {
}
bool wifi_manager_is_sta_connected(void) {
if (!s_wifi_enabled) return false;
return WiFi.status() == WL_CONNECTED;
}
int8_t wifi_manager_get_sta_rssi(void) {
if (!s_wifi_enabled) return 0;
if (WiFi.status() == WL_CONNECTED) {
return WiFi.RSSI();
}
@@ -95,6 +152,9 @@ int8_t wifi_manager_get_sta_rssi(void) {
}
String wifi_manager_scan_json(void) {
if (!s_wifi_enabled) {
return "{\"networks\":[],\"error\":\"wifi_disabled\"}";
}
app_log("WIFI", "Scanning for 2.4GHz Wi-Fi networks...");
int n = WiFi.scanNetworks();
JsonDocument doc;
@@ -114,6 +174,7 @@ String wifi_manager_scan_json(void) {
}
bool wifi_manager_save_sta_config(const String& ssid, const String& password) {
if (!s_wifi_enabled) return false;
if (ssid.length() == 0) return false;
s_prefs.putString("ssid", ssid);
@@ -131,6 +192,7 @@ String wifi_manager_get_ap_pass(void) {
}
bool wifi_manager_save_ap_config(const String& ap_password) {
if (!s_wifi_enabled) return false;
String p = ap_password;
p.trim();
if (p.length() > 0 && p.length() < 8) {
@@ -148,4 +210,3 @@ bool wifi_manager_save_ap_config(const String& ap_password) {
}
return true;
}
+3 -1
View File
@@ -12,6 +12,9 @@ extern "C" {
void wifi_manager_init(void);
void wifi_manager_task(void);
bool wifi_manager_get_enabled(void);
bool wifi_manager_set_enabled(bool enabled);
bool wifi_manager_is_ap_running(void);
String wifi_manager_get_ap_ip(void);
String wifi_manager_get_sta_ip(void);
bool wifi_manager_is_sta_connected(void);
@@ -24,4 +27,3 @@ bool wifi_manager_save_ap_config(const String& ap_password);
#ifdef __cplusplus
}
#endif