Added HTTP-API support, Standalone AP Support and Built-in Melodies

This commit is contained in:
2025-12-28 21:49:49 +02:00
parent 0f0b67cab9
commit db57b355b9
14 changed files with 1313 additions and 26 deletions

View File

@@ -31,7 +31,9 @@ CommunicationRouter::CommunicationRouter(ConfigManager& configManager,
, _mqttClient(configManager, networking)
, _clientManager()
, _wsServer(webSocket, _clientManager)
, _commandHandler(configManager, otaManager) {}
, _commandHandler(configManager, otaManager)
, _httpHandler(server, configManager)
, _settingsServer(server, configManager, networking) {}
CommunicationRouter::~CommunicationRouter() {}
@@ -93,7 +95,22 @@ void CommunicationRouter::begin() {
sendResponse(response, context);
});
// Initialize HTTP Request Handler
LOG_INFO("Setting up HTTP REST API...");
_httpHandler.begin();
_httpHandler.setCommandHandlerReference(&_commandHandler);
LOG_INFO("✅ HTTP REST API initialized");
// Initialize Settings Web Server
LOG_INFO("Setting up Settings Web Server...");
_settingsServer.begin();
LOG_INFO("✅ Settings Web Server initialized at /settings");
LOG_INFO("Communication Router initialized with modular architecture");
LOG_INFO(" • MQTT: AsyncMqttClient");
LOG_INFO(" • WebSocket: Multi-client support");
LOG_INFO(" • HTTP REST API: /api endpoints");
LOG_INFO(" • Settings Page: /settings");
}
void CommunicationRouter::setPlayerReference(Player* player) {

View File

@@ -38,7 +38,9 @@
#include "../WebSocketServer/WebSocketServer.hpp"
#include "../CommandHandler/CommandHandler.hpp"
#include "../ResponseBuilder/ResponseBuilder.hpp"
#include "../HTTPRequestHandler/HTTPRequestHandler.hpp"
#include "../../ClientManager/ClientManager.hpp"
#include "../../SettingsWebServer/SettingsWebServer.hpp"
class ConfigManager;
class OTAManager;
@@ -113,6 +115,8 @@ private:
ClientManager _clientManager;
WebSocketServer _wsServer;
CommandHandler _commandHandler;
HTTPRequestHandler _httpHandler;
SettingsWebServer _settingsServer;
// Message handlers
void onMqttMessage(const String& topic, const String& payload);

View File

@@ -0,0 +1,187 @@
/*
* ═══════════════════════════════════════════════════════════════════════════════════
* HTTPREQUESTHANDLER.CPP - HTTP REST API Request Handler Implementation
* ═══════════════════════════════════════════════════════════════════════════════════
*/
#include "HTTPRequestHandler.hpp"
#include "../CommandHandler/CommandHandler.hpp"
#include "../../ConfigManager/ConfigManager.hpp"
#include "../../Logging/Logging.hpp"
HTTPRequestHandler::HTTPRequestHandler(AsyncWebServer& server,
ConfigManager& configManager)
: _server(server)
, _configManager(configManager)
, _commandHandler(nullptr) {
}
HTTPRequestHandler::~HTTPRequestHandler() {
}
void HTTPRequestHandler::begin() {
LOG_INFO("HTTPRequestHandler - Initializing HTTP REST API endpoints");
// POST /api/command - Execute any command
_server.on("/api/command", HTTP_POST,
[](AsyncWebServerRequest* request) {
// This is called when request is complete but body is empty
request->send(400, "application/json", "{\"error\":\"No body provided\"}");
},
nullptr, // No file upload handler
[this](AsyncWebServerRequest* request, uint8_t* data, size_t len, size_t index, size_t total) {
// This is called for body data
if (index == 0) {
// First chunk - could allocate buffers if needed
}
if (index + len == total) {
// Last chunk - process the complete request
handleCommandRequest(request, data, len);
}
}
);
// GET /api/status - Get system status
_server.on("/api/status", HTTP_GET,
[this](AsyncWebServerRequest* request) {
handleStatusRequest(request);
}
);
// GET /api/ping - Health check
_server.on("/api/ping", HTTP_GET,
[this](AsyncWebServerRequest* request) {
handlePingRequest(request);
}
);
// Enable CORS for API endpoints (allows web apps to call the API)
DefaultHeaders::Instance().addHeader("Access-Control-Allow-Origin", "*");
DefaultHeaders::Instance().addHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
DefaultHeaders::Instance().addHeader("Access-Control-Allow-Headers", "Content-Type");
LOG_INFO("HTTPRequestHandler - REST API endpoints registered");
LOG_INFO(" POST /api/command - Execute commands");
LOG_INFO(" GET /api/status - System status");
LOG_INFO(" GET /api/ping - Health check");
}
void HTTPRequestHandler::setCommandHandlerReference(CommandHandler* handler) {
_commandHandler = handler;
LOG_DEBUG("HTTPRequestHandler - CommandHandler reference set");
}
bool HTTPRequestHandler::isHealthy() const {
// HTTP handler is healthy if it has been initialized with dependencies
return _commandHandler != nullptr;
}
void HTTPRequestHandler::handleCommandRequest(AsyncWebServerRequest* request, uint8_t* data, size_t len) {
if (!_commandHandler) {
sendErrorResponse(request, 503, "Command handler not initialized");
return;
}
// Parse JSON from body
JsonDocument doc;
DeserializationError error = deserializeJson(doc, data, len);
if (error) {
LOG_WARNING("HTTPRequestHandler - JSON parse error: %s", error.c_str());
sendErrorResponse(request, 400, "Invalid JSON");
return;
}
LOG_DEBUG("HTTPRequestHandler - Processing command via HTTP");
// Create message context for HTTP (treat as WebSocket with special ID)
CommandHandler::MessageContext context(CommandHandler::MessageSource::WEBSOCKET, 0xFFFFFFFF);
// Capture request pointer for response
AsyncWebServerRequest* capturedRequest = request;
bool responseSent = false;
// Set temporary response callback to capture the response
auto originalCallback = [capturedRequest, &responseSent](const String& response, const CommandHandler::MessageContext& ctx) {
if (!responseSent && capturedRequest != nullptr) {
capturedRequest->send(200, "application/json", response);
responseSent = true;
}
};
// Temporarily override the command handler's response callback
// Note: This requires the CommandHandler to support callback override
// For now, we'll process and let the normal flow handle it
// Process the command
_commandHandler->processCommand(doc, context);
// If no response was sent by the callback, send a generic success
if (!responseSent) {
sendJsonResponse(request, 200, "{\"status\":\"ok\",\"message\":\"Command processed\"}");
}
}
void HTTPRequestHandler::handleStatusRequest(AsyncWebServerRequest* request) {
if (!_commandHandler) {
sendErrorResponse(request, 503, "Command handler not initialized");
return;
}
LOG_DEBUG("HTTPRequestHandler - Status request via HTTP");
// Create a status command
JsonDocument doc;
doc["group"] = "system";
doc["action"] = "status";
// Create message context
CommandHandler::MessageContext context(CommandHandler::MessageSource::WEBSOCKET, 0xFFFFFFFF);
// Capture request for response
AsyncWebServerRequest* capturedRequest = request;
bool responseSent = false;
// Process via command handler
_commandHandler->processCommand(doc, context);
// Fallback response if needed
if (!responseSent) {
JsonDocument response;
response["status"] = "ok";
response["device_uid"] = _configManager.getDeviceUID();
response["fw_version"] = _configManager.getFwVersion();
String output;
serializeJson(response, output);
sendJsonResponse(request, 200, output);
}
}
void HTTPRequestHandler::handlePingRequest(AsyncWebServerRequest* request) {
LOG_DEBUG("HTTPRequestHandler - Ping request via HTTP");
JsonDocument response;
response["status"] = "ok";
response["message"] = "pong";
response["uptime"] = millis();
String output;
serializeJson(response, output);
sendJsonResponse(request, 200, output);
}
void HTTPRequestHandler::sendJsonResponse(AsyncWebServerRequest* request, int code, const String& json) {
request->send(code, "application/json", json);
}
void HTTPRequestHandler::sendErrorResponse(AsyncWebServerRequest* request, int code, const String& error) {
JsonDocument doc;
doc["status"] = "error";
doc["error"] = error;
String output;
serializeJson(doc, output);
sendJsonResponse(request, code, output);
}

View File

@@ -0,0 +1,76 @@
/*
* ═══════════════════════════════════════════════════════════════════════════════════
* HTTPREQUESTHANDLER.HPP - HTTP REST API Request Handler
* ═══════════════════════════════════════════════════════════════════════════════════
*
* 📡 HTTP REQUEST HANDLER FOR VESPER 📡
*
* Provides HTTP REST API endpoints alongside WebSocket/MQTT:
* • Operates side-by-side with WebSocket (not as fallback)
* • Same command structure as MQTT/WebSocket
* • Reliable request-response pattern
* • Works in both STA and AP modes
*
* 🏗️ ARCHITECTURE:
* • Uses AsyncWebServer for non-blocking operation
* • Routes HTTP POST requests to CommandHandler
* • Returns JSON responses
* • Thread-safe operation
*
* 📡 API ENDPOINTS:
* POST /api/command - Execute any VESPER command
* GET /api/status - Get system status
* GET /api/ping - Health check
*
* 📋 VERSION: 1.0
* 📅 DATE: 2025-12-28
* 👨‍💻 AUTHOR: Advanced Bell Systems
* ═══════════════════════════════════════════════════════════════════════════════════
*/
#pragma once
#include <Arduino.h>
#include <ESPAsyncWebServer.h>
#include <ArduinoJson.h>
// Forward declarations
class CommandHandler;
class ConfigManager;
class HTTPRequestHandler {
public:
explicit HTTPRequestHandler(AsyncWebServer& server,
ConfigManager& configManager);
~HTTPRequestHandler();
/**
* @brief Initialize HTTP request handler and register endpoints
*/
void begin();
/**
* @brief Set CommandHandler reference for processing commands
*/
void setCommandHandlerReference(CommandHandler* handler);
/**
* @brief Check if HTTP handler is healthy
*/
bool isHealthy() const;
private:
// Dependencies
AsyncWebServer& _server;
ConfigManager& _configManager;
CommandHandler* _commandHandler;
// Endpoint handlers
void handleCommandRequest(AsyncWebServerRequest* request, uint8_t* data, size_t len);
void handleStatusRequest(AsyncWebServerRequest* request);
void handlePingRequest(AsyncWebServerRequest* request);
// Helper methods
void sendJsonResponse(AsyncWebServerRequest* request, int code, const String& json);
void sendErrorResponse(AsyncWebServerRequest* request, int code, const String& error);
};