Add websocket ping for detect connection lost for client
Signed-off-by: YoungSoo Shin <shinys000114@gmail.com>
This commit is contained in:
@@ -13,6 +13,7 @@
|
|||||||
#include "pb.h"
|
#include "pb.h"
|
||||||
#include "pb_encode.h"
|
#include "pb_encode.h"
|
||||||
#include "status.pb.h"
|
#include "status.pb.h"
|
||||||
|
#include "string.h" // Added for strlen and strncmp
|
||||||
#include "webserver.h"
|
#include "webserver.h"
|
||||||
|
|
||||||
#define UART_NUM UART_NUM_1
|
#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};
|
httpd_ws_frame_t ws_pkt = {0};
|
||||||
uint8_t buf[BUF_SIZE];
|
uint8_t buf[BUF_SIZE];
|
||||||
ws_pkt.payload = buf;
|
ws_pkt.payload = buf;
|
||||||
ws_pkt.type = HTTPD_WS_TYPE_BINARY;
|
|
||||||
|
|
||||||
esp_err_t ret = httpd_ws_recv_frame(req, &ws_pkt, BUF_SIZE);
|
esp_err_t ret = httpd_ws_recv_frame(req, &ws_pkt, BUF_SIZE);
|
||||||
if (ret != ESP_OK)
|
if (ret != ESP_OK)
|
||||||
@@ -273,7 +273,33 @@ static esp_err_t ws_handler(httpd_req_t* req)
|
|||||||
return ret;
|
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;
|
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); }
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
* @file websocket.js
|
* @file websocket.js
|
||||||
* @description This module handles the WebSocket connection for real-time, two-way
|
* @description This module handles the WebSocket connection for real-time, two-way
|
||||||
* communication with the server. It provides functions to initialize the connection
|
* 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.
|
// The WebSocket instance, exported for potential direct access if needed.
|
||||||
@@ -11,14 +11,58 @@ export let websocket;
|
|||||||
// The WebSocket server address, derived from the current page's host (hostname + port).
|
// The WebSocket server address, derived from the current page's host (hostname + port).
|
||||||
const baseGateway = `ws://${window.location.host}/ws`;
|
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.
|
* Starts the heartbeat mechanism.
|
||||||
* @param {Object} callbacks - An object containing callback functions for WebSocket events.
|
* Sends a 'ping' message to the server at regular intervals and sets a timeout
|
||||||
* @param {function} callbacks.onOpen - Called when the connection is successfully opened.
|
* to detect if a 'pong' response is not received.
|
||||||
* @param {function} callbacks.onClose - Called when the connection is closed.
|
|
||||||
* @param {function} callbacks.onMessage - Called when a message is received from the server.
|
|
||||||
*/
|
*/
|
||||||
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');
|
||||||
|
console.log('WebSocket: Ping sent.');
|
||||||
|
|
||||||
|
// 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');
|
const token = localStorage.getItem('authToken');
|
||||||
let gateway = baseGateway;
|
let gateway = baseGateway;
|
||||||
|
|
||||||
@@ -31,10 +75,45 @@ export function initWebSocket({onOpen, onClose, onMessage}) {
|
|||||||
// Set binary type to arraybuffer to handle raw binary data from the UART.
|
// Set binary type to arraybuffer to handle raw binary data from the UART.
|
||||||
websocket.binaryType = "arraybuffer";
|
websocket.binaryType = "arraybuffer";
|
||||||
|
|
||||||
// Assign event handlers from the provided callbacks
|
// Assign event handlers, wrapping user-provided callbacks to include heartbeat logic
|
||||||
if (onOpen) websocket.onopen = onOpen;
|
websocket.onopen = (event) => {
|
||||||
if (onClose) websocket.onclose = onClose;
|
console.log('WebSocket connection opened.');
|
||||||
if (onMessage) websocket.onmessage = onMessage;
|
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') {
|
||||||
|
console.log('WebSocket: Pong received.');
|
||||||
|
// 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 +123,7 @@ export function initWebSocket({onOpen, onClose, onMessage}) {
|
|||||||
export function sendWebsocketMessage(data) {
|
export function sendWebsocketMessage(data) {
|
||||||
if (websocket && websocket.readyState === WebSocket.OPEN) {
|
if (websocket && websocket.readyState === WebSocket.OPEN) {
|
||||||
websocket.send(data);
|
websocket.send(data);
|
||||||
|
} else {
|
||||||
|
console.warn('WebSocket is not open. Message not sent:', data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user