Complete Rebuild, with Subsystems for each component. RTOS Tasks. (help by Claude)
This commit is contained in:
1346
vesper/src/Communication/Communication.cpp
Normal file
1346
vesper/src/Communication/Communication.cpp
Normal file
File diff suppressed because it is too large
Load Diff
232
vesper/src/Communication/Communication.hpp
Normal file
232
vesper/src/Communication/Communication.hpp
Normal 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.
|
||||
};
|
||||
157
vesper/src/Communication/ResponseBuilder.cpp
Normal file
157
vesper/src/Communication/ResponseBuilder.cpp
Normal 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";
|
||||
}
|
||||
}
|
||||
87
vesper/src/Communication/ResponseBuilder.hpp
Normal file
87
vesper/src/Communication/ResponseBuilder.hpp
Normal 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
|
||||
};
|
||||
Reference in New Issue
Block a user