7 Commits

13 changed files with 489 additions and 34 deletions

View File

@@ -42,6 +42,8 @@ enum nconfig_type
VIN_CURRENT_LIMIT, ///< The maximum current limit for the VIN.
MAIN_CURRENT_LIMIT, ///< The maximum current limit for the MAIN out.
USB_CURRENT_LIMIT, ///< The maximum current limit for the USB out.
PAGE_USERNAME, ///< Webpage username
PAGE_PASSWORD, ///< Webpage password
NCONFIG_TYPE_MAX, ///< Sentinel for the maximum number of configuration types.
};

View File

@@ -28,6 +28,8 @@ const static char* keys[NCONFIG_TYPE_MAX] = {
[VIN_CURRENT_LIMIT] = "vin_climit",
[MAIN_CURRENT_LIMIT] = "main_climit",
[USB_CURRENT_LIMIT] = "usb_climit",
[PAGE_USERNAME] = "username",
[PAGE_PASSWORD] = "password",
};
struct default_value
@@ -50,6 +52,8 @@ struct default_value const default_values[] = {
{VIN_CURRENT_LIMIT, "4.0"},
{MAIN_CURRENT_LIMIT, "3.0"},
{USB_CURRENT_LIMIT, "3.0"},
{PAGE_USERNAME, "admin"},
{PAGE_PASSWORD, "password"},
};
esp_err_t init_nconfig()

182
main/service/dbg_console.c Normal file
View File

@@ -0,0 +1,182 @@
#include "dbg_console.h"
#include <stdio.h>
#include <string.h>
#include "argtable3/argtable3.h"
#include "esp_console.h"
#include "esp_log.h"
#include "esp_netif.h"
#include "esp_system.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "wifi.h"
/* 'wifi_scan' command */
static int wifi_scan_handler(int argc, char** argv)
{
printf("Scanning for Wi-Fi networks...\n");
wifi_ap_record_t* ap_records;
uint16_t count = 0;
wifi_scan_aps(&ap_records, &count);
if (count == 0)
{
printf("No APs found.\n");
return 0;
}
printf("Found %d APs:\n", count);
printf(" %-32s %-4s %s\n", "SSID", "RSSI", "Auth Mode");
for (int i = 0; i < count; i++)
{
printf(" %-32s %-4d %s\n", ap_records[i].ssid, ap_records[i].rssi, auth_mode_str(ap_records[i].authmode));
}
if (count > 0)
{
free(ap_records);
}
return 0;
}
static void register_wifi_scan(void)
{
const esp_console_cmd_t cmd = {
.command = "wifi_scan",
.help = "Scan for available Wi-Fi networks",
.hint = NULL,
.func = &wifi_scan_handler,
};
ESP_ERROR_CHECK(esp_console_cmd_register(&cmd));
}
static struct
{
struct arg_str* ssid;
struct arg_str* password;
struct arg_end* end;
} wifi_connect_args;
static int wifi_connect_handler(int argc, char** argv)
{
int nerrors = arg_parse(argc, argv, (void**)&wifi_connect_args);
if (nerrors != 0)
{
arg_print_errors(stderr, wifi_connect_args.end, argv[0]);
return 1;
}
const char* ssid = wifi_connect_args.ssid->sval[0];
char password[64] = "";
if (wifi_connect_args.password->count != 0)
strncpy(password, wifi_connect_args.password->sval[0], sizeof(password));
printf("Attempting to connect to SSID: %s\n", ssid);
esp_err_t err = wifi_sta_set_ap(ssid, password);
if (err == ESP_OK)
{
printf("Wi-Fi credentials set. The device will attempt to connect.\n");
}
else
{
printf("Failed to set Wi-Fi credentials.\n");
}
return 0;
}
static void register_wifi_connect(void)
{
wifi_connect_args.ssid = arg_str1(NULL, NULL, "<ssid>", "SSID of the network to connect to");
wifi_connect_args.password = arg_str0(NULL, NULL, "<password>", "Password of the network");
wifi_connect_args.end = arg_end(2);
const esp_console_cmd_t cmd = {.command = "wifi_connect",
.help = "Connect to a Wi-Fi network",
.hint = NULL,
.func = &wifi_connect_handler,
.argtable = &wifi_connect_args};
ESP_ERROR_CHECK(esp_console_cmd_register(&cmd));
}
/* 'wifi_status' command */
static int wifi_status_handler(int argc, char** argv)
{
wifi_ap_record_t ap_info;
esp_err_t err = wifi_get_current_ap_info(&ap_info);
if (err == ESP_OK)
{
printf("Connected to AP:\n");
printf(" SSID: %s\n", (char*)ap_info.ssid);
printf(" RSSI: %d\n", ap_info.rssi);
esp_netif_ip_info_t ip_info;
err = wifi_get_current_ip_info(&ip_info);
if (err == ESP_OK)
{
char ip_str[16];
esp_ip4addr_ntoa(&ip_info.ip, ip_str, sizeof(ip_str));
printf(" IP Address: %s\n", ip_str);
esp_ip4addr_ntoa(&ip_info.gw, ip_str, sizeof(ip_str));
printf(" Gateway: %s\n", ip_str);
esp_ip4addr_ntoa(&ip_info.netmask, ip_str, sizeof(ip_str));
printf(" Subnet Mask: %s\n", ip_str);
}
else
{
printf(" Could not get IP information: %s\n", esp_err_to_name(err));
}
}
else
{
printf("Not connected to any AP.\n");
}
return 0;
}
static void register_wifi_status(void)
{
const esp_console_cmd_t cmd = {
.command = "wifi_status",
.help = "Get current Wi-Fi connection status and IP information",
.hint = NULL,
.func = &wifi_status_handler,
};
ESP_ERROR_CHECK(esp_console_cmd_register(&cmd));
}
esp_err_t initialize_dbg_console(void)
{
esp_console_repl_t* repl = NULL;
esp_console_repl_config_t repl_config = ESP_CONSOLE_REPL_CONFIG_DEFAULT();
repl_config.prompt = "powermate >";
repl_config.max_cmdline_length = 512;
esp_console_dev_usb_serial_jtag_config_t hw_config = ESP_CONSOLE_DEV_USB_SERIAL_JTAG_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_console_new_repl_usb_serial_jtag(&hw_config, &repl_config, &repl));
esp_console_register_help_command();
register_wifi_scan();
register_wifi_connect();
register_wifi_status();
printf("Debug console initialized.\n");
esp_console_start_repl(repl);
return ESP_OK;
}

