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:
commit
b339f3525d
14 files changed
+242
-42
No files matched your search
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+120
-19
@@ -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"
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -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();
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in new issue
Block a user