feat: Migrate firmware to PlatformIO (ESP32-S3, vesper-v1 env)
Replaces Arduino IDE with PlatformIO as the build system. Entry point moved from vesper.ino to src/main.cpp. All library dependencies are now declared in platformio.ini and downloaded automatically via lib_deps. Board: Kincony KC868-A6 (ESP32-S3, 4MB flash) → env:vesper-v1 Future variants Vesper+ and Vesper Pro are pre-configured but commented out. Compatibility fixes applied for this framework version: - Removed ETH.h dependency (Ethernet was disabled in v138) - Watchdog init updated to IDF v4 API (esp_task_wdt_init signature) - ETH.linkUp() check removed from OTAManager Also adds .pio/ to .gitignore and commits the manufacturing plan docs. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
536
vesper/src/main.cpp
Normal file
536
vesper/src/main.cpp
Normal file
@@ -0,0 +1,536 @@
|
||||
/*
|
||||
|
||||
█████ █████ ██████████ █████████ ███████████ ██████████ ███████████
|
||||
▒▒███ ▒▒███ ▒▒███▒▒▒▒▒█ ███▒▒▒▒▒███▒▒███▒▒▒▒▒███▒▒███▒▒▒▒▒█▒▒███▒▒▒▒▒███
|
||||
▒███ ▒███ ▒███ █ ▒ ▒███ ▒▒▒ ▒███ ▒███ ▒███ █ ▒ ▒███ ▒███
|
||||
▒███ ▒███ ▒██████ ▒▒█████████ ▒██████████ ▒██████ ▒██████████
|
||||
▒▒███ ███ ▒███▒▒█ ▒▒▒▒▒▒▒▒███ ▒███▒▒▒▒▒▒ ▒███▒▒█ ▒███▒▒▒▒▒███
|
||||
▒▒▒█████▒ ▒███ ▒ █ ███ ▒███ ▒███ ▒███ ▒ █ ▒███ ▒███
|
||||
▒▒███ ██████████▒▒█████████ █████ ██████████ █████ █████
|
||||
▒▒▒ ▒▒▒▒▒▒▒▒▒▒ ▒▒▒▒▒▒▒▒▒ ▒▒▒▒▒ ▒▒▒▒▒▒▒▒▒▒ ▒▒▒▒▒ ▒▒▒▒▒
|
||||
|
||||
* ═══════════════════════════════════════════════════════════════════════════════════
|
||||
* 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 + Permanent AP Mode)
|
||||
* ✅ Multi-protocol communication (MQTT + WebSocket + HTTP REST API)
|
||||
* ✅ Web settings interface for network mode switching
|
||||
* ✅ 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 AsyncMqttClient on Core 0)
|
||||
* • WebSocket (Real-time web interface)
|
||||
* • HTTP REST API (Command execution via HTTP)
|
||||
* • 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 "154"
|
||||
|
||||
|
||||
/*
|
||||
* ═══════════════════════════════════════════════════════════════════════════════
|
||||
* 📅 VERSION HISTORY:
|
||||
* NOTE: Versions are now stored as integers (v1.3 = 130)
|
||||
* ═══════════════════════════════════════════════════════════════════════════════
|
||||
* v0.1 (100) - Vesper Launch Beta
|
||||
* v1.2 (120) - Added Log Level Configuration via App/MQTT
|
||||
* v1.3 (130) - Added Telemetry Reports to App, Various Playback Fixes
|
||||
* v137 - Made OTA and MQTT delays Async
|
||||
* v138 - Removed Ethernet, added default WiFi creds (Mikrotik AP) and fixed various Clock issues
|
||||
* v140 - Changed FW Updates to Direct-to-Flash and added manual update functionality with version check
|
||||
* v151 - Fixed Clock Alerts not running properly
|
||||
* v152 - Fix RTC Time Reports, added sync_time_to_LCD functionality
|
||||
* v153 - Fix Infinite Loop Bug and Melody Download crashes.
|
||||
* ═══════════════════════════════════════════════════════════════════════════════
|
||||
*/
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════════════
|
||||
// SYSTEM LIBRARIES - Core ESP32 and Arduino functionality
|
||||
// ═══════════════════════════════════════════════════════════════════════════════════
|
||||
#include <SD.h> // SD card file system operations
|
||||
#include <FS.h> // File system base class
|
||||
#include <ETH.h> // Ethernet connectivity (W5500 support)
|
||||
#include <SPI.h> // SPI communication protocol
|
||||
#include <Arduino.h> // Arduino core framework
|
||||
#include <WiFi.h> // WiFi connectivity management
|
||||
#include <HTTPClient.h> // HTTP client for OTA updates
|
||||
#include <Update.h> // Firmware update utilities
|
||||
#include <Wire.h> // I2C communication protocol
|
||||
#include <esp_task_wdt.h> // Task watchdog timer
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════════════
|
||||
// NETWORKING LIBRARIES - Advanced networking and communication
|
||||
// ═══════════════════════════════════════════════════════════════════════════════════
|
||||
#include <WiFiManager.h> // WiFi configuration portal
|
||||
#include <ESPAsyncWebServer.h> // Async web server for WebSocket support
|
||||
#include <AsyncUDP.h> // UDP for discovery service
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════════════
|
||||
// DATA PROCESSING LIBRARIES - JSON parsing and data structures
|
||||
// ═══════════════════════════════════════════════════════════════════════════════════
|
||||
#include <ArduinoJson.h> // Efficient JSON processing
|
||||
#include <string> // STL string support
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════════════
|
||||
// HARDWARE LIBRARIES - Peripheral device control
|
||||
// ═══════════════════════════════════════════════════════════════════════════════════
|
||||
#include <Adafruit_PCF8574.h> // I2C GPIO expander for relay control
|
||||
#include <RTClib.h> // Real-time clock functionality
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════════════
|
||||
// CUSTOM CLASSES - Include Custom Classes and Functions
|
||||
// ═══════════════════════════════════════════════════════════════════════════════════
|
||||
#include "SDCardMutex/SDCardMutex.hpp" // ⚠️ MUST be included before any SD-using classes
|
||||
#include "ConfigManager/ConfigManager.hpp"
|
||||
#include "FileManager/FileManager.hpp"
|
||||
#include "TimeKeeper/TimeKeeper.hpp"
|
||||
#include "Logging/Logging.hpp"
|
||||
#include "Telemetry/Telemetry.hpp"
|
||||
#include "OTAManager/OTAManager.hpp"
|
||||
#include "Networking/Networking.hpp"
|
||||
#include "Communication/CommunicationRouter/CommunicationRouter.hpp"
|
||||
#include "ClientManager/ClientManager.hpp"
|
||||
#include "Communication/ResponseBuilder/ResponseBuilder.hpp"
|
||||
#include "Player/Player.hpp"
|
||||
#include "BellEngine/BellEngine.hpp"
|
||||
#include "OutputManager/OutputManager.hpp"
|
||||
#include "HealthMonitor/HealthMonitor.hpp"
|
||||
#include "FirmwareValidator/FirmwareValidator.hpp"
|
||||
#include "InputManager/InputManager.hpp"
|
||||
|
||||
#define TAG "Main"
|
||||
|
||||
// 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;
|
||||
TimerHandle_t ntpSyncTimer; // Non-blocking delayed NTP sync timer
|
||||
|
||||
|
||||
|
||||
void handleFactoryReset() {
|
||||
if (configManager.resetAllToDefaults()) {
|
||||
delay(3000);
|
||||
ESP.restart();
|
||||
}
|
||||
}
|
||||
|
||||
// Non-blocking NTP sync timer callback
|
||||
void ntpSyncTimerCallback(TimerHandle_t xTimer) {
|
||||
LOG_DEBUG(TAG, "Network stabilization complete - starting NTP sync");
|
||||
if (!networking.isInAPMode()) {
|
||||
timekeeper.syncTimeWithNTP();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void setup()
|
||||
{
|
||||
// Initialize Serial Communications (for debugging) & I2C Bus (for Hardware Control)
|
||||
Serial.begin(115200);
|
||||
Serial.print("VESPER System Booting UP! - Version ");
|
||||
Serial.println(FW_VERSION);
|
||||
Wire.begin(4,15);
|
||||
auto& hwConfig = configManager.getHardwareConfig();
|
||||
SPI.begin(hwConfig.ethSpiSck, hwConfig.ethSpiMiso, hwConfig.ethSpiMosi);
|
||||
delay(50);
|
||||
|
||||
// 🔒 CRITICAL: Initialize SD Card Mutex BEFORE any SD operations
|
||||
// This prevents concurrent SD access from multiple FreeRTOS tasks
|
||||
if (!SDCardMutex::getInstance().begin()) {
|
||||
Serial.println("❌ FATAL: Failed to initialize SD card mutex!");
|
||||
Serial.println(" System cannot continue safely - entering infinite loop");
|
||||
while(1) { delay(1000); } // Halt system - unsafe to proceed
|
||||
}
|
||||
Serial.println("✅ SD card mutex initialized");
|
||||
|
||||
// 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(TAG, "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)
|
||||
|
||||
// 🔥 MIGRATION: Convert old float-style version to integer format
|
||||
String currentVersion = configManager.getFwVersion();
|
||||
if (currentVersion.indexOf('.') != -1) {
|
||||
// Old format detected (e.g., "1.3"), convert to integer ("130")
|
||||
float versionFloat = currentVersion.toFloat();
|
||||
uint16_t versionInt = (uint16_t)(versionFloat * 100.0f);
|
||||
configManager.setFwVersion(String(versionInt));
|
||||
configManager.saveDeviceConfig();
|
||||
LOG_INFO(TAG, "⚠️ Migrated version format: %s -> %u", currentVersion.c_str(), versionInt);
|
||||
}
|
||||
|
||||
configManager.setFwVersion(FW_VERSION);
|
||||
LOG_INFO(TAG, "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(TAG, "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(TAG, "💀 STARTUP VALIDATION FAILED - SYSTEM HALTED");
|
||||
while(1) { delay(1000); } // Should not reach here
|
||||
}
|
||||
|
||||
LOG_INFO(TAG, "✅ 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);
|
||||
timekeeper.setPlayer(&player); // 🔥 Connect for playback coordination
|
||||
// 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);
|
||||
player.setTimekeeper(&timekeeper); // 🔥 Connect for alert coordination
|
||||
|
||||
// Register Communication with health monitor
|
||||
healthMonitor.setCommunication(&communication);
|
||||
|
||||
// 🔔 CONNECT BELLENGINE TO COMMUNICATION FOR DING NOTIFICATIONS!
|
||||
bellEngine.setCommunicationManager(&communication);
|
||||
|
||||
// Track if AsyncWebServer has been started to prevent duplicates
|
||||
static bool webServerStarted = false;
|
||||
|
||||
// Create NTP sync timer (one-shot, 3 second delay for network stabilization)
|
||||
ntpSyncTimer = xTimerCreate(
|
||||
"NTPSync", // Timer name
|
||||
pdMS_TO_TICKS(3000), // 3 second delay (network stabilization)
|
||||
pdFALSE, // One-shot timer (not auto-reload)
|
||||
NULL, // Timer ID (not used)
|
||||
ntpSyncTimerCallback // Callback function
|
||||
);
|
||||
|
||||
// Set up network callbacks
|
||||
networking.setNetworkCallbacks(
|
||||
[&webServerStarted]() {
|
||||
communication.onNetworkConnected();
|
||||
|
||||
// Schedule non-blocking NTP sync after 3s network stabilization (like MQTT)
|
||||
// Skip NTP sync in AP mode (no internet connection)
|
||||
if (!networking.isInAPMode() && ntpSyncTimer) {
|
||||
LOG_DEBUG(TAG, "Network connected - scheduling NTP sync after 3s stabilization (non-blocking)");
|
||||
xTimerStart(ntpSyncTimer, 0);
|
||||
}
|
||||
|
||||
// Start AsyncWebServer when network becomes available (only once!)
|
||||
if (!webServerStarted && networking.getState() != NetworkState::WIFI_PORTAL_MODE) {
|
||||
LOG_INFO(TAG, "🚀 Starting AsyncWebServer on port 80...");
|
||||
server.begin();
|
||||
LOG_INFO(TAG, "✅ AsyncWebServer started on http://%s", networking.getLocalIP().c_str());
|
||||
webServerStarted = true;
|
||||
}
|
||||
}, // onConnected
|
||||
[]() { communication.onNetworkDisconnected(); } // onDisconnected
|
||||
);
|
||||
|
||||
// If already connected, trigger MQTT connection and setup manually
|
||||
if (networking.isConnected()) {
|
||||
LOG_INFO(TAG, "Network already connected - initializing services");
|
||||
communication.onNetworkConnected();
|
||||
|
||||
// Schedule non-blocking NTP sync after 3s network stabilization (like MQTT)
|
||||
// Skip NTP sync in AP mode (no internet connection)
|
||||
if (!networking.isInAPMode() && ntpSyncTimer) {
|
||||
LOG_DEBUG(TAG, "Network already connected - scheduling NTP sync after 3s stabilization (non-blocking)");
|
||||
xTimerStart(ntpSyncTimer, 0);
|
||||
}
|
||||
|
||||
// 🔥 CRITICAL: Start AsyncWebServer ONLY when network is ready
|
||||
// Do NOT start if WiFiManager portal is active (port 80 conflict!)
|
||||
if (!webServerStarted && networking.getState() != NetworkState::WIFI_PORTAL_MODE) {
|
||||
LOG_INFO(TAG, "🚀 Starting AsyncWebServer on port 80...");
|
||||
server.begin();
|
||||
LOG_INFO(TAG, "✅ AsyncWebServer started on http://%s", networking.getLocalIP().c_str());
|
||||
webServerStarted = true;
|
||||
}
|
||||
} else {
|
||||
LOG_WARNING(TAG, "⚠️ Network not ready - services will start after connection");
|
||||
}
|
||||
|
||||
// Initialize OTA Manager
|
||||
otaManager.begin();
|
||||
otaManager.setFileManager(&fileManager);
|
||||
otaManager.setPlayer(&player); // Set player reference for idle check
|
||||
otaManager.setTimeKeeper(&timekeeper); // Set timekeeper reference for freeze mode
|
||||
otaManager.setTelemetry(&telemetry); // Set telemetry reference for freeze mode
|
||||
|
||||
// 🔥 FIX: OTA check will happen asynchronously via scheduled timer (no blocking delay)
|
||||
// UDP discovery setup can happen immediately without conflicts
|
||||
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(TAG, "🏃 Starting runtime validation - firmware will be tested for %lu seconds",
|
||||
firmwareValidator.getValidationConfig().runtimeTimeoutMs / 1000);
|
||||
firmwareValidator.startRuntimeValidation();
|
||||
} else {
|
||||
LOG_INFO(TAG, "✅ 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;
|
||||
}
|
||||
}
|
||||
|
||||
// 🔥 CRITICAL: Clean up dead WebSocket connections every 2 seconds
|
||||
// This prevents ghost connections from blocking new clients
|
||||
static unsigned long lastWsCleanup = 0;
|
||||
if (millis() - lastWsCleanup > 2000) {
|
||||
ws.cleanupClients();
|
||||
lastWsCleanup = millis();
|
||||
}
|
||||
|
||||
// Process UART command input from external devices (LCD panel, buttons)
|
||||
communication.loop();
|
||||
|
||||
// 🔥 DEBUG: Log every 10 seconds to verify we're still running
|
||||
static unsigned long lastLog = 0;
|
||||
if (millis() - lastLog > 10000) {
|
||||
LOG_DEBUG(TAG, "❤️ Loop alive | Free heap: %d bytes (%.1f KB) | Min free: %d | Largest block: %d",
|
||||
ESP.getFreeHeap(),
|
||||
ESP.getFreeHeap() / 1024.0,
|
||||
ESP.getMinFreeHeap(),
|
||||
ESP.getMaxAllocHeap());
|
||||
lastLog = millis();
|
||||
}
|
||||
|
||||
// Keep the loop responsive but not busy
|
||||
delay(100); // ⏱️ 100ms delay to prevent busy waiting
|
||||
}
|
||||
Reference in New Issue
Block a user