View File

@@ -0,0 +1,13 @@
#ifndef ODROID_POWER_MATE_DBG_CONSOLE_H
#define ODROID_POWER_MATE_DBG_CONSOLE_H
#include "esp_err.h"
/**
* @brief Initialize the debug console.
*
* @return ESP_OK on success, or an error code on failure.
*/
esp_err_t initialize_dbg_console(void);
#endif // ODROID_POWER_MATE_DBG_CONSOLE_H

View File

@@ -177,6 +177,8 @@ static esp_err_t setting_post_handler(httpd_req_t* req)
cJSON* vin_climit_item = cJSON_GetObjectItem(root, "vin_current_limit");
cJSON* main_climit_item = cJSON_GetObjectItem(root, "main_current_limit");
cJSON* usb_climit_item = cJSON_GetObjectItem(root, "usb_current_limit");
cJSON* new_username_item = cJSON_GetObjectItem(root, "new_username");
cJSON* new_password_item = cJSON_GetObjectItem(root, "new_password");
if (mode_item && cJSON_IsString(mode_item))
{
@@ -322,6 +324,17 @@ static esp_err_t setting_post_handler(httpd_req_t* req)
}
httpd_resp_sendstr(req, "{\"status\":\"current_limit_updated\"}");
}
else if (new_username_item && cJSON_IsString(new_username_item) && new_password_item &&
cJSON_IsString(new_password_item))
{
const char* new_username = new_username_item->valuestring;
const char* new_password = new_password_item->valuestring;
nconfig_write(PAGE_USERNAME, new_username);
nconfig_write(PAGE_PASSWORD, new_password);
ESP_LOGI(TAG, "Username and password updated successfully.");
httpd_resp_sendstr(req, "{\"status\":\"user_credentials_updated\"}");
}
else
{
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Invalid payload");

View File

@@ -3,6 +3,7 @@
#include <string.h>
#include "auth.h"
#include "cJSON.h"
#include "dbg_console.h"
#include "esp_http_server.h"
#include "esp_log.h"
#include "esp_wifi.h"
@@ -80,12 +81,58 @@ static esp_err_t login_handler(httpd_req_t* req)
return ESP_FAIL;
}
const char* username = username_json->valuestring;
const char* password = password_json->valuestring;
const char* received_username = username_json->valuestring;
const char* received_password = password_json->valuestring;
// TODO: Implement actual credential validation
// For now, a simple hardcoded check
if (strcmp(username, "admin") == 0 && strcmp(password, "password") == 0)
// Get stored username and password from nconfig
size_t stored_username_len = 0;
size_t stored_password_len = 0;
char* stored_username = NULL;
char* stored_password = NULL;
bool credentials_match = false;
if (nconfig_get_str_len(PAGE_USERNAME, &stored_username_len) == ESP_OK && stored_username_len > 1)
{
stored_username = (char*)malloc(stored_username_len);
if (stored_username)
{
if (nconfig_read(PAGE_USERNAME, stored_username, stored_username_len) != ESP_OK)
{
ESP_LOGE(TAG, "Failed to read stored username from nconfig");
free(stored_username);
stored_username = NULL;
}
}
}
if (nconfig_get_str_len(PAGE_PASSWORD, &stored_password_len) == ESP_OK && stored_password_len > 1)
{
stored_password = (char*)malloc(stored_password_len);
if (stored_password)
{
if (nconfig_read(PAGE_PASSWORD, stored_password, stored_password_len) != ESP_OK)
{
ESP_LOGE(TAG, "Failed to read stored password from nconfig");
free(stored_password);
stored_password = NULL;
}
}
}
if (stored_username && stored_password)
{
if (strcmp(received_username, stored_username) == 0 && strcmp(received_password, stored_password) == 0)
{
credentials_match = true;
}
}
if (stored_username)
free(stored_username);
if (stored_password)
free(stored_password);
if (credentials_match)
{
char* token = auth_generate_token();
if (token)
@@ -147,4 +194,6 @@ void start_webserver(void)
register_version_endpoint(server);
init_status_monitor();
initialize_dbg_console();
}

