Complete Rebuild, with Subsystems for each component. RTOS Tasks. (help by Claude)

This commit is contained in:
2025-10-01 12:42:00 +03:00
parent 104c1d04d4
commit f696984cd1
57 changed files with 11757 additions and 2290 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,232 @@
/*
* ═══════════════════════════════════════════════════════════════════════════════════
* COMMUNICATION.HPP - Multi-Protocol Communication Manager v3.0
* ═══════════════════════════════════════════════════════════════════════════════════
*
* 📡 THE COMMUNICATION HUB OF VESPER 📡
*
* This class manages all external communication protocols including MQTT,
* WebSocket, and UDP discovery. It provides a unified interface for
* grouped command handling and status reporting across multiple protocols.
*
* 🏗️ ARCHITECTURE:
* • Multi-protocol support with unified grouped command processing
* • Multi-client WebSocket support with device type identification
* • Automatic connection management and reconnection
* • Unified response system for consistent messaging
* • Thread-safe operation with proper resource management
* • Batch command support for efficient configuration
*
* 📡 SUPPORTED PROTOCOLS:
* • MQTT: Primary control interface with auto-reconnection
* • WebSocket: Real-time multi-client web interface communication
* • UDP Discovery: Auto-discovery service for network scanning
*
* 📱 CLIENT MANAGEMENT:
* • Support for multiple WebSocket clients (master/secondary devices)
* • Client type identification and targeted messaging
* • Automatic cleanup of disconnected clients
* • Broadcast capabilities for status updates
*
* 🔄 MESSAGE ROUTING:
* • Commands accepted from both MQTT and WebSocket
* • Responses sent only to originating protocol/client
* • Status broadcasts sent to all WebSocket clients + MQTT
* • Grouped command processing for all protocols
*
* 📋 VERSION: 3.0 (Grouped commands + batch processing)
* 📅 DATE: 2025
* 👨‍💻 AUTHOR: Advanced Bell Systems
* ═══════════════════════════════════════════════════════════════════════════════════
*/
#pragma once
#include <Arduino.h>
#include <AsyncMqttClient.h>
#include <ESPAsyncWebServer.h>
#include <AsyncUDP.h>
#include <ArduinoJson.h>
#include "ResponseBuilder.hpp"
#include "../ClientManager/ClientManager.hpp"
class ConfigManager;
class OTAManager;
class Player;
class FileManager;
class Timekeeper;
class Networking;
class FirmwareValidator;
class Communication {
public:
// Message source identification for response routing
enum class MessageSource {
MQTT,
WEBSOCKET
};
struct MessageContext {
MessageSource source;
uint32_t clientId; // Only used for WebSocket messages
MessageContext(MessageSource src, uint32_t id = 0)
: source(src), clientId(id) {}
};
explicit Communication(ConfigManager& configManager,
OTAManager& otaManager,
Networking& networking,
AsyncMqttClient& mqttClient,
AsyncWebServer& server,
AsyncWebSocket& webSocket,
AsyncUDP& udp);
~Communication();
void begin();
void setPlayerReference(Player* player) { _player = player; }
void setFileManagerReference(FileManager* fm) { _fileManager = fm; }
void setTimeKeeperReference(Timekeeper* tk) { _timeKeeper = tk; }
void setFirmwareValidatorReference(FirmwareValidator* fv) { _firmwareValidator = fv; }
void setupUdpDiscovery();
// Public methods for timer callbacks
void connectToMqtt();
void subscribeMqtt();
// Status methods
bool isMqttConnected() const { return _mqttClient.connected(); }
bool hasActiveWebSocketClients() const { return _clientManager.hasClients(); }
size_t getWebSocketClientCount() const { return _clientManager.getClientCount(); }
// Response methods - unified response system
void sendResponse(const String& response, const MessageContext& context);
void sendSuccessResponse(const String& type, const String& payload, const MessageContext& context);
void sendErrorResponse(const String& type, const String& message, const MessageContext& context);
// Broadcast methods - for status updates that go to everyone
void broadcastStatus(const String& statusMessage);
void broadcastStatus(const JsonDocument& statusJson);
void broadcastToMasterClients(const String& message);
void broadcastToSecondaryClients(const String& message);
void broadcastToAllWebSocketClients(const String& message);
void broadcastToAllWebSocketClients(const JsonDocument& message);
void publishToMqtt(const String& data);
// ═══════════════════════════════════════════════════════════════════════════════
// HEALTH CHECK METHOD
// ═══════════════════════════════════════════════════════════════════════════════
/** @brief Check if Communication is in healthy state */
bool isHealthy() const;
// Bell overload notification
void sendBellOverloadNotification(const std::vector<uint8_t>& bellNumbers,
const std::vector<uint16_t>& bellLoads,
const String& severity);
// Network connection callbacks (called by Networking)
void onNetworkConnected();
void onNetworkDisconnected();
// Static instance for callbacks
static Communication* _instance;
private:
// Dependencies
ConfigManager& _configManager;
OTAManager& _otaManager;
Networking& _networking;
AsyncMqttClient& _mqttClient;
AsyncWebServer& _server;
AsyncWebSocket& _webSocket;
AsyncUDP& _udp;
Player* _player;
FileManager* _fileManager;
Timekeeper* _timeKeeper;
FirmwareValidator* _firmwareValidator;
// Client manager
ClientManager _clientManager;
// State
TimerHandle_t _mqttReconnectTimer;
// Reusable JSON documents
static StaticJsonDocument<2048> _parseDocument;
// MQTT methods
void initMqtt();
static void onMqttConnect(bool sessionPresent);
static void onMqttDisconnect(AsyncMqttClientDisconnectReason reason);
static void onMqttMessage(char* topic, char* payload, AsyncMqttClientMessageProperties properties,
size_t len, size_t index, size_t total);
static void onMqttSubscribe(uint16_t packetId, uint8_t qos);
static void onMqttUnsubscribe(uint16_t packetId);
static void onMqttPublish(uint16_t packetId);
// WebSocket methods
void initWebSocket();
static void onWebSocketEvent(AsyncWebSocket* server, AsyncWebSocketClient* client,
AwsEventType type, void* arg, uint8_t* data, size_t len);
void onWebSocketConnect(AsyncWebSocketClient* client);
void onWebSocketDisconnect(AsyncWebSocketClient* client);
void onWebSocketReceived(AsyncWebSocketClient* client, void* arg, uint8_t* data, size_t len);
void handleClientIdentification(AsyncWebSocketClient* client, JsonDocument& command);
// Command processing - unified for both MQTT and WebSocket with grouped commands
JsonDocument parsePayload(char* payload);
void handleCommand(JsonDocument& command, const MessageContext& context);
// ═════════════════════════════════════════════════════════════════════════════════
// GROUPED COMMAND HANDLERS
// ═════════════════════════════════════════════════════════════════════════════════
// System commands
void handleSystemCommand(JsonVariant contents, const MessageContext& context);
void handleSystemInfoCommand(JsonVariant contents, const MessageContext& context);
void handlePlaybackCommand(JsonVariant contents, const MessageContext& context);
void handleFileManagerCommand(JsonVariant contents, const MessageContext& context);
void handleRelaySetupCommand(JsonVariant contents, const MessageContext& context);
void handleClockSetupCommand(JsonVariant contents, const MessageContext& context);
// System sub-commands
void handlePingCommand(const MessageContext& context);
void handleStatusCommand(const MessageContext& context);
void handleIdentifyCommand(JsonVariant contents, const MessageContext& context);
void handleGetDeviceTimeCommand(const MessageContext& context);
void handleGetClockTimeCommand(const MessageContext& context);
// Firmware management commands
void handleCommitFirmwareCommand(const MessageContext& context);
void handleRollbackFirmwareCommand(const MessageContext& context);
void handleGetFirmwareStatusCommand(const MessageContext& context);
// Network configuration command
void handleSetNetworkConfigCommand(JsonVariant contents, const MessageContext& context);
// File Manager sub-commands
void handleListMelodiesCommand(const MessageContext& context);
void handleDownloadMelodyCommand(JsonVariant contents, const MessageContext& context);
void handleDeleteMelodyCommand(JsonVariant contents, const MessageContext& context);
// Relay Setup sub-commands
void handleSetRelayTimersCommand(JsonVariant contents, const MessageContext& context);
void handleSetRelayOutputsCommand(JsonVariant contents, const MessageContext& context);
// Clock Setup sub-commands
void handleSetClockOutputsCommand(JsonVariant contents, const MessageContext& context);
void handleSetClockTimingsCommand(JsonVariant contents, const MessageContext& context);
void handleSetClockAlertsCommand(JsonVariant contents, const MessageContext& context);
void handleSetClockBacklightCommand(JsonVariant contents, const MessageContext& context);
void handleSetClockSilenceCommand(JsonVariant contents, const MessageContext& context);
void handleSetRtcTimeCommand(JsonVariant contents, const MessageContext& context);
void handleSetPhysicalClockTimeCommand(JsonVariant contents, const MessageContext& context);
void handlePauseClockUpdatesCommand(JsonVariant contents, const MessageContext& context);
void handleSetClockEnabledCommand(JsonVariant contents, const MessageContext& context);
// Utility methods
String getPayloadContent(char* data, size_t len);
int extractBellNumber(const String& key); // Extract bell number from "b1", "c1", etc.
};

