Compare commits
7 Commits
e07aad2d7d
...
v1.0.3
| Author | SHA1 | Date | |
|---|---|---|---|
| 69f7471953 | |||
| e1f232eace | |||
| 274b70dbbe | |||
| 3e7d3cabfe | |||
| 40fd0a667a | |||
| 89e17efcfc | |||
| fb2ab88bc9 |
@@ -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.
|
||||
};
|
||||
|
||||
|
||||
@@ -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
182
main/service/dbg_console.c
Normal 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;
|
||||
}
|
||||
13
main/service/dbg_console.h
Normal file
13
main/service/dbg_console.h
Normal 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
|
||||
@@ -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");
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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); }
|
||||
@@ -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>
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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.");
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user