View File

@@ -13,6 +13,7 @@
#include "pb.h"
#include "pb_encode.h"
#include "status.pb.h"
#include "string.h" // Added for strlen and strncmp
#include "webserver.h"
#define UART_NUM UART_NUM_1
@@ -264,7 +265,6 @@ static esp_err_t ws_handler(httpd_req_t* req)
httpd_ws_frame_t ws_pkt = {0};
uint8_t buf[BUF_SIZE];
ws_pkt.payload = buf;
ws_pkt.type = HTTPD_WS_TYPE_BINARY;
esp_err_t ret = httpd_ws_recv_frame(req, &ws_pkt, BUF_SIZE);
if (ret != ESP_OK)
@@ -273,7 +273,33 @@ static esp_err_t ws_handler(httpd_req_t* req)
return ret;
}
uart_write_bytes(UART_NUM, (const char*)ws_pkt.payload, ws_pkt.len);
if (ws_pkt.type == HTTPD_WS_TYPE_TEXT && ws_pkt.len == strlen("ping") &&
strncmp((const char*)ws_pkt.payload, "ping", ws_pkt.len) == 0)
{
ESP_LOGD(TAG, "Received application-level ping from client, sending pong.");
httpd_ws_frame_t pong_pkt = {
.payload = (uint8_t*)"pong", .len = strlen("pong"), .type = HTTPD_WS_TYPE_TEXT, .final = true};
return httpd_ws_send_frame(req, &pong_pkt);
}
else if (ws_pkt.type == HTTPD_WS_TYPE_CLOSE)
{
ESP_LOGI(TAG, "Client sent close frame, closing connection.");
return ESP_OK;
}
else if (ws_pkt.type == HTTPD_WS_TYPE_PING)
{
ESP_LOGD(TAG, "Received WebSocket PING control frame (handled by httpd).");
return ESP_OK;
}
else if (ws_pkt.type == HTTPD_WS_TYPE_PONG)
{
ESP_LOGD(TAG, "Received WebSocket PONG control frame.");
return ESP_OK;
}
else
{
uart_write_bytes(UART_NUM, (const char*)ws_pkt.payload, ws_pkt.len);
}
return ESP_OK;
}
@@ -327,4 +353,4 @@ void push_data_to_ws(const uint8_t* data, size_t len)
}
}
esp_err_t change_baud_rate(int baud_rate) { return uart_set_baudrate(UART_NUM, baud_rate); }
esp_err_t change_baud_rate(int baud_rate) { return uart_set_baudrate(UART_NUM, baud_rate); }