View File

@@ -0,0 +1,157 @@
#include "ResponseBuilder.hpp"
#include "../Logging/Logging.hpp"
// Static member initialization
StaticJsonDocument<512> ResponseBuilder::_responseDoc;
String ResponseBuilder::success(const String& type, const String& payload) {
return buildResponse(Status::SUCCESS, type, payload);
}
String ResponseBuilder::success(const String& type, const JsonObject& payload) {
return buildResponse(Status::SUCCESS, type, payload);
}
String ResponseBuilder::error(const String& type, const String& message) {
return buildResponse(Status::ERROR, type, message);
}
String ResponseBuilder::status(const String& type, const JsonObject& data) {
return buildResponse(Status::SUCCESS, type, data);
}
String ResponseBuilder::status(const String& type, const String& data) {
return buildResponse(Status::SUCCESS, type, data);
}
String ResponseBuilder::acknowledgment(const String& commandType) {
return success(commandType, "Command acknowledged");
}
String ResponseBuilder::pong() {
return success("pong", "");
}
String ResponseBuilder::deviceStatus(PlayerStatus playerStatus, uint32_t timeElapsed, uint64_t projectedRunTime) {
StaticJsonDocument<512> statusDoc; // Increased size for additional data
statusDoc["status"] = "SUCCESS";
statusDoc["type"] = "current_status";
// Create payload object with the exact format expected by Flutter
JsonObject payload = statusDoc.createNestedObject("payload");
// Convert PlayerStatus to string
const char* statusStr;
switch (playerStatus) {
case PlayerStatus::PLAYING:
statusStr = "playing";
break;
case PlayerStatus::PAUSED:
statusStr = "paused";
break;
case PlayerStatus::STOPPING:
statusStr = "stopping";
break;
case PlayerStatus::STOPPED:
default:
statusStr = "idle"; // STOPPED maps to "idle" in Flutter
break;
}
payload["player_status"] = statusStr;
payload["time_elapsed"] = timeElapsed; // in milliseconds
payload["projected_run_time"] = projectedRunTime; // NEW: total projected duration
String result;
serializeJson(statusDoc, result);
LOG_DEBUG("Device status response: %s", result.c_str());
return result;
}
String ResponseBuilder::melodyList(const String& fileListJson) {
// The fileListJson is already a JSON string, so we pass it as payload
return success("melody_list", fileListJson);
}
String ResponseBuilder::downloadResult(bool success, const String& filename) {
if (success) {
String message = "Download successful";
if (filename.length() > 0) {
message += ": " + filename;
}
return ResponseBuilder::success("download", message);
} else {
String message = "Download failed";
if (filename.length() > 0) {
message += ": " + filename;
}
return error("download", message);
}
}
String ResponseBuilder::configUpdate(const String& configType) {
return success(configType, configType + " configuration updated");
}
String ResponseBuilder::invalidCommand(const String& command) {
return error("invalid_command", "Unknown command: " + command);
}
String ResponseBuilder::missingParameter(const String& parameter) {
return error("missing_parameter", "Required parameter missing: " + parameter);
}
String ResponseBuilder::operationFailed(const String& operation, const String& reason) {
String message = operation + " failed";
if (reason.length() > 0) {
message += ": " + reason;
}
return error(operation, message);
}
String ResponseBuilder::deviceBusy() {
return error("device_busy", "Device is currently busy, try again later");
}
String ResponseBuilder::unauthorized() {
return error("unauthorized", "Operation not authorized for this client");
}
// Response Builder with String Payload
String ResponseBuilder::buildResponse(Status status, const String& type, const String& payload) {
_responseDoc.clear();
_responseDoc["status"] = statusToString(status);
_responseDoc["type"] = type;
_responseDoc["payload"] = payload;
String result;
serializeJson(_responseDoc, result);
LOG_DEBUG("Response built: %s", result.c_str());
return result;
}
// Response Builder with JSON Payload
String ResponseBuilder::buildResponse(Status status, const String& type, const JsonObject& payload) {
_responseDoc.clear();
_responseDoc["status"] = statusToString(status);
_responseDoc["type"] = type;
_responseDoc["payload"] = payload;
String result;
serializeJson(_responseDoc, result);
LOG_DEBUG("Response built: %s", result.c_str());
return result;
}
const char* ResponseBuilder::statusToString(Status status) {
switch (status) {
case Status::SUCCESS: return "SUCCESS";
case Status::ERROR: return "ERROR";
case Status::INFO: return "INFO";
default: return "UNKNOWN";
}
}

