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

View File

@@ -0,0 +1,389 @@
/*
* ═══════════════════════════════════════════════════════════════════════════════════
* BELLENGINE.CPP - High-Precision Bell Timing Engine Implementation
* ═══════════════════════════════════════════════════════════════════════════════════
*
* This file contains the implementation of the BellEngine class - the core
* precision timing system that controls bell activation with microsecond accuracy.
*
* 🔥 CRITICAL PERFORMANCE SECTION 🔥
*
* The code in this file is performance-critical and runs on a dedicated
* FreeRTOS task with maximum priority on Core 1. Any modifications should
* be thoroughly tested for timing impact.
*
* 📋 VERSION: 2.0 (Rewritten for modular architecture)
* 📅 DATE: 2025
* 👨‍💻 AUTHOR: Advanced Bell Systems
* ═══════════════════════════════════════════════════════════════════════════════════
*/
// ═════════════════════════════════════════════════════════════════════════════════
// DEPENDENCY INCLUDES - Required system components
// ═════════════════════════════════════════════════════════════════════════════════
#include "BellEngine.hpp" // Header file with class definition
#include "../Player/Player.hpp" // Melody playback controller
#include "../ConfigManager/ConfigManager.hpp" // Configuration and settings
#include "../Telemetry/Telemetry.hpp" // System monitoring and analytics
#include "../OutputManager/OutputManager.hpp" // Hardware abstraction layer
#include "../Communication/Communication.hpp" // Communication system for notifications
// ═════════════════════════════════════════════════════════════════════════════════
// CONSTRUCTOR & DESTRUCTOR IMPLEMENTATION
// ═════════════════════════════════════════════════════════════════════════════════
/**
* @brief Constructor - Initialize BellEngine with dependency injection
*
* Sets up all dependency references and initializes the OutputManager integration.
* Note: The OutputManager now handles all relay duration tracking, providing
* clean separation of concerns and hardware abstraction.
*/
BellEngine::BellEngine(Player& player, ConfigManager& configManager, Telemetry& telemetry, OutputManager& outputManager)
: _player(player) // Reference to melody playback controller
, _configManager(configManager) // Reference to configuration manager
, _telemetry(telemetry) // Reference to system monitoring
, _outputManager(outputManager) // 🔥 Reference to hardware abstraction layer
, _communicationManager(nullptr) { // Initialize communication manager to nullptr
// 🏗️ ARCHITECTURAL NOTE:
// OutputManager now handles all relay duration tracking automatically!
// This provides clean separation of concerns and hardware abstraction.
}
/**
* @brief Destructor - Ensures safe cleanup
*
* Automatically calls emergencyShutdown() to ensure all relays are turned off
* and the system is in a safe state before object destruction.
*/
BellEngine::~BellEngine() {
emergencyShutdown(); // 🚑 Ensure safe shutdown on destruction
}
// ═════════════════════════════════════════════════════════════════════════════════
// CORE INITIALIZATION IMPLEMENTATION
// ═════════════════════════════════════════════════════════════════════════════════
/**
* @brief Initialize the BellEngine system
*
* Creates the high-priority timing task on Core 1 with maximum priority.
* This task provides the microsecond-precision timing that makes the
* bell system so accurate and reliable.
*
*/
void BellEngine::begin() {
LOG_DEBUG("Initializing BellEngine with high-precision timing");
// Create engine task with HIGHEST priority on dedicated Core 1
// This ensures maximum performance and timing precision
xTaskCreatePinnedToCore(
engineTask, // 📋 Task function pointer
"BellEngine", // 🏷️ Task name for debugging
12288, // 💾 Stack size (12KB - increased for safety)
this, // 🔗 Parameter (this instance)
6, // ⚡ HIGHEST Priority (0-7, 7 is highest)
&_engineTaskHandle, // 💼 Task handle storage
1 // 💻 Pin to Core 1 (dedicated)
);
LOG_INFO("BellEngine initialized - Ready for MAXIMUM PRECISION! 🎯");
}
/**
* @brief Set Communication manager reference for bell notifications
*/
void BellEngine::setCommunicationManager(Communication* commManager) {
_communicationManager = commManager;
LOG_DEBUG("BellEngine: Communication manager %s",
commManager ? "connected" : "disconnected");
}
// ═════════════════════════════════════════════════════════════════════════════════
// ENGINE CONTROL IMPLEMENTATION (Thread-safe)
// ═════════════════════════════════════════════════════════════════════════════════
/**
* @brief Start the precision timing engine
*
* Activates the high-precision timing loop. Requires melody data to be
* loaded via setMelodyData() before calling. Uses atomic operations
* for thread-safe state management.
*
* @note Will log error and return if no melody data is available
*/
void BellEngine::start() {
// Validate that melody data is ready before starting
if (!_melodyDataReady.load()) {
LOG_ERROR("Cannot start BellEngine: No melody data loaded");
return; // ⛔ Early exit if no melody data
}
LOG_INFO("🚀 BellEngine IGNITION - Starting precision playback");
_emergencyStop.store(false); // ✅ Clear any emergency stop state
_engineRunning.store(true); // ✅ Activate the engine atomically
}
void BellEngine::stop() {
LOG_INFO("BellEngine stopping gracefully");
_engineRunning.store(false);
}
void BellEngine::emergencyStop() {
LOG_INFO("🛑 EMERGENCY STOP ACTIVATED");
_emergencyStop.store(true);
_engineRunning.store(false);
emergencyShutdown();
}
void BellEngine::setMelodyData(const std::vector<uint16_t>& melodySteps) {
portENTER_CRITICAL(&_melodyMutex);
_melodySteps = melodySteps;
_melodyDataReady.store(true);
portEXIT_CRITICAL(&_melodyMutex);
LOG_DEBUG("BellEngine loaded melody: %d steps", melodySteps.size());
}
void BellEngine::clearMelodyData() {
portENTER_CRITICAL(&_melodyMutex);
_melodySteps.clear();
_melodyDataReady.store(false);
portEXIT_CRITICAL(&_melodyMutex);
LOG_DEBUG("BellEngine melody data cleared");
}
// ================== CRITICAL TIMING SECTION ==================
// This is where the magic happens! Maximum precision required !
void BellEngine::engineTask(void* parameter) {
BellEngine* engine = static_cast<BellEngine*>(parameter);
LOG_DEBUG("🔥 BellEngine task started on Core %d with MAXIMUM priority", xPortGetCoreID());
while (true) {
if (engine->_engineRunning.load() && !engine->_emergencyStop.load()) {
engine->engineLoop();
} else {
// Low-power wait when not running
vTaskDelay(pdMS_TO_TICKS(10));
}
}
}
void BellEngine::engineLoop() {
uint64_t loopStartTime = getMicros();
// Safety check. Stop if Emergency Stop is Active
if (_emergencyStop.load()) {
emergencyShutdown();
return;
}
playbackLoop();
// Pause handling AFTER complete loop - never interrupt mid-melody!
while (_player.isPaused && _player.isPlaying && !_player.hardStop) {
LOG_DEBUG("⏸️ Pausing between melody loops");
vTaskDelay(pdMS_TO_TICKS(10)); // Wait during pause
}
uint64_t loopEndTime = getMicros();
uint32_t loopTime = (uint32_t)(loopEndTime - loopStartTime);
}
void BellEngine::playbackLoop() {
// Check if player wants us to run
if (!_player.isPlaying || _player.hardStop) {
_engineRunning.store(false);
return;
}
// Get melody data safely
portENTER_CRITICAL(&_melodyMutex);
auto melodySteps = _melodySteps; // Fast copy
portEXIT_CRITICAL(&_melodyMutex);
if (melodySteps.empty()) {
LOG_ERROR("Empty melody in playback loop!");
return;
}
LOG_DEBUG("🎵 Starting melody loop (%d steps)", melodySteps.size());
// CRITICAL TIMING LOOP - Complete the entire melody without interruption
for (uint16_t note : melodySteps) {
// Emergency exit check (only emergency stops can interrupt mid-loop)
if (_emergencyStop.load() || _player.hardStop) {
LOG_DEBUG("Emergency exit from playback loop");
return;
}
// Activate note with MAXIMUM PRECISION
activateNote(note);
// Precise timing delay
uint32_t tempoMicros = _player.speed * 1000; // Convert ms to microseconds
preciseDelay(tempoMicros);
}
// Mark segment completion and notify Player
_player.segmentCmpltTime = millis();
_player.onMelodyLoopCompleted(); // 🔥 Notify Player that melody actually finished!
LOG_DEBUG("🎵 Melody loop completed with PRECISION");
}
void BellEngine::activateNote(uint16_t note) {
// Track which bells we've already added to prevent duplicates
bool bellFired[16] = {false};
std::vector<std::pair<uint8_t, uint16_t>> bellDurations; // For batch firing
std::vector<uint8_t> firedBellIndices; // Track which bells were fired for notification
// Iterate through each bit position (note index)
for (uint8_t noteIndex = 0; noteIndex < 16; noteIndex++) {
if (note & (1 << noteIndex)) {
// Get bell mapping
uint8_t bellIndex = _player.noteAssignments[noteIndex];
// Skip if no bell assigned
if (bellIndex == 0) continue;
// Convert to 0-based indexing
bellIndex = bellIndex - 1;
// Additional safety check to prevent underflow crashes
if (bellIndex >= 255) {
LOG_ERROR("🚨 UNDERFLOW ERROR: bellIndex underflow for noteIndex %d", noteIndex);
continue;
}
// Bounds check (CRITICAL SAFETY)
if (bellIndex >= 16) {
LOG_ERROR("🚨 BOUNDS ERROR: bellIndex %d >= 16", bellIndex);
continue;
}
// Check for duplicate bell firing in this note
if (bellFired[bellIndex]) {
LOG_DEBUG("⚠️ DUPLICATE BELL: Skipping duplicate firing of bell %d for note %d", bellIndex, noteIndex);
continue;
}
// Check if bell is configured (OutputManager will validate this)
uint8_t physicalOutput = _outputManager.getPhysicalOutput(bellIndex);
if (physicalOutput == 255) {
LOG_DEBUG("⚠️ UNCONFIGURED: Bell %d not configured, skipping", bellIndex);
continue;
}
// Mark this bell as fired
bellFired[bellIndex] = true;
// Get duration from config
uint16_t durationMs = _configManager.getBellDuration(bellIndex);
// Add to batch firing list
bellDurations.push_back({bellIndex, durationMs});
// Add to notification list (convert to 1-indexed for display)
firedBellIndices.push_back(bellIndex + 1);
// Record telemetry
_telemetry.recordBellStrike(bellIndex);
LOG_VERBOSE("🔨 STRIKE! Note:%d → Bell:%d for %dms", noteIndex, bellIndex, durationMs);
}
}
// 🚀 FIRE ALL BELLS SIMULTANEOUSLY!
if (!bellDurations.empty()) {
_outputManager.fireOutputsBatchForDuration(bellDurations);
LOG_VERBOSE("🔥🔥 BATCH FIRED %d bells SIMULTANEOUSLY!", bellDurations.size());
// 🔔 NOTIFY WEBSOCKET CLIENTS OF BELL DINGS!
notifyBellsFired(firedBellIndices);
}
}
void BellEngine::preciseDelay(uint32_t microseconds) {
uint64_t start = getMicros();
uint64_t target = start + microseconds;
// For delays > 1ms, use task delay for most of it
if (microseconds > 1000) {
uint32_t taskDelayMs = (microseconds - 500) / 1000; // Leave 500µs for busy wait
vTaskDelay(pdMS_TO_TICKS(taskDelayMs));
}
// Busy wait for final precision
while (getMicros() < target) {
// Tight loop for maximum precision
asm volatile("nop");
}
}
void BellEngine::emergencyShutdown() {
LOG_INFO("🚨 EMERGENCY SHUTDOWN - Using OutputManager");
_outputManager.emergencyShutdown();
}
void BellEngine::notifyBellsFired(const std::vector<uint8_t>& bellIndices) {
if (!_communicationManager || bellIndices.empty()) {
return; // No communication manager or no bells fired
}
try {
// Create notification message
StaticJsonDocument<256> dingMsg;
dingMsg["status"] = "INFO";
dingMsg["type"] = "ding";
// Create payload array with fired bell numbers (1-indexed for display)
JsonArray bellsArray = dingMsg["payload"].to<JsonArray>();
for (uint8_t bellIndex : bellIndices) {
bellsArray.add(bellIndex); // Already converted to 1-indexed in activateNote
}
// Send notification to WebSocket clients only (not MQTT)
_communicationManager->broadcastToAllWebSocketClients(dingMsg);
LOG_DEBUG("🔔 DING notification sent for %d bells", bellIndices.size());
} catch (...) {
LOG_ERROR("Failed to send ding notification");
}
}
// ════════════════════════════════════════════════════════════════════════════
// HEALTH CHECK IMPLEMENTATION
// ════════════════════════════════════════════════════════════════════════════
bool BellEngine::isHealthy() const {
// Check if engine task is created and running
if (_engineTaskHandle == NULL) {
LOG_DEBUG("BellEngine: Unhealthy - Task not created");
return false;
}
// Check if task is still alive
eTaskState taskState = eTaskGetState(_engineTaskHandle);
if (taskState == eDeleted || taskState == eInvalid) {
LOG_DEBUG("BellEngine: Unhealthy - Task deleted or invalid");
return false;
}
// Check if we're not in emergency stop state
if (_emergencyStop.load()) {
LOG_DEBUG("BellEngine: Unhealthy - Emergency stop active");
return false;
}
// Check if OutputManager is properly connected and healthy
if (!_outputManager.isInitialized()) {
LOG_DEBUG("BellEngine: Unhealthy - OutputManager not initialized");
return false;
}
return true;
}