View File

@@ -53,7 +53,10 @@
<span id="power-display" class="text-primary">--.-- W</span>
</div>
</div>
<h1 class="text-primary text-center order-md-2 mx-auto">ODROID Power Mate</h1>
<div class="text-center order-md-2 mx-auto">
<h1 class="text-primary mb-0">ODROID Power Mate</h1>
<small class="text-muted" id="version-info"></small>
</div>
<div class="d-flex align-items-center justify-content-end order-md-3 header-controls" style="flex: 1;">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="theme-toggle">
@@ -159,12 +162,9 @@
</div>
</main>
<footer class="bg-body-tertiary text-center p-3 position-relative">
<footer class="bg-body-tertiary text-center p-3">
<a href="https://www.hardkernel.com/" target="_blank" class="link-secondary text-decoration-none">Hardkernel</a> |
<a href="https://wiki.odroid.com/start" target="_blank" class="link-secondary text-decoration-none">Wiki</a>
<div class="position-absolute end-0 top-50 translate-middle-y pe-3">
<small class="text-muted" id="version-info"></small>
</div>
</footer>
<!-- Settings Modal -->
@@ -197,6 +197,11 @@
id="current-limit-settings-tab" role="tab" type="button">Current Limit
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" data-bs-target="#user-settings-pane" data-bs-toggle="tab"
id="user-settings-tab" role="tab" type="button">User Settings
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="device-settings-tab" data-bs-toggle="tab"
data-bs-target="#device-settings-pane" type="button" role="tab">Device
@@ -331,6 +336,28 @@
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
</div>
</div>
<div class="tab-pane fade" id="user-settings-pane" role="tabpanel">
<form id="user-settings-form">
<div class="mb-3">
<label class="form-label" for="new-username">New Username</label>
<input class="form-control" id="new-username" required type="text">
</div>
<div class="mb-3">
<label class="form-label" for="new-password">New Password</label>
<input class="form-control" id="new-password" required type="password">
</div>
<div class="mb-3">
<label class="form-label" for="confirm-password">Confirm New Password</label>
<input class="form-control" id="confirm-password" required type="password">
</div>
<div class="d-flex justify-content-end pt-3 border-top mt-3">
<button class="btn btn-primary me-2" id="user-settings-apply-button" type="submit">
Apply
</button>
<button class="btn btn-secondary" data-bs-dismiss="modal" type="button">Close</button>
</div>
</form>
</div>
<div class="tab-pane fade" id="device-settings-pane" role="tabpanel">
<div class="mb-3">
<label for="baud-rate-select" class="form-label">UART Baud Rate</label>

View File

@@ -171,3 +171,22 @@ export async function fetchVersion() {
});
return await handleResponse(response).then(res => res.json());
}
/**
* Updates the user's username and password on the server.
* @param {string} newUsername The new username.
* @param {string} newPassword The new password.
* @returns {Promise<Object>} A promise that resolves to the server's JSON response.
* @throws {Error} Throws an error if the update fails.
*/
export async function updateUserSettings(newUsername, newPassword) {
const response = await fetch('/api/setting', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
...getAuthHeaders(),
},
body: JSON.stringify({new_username: newUsername, new_password: newPassword}),
});
return await handleResponse(response).then(res => res.json());
}

View File

@@ -7,10 +7,10 @@
import * as dom from './dom.js';
import * as api from './api.js';
import {getAuthHeaders, handleResponse} from './api.js'; // Import auth functions
import * as ui from './ui.js';
import {clearTerminal, downloadTerminalOutput, fitTerminal} from './terminal.js';
import {debounce, isMobile} from './utils.js';
import {getAuthHeaders, handleResponse} from './api.js'; // Import auth functions
// A flag to track if charts have been initialized
let chartsInitialized = false;
@@ -60,7 +60,6 @@ export function setupEventListeners() {
console.log("Event listeners already attached. Skipping.");
return;
}
console.log("Attaching event listeners...");
// --- Terminal Controls ---
dom.clearButton.addEventListener('click', clearTerminal);
@@ -189,5 +188,4 @@ export function setupEventListeners() {
window.addEventListener('resize', debounce(ui.handleResize, 150));
listenersAttached = true;
console.log("Event listeners attached successfully.");
}

