Fixed MQTT and WS Routing - w/o SSL
This commit is contained in:
157
vesper/src/Communication/WebSocketServer/WebSocketServer.cpp
Normal file
157
vesper/src/Communication/WebSocketServer/WebSocketServer.cpp
Normal file
@@ -0,0 +1,157 @@
|
||||
/*
|
||||
* WEBSOCKETSERVER.CPP - WebSocket Server Implementation
|
||||
*/
|
||||
|
||||
#include "WebSocketServer.hpp"
|
||||
#include "../../Logging/Logging.hpp"
|
||||
#include "../ResponseBuilder/ResponseBuilder.hpp"
|
||||
|
||||
// Static instance for callback
|
||||
WebSocketServer* WebSocketServer::_instance = nullptr;
|
||||
|
||||
WebSocketServer::WebSocketServer(AsyncWebSocket& webSocket, ClientManager& clientManager)
|
||||
: _webSocket(webSocket)
|
||||
, _clientManager(clientManager)
|
||||
, _messageCallback(nullptr) {
|
||||
|
||||
_instance = this;
|
||||
}
|
||||
|
||||
WebSocketServer::~WebSocketServer() {
|
||||
_instance = nullptr;
|
||||
}
|
||||
|
||||
void WebSocketServer::begin() {
|
||||
_webSocket.onEvent(onEvent);
|
||||
LOG_INFO("WebSocket server initialized on /ws");
|
||||
|
||||
// 🔥 CRITICAL: This line was missing - attach WebSocket to the AsyncWebServer
|
||||
// Without this, the server doesn't know about the WebSocket handler!
|
||||
// Note: We can't access _server here directly, so this must be done in CommunicationRouter
|
||||
}
|
||||
|
||||
void WebSocketServer::setCallback(MessageCallback callback) {
|
||||
_messageCallback = callback;
|
||||
}
|
||||
|
||||
void WebSocketServer::sendToClient(uint32_t clientId, const String& message) {
|
||||
_clientManager.sendToClient(clientId, message);
|
||||
}
|
||||
|
||||
void WebSocketServer::broadcastToAll(const String& message) {
|
||||
_clientManager.broadcastToAll(message);
|
||||
LOG_DEBUG("Broadcast to all WebSocket clients: %s", message.c_str());
|
||||
}
|
||||
|
||||
void WebSocketServer::broadcastToMaster(const String& message) {
|
||||
_clientManager.sendToMasterClients(message);
|
||||
LOG_DEBUG("Broadcast to master clients: %s", message.c_str());
|
||||
}
|
||||
|
||||
void WebSocketServer::broadcastToSecondary(const String& message) {
|
||||
_clientManager.sendToSecondaryClients(message);
|
||||
LOG_DEBUG("Broadcast to secondary clients: %s", message.c_str());
|
||||
}
|
||||
|
||||
bool WebSocketServer::hasClients() const {
|
||||
return _clientManager.hasClients();
|
||||
}
|
||||
|
||||
size_t WebSocketServer::getClientCount() const {
|
||||
return _clientManager.getClientCount();
|
||||
}
|
||||
|
||||
void WebSocketServer::onEvent(AsyncWebSocket* server, AsyncWebSocketClient* client,
|
||||
AwsEventType type, void* arg, uint8_t* data, size_t len) {
|
||||
if (!_instance) {
|
||||
LOG_ERROR("WebSocketServer static instance is NULL - callback ignored!");
|
||||
return;
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case WS_EVT_CONNECT:
|
||||
_instance->onConnect(client);
|
||||
break;
|
||||
|
||||
case WS_EVT_DISCONNECT:
|
||||
_instance->onDisconnect(client);
|
||||
break;
|
||||
|
||||
case WS_EVT_DATA:
|
||||
_instance->onData(client, arg, data, len);
|
||||
break;
|
||||
|
||||
case WS_EVT_ERROR:
|
||||
LOG_ERROR("WebSocket client #%u error(%u): %s",
|
||||
client->id(), *((uint16_t*)arg), (char*)data);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void WebSocketServer::onConnect(AsyncWebSocketClient* client) {
|
||||
LOG_INFO("WebSocket client #%u connected from %s",
|
||||
client->id(), client->remoteIP().toString().c_str());
|
||||
|
||||
// Add client to manager (type UNKNOWN until they identify)
|
||||
_clientManager.addClient(client, ClientManager::DeviceType::UNKNOWN);
|
||||
|
||||
// Send welcome message
|
||||
String welcomeMsg = ResponseBuilder::success("connection", "Connected to Vesper");
|
||||
_clientManager.sendToClient(client->id(), welcomeMsg);
|
||||
}
|
||||
|
||||
void WebSocketServer::onDisconnect(AsyncWebSocketClient* client) {
|
||||
LOG_INFO("WebSocket client #%u disconnected", client->id());
|
||||
|
||||
_clientManager.removeClient(client->id());
|
||||
_clientManager.cleanupDisconnectedClients();
|
||||
}
|
||||
|
||||
void WebSocketServer::onData(AsyncWebSocketClient* client, void* arg, uint8_t* data, size_t len) {
|
||||
AwsFrameInfo* info = (AwsFrameInfo*)arg;
|
||||
|
||||
// Only handle complete, single-frame text messages
|
||||
if (info->final && info->index == 0 && info->len == len && info->opcode == WS_TEXT) {
|
||||
// Allocate buffer for payload
|
||||
char* payload = (char*)malloc(len + 1);
|
||||
if (!payload) {
|
||||
LOG_ERROR("Failed to allocate memory for WebSocket payload");
|
||||
String errorResponse = ResponseBuilder::error("memory_error", "Out of memory");
|
||||
_clientManager.sendToClient(client->id(), errorResponse);
|
||||
return;
|
||||
}
|
||||
|
||||
memcpy(payload, data, len);
|
||||
payload[len] = '\0';
|
||||
|
||||
LOG_DEBUG("WebSocket client #%u sent: %s", client->id(), payload);
|
||||
|
||||
// Parse JSON
|
||||
StaticJsonDocument<2048> doc;
|
||||
DeserializationError error = deserializeJson(doc, payload);
|
||||
|
||||
if (error) {
|
||||
LOG_ERROR("Failed to parse WebSocket JSON from client #%u: %s", client->id(), error.c_str());
|
||||
String errorResponse = ResponseBuilder::error("parse_error", "Invalid JSON");
|
||||
_clientManager.sendToClient(client->id(), errorResponse);
|
||||
} else {
|
||||
// Update client last seen time
|
||||
_clientManager.updateClientLastSeen(client->id());
|
||||
|
||||
// Call user callback if set
|
||||
if (_messageCallback) {
|
||||
LOG_DEBUG("Routing message from client #%u to callback handler", client->id());
|
||||
_messageCallback(client->id(), doc);
|
||||
} else {
|
||||
LOG_WARNING("WebSocket message received but no callback handler is set!");
|
||||
}
|
||||
}
|
||||
|
||||
free(payload);
|
||||
} else {
|
||||
LOG_WARNING("Received fragmented or non-text WebSocket message from client #%u - ignoring", client->id());
|
||||
}
|
||||
}
|
||||
103
vesper/src/Communication/WebSocketServer/WebSocketServer.hpp
Normal file
103
vesper/src/Communication/WebSocketServer/WebSocketServer.hpp
Normal file
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
* ═══════════════════════════════════════════════════════════════════════════════════
|
||||
* WEBSOCKETSERVER.HPP - WebSocket Server Manager
|
||||
* ═══════════════════════════════════════════════════════════════════════════════════
|
||||
*
|
||||
* 📡 WEBSOCKET MULTI-CLIENT MANAGER 📡
|
||||
*
|
||||
* Handles WebSocket connections with:
|
||||
* • Multi-client support with device type identification
|
||||
* • Automatic client cleanup
|
||||
* • Broadcast and targeted messaging
|
||||
* • Integration with ClientManager
|
||||
*
|
||||
* 📋 VERSION: 1.0
|
||||
* 📅 DATE: 2025-10-01
|
||||
* 👨💻 AUTHOR: Advanced Bell Systems
|
||||
* ═══════════════════════════════════════════════════════════════════════════════════
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <ESPAsyncWebServer.h>
|
||||
#include <ArduinoJson.h>
|
||||
#include "../../ClientManager/ClientManager.hpp"
|
||||
|
||||
class WebSocketServer {
|
||||
public:
|
||||
// Message callback type
|
||||
using MessageCallback = std::function<void(uint32_t clientId, const JsonDocument& message)>;
|
||||
|
||||
explicit WebSocketServer(AsyncWebSocket& webSocket, ClientManager& clientManager);
|
||||
~WebSocketServer();
|
||||
|
||||
/**
|
||||
* @brief Initialize WebSocket server
|
||||
*/
|
||||
void begin();
|
||||
|
||||
/**
|
||||
* @brief Set message received callback
|
||||
*/
|
||||
void setCallback(MessageCallback callback);
|
||||
|
||||
/**
|
||||
* @brief Send message to specific client
|
||||
*/
|
||||
void sendToClient(uint32_t clientId, const String& message);
|
||||
|
||||
/**
|
||||
* @brief Broadcast to all connected clients
|
||||
*/
|
||||
void broadcastToAll(const String& message);
|
||||
|
||||
/**
|
||||
* @brief Broadcast to master devices only
|
||||
*/
|
||||
void broadcastToMaster(const String& message);
|
||||
|
||||
/**
|
||||
* @brief Broadcast to secondary devices only
|
||||
*/
|
||||
void broadcastToSecondary(const String& message);
|
||||
|
||||
/**
|
||||
* @brief Check if any clients are connected
|
||||
*/
|
||||
bool hasClients() const;
|
||||
|
||||
/**
|
||||
* @brief Get number of connected clients
|
||||
*/
|
||||
size_t getClientCount() const;
|
||||
|
||||
private:
|
||||
AsyncWebSocket& _webSocket;
|
||||
ClientManager& _clientManager;
|
||||
MessageCallback _messageCallback;
|
||||
|
||||
/**
|
||||
* @brief Static WebSocket event handler
|
||||
*/
|
||||
static void onEvent(AsyncWebSocket* server, AsyncWebSocketClient* client,
|
||||
AwsEventType type, void* arg, uint8_t* data, size_t len);
|
||||
|
||||
/**
|
||||
* @brief Handle client connection
|
||||
*/
|
||||
void onConnect(AsyncWebSocketClient* client);
|
||||
|
||||
/**
|
||||
* @brief Handle client disconnection
|
||||
*/
|
||||
void onDisconnect(AsyncWebSocketClient* client);
|
||||
|
||||
/**
|
||||
* @brief Handle received data
|
||||
*/
|
||||
void onData(AsyncWebSocketClient* client, void* arg, uint8_t* data, size_t len);
|
||||
|
||||
// Static instance for callback routing
|
||||
static WebSocketServer* _instance;
|
||||
};
|
||||
Reference in New Issue
Block a user