#include "Telemetry.hpp" #include void Telemetry::begin() { // Initialize arrays for (uint8_t i = 0; i < 16; i++) { strikeCounters[i] = 0; bellLoad[i] = 0; bellMaxLoad[i] = 200; // Default max load } coolingActive = false; // Load strike counters from SD if available loadStrikeCounters(); // Create the telemetry task xTaskCreatePinnedToCore(telemetryTask, "TelemetryTask", 4096, this, 2, &telemetryTaskHandle, 1); LOG_INFO("Telemetry initialized"); } void Telemetry::setPlayerReference(bool* isPlayingPtr) { playerIsPlayingPtr = isPlayingPtr; LOG_DEBUG("Player reference set"); } void Telemetry::setFileManager(FileManager* fm) { fileManager = fm; LOG_DEBUG("FileManager reference set"); } void Telemetry::setForceStopCallback(void (*callback)()) { forceStopCallback = callback; LOG_DEBUG("Force stop callback set"); } void Telemetry::recordBellStrike(uint8_t bellIndex) { if (bellIndex >= 16) { LOG_ERROR("Invalid bell index: %d", bellIndex); return; } // Critical section - matches your original code portENTER_CRITICAL(&telemetrySpinlock); strikeCounters[bellIndex]++; // Count strikes per bell (warranty) bellLoad[bellIndex]++; // Load per bell (heat simulation) coolingActive = true; // System needs cooling portEXIT_CRITICAL(&telemetrySpinlock); } uint32_t Telemetry::getStrikeCount(uint8_t bellIndex) { if (bellIndex >= 16) { LOG_ERROR("Invalid bell index: %d", bellIndex); return 0; } return strikeCounters[bellIndex]; } void Telemetry::resetStrikeCounters() { portENTER_CRITICAL(&telemetrySpinlock); for (uint8_t i = 0; i < 16; i++) { strikeCounters[i] = 0; } portEXIT_CRITICAL(&telemetrySpinlock); LOG_WARNING("Strike counters reset by user"); } uint16_t Telemetry::getBellLoad(uint8_t bellIndex) { if (bellIndex >= 16) { LOG_ERROR("Invalid bell index: %d", bellIndex); return 0; } return bellLoad[bellIndex]; } void Telemetry::setBellMaxLoad(uint8_t bellIndex, uint16_t maxLoad) { if (bellIndex >= 16) { LOG_ERROR("Invalid bell index: %d", bellIndex); return; } bellMaxLoad[bellIndex] = maxLoad; LOG_INFO("Bell %d max load set to %d", bellIndex, maxLoad); } bool Telemetry::isOverloaded(uint8_t bellIndex) { if (bellIndex >= 16) { LOG_ERROR("Invalid bell index: %d", bellIndex); return false; } return bellLoad[bellIndex] > bellMaxLoad[bellIndex]; } bool Telemetry::isCoolingActive() { return coolingActive; } void Telemetry::logTemperature(float temperature) { // Future implementation for temperature logging LOG_INFO("Temperature: %.2f°C", temperature); } void Telemetry::logVibration(float vibration) { // Future implementation for vibration logging LOG_INFO("Vibration: %.2f", vibration); } void Telemetry::checkBellLoads() { coolingActive = false; // Reset cooling flag // Collect overloaded bells for batch notification std::vector criticalBells; std::vector criticalLoads; std::vector warningBells; std::vector warningLoads; bool anyOverload = false; for (uint8_t i = 0; i < 16; i++) { if (bellLoad[i] > 0) { bellLoad[i]--; coolingActive = true; // Still has heat left } // Check for critical overload (90% of max load) uint16_t criticalThreshold = (bellMaxLoad[i] * 90) / 100; // Check for warning overload (60% of max load) uint16_t warningThreshold = (bellMaxLoad[i] * 60) / 100; // Critical overload - protection kicks in if (bellLoad[i] > bellMaxLoad[i]) { LOG_ERROR("Bell %d OVERLOADED! load=%d max=%d", i, bellLoad[i], bellMaxLoad[i]); criticalBells.push_back(i); criticalLoads.push_back(bellLoad[i]); anyOverload = true; } else if (bellLoad[i] > criticalThreshold) { // Critical warning - approaching overload LOG_WARNING("Bell %d approaching overload! load=%d (critical threshold=%d)", i, bellLoad[i], criticalThreshold); criticalBells.push_back(i); criticalLoads.push_back(bellLoad[i]); } else if (bellLoad[i] > warningThreshold) { // Warning - moderate load LOG_INFO("Bell %d moderate load warning! load=%d (warning threshold=%d)", i, bellLoad[i], warningThreshold); warningBells.push_back(i); warningLoads.push_back(bellLoad[i]); } } // Note: Notifications now handled by BellEngine which has Communication reference // BellEngine monitors telemetry and sends notifications when overloads detected // Trigger force stop if any bell is actually overloaded if (anyOverload && forceStopCallback != nullptr) { forceStopCallback(); } } void Telemetry::telemetryTask(void* parameter) { Telemetry* telemetry = static_cast(parameter); LOG_INFO("Telemetry task started"); while(1) { // Only run if player is playing OR we're still cooling bool isPlaying = (telemetry->playerIsPlayingPtr != nullptr) ? *(telemetry->playerIsPlayingPtr) : false; if (isPlaying || telemetry->coolingActive) { telemetry->checkBellLoads(); } vTaskDelay(pdMS_TO_TICKS(1000)); // Run every 1s } } // ════════════════════════════════════════════════════════════════════════════ // STRIKE COUNTER PERSISTENCE // ════════════════════════════════════════════════════════════════════════════ void Telemetry::saveStrikeCounters() { if (!fileManager) { LOG_WARNING("Cannot save strike counters: FileManager not set"); return; } StaticJsonDocument<512> doc; JsonArray counters = doc.createNestedArray("strikeCounters"); // Thread-safe read of strike counters portENTER_CRITICAL(&telemetrySpinlock); for (uint8_t i = 0; i < 16; i++) { counters.add(strikeCounters[i]); } portEXIT_CRITICAL(&telemetrySpinlock); if (fileManager->writeJsonFile("/telemetry_data.json", doc)) { LOG_INFO("Strike counters saved to SD card"); } else { LOG_ERROR("Failed to save strike counters to SD card"); } } void Telemetry::loadStrikeCounters() { if (!fileManager) { LOG_WARNING("Cannot load strike counters: FileManager not set"); return; } StaticJsonDocument<512> doc; if (!fileManager->readJsonFile("/telemetry_data.json", doc)) { LOG_INFO("No previous strike counter data found, starting fresh"); return; } JsonArray counters = doc["strikeCounters"]; if (counters.isNull()) { LOG_WARNING("Invalid telemetry data format"); return; } // Thread-safe write of strike counters portENTER_CRITICAL(&telemetrySpinlock); for (uint8_t i = 0; i < 16 && i < counters.size(); i++) { strikeCounters[i] = counters[i].as(); } portEXIT_CRITICAL(&telemetrySpinlock); LOG_INFO("Strike counters loaded from SD card"); } // ════════════════════════════════════════════════════════════════════════════ // HEALTH CHECK IMPLEMENTATION // ════════════════════════════════════════════════════════════════════════════ bool Telemetry::isHealthy() const { // Check if telemetry task is created and running if (telemetryTaskHandle == NULL) { LOG_DEBUG("Telemetry: Unhealthy - Task not created"); return false; } // Check if task is still alive eTaskState taskState = eTaskGetState(telemetryTaskHandle); if (taskState == eDeleted || taskState == eInvalid) { LOG_DEBUG("Telemetry: Unhealthy - Task deleted or invalid"); return false; } // Check if player reference is set if (playerIsPlayingPtr == nullptr) { LOG_DEBUG("Telemetry: Unhealthy - Player reference not set"); return false; } // Check for any critical overloads that would indicate system stress bool hasCriticalOverload = false; for (uint8_t i = 0; i < 16; i++) { if (bellLoad[i] > bellMaxLoad[i]) { hasCriticalOverload = true; break; } } if (hasCriticalOverload) { LOG_DEBUG("Telemetry: Unhealthy - Critical bell overload detected"); return false; } return true; }