View File

@@ -44,21 +44,23 @@ const themeIconLogin = document.getElementById('theme-icon-login');
const themeToggleMain = document.getElementById('theme-toggle');
const themeIconMain = document.getElementById('theme-icon');
// User Settings DOM Elements
const userSettingsForm = document.getElementById('user-settings-form');
const newUsernameInput = document.getElementById('new-username');
const newPasswordInput = document.getElementById('new-password');
const confirmPasswordInput = document.getElementById('confirm-password');
// --- WebSocket Event Handlers ---
function onWsOpen() {
updateWebsocketStatus(true);
if (term) {
term.write('\x1b[32mConnected to WebSocket Server\x1b[0m\r\n');
}
console.log('Connected to WebSocket Server');
}
function onWsClose() {
updateWebsocketStatus(false);
if (term) {
term.write('\r\n\x1b[31mConnection closed. Reconnecting...\x1b[0m\r\n');
}
console.warn('Connection closed. Reconnecting...');
setTimeout(connect, 2000);
}
@@ -168,6 +170,38 @@ function handleLogout() {
// For now, just hide the main content.
}
// --- User Settings Functions ---
async function handleUserSettingsSubmit(event) {
event.preventDefault();
const newUsername = newUsernameInput.value;
const newPassword = newPasswordInput.value;
const confirmPassword = confirmPasswordInput.value;
if (!newUsername || !newPassword || !confirmPassword) {
alert('Please fill in all fields for username and password.');
return;
}
if (newPassword !== confirmPassword) {
alert('New password and confirm password do not match.');
return;
}
try {
const response = await api.updateUserSettings(newUsername, newPassword);
if (response && response.status === 'user_credentials_updated') {
alert('Username and password updated successfully. Please log in again with new credentials.');
handleLogout(); // Force logout to re-authenticate with new credentials
} else {
alert(`Failed to update credentials: ${response.message || 'Unknown error'}`);
}
} catch (error) {
console.error('Error updating user settings:', error);
alert(`Error updating user settings: ${error.message}`);
}
}
// --- Theme Toggle Functions ---
function setupThemeToggles() {
// Initialize theme for login page
@@ -230,6 +264,11 @@ function initializeMainAppContent() {
setupEventListeners(); // Attach main app event listeners
logoutButton.addEventListener('click', handleLogout); // Attach logout listener
connect();
// Attach user settings form listener
if (userSettingsForm) {
userSettingsForm.addEventListener('submit', handleUserSettingsSubmit);
}
}
function initialize() {

View File

@@ -2,7 +2,7 @@
* @file websocket.js
* @description This module handles the WebSocket connection for real-time, two-way
* communication with the server. It provides functions to initialize the connection
* and send messages.
* and send messages, including a heartbeat mechanism to detect disconnections.
*/
// The WebSocket instance, exported for potential direct access if needed.
@@ -11,14 +11,57 @@ export let websocket;
// The WebSocket server address, derived from the current page's host (hostname + port).
const baseGateway = `ws://${window.location.host}/ws`;
// Heartbeat related variables
let pingIntervalId = null;
let pongTimeoutId = null;
const HEARTBEAT_INTERVAL = 10000; // 10 seconds: How often to send a 'ping'
const HEARTBEAT_TIMEOUT = 5000; // 5 seconds: How long to wait for a 'pong' after sending a 'ping'
/**
* Initializes the WebSocket connection and sets up event handlers.
* @param {Object} callbacks - An object containing callback functions for WebSocket events.
* @param {function} callbacks.onOpen - Called when the connection is successfully opened.
* @param {function} callbacks.onClose - Called when the connection is closed.
* @param {function} callbacks.onMessage - Called when a message is received from the server.
* Starts the heartbeat mechanism.
* Sends a 'ping' message to the server at regular intervals and sets a timeout
* to detect if a 'pong' response is not received.
*/
export function initWebSocket({onOpen, onClose, onMessage}) {
function startHeartbeat() {
stopHeartbeat(); // Ensure any previous heartbeat is stopped before starting a new one
pingIntervalId = setInterval(() => {
if (websocket && websocket.readyState === WebSocket.OPEN) {
websocket.send('ping');
// Set a timeout to check if a pong is received within HEARTBEAT_TIMEOUT
pongTimeoutId = setTimeout(() => {
console.warn('WebSocket: No pong received within timeout, closing connection.');
// If no pong is received, close the connection. This will trigger the onClose handler.
websocket.close();
}, HEARTBEAT_TIMEOUT);
}
}, HEARTBEAT_INTERVAL);
}
/**
* Stops the heartbeat mechanism by clearing the ping interval and pong timeout.
*/
function stopHeartbeat() {
if (pingIntervalId) {
clearInterval(pingIntervalId);
pingIntervalId = null;
}
if (pongTimeoutId) {
clearTimeout(pongTimeoutId);
pongTimeoutId = null;
}
}
/**
* Initializes the WebSocket connection and sets up event handlers, including a heartbeat mechanism.
* @param {Object} callbacks - An object containing callback functions for WebSocket events.
* @param {function} [callbacks.onOpen] - Called when the connection is successfully opened.
* @param {function} [callbacks.onClose] - Called when the connection is closed.
* @param {function} [callbacks.onMessage] - Called when a message is received from the server (excluding 'pong' messages).
* @param {function} [callbacks.onError] - Called when an error occurs with the WebSocket connection.
*/
export function initWebSocket({onOpen, onClose, onMessage, onError}) {
const token = localStorage.getItem('authToken');
let gateway = baseGateway;
@@ -31,10 +74,44 @@ export function initWebSocket({onOpen, onClose, onMessage}) {
// Set binary type to arraybuffer to handle raw binary data from the UART.
websocket.binaryType = "arraybuffer";
// Assign event handlers from the provided callbacks
if (onOpen) websocket.onopen = onOpen;
if (onClose) websocket.onclose = onClose;
if (onMessage) websocket.onmessage = onMessage;
// Assign event handlers, wrapping user-provided callbacks to include heartbeat logic
websocket.onopen = (event) => {
console.log('WebSocket connection opened.');
startHeartbeat(); // Start heartbeat on successful connection
if (onOpen) {
onOpen(event);
}
};
websocket.onclose = (event) => {
console.log('WebSocket connection closed:', event);
stopHeartbeat(); // Stop heartbeat when connection closes
if (onClose) {
onClose(event);
}
};
websocket.onmessage = (event) => {
if (event.data === 'pong') {
// Clear the timeout as pong was received, resetting for the next ping
clearTimeout(pongTimeoutId);
pongTimeoutId = null;
} else {
// If it's not a pong message, pass it to the user's onMessage callback
if (onMessage) {
onMessage(event);
} else {
console.log('WebSocket message received:', event.data);
}
}
};
websocket.onerror = (error) => {
console.error('WebSocket error:', error);
if (onError) {
onError(error);
}
};
}
/**
@@ -44,5 +121,7 @@ export function initWebSocket({onOpen, onClose, onMessage}) {
export function sendWebsocketMessage(data) {
if (websocket && websocket.readyState === WebSocket.OPEN) {
websocket.send(data);
} else {
console.warn('WebSocket is not open. Message not sent:', data);
}
}
}

View File

@@ -7,7 +7,11 @@ CONFIG_ESPTOOLPY_FLASHMODE_QIO=y
CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y
CONFIG_PARTITION_TABLE_CUSTOM=y
CONFIG_UART_ISR_IN_IRAM=y
CONFIG_HTTPD_MAX_REQ_HDR_LEN=2048
CONFIG_HTTPD_MAX_URI_LEN=1024
CONFIG_HTTPD_WS_SUPPORT=y
CONFIG_ESP_HTTPS_SERVER_ENABLE=y
CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG=y
CONFIG_ESP_WIFI_STATIC_RX_BUFFER_NUM=20
CONFIG_ESP_WIFI_DYNAMIC_RX_BUFFER_NUM=40
CONFIG_ESP_WIFI_DYNAMIC_TX_BUFFER_NUM=40