283 lines
9.1 KiB
C++
283 lines
9.1 KiB
C++
#include "Telemetry.hpp"
|
|
#include <ArduinoJson.h>
|
|
|
|
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<uint8_t> criticalBells;
|
|
std::vector<uint16_t> criticalLoads;
|
|
std::vector<uint8_t> warningBells;
|
|
std::vector<uint16_t> 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<Telemetry*>(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<uint32_t>();
|
|
}
|
|
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;
|
|
}
|