/* █████ █████ ██████████ █████████ ███████████ ██████████ ███████████ ▒▒███ ▒▒███ ▒▒███▒▒▒▒▒█ ███▒▒▒▒▒███▒▒███▒▒▒▒▒███▒▒███▒▒▒▒▒█▒▒███▒▒▒▒▒███ ▒███ ▒███ ▒███ █ ▒ ▒███ ▒▒▒ ▒███ ▒███ ▒███ █ ▒ ▒███ ▒███ ▒███ ▒███ ▒██████ ▒▒█████████ ▒██████████ ▒██████ ▒██████████ ▒▒███ ███ ▒███▒▒█ ▒▒▒▒▒▒▒▒███ ▒███▒▒▒▒▒▒ ▒███▒▒█ ▒███▒▒▒▒▒███ ▒▒▒█████▒ ▒███ ▒ █ ███ ▒███ ▒███ ▒███ ▒ █ ▒███ ▒███ ▒▒███ ██████████▒▒█████████ █████ ██████████ █████ █████ ▒▒▒ ▒▒▒▒▒▒▒▒▒▒ ▒▒▒▒▒▒▒▒▒ ▒▒▒▒▒ ▒▒▒▒▒▒▒▒▒▒ ▒▒▒▒▒ ▒▒▒▒▒ * ═══════════════════════════════════════════════════════════════════════════════════ * Project VESPER - BELL AUTOMATION SYSTEM - Main Firmware Entry Point * ═══════════════════════════════════════════════════════════════════════════════════ * * 🔔 DESCRIPTION: * High-precision automated bell control system with multi-protocol communication, * real-time telemetry, OTA updates, and modular hardware abstraction. * * 🏗️ ARCHITECTURE: * Clean modular design with dependency injection and proper separation of concerns. * Each major system is encapsulated in its own class with well-defined interfaces. * * 🎯 KEY FEATURES: * ✅ Microsecond-precision bell timing (BellEngine) * ✅ Multi-hardware support (PCF8574, GPIO, Mock) * ✅ Dual network connectivity (Ethernet + WiFi) * ✅ Dual Communication Support (MQTT + WebSocket) * ✅ Real-time telemetry and load monitoring * ✅ Over-the-air firmware updates * ✅ SD card configuration and file management * ✅ NTP time synchronization * ✅ Comprehensive logging system * * 📡 COMMUNICATION PROTOCOLS: * • MQTT (SSL/TLS via PubSubClient on Core 0) * • WebSocket (Real-time web interface) * • UDP Discovery (Auto-discovery service) * • HTTP/HTTPS (OTA updates) * * 🔧 HARDWARE ABSTRACTION: * OutputManager provides clean interface for different relay systems: * - PCF8574OutputManager: I2C GPIO expander (8 outputs, 6 on Kincony A6 Board) * - GPIOOutputManager: Direct ESP32 pins (for DIY projects) * - MockOutputManager: Testing without hardware * * ⚡ PERFORMANCE: * High-priority FreeRTOS tasks ensure microsecond timing precision. * Core 1 dedicated to BellEngine for maximum performance. * * ═══════════════════════════════════════════════════════════════════════════════════ */ /* * ═══════════════════════════════════════════════════════════════════════════════ * 📋 VERSION CONFIGURATION * ═══════════════════════════════════════════════════════════════════════════════ * 📅 DATE: 2025-10-10 * 👨‍💻 AUTHOR: BellSystems bonamin */ #define FW_VERSION "1.3" /* * ═══════════════════════════════════════════════════════════════════════════════ * 📅 VERSION HISTORY: * ═══════════════════════════════════════════════════════════════════════════════ * v0.1 - Vesper Launch Beta * v1.2 - Added Log Level Configuration via App/MQTT * v1.3 - Added Telemtry Reports to App, Various Playback Fixes * ═══════════════════════════════════════════════════════════════════════════════ */ // ═══════════════════════════════════════════════════════════════════════════════════ // SYSTEM LIBRARIES - Core ESP32 and Arduino functionality // ═══════════════════════════════════════════════════════════════════════════════════ #include // SD card file system operations #include // File system base class #include // Ethernet connectivity (W5500 support) #include // SPI communication protocol #include // Arduino core framework #include // WiFi connectivity management #include // HTTP client for OTA updates #include // Firmware update utilities #include // I2C communication protocol #include // Task watchdog timer // ═══════════════════════════════════════════════════════════════════════════════════ // NETWORKING LIBRARIES - Advanced networking and communication // ═══════════════════════════════════════════════════════════════════════════════════ #include // WiFi configuration portal #include // Async web server for WebSocket support #include // UDP for discovery service // ═══════════════════════════════════════════════════════════════════════════════════ // DATA PROCESSING LIBRARIES - JSON parsing and data structures // ═══════════════════════════════════════════════════════════════════════════════════ #include // Efficient JSON processing #include // STL string support // ═══════════════════════════════════════════════════════════════════════════════════ // HARDWARE LIBRARIES - Peripheral device control // ═══════════════════════════════════════════════════════════════════════════════════ #include // I2C GPIO expander for relay control #include // Real-time clock functionality // ═══════════════════════════════════════════════════════════════════════════════════ // CUSTOM CLASSES - Include Custom Classes and Functions // ═══════════════════════════════════════════════════════════════════════════════════ #include "src/ConfigManager/ConfigManager.hpp" #include "src/FileManager/FileManager.hpp" #include "src/TimeKeeper/TimeKeeper.hpp" #include "src/Logging/Logging.hpp" #include "src/Telemetry/Telemetry.hpp" #include "src/OTAManager/OTAManager.hpp" #include "src/Networking/Networking.hpp" #include "src/Communication/CommunicationRouter/CommunicationRouter.hpp" #include "src/ClientManager/ClientManager.hpp" #include "src/Communication/ResponseBuilder/ResponseBuilder.hpp" #include "src/Player/Player.hpp" #include "src/BellEngine/BellEngine.hpp" #include "src/OutputManager/OutputManager.hpp" #include "src/HealthMonitor/HealthMonitor.hpp" #include "src/FirmwareValidator/FirmwareValidator.hpp" #include "src/InputManager/InputManager.hpp" // Class Constructors ConfigManager configManager; FileManager fileManager(&configManager); Timekeeper timekeeper; Telemetry telemetry; OTAManager otaManager(configManager); Player player; AsyncWebServer server(80); AsyncWebSocket ws("/ws"); AsyncUDP udp; Networking networking(configManager); CommunicationRouter communication(configManager, otaManager, networking, server, ws, udp); HealthMonitor healthMonitor; FirmwareValidator firmwareValidator; InputManager inputManager; // 🔥 OUTPUT SYSTEM - PCF8574/PCF8575 I2C Expanders Configuration // Choose one of the following configurations (with active output counts): // Option 1: Single PCF8574 (6 active outputs out of 8 max) PCF8574OutputManager outputManager(0x24, ChipType::PCF8574, 6); // Option 2: Single PCF8575 (8 active outputs out of 16 max) //PCF8574OutputManager outputManager(0x24, ChipType::PCF8575, 8); // Option 3: PCF8574 + PCF8575 (6 + 8 = 14 total virtual outputs) //PCF8574OutputManager outputManager(0x24, ChipType::PCF8574, 6, 0x21, ChipType::PCF8575, 8); // Option 4: Dual PCF8575 (8 + 8 = 16 total virtual outputs) //PCF8574OutputManager outputManager(0x24, ChipType::PCF8575, 8, 0x21, ChipType::PCF8575, 8); // Virtual Output Mapping Examples: // Option 1: Virtual outputs 0-5 → PCF8574[0x20] pins 0-5 // Option 3: Virtual outputs 0-5 → PCF8574[0x20] pins 0-5, Virtual outputs 6-13 → PCF8575[0x21] pins 0-7 // Option 4: Virtual outputs 0-7 → PCF8575[0x20] pins 0-7, Virtual outputs 8-15 → PCF8575[0x21] pins 0-7 // Legacy backward-compatible (defaults to 8 active outputs): //PCF8574OutputManager outputManager(0x20, ChipType::PCF8574); // 8/8 active outputs BellEngine bellEngine(player, configManager, telemetry, outputManager); // 🔥 THE ULTIMATE BEAST! TaskHandle_t bellEngineHandle = NULL; // Legacy - will be removed TimerHandle_t schedulerTimer; void handleFactoryReset() { if (configManager.resetAllToDefaults()) { delay(3000); ESP.restart(); } } void setup() { // Initialize Serial Communications (for debugging) & I2C Bus (for Hardware Control) Serial.begin(115200); Serial.println("Hello, VESPER System Initialized! - PontikoTest"); Wire.begin(4,15); auto& hwConfig = configManager.getHardwareConfig(); SPI.begin(hwConfig.ethSpiSck, hwConfig.ethSpiMiso, hwConfig.ethSpiMosi); delay(50); // 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); // ═══════════════════════════════════════════════════════════════════════════════ // REMOVED: Manual device identity setters // Device identity (UID, hwType, hwVersion) is now READ-ONLY in production firmware // These values are set by factory firmware and stored permanently in NVS // Production firmware loads them once at boot and keeps them in RAM // ═══════════════════════════════════════════════════════════════════════════════ // Update firmware version (this is the ONLY identity field that can be set) configManager.setFwVersion(FW_VERSION); LOG_INFO("Firmware version: %s", FW_VERSION); // Display device information after configuration is loaded Serial.println("\n=== DEVICE IDENTITY ==="); Serial.printf("Device UID: %s\n", configManager.getDeviceUID().c_str()); Serial.printf("Hardware Type: %s\n", configManager.getHwType().c_str()); Serial.printf("Hardware Version: %s\n", configManager.getHwVersion().c_str()); Serial.printf("Firmware Version: %s\n", configManager.getFwVersion().c_str()); Serial.printf("AP SSID: %s\n", configManager.getAPSSID().c_str()); Serial.println("=====================\n"); // 🔥 CRITICAL: Initialize Health Monitor FIRST (required for firmware validation) healthMonitor.begin(); // Register all subsystems with health monitor for continuous monitoring healthMonitor.setConfigManager(&configManager); healthMonitor.setFileManager(&fileManager); // Initialize Output Manager - 🔥 THE NEW WAY! outputManager.setConfigManager(&configManager); if (!outputManager.initialize()) { LOG_ERROR("Failed to initialize OutputManager!"); // Continue anyway for now } // Register OutputManager with health monitor healthMonitor.setOutputManager(&outputManager); // Initialize BellEngine early for health validation bellEngine.begin(); healthMonitor.setBellEngine(&bellEngine); delay(100); // 🔥 BULLETPROOF: Initialize Firmware Validator and perform startup validation firmwareValidator.begin(&healthMonitor, &configManager); delay(100); // 💀 CRITICAL SAFETY CHECK: Perform startup validation // This MUST happen early before initializing other subsystems if (!firmwareValidator.performStartupValidation()) { // If we reach here, startup validation failed and rollback was triggered // The system should reboot automatically to the previous firmware LOG_ERROR("💀 STARTUP VALIDATION FAILED - SYSTEM HALTED"); while(1) { delay(1000); } // Should not reach here } LOG_INFO("✅ Firmware startup validation PASSED - proceeding with initialization"); // Initialize remaining subsystems... // SD Card initialization is now handled by ConfigManager // Initialize timekeeper with NO clock outputs timekeeper.begin(); // No parameters needed // Connect the timekeeper to dependencies (CLEAN!) timekeeper.setOutputManager(&outputManager); timekeeper.setConfigManager(&configManager); timekeeper.setNetworking(&networking); // Clock outputs now configured via ConfigManager/Communication commands // Register TimeKeeper with health monitor healthMonitor.setTimeKeeper(&timekeeper); // Initialize Telemetry telemetry.setPlayerReference(&player.isPlaying); // 🚑 CRITICAL: Connect force stop callback for overload protection! telemetry.setForceStopCallback([]() { player.forceStop(); }); telemetry.setFileManager(&fileManager); telemetry.begin(); // Register Telemetry with health monitor healthMonitor.setTelemetry(&telemetry); // Initialize Networking (handles everything automatically) networking.begin(); // Register Networking with health monitor healthMonitor.setNetworking(&networking); // Initialize Player player.begin(); // Register Player with health monitor healthMonitor.setPlayer(&player); // BellEngine already initialized and registered earlier for health validation // Initialize Communication Manager (now with PubSubClient MQTT) communication.begin(); communication.setPlayerReference(&player); communication.setFileManagerReference(&fileManager); communication.setTimeKeeperReference(&timekeeper); communication.setFirmwareValidatorReference(&firmwareValidator); communication.setTelemetryReference(&telemetry); player.setDependencies(&communication, &fileManager); player.setBellEngine(&bellEngine); // Connect the beast! player.setTelemetry(&telemetry); // Register Communication with health monitor healthMonitor.setCommunication(&communication); // 🔔 CONNECT BELLENGINE TO COMMUNICATION FOR DING NOTIFICATIONS! bellEngine.setCommunicationManager(&communication); // Set up network callbacks networking.setNetworkCallbacks( []() { communication.onNetworkConnected(); // Start AsyncWebServer when network becomes available if (networking.getState() != NetworkState::WIFI_PORTAL_MODE) { LOG_INFO("🚀 Starting AsyncWebServer on port 80..."); server.begin(); LOG_INFO("✅ AsyncWebServer started on http://%s", networking.getLocalIP().c_str()); } }, // onConnected []() { communication.onNetworkDisconnected(); } // onDisconnected ); // If already connected, trigger MQTT connection manually if (networking.isConnected()) { LOG_INFO("Network already connected - triggering MQTT connection"); communication.onNetworkConnected(); // 🔥 CRITICAL: Start AsyncWebServer ONLY when network is ready // Do NOT start if WiFiManager portal is active (port 80 conflict!) LOG_INFO("🚀 Starting AsyncWebServer on port 80..."); server.begin(); LOG_INFO("✅ AsyncWebServer started and listening on http://%s", networking.getLocalIP().c_str()); } else { LOG_WARNING("⚠️ Network not ready - AsyncWebServer will start after connection"); } delay(500); // Initialize OTA Manager and check for updates otaManager.begin(); otaManager.setFileManager(&fileManager); otaManager.setPlayer(&player); // Set player reference for idle check // 🔥 CRITICAL: Delay OTA check to avoid UDP socket race with MQTT // Both MQTT and OTA HTTP use UDP sockets, must sequence them! delay(2000); LOG_INFO("Starting OTA update check after network stabilization..."); otaManager.checkForUpdates(); communication.setupUdpDiscovery(); // Register OTA Manager with health monitor healthMonitor.setOTAManager(&otaManager); // Note: AsyncWebServer will be started by network callbacks when connection is ready // This avoids port 80 conflicts with WiFiManager's captive portal // 🔥 START RUNTIME VALIDATION: All subsystems are now initialized // Begin extended runtime validation if we're in testing mode if (firmwareValidator.isInTestingMode()) { LOG_INFO("🏃 Starting runtime validation - firmware will be tested for %lu seconds", firmwareValidator.getValidationConfig().runtimeTimeoutMs / 1000); firmwareValidator.startRuntimeValidation(); } else { LOG_INFO("✅ Firmware already validated - normal operation mode"); } // ═══════════════════════════════════════════════════════════════════════════════ // INITIALIZATION COMPLETE // ═══════════════════════════════════════════════════════════════════════════════ // ✅ All automatic task creation handled by individual components: // • BellEngine creates high-priority timing task on Core 1 // • Telemetry creates monitoring task for load tracking // • Player creates duration timer for playback control // • Communication creates MQTT task on Core 0 with PubSubClient // • Networking creates connection management timers // ✅ Bell configuration automatically loaded by ConfigManager // ✅ System ready for MQTT commands, WebSocket connections, and UDP discovery } // ███████████████████████████████████████████████████████████████████████████████████ // █ MAIN LOOP █ // ███████████████████████████████████████████████████████████████████████████████████ // The main loop is intentionally kept minimal in this architecture. All critical // functionality runs in dedicated FreeRTOS tasks for optimal performance and timing. // This ensures the main loop doesn't interfere with precision bell timing. /** * @brief Main execution loop - Minimal by design * * In the new modular architecture, all heavy lifting is done by dedicated tasks: * • BellEngine: High-priority task on Core 1 for microsecond timing * • Telemetry: Background monitoring task for system health * • Player: Timer-based duration control for melody playback * • Communication: MQTT task on Core 0 + Event-driven WebSocket * • Networking: Automatic connection management * * The main loop only handles lightweight operations that don't require * precise timing or could benefit from running on Core 0. * * @note This loop runs on Core 0 and should remain lightweight to avoid * interfering with the precision timing on Core 1. */ void loop() { // Feed watchdog only during firmware validation if (firmwareValidator.isInTestingMode()) { esp_task_wdt_reset(); } else { // Remove task from watchdog if validation completed static bool taskRemoved = false; if (!taskRemoved) { esp_task_wdt_delete(NULL); // Remove current task taskRemoved = true; } } // 🔥 DEBUG: Log every 10 seconds to verify we're still running static unsigned long lastLog = 0; if (millis() - lastLog > 10000) { LOG_DEBUG("❤️ Loop alive, free heap: %d", ESP.getFreeHeap()); lastLog = millis(); } // Keep the loop responsive but not busy delay(100); // ⏱️ 100ms delay to prevent busy waiting }