From d1835beff51f86aaf3085ba366778f98f9db3412 Mon Sep 17 00:00:00 2001 From: bonamin Date: Thu, 23 Oct 2025 09:34:44 +0300 Subject: [PATCH] Added Set Log Level Commands --- vesper/src/BellEngine/BellEngine.cpp | 5 +- .../CommandHandler/CommandHandler.cpp | 67 ++++++++++++++++ .../CommandHandler/CommandHandler.hpp | 4 + vesper/src/ConfigManager/ConfigManager.cpp | 79 +++++++++++++++++++ vesper/src/ConfigManager/ConfigManager.hpp | 21 +++++ vesper/src/Player/Player.cpp | 24 ++++-- vesper/vesper.ino | 8 +- 7 files changed, 199 insertions(+), 9 deletions(-) diff --git a/vesper/src/BellEngine/BellEngine.cpp b/vesper/src/BellEngine/BellEngine.cpp index df2c10d..ca37c4a 100644 --- a/vesper/src/BellEngine/BellEngine.cpp +++ b/vesper/src/BellEngine/BellEngine.cpp @@ -259,7 +259,7 @@ void BellEngine::activateNote(uint16_t note) { if (bellConfig == 0) continue; // Convert 1-indexed config to 0-indexed bellIndex - uint8_t bellIndex = bellConfig-1; + uint8_t bellIndex = bellConfig - 1; // Additional safety check to prevent underflow crashes if (bellIndex >= 255) { @@ -311,7 +311,8 @@ void BellEngine::activateNote(uint16_t note) { LOG_VERBOSE("🔥🔥 BATCH FIRED %d bells SIMULTANEOUSLY!", bellDurations.size()); // 🔔 NOTIFY WEBSOCKET CLIENTS OF BELL DINGS! - notifyBellsFired(firedBellIndices); + // * deactivated currently, since unstable and causes performance issues * + // notifyBellsFired(firedBellIndices); } } diff --git a/vesper/src/Communication/CommandHandler/CommandHandler.cpp b/vesper/src/Communication/CommandHandler/CommandHandler.cpp index 4d247c9..5920223 100644 --- a/vesper/src/Communication/CommandHandler/CommandHandler.cpp +++ b/vesper/src/Communication/CommandHandler/CommandHandler.cpp @@ -1005,9 +1005,76 @@ void CommandHandler::handleSystemCommand(JsonVariant contents, const MessageCont handleGetFirmwareStatusCommand(context); } else if (action == "set_network_config") { handleSetNetworkConfigCommand(contents, context); + } else if (action == "set_serial_log_level") { + handleSetSerialLogLevelCommand(contents, context); + } else if (action == "set_sd_log_level") { + handleSetSdLogLevelCommand(contents, context); } else { LOG_WARNING("Unknown system action: %s", action.c_str()); sendErrorResponse("system", "Unknown action: " + action, context); } } +// ════════════════════════════════════════════════════════════════════════════ +// LOG LEVEL COMMANDS +// ════════════════════════════════════════════════════════════════════════════ + +void CommandHandler::handleSetSerialLogLevelCommand(JsonVariant contents, const MessageContext& context) { + if (!contents.containsKey("level")) { + sendErrorResponse("set_serial_log_level", "Missing level parameter", context); + return; + } + + uint8_t level = contents["level"].as(); + + // Set the level in ConfigManager + if (_configManager.setSerialLogLevel(level)) { + // Apply the level to Logging immediately + Logging::setLevel((Logging::LogLevel)level); + + // Save to SD card + bool saved = _configManager.saveGeneralConfig(); + + if (saved) { + sendSuccessResponse("set_serial_log_level", + "Serial log level set to " + String(level) + " and saved", context); + LOG_INFO("Serial log level updated to %d", level); + } else { + sendErrorResponse("set_serial_log_level", + "Log level set but failed to save to SD card", context); + LOG_ERROR("Failed to save serial log level to SD card"); + } + } else { + sendErrorResponse("set_serial_log_level", + "Invalid log level (must be 0-5)", context); + } +} + +void CommandHandler::handleSetSdLogLevelCommand(JsonVariant contents, const MessageContext& context) { + if (!contents.containsKey("level")) { + sendErrorResponse("set_sd_log_level", "Missing level parameter", context); + return; + } + + uint8_t level = contents["level"].as(); + + // Set the level in ConfigManager + if (_configManager.setSdLogLevel(level)) { + // Save to SD card + bool saved = _configManager.saveGeneralConfig(); + + if (saved) { + sendSuccessResponse("set_sd_log_level", + "SD log level set to " + String(level) + " and saved", context); + LOG_INFO("SD log level updated to %d (not yet implemented)", level); + } else { + sendErrorResponse("set_sd_log_level", + "Log level set but failed to save to SD card", context); + LOG_ERROR("Failed to save SD log level to SD card"); + } + } else { + sendErrorResponse("set_sd_log_level", + "Invalid log level (must be 0-5)", context); + } +} + diff --git a/vesper/src/Communication/CommandHandler/CommandHandler.hpp b/vesper/src/Communication/CommandHandler/CommandHandler.hpp index f844482..b68e5f9 100644 --- a/vesper/src/Communication/CommandHandler/CommandHandler.hpp +++ b/vesper/src/Communication/CommandHandler/CommandHandler.hpp @@ -139,4 +139,8 @@ private: // System Config void handleResetDefaultsCommand(const MessageContext& context); + + // Log Level Commands + void handleSetSerialLogLevelCommand(JsonVariant contents, const MessageContext& context); + void handleSetSdLogLevelCommand(JsonVariant contents, const MessageContext& context); }; diff --git a/vesper/src/ConfigManager/ConfigManager.cpp b/vesper/src/ConfigManager/ConfigManager.cpp index 11fac75..2c029ad 100644 --- a/vesper/src/ConfigManager/ConfigManager.cpp +++ b/vesper/src/ConfigManager/ConfigManager.cpp @@ -99,6 +99,11 @@ bool ConfigManager::begin() { LOG_INFO("ConfigManager: Creating default clock state file"); saveClockState(); } + + if (!loadGeneralConfig()) { + LOG_INFO("ConfigManager: Creating default general config file"); + saveGeneralConfig(); + } LOG_INFO("ConfigManager: Initialization complete - UID: %s, Hostname: %s", deviceConfig.deviceUID.c_str(), networkConfig.hostname.c_str()); @@ -1000,3 +1005,77 @@ String ConfigManager::getAllSettingsAsJson() const { serializeJson(doc, output); return output; } + +// ═══════════════════════════════════════════════════════════════════════════════ +// GENERAL CONFIGURATION - LOG LEVELS +// ═══════════════════════════════════════════════════════════════════════════════ + +bool ConfigManager::setSerialLogLevel(uint8_t level) { + if (level > 5) { // Max level is VERBOSE (5) + LOG_WARNING("ConfigManager: Invalid serial log level %d, valid range is 0-5", level); + return false; + } + generalConfig.serialLogLevel = level; + LOG_INFO("ConfigManager: Serial log level set to %d", level); + return true; +} + +bool ConfigManager::setSdLogLevel(uint8_t level) { + if (level > 5) { // Max level is VERBOSE (5) + LOG_WARNING("ConfigManager: Invalid SD log level %d, valid range is 0-5", level); + return false; + } + generalConfig.sdLogLevel = level; + LOG_INFO("ConfigManager: SD log level set to %d", level); + return true; +} + +bool ConfigManager::loadGeneralConfig() { + if (!ensureSDCard()) return false; + + File file = SD.open("/settings/generalConfig.json", FILE_READ); + if (!file) { + LOG_WARNING("ConfigManager: General config file not found - using defaults"); + return false; + } + + StaticJsonDocument<256> doc; + DeserializationError error = deserializeJson(doc, file); + file.close(); + + if (error) { + LOG_ERROR("ConfigManager: Failed to parse general config from SD: %s", error.c_str()); + return false; + } + + if (doc.containsKey("serialLogLevel")) { + generalConfig.serialLogLevel = doc["serialLogLevel"].as(); + } + if (doc.containsKey("sdLogLevel")) { + generalConfig.sdLogLevel = doc["sdLogLevel"].as(); + } + + LOG_INFO("ConfigManager: General config loaded - Serial log level: %d, SD log level: %d", + generalConfig.serialLogLevel, generalConfig.sdLogLevel); + return true; +} + +bool ConfigManager::saveGeneralConfig() { + if (!ensureSDCard()) return false; + + StaticJsonDocument<256> doc; + doc["serialLogLevel"] = generalConfig.serialLogLevel; + doc["sdLogLevel"] = generalConfig.sdLogLevel; + + char buffer[256]; + size_t len = serializeJson(doc, buffer, sizeof(buffer)); + + if (len == 0 || len >= sizeof(buffer)) { + LOG_ERROR("ConfigManager: Failed to serialize general config JSON"); + return false; + } + + saveFileToSD("/settings", "generalConfig.json", buffer); + LOG_INFO("ConfigManager: General config saved"); + return true; +} diff --git a/vesper/src/ConfigManager/ConfigManager.hpp b/vesper/src/ConfigManager/ConfigManager.hpp index cf0dc86..0adba47 100644 --- a/vesper/src/ConfigManager/ConfigManager.hpp +++ b/vesper/src/ConfigManager/ConfigManager.hpp @@ -203,6 +203,18 @@ public: String nighttimeSilenceOffTime = "07:00"; // 🌙 End of nighttime silence }; + + /** + * @struct General Config + * @brief General configuration (loaded from SD) + * + * All clock settings are loaded from SD card at startup. + */ + struct GeneralConfig { + uint8_t serialLogLevel = 0; + uint8_t sdLogLevel = 0; + }; + private: // ═══════════════════════════════════════════════════════════════════════════════ // MEMBER VARIABLES - Clean deployment-ready storage @@ -215,6 +227,7 @@ private: UpdateConfig updateConfig; BellConfig bellConfig; ClockConfig clockConfig; + GeneralConfig generalConfig; bool sdInitialized = false; std::vector updateServers; @@ -286,6 +299,7 @@ public: const UpdateConfig& getUpdateConfig() const { return updateConfig; } const BellConfig& getBellConfig() const { return bellConfig; } const ClockConfig& getClockConfig() const { return clockConfig; } + const GeneralConfig& getGeneralConfig() const { return generalConfig; } // Device identity methods (READ-ONLY - factory set via separate factory firmware) // These values are loaded ONCE at boot from NVS and kept in RAM @@ -376,6 +390,12 @@ public: void setNighttimeSilenceOnTime(const String& time) { clockConfig.nighttimeSilenceOnTime = time; } void setNighttimeSilenceOffTime(const String& time) { clockConfig.nighttimeSilenceOffTime = time; } + // General Config methods + bool setSerialLogLevel(uint8_t level); + bool setSdLogLevel(uint8_t level); + bool loadGeneralConfig(); + bool saveGeneralConfig(); + // Other methods (unchanged) void updateClockAlerts(JsonVariant doc); void updateClockBacklight(JsonVariant doc); @@ -395,6 +415,7 @@ public: String getAPSSID() const { return networkConfig.apSsid; } bool isHealthy() const; + /** * @brief Get all configuration settings as a JSON string * @return JSON string containing all current settings diff --git a/vesper/src/Player/Player.cpp b/vesper/src/Player/Player.cpp index c8ee2d9..b1be311 100644 --- a/vesper/src/Player/Player.cpp +++ b/vesper/src/Player/Player.cpp @@ -368,12 +368,23 @@ void Player::onMelodyLoopCompleted() { // Check if it's time to pause playback bool Player::timeToPause(unsigned long now) { if (isPlaying && continuous_loop) { - uint64_t timeToPause = segmentStartTime + segment_duration; - LOG_DEBUG("PTL: %llu // NOW: %lu", timeToPause, now); - if (now >= timeToPause && !isPaused) { - LOG_DEBUG("(TimerFunction) Segment Duration Reached. Pausing."); - pauseTime = now; - return true; + // Special case: segment_duration = 0 means "one loop only" + if (segment_duration == 0) { + // Only pause after first loop completes (segmentCmpltTime updated) + if (segmentCmpltTime > segmentStartTime && !isPaused) { + LOG_DEBUG("(TimerFunction) One-loop segment completed. Pausing."); + pauseTime = now; + return true; + } + } else { + // Normal duration-based pausing + uint64_t timeToPause = segmentStartTime + segment_duration; + LOG_DEBUG("PTL: %llu // NOW: %lu", timeToPause, now); + if (now >= timeToPause && !isPaused) { + LOG_DEBUG("(TimerFunction) Segment Duration Reached. Pausing."); + pauseTime = now; + return true; + } } } return false; @@ -385,6 +396,7 @@ bool Player::timeToResume(unsigned long now) { uint64_t timeToResume = segmentCmpltTime + pause_duration; if (now >= timeToResume) { LOG_DEBUG("(TimerFunction) Pause Duration Reached. Resuming"); + segmentStartTime = now; // Reset segment start time for next cycle return true; } } diff --git a/vesper/vesper.ino b/vesper/vesper.ino index ebc9e46..699a754 100644 --- a/vesper/vesper.ino +++ b/vesper/vesper.ino @@ -62,7 +62,7 @@ * 👨‍💻 AUTHOR: BellSystems bonamin */ -#define FW_VERSION "1.0" +#define FW_VERSION "1.2" /* @@ -70,6 +70,7 @@ * 📅 VERSION HISTORY: * ═══════════════════════════════════════════════════════════════════════════════ * v0.1 - Vesper Launch Beta + * v1.2 - Added Log Level Configuration via App/MQTT * ═══════════════════════════════════════════════════════════════════════════════ */ @@ -201,6 +202,11 @@ void setup() // Initialize Configuration (loads factory identity from NVS + user settings from SD) configManager.begin(); + // Apply log level from config (loaded from SD) + uint8_t logLevel = configManager.getGeneralConfig().serialLogLevel; + Logging::setLevel((Logging::LogLevel)logLevel); + LOG_INFO("Log level set to %d from configuration", logLevel); + inputManager.begin(); inputManager.setFactoryResetLongPressCallback(handleFactoryReset);