View File

@@ -0,0 +1,87 @@
/*
* ═══════════════════════════════════════════════════════════════════════════════════
* RESPONSEBUILDER.HPP - Unified Response Generation System
* ═══════════════════════════════════════════════════════════════════════════════════
*
* 📡 STANDARDIZED COMMUNICATION RESPONSES 📡
*
* This class provides a unified interface for generating consistent JSON responses
* across all communication protocols (MQTT, WebSocket). It ensures all responses
* follow the same format and structure.
*
* 🏗️ ARCHITECTURE:
* • Static methods for response generation
* • Consistent JSON structure across all protocols
* • Memory-efficient response building
* • Type-safe response categories
*
* 📡 RESPONSE TYPES:
* • Success: Successful command execution
* • Error: Command execution failures
* • Status: System status reports and updates
* • Data: Information requests and telemetry
*
* 🔄 RESPONSE STRUCTURE:
* {
* "status": "OK|ERROR",
* "type": "command_type",
* "payload": "data_or_message"
* }
*
* 📋 USAGE EXAMPLES:
* • ResponseBuilder::success("playback", "Started playing melody")
* • ResponseBuilder::error("download", "File not found")
* • ResponseBuilder::status("telemetry", telemetryData)
*
* 📋 VERSION: 1.0 (Initial unified response system)
* 📅 DATE: 2025
* 👨‍💻 AUTHOR: Advanced Bell Systems
* ═══════════════════════════════════════════════════════════════════════════════════
*/
#pragma once
#include <Arduino.h>
#include <ArduinoJson.h>
#include "../Player/Player.hpp" // For PlayerStatus enum
class ResponseBuilder {
public:
// Response status types
enum class Status {
SUCCESS,
ERROR,
INFO
};
// Main response builders
static String success(const String& type, const String& payload = "");
static String success(const String& type, const JsonObject& payload);
static String error(const String& type, const String& message);
static String status(const String& type, const JsonObject& data);
static String status(const String& type, const String& data);
// Specialized response builders for common scenarios
static String acknowledgment(const String& commandType);
static String pong();
static String deviceStatus(PlayerStatus playerStatus, uint32_t timeElapsedMs, uint64_t projectedRunTime = 0);
static String melodyList(const String& fileListJson);
static String downloadResult(bool success, const String& filename = "");
static String configUpdate(const String& configType);
// Error response builders
static String invalidCommand(const String& command);
static String missingParameter(const String& parameter);
static String operationFailed(const String& operation, const String& reason = "");
static String deviceBusy();
static String unauthorized();
// Utility methods
static String buildResponse(Status status, const String& type, const String& payload);
static String buildResponse(Status status, const String& type, const JsonObject& payload);
private:
// Internal helper methods
static const char* statusToString(Status status);
static StaticJsonDocument<512> _responseDoc; // Reusable document for efficiency
};