Fixed MQTT and WS Routing - w/o SSL

This commit is contained in:
2025-10-13 17:34:54 +03:00
parent f696984cd1
commit 956786321a
29 changed files with 2043 additions and 1210 deletions

View 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());
}
}

View 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;
};