feat: Add per-subsystem log tags to all firmware modules

Refactored logging system to require a TAG as first argument on all
LOG_* macros, enabling per-subsystem log filtering and cleaner output.
Each subsystem now defines its own TAG (e.g. "BellEngine", "Player").
Also overhauled Logging.hpp/cpp with improved level control and output.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-26 17:31:28 +02:00
parent c656835d8e
commit fe6b1d871a
26 changed files with 1334 additions and 1074 deletions

View File

@@ -3,6 +3,8 @@
*/
#include "CommandHandler.hpp"
#define TAG "CommandHandler"
#include "../../ConfigManager/ConfigManager.hpp"
#include "../../OTAManager/OTAManager.hpp"
#include "../../Player/Player.hpp"
@@ -65,7 +67,7 @@ void CommandHandler::processCommand(JsonDocument& command, const MessageContext&
String cmd = command["cmd"];
JsonVariant contents = command["contents"];
LOG_DEBUG("Processing command: %s from %s", cmd.c_str(),
LOG_DEBUG(TAG, "Processing command: %s from %s", cmd.c_str(),
context.source == MessageSource::MQTT ? "MQTT" : "WebSocket");
if (cmd == "ping") {
@@ -85,7 +87,7 @@ void CommandHandler::processCommand(JsonDocument& command, const MessageContext&
} else if (cmd == "system") {
handleSystemCommand(contents, context);
} else {
LOG_WARNING("Unknown command received: %s", cmd.c_str());
LOG_WARNING(TAG, "Unknown command received: %s", cmd.c_str());
sendErrorResponse("unknown_command", "Command not recognized: " + cmd, context);
}
}
@@ -146,7 +148,7 @@ void CommandHandler::handleIdentifyCommand(JsonVariant contents, const MessageCo
// 🛡️ SAFETY CHECK: Ensure ClientManager reference is set
if (!_clientManager) {
LOG_ERROR("ClientManager reference not set in CommandHandler!");
LOG_ERROR(TAG, "ClientManager reference not set in CommandHandler!");
sendErrorResponse("identify", "Internal error: ClientManager not available", context);
return;
}
@@ -168,7 +170,7 @@ void CommandHandler::handleIdentifyCommand(JsonVariant contents, const MessageCo
if (deviceType != ClientManager::DeviceType::UNKNOWN) {
_clientManager->updateClientType(context.clientId, deviceType);
sendSuccessResponse("identify", "Device identified as " + deviceTypeStr, context);
LOG_INFO("Client #%u identified as %s device", context.clientId, deviceTypeStr.c_str());
LOG_INFO(TAG, "Client #%u identified as %s device", context.clientId, deviceTypeStr.c_str());
} else {
sendErrorResponse("identify", "Invalid device_type. Use 'master' or 'secondary'", context);
}
@@ -184,7 +186,7 @@ void CommandHandler::handlePlaybackCommand(JsonVariant contents, const MessageCo
sendErrorResponse("playback", "Playback command failed", context);
}
} else {
LOG_ERROR("Player reference not set");
LOG_ERROR(TAG, "Player reference not set");
sendErrorResponse("playback", "Player not available", context);
}
}
@@ -196,7 +198,7 @@ void CommandHandler::handleFileManagerCommand(JsonVariant contents, const Messag
}
String action = contents["action"];
LOG_DEBUG("Processing file manager action: %s", action.c_str());
LOG_DEBUG(TAG, "Processing file manager action: %s", action.c_str());
if (action == "list_melodies") {
handleListMelodiesCommand(context);
@@ -205,7 +207,7 @@ void CommandHandler::handleFileManagerCommand(JsonVariant contents, const Messag
} else if (action == "delete_melody") {
handleDeleteMelodyCommand(contents, context);
} else {
LOG_WARNING("Unknown file manager action: %s", action.c_str());
LOG_WARNING(TAG, "Unknown file manager action: %s", action.c_str());
sendErrorResponse("file_manager", "Unknown action: " + action, context);
}
}
@@ -217,14 +219,14 @@ void CommandHandler::handleRelaySetupCommand(JsonVariant contents, const Message
}
String action = contents["action"];
LOG_DEBUG("Processing relay setup action: %s", action.c_str());
LOG_DEBUG(TAG, "Processing relay setup action: %s", action.c_str());
if (action == "set_timings") {
handleSetRelayTimersCommand(contents, context);
} else if (action == "set_outputs") {
handleSetRelayOutputsCommand(contents, context);
} else {
LOG_WARNING("Unknown relay setup action: %s", action.c_str());
LOG_WARNING(TAG, "Unknown relay setup action: %s", action.c_str());
sendErrorResponse("relay_setup", "Unknown action: " + action, context);
}
}
@@ -236,7 +238,7 @@ void CommandHandler::handleClockSetupCommand(JsonVariant contents, const Message
}
String action = contents["action"];
LOG_DEBUG("Processing clock setup action: %s", action.c_str());
LOG_DEBUG(TAG, "Processing clock setup action: %s", action.c_str());
if (action == "set_outputs") {
handleSetClockOutputsCommand(contents, context);
@@ -257,7 +259,7 @@ void CommandHandler::handleClockSetupCommand(JsonVariant contents, const Message
} else if (action == "set_enabled") {
handleSetClockEnabledCommand(contents, context);
} else {
LOG_WARNING("Unknown clock setup action: %s", action.c_str());
LOG_WARNING(TAG, "Unknown clock setup action: %s", action.c_str());
sendErrorResponse("clock_setup", "Unknown action: " + action, context);
}
}
@@ -269,7 +271,7 @@ void CommandHandler::handleSystemInfoCommand(JsonVariant contents, const Message
}
String action = contents["action"];
LOG_DEBUG("Processing system info action: %s", action.c_str());
LOG_DEBUG(TAG, "Processing system info action: %s", action.c_str());
if (action == "report_status") {
handleStatusCommand(context);
@@ -286,7 +288,7 @@ void CommandHandler::handleSystemInfoCommand(JsonVariant contents, const Message
} else if (action == "sync_time_to_lcd") {
handleSyncTimeToLcdCommand(context);
} else {
LOG_WARNING("Unknown system info action: %s", action.c_str());
LOG_WARNING(TAG, "Unknown system info action: %s", action.c_str());
sendErrorResponse("system_info", "Unknown action: " + action, context);
}
}
@@ -303,7 +305,7 @@ void CommandHandler::handleListMelodiesCommand(const MessageContext& context) {
DeserializationError error = deserializeJson(doc, fileListJson);
if (error) {
LOG_ERROR("Failed to parse file list JSON: %s", error.c_str());
LOG_ERROR(TAG, "Failed to parse file list JSON: %s", error.c_str());
sendErrorResponse("list_melodies", "Failed to parse file list", context);
return;
}
@@ -362,14 +364,14 @@ void CommandHandler::handleSetRelayTimersCommand(JsonVariant contents, const Mes
bool saved = _configManager.saveBellDurations();
if (saved) {
sendSuccessResponse("set_relay_timers", "Relay timers updated and saved", context);
LOG_INFO("Relay timers updated and saved successfully");
LOG_INFO(TAG, "Relay timers updated and saved successfully");
} else {
sendErrorResponse("set_relay_timers", "Failed to save relay timers to SD card", context);
LOG_ERROR("Failed to save relay timers configuration");
LOG_ERROR(TAG, "Failed to save relay timers configuration");
}
} catch (...) {
sendErrorResponse("set_relay_timers", "Failed to update relay timers", context);
LOG_ERROR("Exception occurred while updating relay timers");
LOG_ERROR(TAG, "Exception occurred while updating relay timers");
}
}
@@ -380,14 +382,14 @@ void CommandHandler::handleSetRelayOutputsCommand(JsonVariant contents, const Me
bool saved = _configManager.saveBellOutputs();
if (saved) {
sendSuccessResponse("set_relay_outputs", "Relay outputs updated and saved", context);
LOG_INFO("Relay outputs updated and saved successfully");
LOG_INFO(TAG, "Relay outputs updated and saved successfully");
} else {
sendErrorResponse("set_relay_outputs", "Failed to save relay outputs to SD card", context);
LOG_ERROR("Failed to save relay outputs configuration");
LOG_ERROR(TAG, "Failed to save relay outputs configuration");
}
} catch (...) {
sendErrorResponse("set_relay_outputs", "Failed to update relay outputs", context);
LOG_ERROR("Exception occurred while updating relay outputs");
LOG_ERROR(TAG, "Exception occurred while updating relay outputs");
}
}
@@ -398,14 +400,14 @@ void CommandHandler::handleSetClockOutputsCommand(JsonVariant contents, const Me
bool saved = _configManager.saveClockConfig();
if (saved) {
sendSuccessResponse("set_clock_outputs", "Clock outputs updated and saved", context);
LOG_INFO("Clock outputs updated and saved successfully");
LOG_INFO(TAG, "Clock outputs updated and saved successfully");
} else {
sendErrorResponse("set_clock_outputs", "Failed to save clock outputs to SD card", context);
LOG_ERROR("Failed to save clock outputs configuration");
LOG_ERROR(TAG, "Failed to save clock outputs configuration");
}
} catch (...) {
sendErrorResponse("set_clock_outputs", "Failed to update clock outputs", context);
LOG_ERROR("Exception occurred while updating clock outputs");
LOG_ERROR(TAG, "Exception occurred while updating clock outputs");
}
}
@@ -416,14 +418,14 @@ void CommandHandler::handleSetClockTimingsCommand(JsonVariant contents, const Me
bool saved = _configManager.saveClockConfig();
if (saved) {
sendSuccessResponse("set_clock_timings", "Clock timings updated and saved", context);
LOG_INFO("Clock timings updated and saved successfully");
LOG_INFO(TAG, "Clock timings updated and saved successfully");
} else {
sendErrorResponse("set_clock_timings", "Failed to save clock timings to SD card", context);
LOG_ERROR("Failed to save clock timings configuration");
LOG_ERROR(TAG, "Failed to save clock timings configuration");
}
} catch (...) {
sendErrorResponse("set_clock_timings", "Failed to update clock timings", context);
LOG_ERROR("Exception occurred while updating clock timings");
LOG_ERROR(TAG, "Exception occurred while updating clock timings");
}
}
@@ -434,14 +436,14 @@ void CommandHandler::handleSetClockAlertsCommand(JsonVariant contents, const Mes
bool saved = _configManager.saveClockConfig();
if (saved) {
sendSuccessResponse("set_clock_alerts", "Clock alerts updated and saved", context);
LOG_INFO("Clock alerts updated and saved successfully");
LOG_INFO(TAG, "Clock alerts updated and saved successfully");
} else {
sendErrorResponse("set_clock_alerts", "Failed to save clock alerts to SD card", context);
LOG_ERROR("Failed to save clock alerts configuration");
LOG_ERROR(TAG, "Failed to save clock alerts configuration");
}
} catch (...) {
sendErrorResponse("set_clock_alerts", "Failed to update clock alerts", context);
LOG_ERROR("Exception occurred while updating clock alerts");
LOG_ERROR(TAG, "Exception occurred while updating clock alerts");
}
}
@@ -452,14 +454,14 @@ void CommandHandler::handleSetClockBacklightCommand(JsonVariant contents, const
bool saved = _configManager.saveClockConfig();
if (saved) {
sendSuccessResponse("set_clock_backlight", "Clock backlight updated and saved", context);
LOG_INFO("Clock backlight updated and saved successfully");
LOG_INFO(TAG, "Clock backlight updated and saved successfully");
} else {
sendErrorResponse("set_clock_backlight", "Failed to save clock backlight to SD card", context);
LOG_ERROR("Failed to save clock backlight configuration");
LOG_ERROR(TAG, "Failed to save clock backlight configuration");
}
} catch (...) {
sendErrorResponse("set_clock_backlight", "Failed to update clock backlight", context);
LOG_ERROR("Exception occurred while updating clock backlight");
LOG_ERROR(TAG, "Exception occurred while updating clock backlight");
}
}
@@ -470,14 +472,14 @@ void CommandHandler::handleSetClockSilenceCommand(JsonVariant contents, const Me
bool saved = _configManager.saveClockConfig();
if (saved) {
sendSuccessResponse("set_clock_silence", "Clock silence periods updated and saved", context);
LOG_INFO("Clock silence periods updated and saved successfully");
LOG_INFO(TAG, "Clock silence periods updated and saved successfully");
} else {
sendErrorResponse("set_clock_silence", "Failed to save clock silence configuration to SD card", context);
LOG_ERROR("Failed to save clock silence configuration");
LOG_ERROR(TAG, "Failed to save clock silence configuration");
}
} catch (...) {
sendErrorResponse("set_clock_silence", "Failed to update clock silence periods", context);
LOG_ERROR("Exception occurred while updating clock silence periods");
LOG_ERROR(TAG, "Exception occurred while updating clock silence periods");
}
}
@@ -514,7 +516,7 @@ void CommandHandler::handleSetRtcTimeCommand(JsonVariant contents, const Message
// Update timezone configuration
_configManager.updateTimeConfig(baseGmtOffset, dstOffset);
LOG_INFO("Timezone updated: %s (GMT%+ld, DST%+ld)",
LOG_INFO(TAG, "Timezone updated: %s (GMT%+ld, DST%+ld)",
timezoneName.c_str(), baseGmtOffset/3600, dstOffset/3600);
// Apply total offset to timestamp
@@ -529,11 +531,11 @@ void CommandHandler::handleSetRtcTimeCommand(JsonVariant contents, const Message
if (verifyTime > 0 && abs((long)verifyTime - (long)localTimestamp) < 5) { // Allow 5 second tolerance
sendSuccessResponse("set_rtc_time",
"RTC time and timezone updated successfully", context);
LOG_INFO("RTC time set with timezone: UTC %lu + %ld = local %lu",
LOG_INFO(TAG, "RTC time set with timezone: UTC %lu + %ld = local %lu",
timestamp, totalOffset, localTimestamp);
} else {
sendErrorResponse("set_rtc_time", "Failed to verify RTC time was set correctly", context);
LOG_ERROR("RTC time verification failed - expected: %lu, got: %lu", localTimestamp, verifyTime);
LOG_ERROR(TAG, "RTC time verification failed - expected: %lu, got: %lu", localTimestamp, verifyTime);
}
} else {
// Legacy method: Use device's existing timezone config
@@ -543,10 +545,10 @@ void CommandHandler::handleSetRtcTimeCommand(JsonVariant contents, const Message
unsigned long verifyTime = _timeKeeper->getTime();
if (verifyTime > 0 && abs((long)verifyTime - (long)timestamp) < 5) { // Allow 5 second tolerance
sendSuccessResponse("set_rtc_time", "RTC time updated successfully", context);
LOG_INFO("RTC time set using device timezone config: %lu", timestamp);
LOG_INFO(TAG, "RTC time set using device timezone config: %lu", timestamp);
} else {
sendErrorResponse("set_rtc_time", "Failed to verify RTC time was set correctly", context);
LOG_ERROR("RTC time verification failed - expected: %lu, got: %lu", timestamp, verifyTime);
LOG_ERROR(TAG, "RTC time verification failed - expected: %lu, got: %lu", timestamp, verifyTime);
}
}
}
@@ -585,11 +587,11 @@ void CommandHandler::handleSetPhysicalClockTimeCommand(JsonVariant contents, con
if (saved) {
sendSuccessResponse("set_physical_clock_time", "Physical clock time updated and saved successfully", context);
LOG_INFO("Physical clock time set to %02d:%02d (12h: %02d:%02d) and saved to SD",
LOG_INFO(TAG, "Physical clock time set to %02d:%02d (12h: %02d:%02d) and saved to SD",
hour, minute, clockHour, minute);
} else {
sendErrorResponse("set_physical_clock_time", "Physical clock time updated but failed to save to SD card", context);
LOG_ERROR("Physical clock time set to %02d:%02d but failed to save to SD", hour, minute);
LOG_ERROR(TAG, "Physical clock time set to %02d:%02d but failed to save to SD", hour, minute);
}
}
@@ -601,12 +603,12 @@ void CommandHandler::handlePauseClockUpdatesCommand(JsonVariant contents, const
if (contents["action"] == "pause_clock_updates") {
_timeKeeper->pauseClockUpdates();
sendSuccessResponse("pause_clock_updates", "Clock updates paused", context);
LOG_DEBUG("Clock updates paused");
LOG_DEBUG(TAG, "Clock updates paused");
return;
} else if (contents["action"] == "resume_clock_updates") {
_timeKeeper->resumeClockUpdates();
sendSuccessResponse("resume_clock_updates", "Clock updates resumed", context);
LOG_DEBUG("Clock updates resumed");
LOG_DEBUG(TAG, "Clock updates resumed");
return;
}
}
@@ -626,14 +628,14 @@ void CommandHandler::handleSetClockEnabledCommand(JsonVariant contents, const Me
if (saved) {
String status = enabled ? "enabled" : "disabled";
sendSuccessResponse("set_clock_enabled", "Clock " + status + " and saved successfully", context);
LOG_INFO("Clock %s via remote command", status.c_str());
LOG_INFO(TAG, "Clock %s via remote command", status.c_str());
} else {
sendErrorResponse("set_clock_enabled", "Clock setting updated but failed to save to SD card", context);
LOG_ERROR("Failed to save clock enabled setting to SD card");
LOG_ERROR(TAG, "Failed to save clock enabled setting to SD card");
}
} catch (...) {
sendErrorResponse("set_clock_enabled", "Failed to update clock enabled setting", context);
LOG_ERROR("Exception occurred while updating clock enabled setting");
LOG_ERROR(TAG, "Exception occurred while updating clock enabled setting");
}
}
@@ -668,14 +670,14 @@ void CommandHandler::handleGetDeviceTimeCommand(const MessageContext& context) {
response["payload"]["local_timestamp"] = millis() / 1000;
response["payload"]["utc_timestamp"] = millis() / 1000;
response["payload"]["rtc_available"] = false;
LOG_WARNING("TimeKeeper reference not set for device time request");
LOG_WARNING(TAG, "TimeKeeper reference not set for device time request");
}
String responseStr;
serializeJson(response, responseStr);
sendResponse(responseStr, context);
LOG_DEBUG("Device time requested");
LOG_DEBUG(TAG, "Device time requested");
}
void CommandHandler::handleGetClockTimeCommand(const MessageContext& context) {
@@ -694,7 +696,7 @@ void CommandHandler::handleGetClockTimeCommand(const MessageContext& context) {
serializeJson(response, responseStr);
sendResponse(responseStr, context);
LOG_DEBUG("Physical clock time requested: %02d:%02d (last sync: %lu)",
LOG_DEBUG(TAG, "Physical clock time requested: %02d:%02d (last sync: %lu)",
_configManager.getPhysicalClockHour(),
_configManager.getPhysicalClockMinute(),
_configManager.getLastSyncTime());
@@ -716,16 +718,16 @@ void CommandHandler::handleCommitFirmwareCommand(const MessageContext& context)
return;
}
LOG_INFO("💾 Manual firmware commit requested via %s",
LOG_INFO(TAG, "💾 Manual firmware commit requested via %s",
context.source == MessageSource::MQTT ? "MQTT" : "WebSocket");
try {
_firmwareValidator->commitFirmware();
sendSuccessResponse("commit_firmware", "Firmware committed successfully", context);
LOG_INFO("✅ Firmware manually committed - system is now stable");
LOG_INFO(TAG, "✅ Firmware manually committed - system is now stable");
} catch (...) {
sendErrorResponse("commit_firmware", "Failed to commit firmware", context);
LOG_ERROR("❌ Failed to commit firmware");
LOG_ERROR(TAG, "❌ Failed to commit firmware");
}
}
@@ -735,18 +737,18 @@ void CommandHandler::handleRollbackFirmwareCommand(const MessageContext& context
return;
}
LOG_WARNING("🔄 Manual firmware rollback requested via %s",
LOG_WARNING(TAG, "🔄 Manual firmware rollback requested via %s",
context.source == MessageSource::MQTT ? "MQTT" : "WebSocket");
try {
_firmwareValidator->rollbackFirmware();
sendSuccessResponse("rollback_firmware", "Firmware rollback initiated - device will reboot", context);
LOG_WARNING("🔄 Firmware rollback initiated - device should reboot shortly");
LOG_WARNING(TAG, "🔄 Firmware rollback initiated - device should reboot shortly");
// Device should reboot automatically, but this response might not be sent
} catch (...) {
sendErrorResponse("rollback_firmware", "Failed to initiate firmware rollback", context);
LOG_ERROR("❌ Failed to initiate firmware rollback");
LOG_ERROR(TAG, "❌ Failed to initiate firmware rollback");
}
}
@@ -820,7 +822,7 @@ void CommandHandler::handleGetFirmwareStatusCommand(const MessageContext& contex
serializeJson(response, responseStr);
sendResponse(responseStr, context);
LOG_DEBUG("Firmware status requested: %s", stateStr.c_str());
LOG_DEBUG(TAG, "Firmware status requested: %s", stateStr.c_str());
}
void CommandHandler::handleNetworkInfoCommand(const MessageContext& context) {
@@ -839,7 +841,7 @@ void CommandHandler::handleNetworkInfoCommand(const MessageContext& context) {
}
void CommandHandler::handleGetFullSettingsCommand(const MessageContext& context) {
LOG_DEBUG("Full settings requested");
LOG_DEBUG(TAG, "Full settings requested");
// Get all settings as JSON string from ConfigManager
String settingsJson = _configManager.getAllSettingsAsJson();
@@ -854,7 +856,7 @@ void CommandHandler::handleGetFullSettingsCommand(const MessageContext& context)
DeserializationError error = deserializeJson(settingsDoc, settingsJson);
if (error) {
LOG_ERROR("Failed to parse settings JSON: %s", error.c_str());
LOG_ERROR(TAG, "Failed to parse settings JSON: %s", error.c_str());
sendErrorResponse("get_full_settings", "Failed to serialize settings", context);
return;
}
@@ -865,7 +867,7 @@ void CommandHandler::handleGetFullSettingsCommand(const MessageContext& context)
serializeJson(response, responseStr);
sendResponse(responseStr, context);
LOG_DEBUG("Full settings sent (%d bytes)", responseStr.length());
LOG_DEBUG(TAG, "Full settings sent (%d bytes)", responseStr.length());
}
void CommandHandler::handleSyncTimeToLcdCommand(const MessageContext& context) {
@@ -880,7 +882,7 @@ void CommandHandler::handleSyncTimeToLcdCommand(const MessageContext& context) {
} else {
// Fallback to millis if TimeKeeper not available
localTimestamp = millis() / 1000;
LOG_WARNING("TimeKeeper not available for LCD time sync");
LOG_WARNING(TAG, "TimeKeeper not available for LCD time sync");
}
// Get timezone offset from ConfigManager (in seconds)
@@ -897,7 +899,7 @@ void CommandHandler::handleSyncTimeToLcdCommand(const MessageContext& context) {
serializeJson(response, responseStr);
sendResponse(responseStr, context);
LOG_DEBUG("LCD time sync: UTC=%lu, offset=%ld", utcTimestamp, totalOffset);
LOG_DEBUG(TAG, "LCD time sync: UTC=%lu, offset=%ld", utcTimestamp, totalOffset);
}
void CommandHandler::handleSetNetworkConfigCommand(JsonVariant contents, const MessageContext& context) {
@@ -933,7 +935,7 @@ void CommandHandler::handleSetNetworkConfigCommand(JsonVariant contents, const M
hostname = newHostname;
configChanged = true;
needsReboot = true;
LOG_INFO("Hostname will be updated to: %s", hostname.c_str());
LOG_INFO(TAG, "Hostname will be updated to: %s", hostname.c_str());
} else {
sendErrorResponse("set_network_config", "Invalid hostname (must be 1-32 characters)", context);
return;
@@ -975,9 +977,9 @@ void CommandHandler::handleSetNetworkConfigCommand(JsonVariant contents, const M
dns2.fromString(contents["dns2"].as<String>());
}
LOG_INFO("Static IP configuration will be applied: %s", ip.toString().c_str());
LOG_INFO(TAG, "Static IP configuration will be applied: %s", ip.toString().c_str());
} else {
LOG_INFO("DHCP mode will be enabled");
LOG_INFO(TAG, "DHCP mode will be enabled");
}
}
@@ -996,10 +998,10 @@ void CommandHandler::handleSetNetworkConfigCommand(JsonVariant contents, const M
responseMsg += ". RESTART DEVICE to apply changes";
}
sendSuccessResponse("set_network_config", responseMsg, context);
LOG_INFO("✅ Network configuration saved to SD card");
LOG_INFO(TAG, "✅ Network configuration saved to SD card");
} else {
sendErrorResponse("set_network_config", "Configuration updated but failed to save to SD card", context);
LOG_ERROR("❌ Failed to save network configuration to SD card");
LOG_ERROR(TAG, "❌ Failed to save network configuration to SD card");
}
} else {
sendSuccessResponse("set_network_config", "No changes detected", context);
@@ -1007,10 +1009,10 @@ void CommandHandler::handleSetNetworkConfigCommand(JsonVariant contents, const M
} catch (const std::exception& e) {
sendErrorResponse("set_network_config", String("Exception: ") + e.what(), context);
LOG_ERROR("Exception in handleSetNetworkConfigCommand: %s", e.what());
LOG_ERROR(TAG, "Exception in handleSetNetworkConfigCommand: %s", e.what());
} catch (...) {
sendErrorResponse("set_network_config", "Unknown error occurred", context);
LOG_ERROR("Unknown exception in handleSetNetworkConfigCommand");
LOG_ERROR(TAG, "Unknown exception in handleSetNetworkConfigCommand");
}
}
@@ -1020,7 +1022,7 @@ void CommandHandler::handleSetNetworkConfigCommand(JsonVariant contents, const M
void CommandHandler::handleResetDefaultsCommand(const MessageContext& context) {
LOG_WARNING("⚠️ Factory reset requested. Proceeding...");
LOG_WARNING(TAG, "⚠️ Factory reset requested. Proceeding...");
try {
// Reset all configurations to defaults
@@ -1028,14 +1030,14 @@ void CommandHandler::handleResetDefaultsCommand(const MessageContext& context) {
if (resetComplete) {
sendSuccessResponse("reset_defaults", "Reset to Defaults completed. Device will Restart to apply changes.", context);
LOG_WARNING("✅ Factory reset completed and all configurations saved to SD card");
LOG_WARNING(TAG, "✅ Factory reset completed and all configurations saved to SD card");
} else {
sendErrorResponse("reset_defaults", "Reset to Defaults applied but failed to save some configurations to SD card", context);
LOG_ERROR("❌ Reset to Defaults applied but failed to save some configurations to SD card");
LOG_ERROR(TAG, "❌ Reset to Defaults applied but failed to save some configurations to SD card");
}
} catch (...) {
sendErrorResponse("reset_defaults", "Failed to perform Reset to Defaults", context);
LOG_ERROR("❌ Exception occurred during Resetting to Defaults");
LOG_ERROR(TAG, "❌ Exception occurred during Resetting to Defaults");
}
}
@@ -1053,7 +1055,7 @@ void CommandHandler::handleSystemCommand(JsonVariant contents, const MessageCont
}
String action = contents["action"];
LOG_DEBUG("Processing system action: %s", action.c_str());
LOG_DEBUG(TAG, "Processing system action: %s", action.c_str());
if (action == "status") {
handleStatusCommand(context);
@@ -1082,7 +1084,7 @@ void CommandHandler::handleSystemCommand(JsonVariant contents, const MessageCont
} else if (action == "custom_update") {
handleCustomUpdateCommand(contents, context);
} else {
LOG_WARNING("Unknown system action: %s", action.c_str());
LOG_WARNING(TAG, "Unknown system action: %s", action.c_str());
sendErrorResponse("system", "Unknown action: " + action, context);
}
}
@@ -1110,11 +1112,11 @@ void CommandHandler::handleSetSerialLogLevelCommand(JsonVariant contents, const
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);
LOG_INFO(TAG, "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");
LOG_ERROR(TAG, "Failed to save serial log level to SD card");
}
} else {
sendErrorResponse("set_serial_log_level",
@@ -1132,17 +1134,20 @@ void CommandHandler::handleSetSdLogLevelCommand(JsonVariant contents, const Mess
// Set the level in ConfigManager
if (_configManager.setSdLogLevel(level)) {
// Apply immediately
Logging::setSdLevel((Logging::LogLevel)level);
// Save to SD card
bool saved = _configManager.saveGeneralConfig();
if (saved) {
sendSuccessResponse("set_sd_log_level",
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);
LOG_INFO(TAG, "SD log level updated to %d", 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");
LOG_ERROR(TAG, "Failed to save SD log level to SD card");
}
} else {
sendErrorResponse("set_sd_log_level",
@@ -1169,11 +1174,11 @@ void CommandHandler::handleSetMqttLogLevelCommand(JsonVariant contents, const Me
if (saved) {
sendSuccessResponse("set_mqtt_log_level",
"MQTT log level set to " + String(level) + " and saved", context);
LOG_INFO("MQTT log level updated to %d", level);
LOG_INFO(TAG, "MQTT log level updated to %d", level);
} else {
sendErrorResponse("set_mqtt_log_level",
"Log level set but failed to save to SD card", context);
LOG_ERROR("Failed to save MQTT log level to SD card");
LOG_ERROR(TAG, "Failed to save MQTT log level to SD card");
}
} else {
sendErrorResponse("set_mqtt_log_level",
@@ -1198,7 +1203,7 @@ void CommandHandler::handleSetMqttEnabledCommand(JsonVariant contents, const Mes
if (saved) {
sendSuccessResponse("set_mqtt_enabled",
String("MQTT ") + (enabled ? "enabled" : "disabled") + " and saved", context);
LOG_INFO("MQTT %s by user command", enabled ? "enabled" : "disabled");
LOG_INFO(TAG, "MQTT %s by user command", enabled ? "enabled" : "disabled");
// If disabling, disconnect MQTT immediately
// If enabling, trigger connection attempt
@@ -1209,12 +1214,12 @@ void CommandHandler::handleSetMqttEnabledCommand(JsonVariant contents, const Mes
_communicationRouter->getMQTTClient().connect();
}
} else {
LOG_WARNING("CommunicationRouter reference not set - cannot control MQTT");
LOG_WARNING(TAG, "CommunicationRouter reference not set - cannot control MQTT");
}
} else {
sendErrorResponse("set_mqtt_enabled",
"MQTT state changed but failed to save to SD card", context);
LOG_ERROR("Failed to save MQTT enabled state to SD card");
LOG_ERROR(TAG, "Failed to save MQTT enabled state to SD card");
}
}
@@ -1223,7 +1228,7 @@ void CommandHandler::handleSetMqttEnabledCommand(JsonVariant contents, const Mes
// ════════════════════════════════════════════════════════════════════════════
void CommandHandler::handleRestartCommand(const MessageContext& context) {
LOG_WARNING("🔄 Device restart requested via command");
LOG_WARNING(TAG, "🔄 Device restart requested via command");
sendSuccessResponse("restart", "Device will restart in 2 seconds", context);
// Small delay to ensure response is sent
@@ -1238,12 +1243,12 @@ void CommandHandler::handleRestartCommand(const MessageContext& context) {
// ════════════════════════════════════════════════════════════════════════════
void CommandHandler::handleForceUpdateCommand(JsonVariant contents, const MessageContext& context) {
LOG_WARNING("🔄 Force OTA update requested via command");
LOG_WARNING(TAG, "🔄 Force OTA update requested via command");
// Check if player is active
if (_player && _player->isCurrentlyPlaying()) {
sendErrorResponse("force_update", "Cannot update while playback is active", context);
LOG_WARNING("Force update rejected - player is active");
LOG_WARNING(TAG, "Force update rejected - player is active");
return;
}
@@ -1264,7 +1269,7 @@ void CommandHandler::handleForceUpdateCommand(JsonVariant contents, const Messag
// Note: If update succeeds, device will reboot and this won't be reached
if (!result) {
LOG_ERROR("Force update failed");
LOG_ERROR(TAG, "Force update failed");
// Error response may not be received if we already restarted
}
}
@@ -1274,7 +1279,7 @@ void CommandHandler::handleForceUpdateCommand(JsonVariant contents, const Messag
// ════════════════════════════════════════════════════════════════════════════
void CommandHandler::handleCustomUpdateCommand(JsonVariant contents, const MessageContext& context) {
LOG_WARNING("🔥 Custom OTA update requested via command");
LOG_WARNING(TAG, "🔥 Custom OTA update requested via command");
// Validate required parameters
if (!contents.containsKey("firmware_url")) {
@@ -1295,11 +1300,11 @@ void CommandHandler::handleCustomUpdateCommand(JsonVariant contents, const Messa
// Check if player is active
if (_player && _player->isCurrentlyPlaying()) {
sendErrorResponse("custom_update", "Cannot update while playback is active", context);
LOG_WARNING("Custom update rejected - player is active");
LOG_WARNING(TAG, "Custom update rejected - player is active");
return;
}
LOG_INFO("Custom update: URL=%s, Checksum=%s, Size=%u, Version=%u",
LOG_INFO(TAG, "Custom update: URL=%s, Checksum=%s, Size=%u, Version=%u",
firmwareUrl.c_str(),
checksum.isEmpty() ? "none" : checksum.c_str(),
fileSize,
@@ -1316,7 +1321,7 @@ void CommandHandler::handleCustomUpdateCommand(JsonVariant contents, const Messa
// Note: If update succeeds, device will reboot and this won't be reached
if (!result) {
LOG_ERROR("Custom update failed");
LOG_ERROR(TAG, "Custom update failed");
// Error response may not be received if we already restarted
}
}

View File

@@ -3,6 +3,8 @@
*/
#include "CommunicationRouter.hpp"
#define TAG "CommRouter"
#include "../../ConfigManager/ConfigManager.hpp"
#include "../../OTAManager/OTAManager.hpp"
#include "../../Networking/Networking.hpp"
@@ -39,11 +41,11 @@ CommunicationRouter::CommunicationRouter(ConfigManager& configManager,
CommunicationRouter::~CommunicationRouter() {}
void CommunicationRouter::begin() {
LOG_INFO("Initializing Communication Router v4.0 (Modular)");
LOG_INFO(TAG, "Initializing Communication Router v4.0 (Modular)");
// 🔥 CRITICAL: Initialize WebSocket FIRST to ensure it's always set up
// Even if MQTT fails, we want WebSocket to work!
LOG_INFO("Setting up WebSocket server...");
LOG_INFO(TAG, "Setting up WebSocket server...");
// Initialize WebSocket server
_wsServer.begin();
@@ -54,11 +56,11 @@ void CommunicationRouter::begin() {
// 🔥 CRITICAL FIX: Attach WebSocket handler to AsyncWebServer
// This MUST happen before any potential failures!
_server.addHandler(&_webSocket);
LOG_INFO("✅ WebSocket handler attached to AsyncWebServer on /ws");
LOG_INFO(TAG, "✅ WebSocket handler attached to AsyncWebServer on /ws");
//Now initialize MQTT client (can fail without breaking WebSocket)
try {
LOG_INFO("Setting up MQTT client...");
LOG_INFO(TAG, "Setting up MQTT client...");
_mqttClient.begin();
_mqttClient.setCallback([this](const String& topic, const String& payload) {
onMqttMessage(topic, payload);
@@ -73,23 +75,36 @@ void CommunicationRouter::begin() {
logTopic
);
// Apply MQTT log level from config
uint8_t mqttLogLevel = _configManager.getMqttLogLevel();
Logging::setMqttLogLevel((Logging::LogLevel)mqttLogLevel);
LOG_INFO("MQTT logging enabled with level %d on topic: %s", mqttLogLevel, logTopic.c_str());
// Apply log levels from config for all three channels
Logging::setSerialLevel((Logging::LogLevel)_configManager.getSerialLogLevel());
Logging::setMqttLevel((Logging::LogLevel)_configManager.getMqttLogLevel());
Logging::setSdLevel((Logging::LogLevel)_configManager.getSdLogLevel());
LOG_INFO(TAG, "Log levels applied — Serial:%d MQTT:%d SD:%d",
_configManager.getSerialLogLevel(),
_configManager.getMqttLogLevel(),
_configManager.getSdLogLevel());
LOG_INFO("✅ MQTT client initialized");
// Silence MQTT-internal subsystems on the MQTT channel to prevent log storms.
// These systems generate logs while sending logs — suppress them over MQTT only.
Logging::setSubsystemMqttLevel("MQTTClient", Logging::NONE);
Logging::setSubsystemMqttLevel("CommRouter", Logging::WARNING);
Logging::setSubsystemMqttLevel("Logger", Logging::NONE);
LOG_INFO(TAG, "✅ MQTT client initialized");
} catch (...) {
LOG_ERROR("❌ MQTT initialization failed, but WebSocket is still available");
LOG_ERROR(TAG, "❌ MQTT initialization failed, but WebSocket is still available");
}
// Wire up SD logging channel (requires FileManager to be set first via setFileManagerReference)
// SD callback is registered lazily in setFileManagerReference once the pointer is available
// 🔥 CRITICAL FIX: Connect ClientManager to CommandHandler
_commandHandler.setClientManagerReference(&_clientManager);
LOG_INFO("ClientManager reference set for CommandHandler");
LOG_INFO(TAG, "ClientManager reference set for CommandHandler");
// 🔥 Set CommunicationRouter reference for MQTT control commands
_commandHandler.setCommunicationRouterReference(this);
LOG_INFO("CommunicationRouter reference set for CommandHandler");
LOG_INFO(TAG, "CommunicationRouter reference set for CommandHandler");
// Setup command handler response callback
_commandHandler.setResponseCallback([this](const String& response, const CommandHandler::MessageContext& context) {
@@ -97,30 +112,30 @@ void CommunicationRouter::begin() {
});
// Initialize HTTP Request Handler
LOG_INFO("Setting up HTTP REST API...");
LOG_INFO(TAG, "Setting up HTTP REST API...");
_httpHandler.begin();
_httpHandler.setCommandHandlerReference(&_commandHandler);
LOG_INFO("✅ HTTP REST API initialized");
LOG_INFO(TAG, "✅ HTTP REST API initialized");
// Initialize Settings Web Server
LOG_INFO("Setting up Settings Web Server...");
LOG_INFO(TAG, "Setting up Settings Web Server...");
_settingsServer.begin();
LOG_INFO("✅ Settings Web Server initialized at /settings");
LOG_INFO(TAG, "✅ Settings Web Server initialized at /settings");
// Initialize UART Command Handler
LOG_INFO("Setting up UART Command Handler...");
LOG_INFO(TAG, "Setting up UART Command Handler...");
_uartHandler.begin();
_uartHandler.setCallback([this](JsonDocument& message) {
onUartMessage(message);
});
LOG_INFO("✅ UART Command Handler initialized (TX: GPIO12, RX: GPIO13)");
LOG_INFO(TAG, "✅ UART Command Handler initialized (TX: GPIO12, RX: GPIO13)");
LOG_INFO("Communication Router initialized with modular architecture");
LOG_INFO(" • MQTT: AsyncMqttClient");
LOG_INFO(" • WebSocket: Multi-client support");
LOG_INFO(" • HTTP REST API: /api endpoints");
LOG_INFO(" • UART: External device control");
LOG_INFO(" • Settings Page: /settings");
LOG_INFO(TAG, "Communication Router initialized with modular architecture");
LOG_INFO(TAG, " • MQTT: AsyncMqttClient");
LOG_INFO(TAG, " • WebSocket: Multi-client support");
LOG_INFO(TAG, " • HTTP REST API: /api endpoints");
LOG_INFO(TAG, " • UART: External device control");
LOG_INFO(TAG, " • Settings Page: /settings");
}
void CommunicationRouter::loop() {
@@ -136,6 +151,14 @@ void CommunicationRouter::setPlayerReference(Player* player) {
void CommunicationRouter::setFileManagerReference(FileManager* fm) {
_fileManager = fm;
_commandHandler.setFileManagerReference(fm);
// Register SD log channel now that FileManager is available
if (fm != nullptr) {
Logging::setSdWriteCallback([fm](const String& line) {
fm->appendLine("/logs/vesper.log", line);
});
LOG_INFO(TAG, "SD log channel registered -> /logs/vesper.log");
}
}
void CommunicationRouter::setTimeKeeperReference(Timekeeper* tk) {
@@ -155,11 +178,11 @@ void CommunicationRouter::setTelemetryReference(Telemetry* telemetry) {
void CommunicationRouter::setupUdpDiscovery() {
uint16_t discoveryPort = _configManager.getNetworkConfig().discoveryPort;
if (_udp.listen(discoveryPort)) {
LOG_INFO("UDP discovery listening on port %u", discoveryPort);
LOG_INFO(TAG, "UDP discovery listening on port %u", discoveryPort);
_udp.onPacket([this](AsyncUDPPacket packet) {
String msg = String((const char*)packet.data(), packet.length());
LOG_DEBUG("UDP from %s:%u -> %s",
LOG_DEBUG(TAG, "UDP from %s:%u -> %s",
packet.remoteIP().toString().c_str(),
packet.remotePort(),
msg.c_str());
@@ -200,7 +223,7 @@ void CommunicationRouter::setupUdpDiscovery() {
packet.remoteIP(), packet.remotePort());
});
} else {
LOG_ERROR("Failed to start UDP discovery.");
LOG_ERROR(TAG, "Failed to start UDP discovery.");
}
}
@@ -219,22 +242,24 @@ size_t CommunicationRouter::getWebSocketClientCount() const {
bool CommunicationRouter::isHealthy() const {
// Check if required references are set
if (!_player || !_fileManager || !_timeKeeper) {
LOG_DEBUG("CommunicationRouter: Unhealthy - Missing references");
LOG_WARNING(TAG, "Unhealthy - missing subsystem references (player=%d fileManager=%d timeKeeper=%d)",
_player != nullptr, _fileManager != nullptr, _timeKeeper != nullptr);
return false;
}
// Check network connectivity first — no point checking connections without a network
if (!_networking.isConnected()) {
LOG_WARNING(TAG, "Unhealthy - no network connection");
return false;
}
// Check if at least one protocol is connected
if (!isMqttConnected() && !hasActiveWebSocketClients()) {
LOG_DEBUG("CommunicationRouter: Unhealthy - No active connections");
LOG_WARNING(TAG, "Unhealthy - no active connections (MQTT=%d, WebSocket=%d)",
isMqttConnected(), hasActiveWebSocketClients());
return false;
}
// Check network connectivity
if (!_networking.isConnected()) {
LOG_DEBUG("CommunicationRouter: Unhealthy - No network connection");
return false;
}
return true;
}
@@ -270,9 +295,9 @@ void CommunicationRouter::broadcastToAllWebSocketClients(const JsonDocument& mes
void CommunicationRouter::publishToMqtt(const String& data) {
if (_mqttClient.isConnected()) {
_mqttClient.publish("data", data, 0, false);
LOG_DEBUG("Published to MQTT: %s", data.c_str());
LOG_DEBUG(TAG, "Published to MQTT: %s", data.c_str());
} else {
LOG_ERROR("MQTT Not Connected! Message Failed: %s", data.c_str());
LOG_ERROR(TAG, "MQTT Not Connected! Message Failed: %s", data.c_str());
}
}
@@ -294,29 +319,29 @@ void CommunicationRouter::sendBellOverloadNotification(const std::vector<uint8_t
overloadMsg["payload"]["severity"] = severity;
broadcastStatus(overloadMsg);
LOG_WARNING("Bell overload notification sent: %d bells, severity: %s",
LOG_WARNING(TAG, "Bell overload notification sent: %d bells, severity: %s",
bellNumbers.size(), severity.c_str());
}
void CommunicationRouter::onNetworkConnected() {
LOG_DEBUG("Network connected - notifying MQTT client");
LOG_DEBUG(TAG, "Network connected - notifying MQTT client");
_mqttClient.onNetworkConnected();
}
void CommunicationRouter::onNetworkDisconnected() {
LOG_DEBUG("Network disconnected - notifying MQTT client");
LOG_DEBUG(TAG, "Network disconnected - notifying MQTT client");
_mqttClient.onNetworkDisconnected();
}
void CommunicationRouter::onMqttMessage(const String& topic, const String& payload) {
LOG_DEBUG("MQTT message received: %s", payload.c_str());
LOG_DEBUG(TAG, "MQTT message received: %s", payload.c_str());
// Parse JSON
StaticJsonDocument<2048> doc;
DeserializationError error = deserializeJson(doc, payload);
if (error) {
LOG_ERROR("Failed to parse MQTT JSON: %s", error.c_str());
LOG_ERROR(TAG, "Failed to parse MQTT JSON: %s", error.c_str());
return;
}
@@ -330,7 +355,7 @@ void CommunicationRouter::onMqttMessage(const String& topic, const String& paylo
void CommunicationRouter::onWebSocketMessage(uint32_t clientId, const JsonDocument& message) {
// Extract command for logging
String cmd = message["cmd"] | "unknown";
LOG_INFO("📨 WebSocket message from client #%u: cmd=%s", clientId, cmd.c_str());
LOG_INFO(TAG, "📨 WebSocket message from client #%u: cmd=%s", clientId, cmd.c_str());
// Create message context for WebSocket with client ID
CommandHandler::MessageContext context(CommandHandler::MessageSource::WEBSOCKET, clientId);
@@ -339,7 +364,7 @@ void CommunicationRouter::onWebSocketMessage(uint32_t clientId, const JsonDocume
JsonDocument& mutableDoc = const_cast<JsonDocument&>(message);
_commandHandler.processCommand(mutableDoc, context);
LOG_DEBUG("WebSocket message from client #%u processed", clientId);
LOG_DEBUG(TAG, "WebSocket message from client #%u processed", clientId);
}
void CommunicationRouter::onUartMessage(JsonDocument& message) {
@@ -360,12 +385,12 @@ void CommunicationRouter::onUartMessage(JsonDocument& message) {
if (!allowed) {
// Silently ignore - do NOT send error response to avoid feedback loop
LOG_DEBUG("UART: Ignoring non-whitelisted command (cmd=%s, action=%s)",
LOG_DEBUG(TAG, "UART: Ignoring non-whitelisted command (cmd=%s, action=%s)",
cmd.c_str(), action.c_str());
return;
}
LOG_INFO("🔌 UART command received: cmd=%s, action=%s", cmd.c_str(), action.c_str());
LOG_INFO(TAG, "🔌 UART command received: cmd=%s, action=%s", cmd.c_str(), action.c_str());
// Create message context for UART
CommandHandler::MessageContext context(CommandHandler::MessageSource::UART);
@@ -373,20 +398,20 @@ void CommunicationRouter::onUartMessage(JsonDocument& message) {
// Forward to command handler
_commandHandler.processCommand(message, context);
LOG_DEBUG("UART message processed");
LOG_DEBUG(TAG, "UART message processed");
}
void CommunicationRouter::sendResponse(const String& response, const CommandHandler::MessageContext& context) {
if (context.source == CommandHandler::MessageSource::MQTT) {
LOG_DEBUG("↗️ Sending response via MQTT: %s", response.c_str());
LOG_DEBUG(TAG, "↗️ Sending response via MQTT: %s", response.c_str());
publishToMqtt(response);
} else if (context.source == CommandHandler::MessageSource::WEBSOCKET) {
LOG_DEBUG("↗️ Sending response to WebSocket client #%u: %s", context.clientId, response.c_str());
LOG_DEBUG(TAG, "↗️ Sending response to WebSocket client #%u: %s", context.clientId, response.c_str());
_wsServer.sendToClient(context.clientId, response);
} else if (context.source == CommandHandler::MessageSource::UART) {
LOG_DEBUG("↗️ Sending response via UART: %s", response.c_str());
LOG_DEBUG(TAG, "↗️ Sending response via UART: %s", response.c_str());
_uartHandler.send(response);
} else {
LOG_ERROR("❌ Unknown message source for response routing!");
LOG_ERROR(TAG, "❌ Unknown message source for response routing!");
}
}

View File

@@ -5,6 +5,8 @@
*/
#include "HTTPRequestHandler.hpp"
#define TAG "HTTPHandler"
#include "../CommandHandler/CommandHandler.hpp"
#include "../../ConfigManager/ConfigManager.hpp"
#include "../../Logging/Logging.hpp"
@@ -20,7 +22,7 @@ HTTPRequestHandler::~HTTPRequestHandler() {
}
void HTTPRequestHandler::begin() {
LOG_INFO("HTTPRequestHandler - Initializing HTTP REST API endpoints");
LOG_INFO(TAG, "HTTPRequestHandler - Initializing HTTP REST API endpoints");
// POST /api/command - Execute any command
_server.on("/api/command", HTTP_POST,
@@ -61,15 +63,15 @@ void HTTPRequestHandler::begin() {
DefaultHeaders::Instance().addHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
DefaultHeaders::Instance().addHeader("Access-Control-Allow-Headers", "Content-Type");
LOG_INFO("HTTPRequestHandler - REST API endpoints registered");
LOG_INFO(" POST /api/command - Execute commands");
LOG_INFO(" GET /api/status - System status");
LOG_INFO(" GET /api/ping - Health check");
LOG_INFO(TAG, "HTTPRequestHandler - REST API endpoints registered");
LOG_INFO(TAG, " POST /api/command - Execute commands");
LOG_INFO(TAG, " GET /api/status - System status");
LOG_INFO(TAG, " GET /api/ping - Health check");
}
void HTTPRequestHandler::setCommandHandlerReference(CommandHandler* handler) {
_commandHandler = handler;
LOG_DEBUG("HTTPRequestHandler - CommandHandler reference set");
LOG_DEBUG(TAG, "HTTPRequestHandler - CommandHandler reference set");
}
bool HTTPRequestHandler::isHealthy() const {
@@ -88,12 +90,12 @@ void HTTPRequestHandler::handleCommandRequest(AsyncWebServerRequest* request, ui
DeserializationError error = deserializeJson(doc, data, len);
if (error) {
LOG_WARNING("HTTPRequestHandler - JSON parse error: %s", error.c_str());
LOG_WARNING(TAG, "HTTPRequestHandler - JSON parse error: %s", error.c_str());
sendErrorResponse(request, 400, "Invalid JSON");
return;
}
LOG_DEBUG("HTTPRequestHandler - Processing command via HTTP");
LOG_DEBUG(TAG, "HTTPRequestHandler - Processing command via HTTP");
// Create message context for HTTP (treat as WebSocket with special ID)
CommandHandler::MessageContext context(CommandHandler::MessageSource::WEBSOCKET, 0xFFFFFFFF);
@@ -129,7 +131,7 @@ void HTTPRequestHandler::handleStatusRequest(AsyncWebServerRequest* request) {
return;
}
LOG_DEBUG("HTTPRequestHandler - Status request via HTTP");
LOG_DEBUG(TAG, "HTTPRequestHandler - Status request via HTTP");
// Create a status command
JsonDocument doc;
@@ -160,7 +162,7 @@ void HTTPRequestHandler::handleStatusRequest(AsyncWebServerRequest* request) {
}
void HTTPRequestHandler::handlePingRequest(AsyncWebServerRequest* request) {
LOG_DEBUG("HTTPRequestHandler - Ping request via HTTP");
LOG_DEBUG(TAG, "HTTPRequestHandler - Ping request via HTTP");
JsonDocument response;
response["status"] = "ok";

View File

@@ -3,6 +3,8 @@
*/
#include "MQTTAsyncClient.hpp"
#define TAG "MQTTClient"
#include "../../ConfigManager/ConfigManager.hpp"
#include "../../Networking/Networking.hpp"
#include "../../Logging/Logging.hpp"
@@ -66,7 +68,7 @@ MQTTAsyncClient::~MQTTAsyncClient() {
}
void MQTTAsyncClient::begin() {
LOG_INFO("Initializing MQTT Async Client");
LOG_INFO(TAG, "Initializing MQTT Async Client");
auto& mqttConfig = _configManager.getMqttConfig();
@@ -76,7 +78,7 @@ void MQTTAsyncClient::begin() {
_dataTopic = "vesper/" + deviceUID + "/data";
_clientId = "vesper-" + deviceUID;
LOG_INFO("MQTT Topics: control=%s, data=%s", _controlTopic.c_str(), _dataTopic.c_str());
LOG_INFO(TAG, "MQTT Topics: control=%s, data=%s", _controlTopic.c_str(), _dataTopic.c_str());
// Setup event handlers
_mqttClient.onConnect([this](bool sessionPresent) {
@@ -110,7 +112,7 @@ void MQTTAsyncClient::begin() {
_mqttClient.setKeepAlive(15);
_mqttClient.setCleanSession(true);
LOG_INFO("✅ MQTT Async Client initialized");
LOG_INFO(TAG, "✅ MQTT Async Client initialized");
}
void MQTTAsyncClient::connect() {
@@ -118,28 +120,28 @@ void MQTTAsyncClient::connect() {
// 🔥 Check if MQTT is enabled
if (!mqttConfig.enabled) {
LOG_DEBUG("MQTT is disabled in configuration - skipping connection");
LOG_DEBUG(TAG, "MQTT is disabled in configuration - skipping connection");
return;
}
if (_mqttClient.connected()) {
LOG_DEBUG("Already connected to MQTT");
LOG_DEBUG(TAG, "Already connected to MQTT");
return;
}
// Track connection attempt
_lastConnectionAttempt = millis();
LOG_INFO("Free heap BEFORE MQTT connect: %d bytes", ESP.getFreeHeap());
LOG_INFO(TAG, "Free heap BEFORE MQTT connect: %d bytes", ESP.getFreeHeap());
_mqttClient.connect();
LOG_INFO("MQTT connect() called - waiting for async connection...");
LOG_INFO(TAG, "MQTT connect() called - waiting for async connection...");
}
void MQTTAsyncClient::disconnect() {
_mqttClient.disconnect();
LOG_INFO("Disconnected from MQTT broker");
LOG_INFO(TAG, "Disconnected from MQTT broker");
}
uint16_t MQTTAsyncClient::publish(const String& topic, const String& payload, int qos, bool retain) {
@@ -155,7 +157,7 @@ uint16_t MQTTAsyncClient::publish(const String& topic, const String& payload, in
uint16_t packetId = _mqttClient.publish(fullTopic.c_str(), qos, retain, payload.c_str());
if (packetId > 0) {
LOG_DEBUG("Published to %s: %s (packetId=%d)", fullTopic.c_str(), payload.c_str(), packetId);
LOG_DEBUG(TAG, "Published to %s: %s (packetId=%d)", fullTopic.c_str(), payload.c_str(), packetId);
}
// REMOVED: Error logging here to prevent infinite recursion with MQTT logs
@@ -175,11 +177,11 @@ void MQTTAsyncClient::onNetworkConnected() {
// 🔥 Only attempt connection if MQTT is enabled
if (!mqttConfig.enabled) {
LOG_DEBUG("Network connected but MQTT is disabled - skipping MQTT connection");
LOG_DEBUG(TAG, "Network connected but MQTT is disabled - skipping MQTT connection");
return;
}
LOG_DEBUG("Network connected - scheduling MQTT connection after 2s stabilization (non-blocking)");
LOG_DEBUG(TAG, "Network connected - scheduling MQTT connection after 2s stabilization (non-blocking)");
// Reset reconnect attempts on fresh network connection
_reconnectAttempts = 0;
@@ -189,14 +191,14 @@ void MQTTAsyncClient::onNetworkConnected() {
if (_networkStabilizationTimer) {
xTimerStart(_networkStabilizationTimer, 0);
} else {
LOG_ERROR("Network stabilization timer not initialized!");
LOG_ERROR(TAG, "Network stabilization timer not initialized!");
// Fallback to immediate connection (better than blocking)
connect();
}
}
void MQTTAsyncClient::onNetworkDisconnected() {
LOG_DEBUG("Network disconnected - MQTT will auto-reconnect when network returns");
LOG_DEBUG(TAG, "Network disconnected - MQTT will auto-reconnect when network returns");
if (_mqttClient.connected()) {
_mqttClient.disconnect(true);
@@ -205,12 +207,12 @@ void MQTTAsyncClient::onNetworkDisconnected() {
void MQTTAsyncClient::subscribe() {
uint16_t packetId = _mqttClient.subscribe(_controlTopic.c_str(), 0);
LOG_INFO("📬 Subscribing to control topic: %s (packetId=%d)", _controlTopic.c_str(), packetId);
LOG_INFO(TAG, "📬 Subscribing to control topic: %s (packetId=%d)", _controlTopic.c_str(), packetId);
}
void MQTTAsyncClient::onMqttConnect(bool sessionPresent) {
LOG_INFO("✅ Connected to MQTT broker (session present: %s)", sessionPresent ? "yes" : "no");
LOG_INFO("🔍 Free heap AFTER MQTT connect: %d bytes", ESP.getFreeHeap());
LOG_INFO(TAG, "✅ Connected to MQTT broker (session present: %s)", sessionPresent ? "yes" : "no");
LOG_INFO(TAG, "🔍 Free heap AFTER MQTT connect: %d bytes", ESP.getFreeHeap());
// Reset reconnection attempts on successful connection
_reconnectAttempts = 0;
@@ -250,14 +252,14 @@ void MQTTAsyncClient::onMqttDisconnect(AsyncMqttClientDisconnectReason reason) {
break;
}
LOG_ERROR("❌ Disconnected from MQTT broker - Reason: %s (%d)", reasonStr, static_cast<int>(reason));
LOG_ERROR(TAG, "❌ Disconnected from MQTT broker - Reason: %s (%d)", reasonStr, static_cast<int>(reason));
// Stop heartbeat timer when disconnected
stopHeartbeat();
// 🔥 Don't attempt reconnection if MQTT is disabled
if (!mqttConfig.enabled) {
LOG_INFO("MQTT is disabled - not attempting reconnection");
LOG_INFO(TAG, "MQTT is disabled - not attempting reconnection");
return;
}
@@ -268,24 +270,24 @@ void MQTTAsyncClient::onMqttDisconnect(AsyncMqttClientDisconnectReason reason) {
// Calculate backoff delay
unsigned long reconnectDelay = getReconnectDelay();
LOG_INFO("Network still connected - scheduling MQTT reconnection #%d in %lu seconds (backoff active)",
LOG_INFO(TAG, "Network still connected - scheduling MQTT reconnection #%d in %lu seconds (backoff active)",
_reconnectAttempts, reconnectDelay / 1000);
// Update timer period with new delay
xTimerChangePeriod(_mqttReconnectTimer, pdMS_TO_TICKS(reconnectDelay), 0);
xTimerStart(_mqttReconnectTimer, 0);
} else {
LOG_INFO("Network is down - waiting for network to reconnect");
LOG_INFO(TAG, "Network is down - waiting for network to reconnect");
}
}
void MQTTAsyncClient::onMqttSubscribe(uint16_t packetId, uint8_t qos) {
LOG_INFO("✅ Subscribed to topic (packetId=%d, QoS=%d)", packetId, qos);
LOG_INFO(TAG, "✅ Subscribed to topic (packetId=%d, QoS=%d)", packetId, qos);
}
void MQTTAsyncClient::onMqttUnsubscribe(uint16_t packetId) {
LOG_DEBUG("Unsubscribed from topic (packetId=%d)", packetId);
LOG_DEBUG(TAG, "Unsubscribed from topic (packetId=%d)", packetId);
}
void MQTTAsyncClient::onMqttMessage(char* topic, char* payload, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total) {
@@ -293,7 +295,7 @@ void MQTTAsyncClient::onMqttMessage(char* topic, char* payload, AsyncMqttClientM
String topicStr = String(topic);
String payloadStr = String(payload).substring(0, len);
LOG_DEBUG("MQTT message received - topic: %s, payload: %s", topicStr.c_str(), payloadStr.c_str());
LOG_DEBUG(TAG, "MQTT message received - topic: %s, payload: %s", topicStr.c_str(), payloadStr.c_str());
// Call user callback
if (_messageCallback) {
@@ -302,16 +304,16 @@ void MQTTAsyncClient::onMqttMessage(char* topic, char* payload, AsyncMqttClientM
}
void MQTTAsyncClient::onMqttPublish(uint16_t packetId) {
LOG_DEBUG("MQTT publish acknowledged (packetId=%d)", packetId);
LOG_DEBUG(TAG, "MQTT publish acknowledged (packetId=%d)", packetId);
}
void MQTTAsyncClient::attemptReconnection() {
// Double-check network is still up
if (_networking.isConnected()) {
LOG_INFO("Attempting MQTT reconnection...");
LOG_INFO(TAG, "Attempting MQTT reconnection...");
connect();
} else {
LOG_WARNING("Network down during reconnect attempt - aborting");
LOG_WARNING(TAG, "Network down during reconnect attempt - aborting");
}
}
@@ -331,7 +333,7 @@ void MQTTAsyncClient::mqttReconnectTimerCallback(TimerHandle_t xTimer) {
void MQTTAsyncClient::startHeartbeat() {
if (_heartbeatTimer) {
LOG_INFO("💓 Starting MQTT heartbeat (every %d seconds)", HEARTBEAT_INTERVAL / 1000);
LOG_INFO(TAG, "💓 Starting MQTT heartbeat (every %d seconds)", HEARTBEAT_INTERVAL / 1000);
// Publish first heartbeat immediately
publishHeartbeat();
@@ -344,13 +346,13 @@ void MQTTAsyncClient::startHeartbeat() {
void MQTTAsyncClient::stopHeartbeat() {
if (_heartbeatTimer) {
xTimerStop(_heartbeatTimer, 0);
LOG_INFO("❤️ Stopped MQTT heartbeat");
LOG_INFO(TAG, "❤️ Stopped MQTT heartbeat");
}
}
void MQTTAsyncClient::publishHeartbeat() {
if (!_mqttClient.connected()) {
LOG_WARNING("⚠️ Cannot publish heartbeat - MQTT not connected");
LOG_WARNING(TAG, "⚠️ Cannot publish heartbeat - MQTT not connected");
return;
}
@@ -397,10 +399,10 @@ void MQTTAsyncClient::publishHeartbeat() {
uint16_t packetId = _mqttClient.publish(heartbeatTopic.c_str(), 1, true, heartbeatMessage.c_str());
if (packetId > 0) {
LOG_DEBUG("💓 Published heartbeat (retained) - IP: %s, Uptime: %lums",
LOG_DEBUG(TAG, "💓 Published heartbeat (retained) - IP: %s, Uptime: %lums",
_networking.getLocalIP().c_str(), uptimeMs);
} else {
LOG_ERROR("❌ Failed to publish heartbeat");
LOG_ERROR(TAG, "❌ Failed to publish heartbeat");
}
}
@@ -415,7 +417,7 @@ void MQTTAsyncClient::heartbeatTimerCallback(TimerHandle_t xTimer) {
// ═══════════════════════════════════════════════════════════════════════════════════
void MQTTAsyncClient::connectAfterStabilization() {
LOG_DEBUG("Network stabilization complete - connecting to MQTT");
LOG_DEBUG(TAG, "Network stabilization complete - connecting to MQTT");
connect();
}

View File

@@ -1,4 +1,6 @@
#include "ResponseBuilder.hpp"
#define TAG "ResponseBuilder"
#include "../../Logging/Logging.hpp"
// Static member initialization
@@ -72,7 +74,7 @@ String ResponseBuilder::deviceStatus(PlayerStatus playerStatus, uint32_t timeEla
String result;
serializeJson(statusDoc, result);
LOG_DEBUG("Device status response: %s", result.c_str());
LOG_DEBUG(TAG, "Device status response: %s", result.c_str());
return result;
}
@@ -135,7 +137,7 @@ String ResponseBuilder::buildResponse(Status status, const String& type, const S
String result;
serializeJson(_responseDoc, result);
LOG_DEBUG("Response built: %s", result.c_str());
LOG_DEBUG(TAG, "Response built: %s", result.c_str());
return result;
}
@@ -149,7 +151,7 @@ String ResponseBuilder::buildResponse(Status status, const String& type, const J
String result;
serializeJson(_responseDoc, result);
LOG_DEBUG("Response built: %s", result.c_str());
LOG_DEBUG(TAG, "Response built: %s", result.c_str());
return result;
}

View File

@@ -3,6 +3,8 @@
*/
#include "UARTCommandHandler.hpp"
#define TAG "UARTHandler"
#include "../../Logging/Logging.hpp"
UARTCommandHandler::UARTCommandHandler(uint8_t txPin, uint8_t rxPin, uint32_t baudRate)
@@ -24,10 +26,10 @@ UARTCommandHandler::~UARTCommandHandler() {
}
void UARTCommandHandler::begin() {
LOG_INFO("Initializing UART Command Handler");
LOG_INFO(" TX Pin: GPIO%d", _txPin);
LOG_INFO(" RX Pin: GPIO%d", _rxPin);
LOG_INFO(" Baud Rate: %u", _baudRate);
LOG_INFO(TAG, "Initializing UART Command Handler");
LOG_INFO(TAG, " TX Pin: GPIO%d", _txPin);
LOG_INFO(TAG, " RX Pin: GPIO%d", _rxPin);
LOG_INFO(TAG, " Baud Rate: %u", _baudRate);
// Initialize Serial2 with custom pins
_serial.begin(_baudRate, SERIAL_8N1, _rxPin, _txPin);
@@ -38,7 +40,7 @@ void UARTCommandHandler::begin() {
}
_ready = true;
LOG_INFO("UART Command Handler ready");
LOG_INFO(TAG, "UART Command Handler ready");
}
void UARTCommandHandler::loop() {
@@ -65,7 +67,7 @@ void UARTCommandHandler::loop() {
_buffer[_bufferIndex++] = c;
} else {
// Buffer overflow - discard and reset
LOG_ERROR("UART buffer overflow, discarding message");
LOG_ERROR(TAG, "UART buffer overflow, discarding message");
_errorCount++;
resetBuffer();
}
@@ -78,7 +80,7 @@ void UARTCommandHandler::setCallback(MessageCallback callback) {
void UARTCommandHandler::send(const String& response) {
if (!_ready) {
LOG_ERROR("UART not ready, cannot send response");
LOG_ERROR(TAG, "UART not ready, cannot send response");
return;
}
@@ -86,11 +88,11 @@ void UARTCommandHandler::send(const String& response) {
_serial.print('\n'); // Newline delimiter
_serial.flush(); // Ensure data is sent
LOG_DEBUG("UART TX: %s", response.c_str());
LOG_DEBUG(TAG, "UART TX: %s", response.c_str());
}
void UARTCommandHandler::processLine(const char* line) {
LOG_DEBUG("UART RX: %s", line);
LOG_DEBUG(TAG, "UART RX: %s", line);
// Skip empty lines or whitespace-only
if (strlen(line) == 0) return;
@@ -100,7 +102,7 @@ void UARTCommandHandler::processLine(const char* line) {
DeserializationError error = deserializeJson(doc, line);
if (error) {
LOG_ERROR("UART JSON parse error: %s", error.c_str());
LOG_ERROR(TAG, "UART JSON parse error: %s", error.c_str());
_errorCount++;
// Send error response back
@@ -121,7 +123,7 @@ void UARTCommandHandler::processLine(const char* line) {
if (_callback) {
_callback(doc);
} else {
LOG_WARNING("UART message received but no callback set");
LOG_WARNING(TAG, "UART message received but no callback set");
}
}

View File

@@ -3,6 +3,8 @@
*/
#include "WebSocketServer.hpp"
#define TAG "WebSocket"
#include "../../Logging/Logging.hpp"
#include "../ResponseBuilder/ResponseBuilder.hpp"
@@ -23,7 +25,7 @@ WebSocketServer::~WebSocketServer() {
void WebSocketServer::begin() {
_webSocket.onEvent(onEvent);
LOG_INFO("WebSocket server initialized on /ws");
LOG_INFO(TAG, "WebSocket server initialized on /ws");
// 🔥 CRITICAL: This line was missing - attach WebSocket to the AsyncWebServer
// Without this, the server doesn't know about the WebSocket handler!
@@ -40,17 +42,17 @@ void WebSocketServer::sendToClient(uint32_t clientId, const String& message) {
void WebSocketServer::broadcastToAll(const String& message) {
_clientManager.broadcastToAll(message);
LOG_DEBUG("Broadcast to all WebSocket clients: %s", message.c_str());
LOG_DEBUG(TAG, "Broadcast to all WebSocket clients: %s", message.c_str());
}
void WebSocketServer::broadcastToMaster(const String& message) {
_clientManager.sendToMasterClients(message);
LOG_DEBUG("Broadcast to master clients: %s", message.c_str());
LOG_DEBUG(TAG, "Broadcast to master clients: %s", message.c_str());
}
void WebSocketServer::broadcastToSecondary(const String& message) {
_clientManager.sendToSecondaryClients(message);
LOG_DEBUG("Broadcast to secondary clients: %s", message.c_str());
LOG_DEBUG(TAG, "Broadcast to secondary clients: %s", message.c_str());
}
bool WebSocketServer::hasClients() const {
@@ -64,7 +66,7 @@ size_t WebSocketServer::getClientCount() const {
void WebSocketServer::onEvent(AsyncWebSocket* server, AsyncWebSocketClient* client,
AwsEventType type, void* arg, uint8_t* data, size_t len) {
if (!_instance) {
LOG_ERROR("WebSocketServer static instance is NULL - callback ignored!");
LOG_ERROR(TAG, "WebSocketServer static instance is NULL - callback ignored!");
return;
}
@@ -82,7 +84,7 @@ void WebSocketServer::onEvent(AsyncWebSocket* server, AsyncWebSocketClient* clie
break;
case WS_EVT_ERROR:
LOG_ERROR("WebSocket client #%u error(%u): %s",
LOG_ERROR(TAG, "WebSocket client #%u error(%u): %s",
client->id(), *((uint16_t*)arg), (char*)data);
break;
@@ -92,7 +94,7 @@ void WebSocketServer::onEvent(AsyncWebSocket* server, AsyncWebSocketClient* clie
}
void WebSocketServer::onConnect(AsyncWebSocketClient* client) {
LOG_INFO("WebSocket client #%u connected from %s",
LOG_INFO(TAG, "WebSocket client #%u connected from %s",
client->id(), client->remoteIP().toString().c_str());
// Add client to manager (type UNKNOWN until they identify)
@@ -104,7 +106,7 @@ void WebSocketServer::onConnect(AsyncWebSocketClient* client) {
}
void WebSocketServer::onDisconnect(AsyncWebSocketClient* client) {
LOG_INFO("WebSocket client #%u disconnected", client->id());
LOG_INFO(TAG, "WebSocket client #%u disconnected", client->id());
_clientManager.removeClient(client->id());
_clientManager.cleanupDisconnectedClients();
@@ -118,7 +120,7 @@ void WebSocketServer::onData(AsyncWebSocketClient* client, void* arg, uint8_t* d
// Allocate buffer for payload
char* payload = (char*)malloc(len + 1);
if (!payload) {
LOG_ERROR("Failed to allocate memory for WebSocket payload");
LOG_ERROR(TAG, "Failed to allocate memory for WebSocket payload");
String errorResponse = ResponseBuilder::error("memory_error", "Out of memory");
_clientManager.sendToClient(client->id(), errorResponse);
return;
@@ -127,14 +129,14 @@ void WebSocketServer::onData(AsyncWebSocketClient* client, void* arg, uint8_t* d
memcpy(payload, data, len);
payload[len] = '\0';
LOG_DEBUG("WebSocket client #%u sent: %s", client->id(), payload);
LOG_DEBUG(TAG, "WebSocket client #%u sent: %s", client->id(), payload);
// Parse JSON
StaticJsonDocument<2048> doc;
DeserializationError error = deserializeJson(doc, payload);
if (error) {
LOG_ERROR("Failed to parse WebSocket JSON from client #%u: %s", client->id(), error.c_str());
LOG_ERROR(TAG, "Failed to parse WebSocket JSON from client #%u: %s", client->id(), error.c_str());
String errorResponse = ResponseBuilder::error("parse_error", "Invalid JSON");
_clientManager.sendToClient(client->id(), errorResponse);
} else {
@@ -143,15 +145,15 @@ void WebSocketServer::onData(AsyncWebSocketClient* client, void* arg, uint8_t* d
// Call user callback if set
if (_messageCallback) {
LOG_DEBUG("Routing message from client #%u to callback handler", client->id());
LOG_DEBUG(TAG, "Routing message from client #%u to callback handler", client->id());
_messageCallback(client->id(), doc);
} else {
LOG_WARNING("WebSocket message received but no callback handler is set!");
LOG_WARNING(TAG, "WebSocket message received but no callback handler is set!");
}
}
free(payload);
} else {
LOG_WARNING("Received fragmented or non-text WebSocket message from client #%u - ignoring", client->id());
LOG_WARNING(TAG, "Received fragmented or non-text WebSocket message from client #%u - ignoring", client->id());
}
}