/
ellersseer
/
MyCheat
Обзор
Документация
Войти
/
ellersseer
/
MyCheat
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
2
CI/CD
Аналитика
Безопасность
master
src/main.cpp
436 строк
14 KB
Dmitry Zhiltsov
fix(core): Исправть рассинхрон
21 фев 2026, 02:07
21 фев 2026, 02:07
a0af0ee
Код
Авторство
О чём код?
/* * My Cheat - DS18B20 Emulator for My Heat controller. * Based on ESP32 and FreeRTOS. * * Author: Dmitriy Zhiltsov * License: MIT */ #include <Arduino.h> #include <WiFi.h> #include <PubSubClient.h> #include <ESPAsyncWebServer.h> #include <OneWireHub.h> #include <DS18B20.h> #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/queue.h" #include "freertos/semphr.h" #include "esp_task_wdt.h" #include "esp_system.h" #include "soc/rtc_wdt.h" #include "soc/timer_group_struct.h" #include "soc/timer_group_reg.h" #include "config.h" #include "settings.h" #include "wifi_manager.h" #include "web_config.h" #include "debug_log.h" #include "app_state.h" #include "onewire_handler.h" #include "mqtt_handler.h" #include "web_server.h" #include "sensor_api.h" #include "ota_handler.h" #include "log_buffer.h" // ============================================ // Global Variable Definitions // ============================================ // 1-Wire OneWireHub hub(ONEWIRE_PIN); DS18B20 *sensors[NUM_SENSORS]; // Shared data (protected by mutex) float temperatures[NUM_SENSORS]; SemaphoreHandle_t tempMutex; // Sensor state management SensorState sensorStates[NUM_SENSORS]; SensorSettings sensorSettings; // Command queue QueueHandle_t sensorCommandQueue; // Statistics SystemStats_t stats = {0}; SemaphoreHandle_t statsMutex; // Network WiFiClient wifiClient; PubSubClient mqttClient(wifiClient); AsyncWebServer webServer(WEB_SERVER_PORT); // Task handles TaskHandle_t oneWireTaskHandle = NULL; TaskHandle_t mqttTaskHandle = NULL; // Flags volatile bool wifiConnected = false; volatile bool mqttConnected = false; // MQTT hot-reload flags volatile bool mqttNeedsReconfigure = false; volatile bool mqttShouldStart = false; // Settings loaded from NVS WifiSettings wifiSettings; MqttSettings mqttSettings; // Reset button tracking volatile unsigned long resetButtonPressStart = 0; volatile bool resetInProgress = false; // Debug log update interval unsigned long lastDebugUpdate = 0; const unsigned long DEBUG_UPDATE_INTERVAL = 5000; // Staleness check and NVS save tracking unsigned long lastStalenessCheck = 0; unsigned long lastNvsSave = 0; volatile bool temperaturesChanged = false; // ============================================ // Forward Declarations // ============================================ void checkResetButton(); // ============================================ // Setup // ============================================ void setup() { // ========================================== // CRITICAL: Release 1-Wire bus IMMEDIATELY // ========================================== // This MUST be the first thing we do! // If ESP32 crashed during 1-Wire communication, the bus might be stuck LOW. // Setting pin to INPUT (Hi-Z) releases it, preventing master lockup. pinMode(ONEWIRE_PIN, INPUT); // Check OTA rollback BEFORE anything else initializes. // If new firmware crashed 3 times, this reverts to the previous partition. checkOtaRollback(); Serial.begin(115200); logCapture.begin(); delay(1000); // Register shutdown handler for clean restarts esp_register_shutdown_handler(shutdownHandler); // Initialize debug logging FIRST - captures reset reason debugLog.begin(); // Watchdog configuration for 1-Wire timing // // Problem: 1-Wire bit-banging requires noInterrupts() which triggers IWDT // Solution: Disable only IWDT, keep Task WDT active for crash protection // // Task WDT: ENABLED - oneWireTask will feed it via esp_task_wdt_reset() in poll() // Interrupt WDT: DISABLED - unavoidable due to noInterrupts() in OneWireHub // RTC WDT: ENABLED - global system protection remains // Disable Interrupt Watchdog via direct register access // This is necessary because 1-Wire bit-banging disables interrupts for extended periods // Only affects timing-critical operations, not overall system stability TIMERG1.wdt_wprotect = TIMG_WDT_WKEY_VALUE; // Unlock TIMERG1.wdt_config0.en = 0; // Disable IWDT TIMERG1.wdt_feed = 1; // Feed to apply TIMERG1.wdt_wprotect = 0; // Lock logCapture.println("[Setup] Interrupt WDT disabled for 1-Wire timing (Task WDT still active)"); logCapture.println("\n\n================================================"); logCapture.println("ESP32 DS18B20 Emulator - FreeRTOS Version"); logCapture.println("================================================\n"); pinMode(STATUS_LED_PIN, OUTPUT); digitalWrite(STATUS_LED_PIN, LOW); // Initialize reset button input pinMode(RESET_BUTTON_PIN, INPUT_PULLUP); // Initialize settings manager and load configuration settingsManager.begin(); settingsManager.loadWifiSettings(wifiSettings); settingsManager.loadMqttSettings(mqttSettings); settingsManager.printSettings(wifiSettings, mqttSettings); // Create synchronization primitives tempMutex = xSemaphoreCreateMutex(); statsMutex = xSemaphoreCreateMutex(); sensorCommandQueue = xQueueCreate(TEMP_COMMAND_QUEUE_SIZE, sizeof(SensorCommand_t)); if (tempMutex == NULL || statsMutex == NULL || sensorCommandQueue == NULL) { logCapture.println("FATAL: Failed to create FreeRTOS primitives!"); while (1) { delay(1000); } } // Load sensor settings from NVS settingsManager.loadSensorSettings(sensorSettings); // Initialize sensor states from saved settings logCapture.print("[Setup] Loaded temperatures from NVS: "); for (int i = 0; i < NUM_SENSORS; i++) { temperatures[i] = sensorSettings.lastTemperatures[i]; sensorStates[i].temperature = sensorSettings.lastTemperatures[i]; sensorStates[i].enabled = sensorSettings.sensorEnabled[i]; sensorStates[i].lastUpdateMs = millis(); sensorStates[i].status = sensorSettings.sensorEnabled[i] ? SensorStatus::ACTIVE : SensorStatus::OFF; logCapture.printf("[%d]=%.1f(%s) ", i, sensorSettings.lastTemperatures[i], sensorSettings.sensorEnabled[i] ? "on" : "off"); } logCapture.println(); // Setup 1-Wire (before tasks start) - respects enabled state setup1Wire(); // Initialize WiFi Manager wifiManager.begin(); // Try to connect to saved WiFi, or start AP if not configured if (wifiSettings.configured && strlen(wifiSettings.ssid) > 0) { logCapture.println("[Setup] Attempting to connect to saved WiFi..."); if (!wifiManager.connectToSaved()) { logCapture.println("[Setup] WiFi connection failed, starting AP mode..."); wifiManager.startAP(); } } else { logCapture.println("[Setup] No WiFi configured, starting AP mode..."); wifiManager.startAP(); } // Update WiFi connected flag wifiConnected = wifiManager.isConnected(); // Log initial WiFi state if (wifiConnected) { DEBUG_EVENTF(EVT_WIFI_CONNECT, "IP: %s", wifiManager.getIP().toString().c_str()); } else if (wifiManager.isAPActive()) { DEBUG_EVENTF(EVT_INFO, "AP mode: %s", wifiManager.getAPSSID()); } // Setup Web Server (event-driven, no dedicated task needed) setupWebServer(); // Add web config routes to the server webConfig.begin(&webServer); // Start captive portal if in AP mode if (wifiManager.isAPActive()) { webConfig.beginCaptivePortal(); logCapture.println("[Setup] Captive portal started"); logCapture.printf("[Setup] Connect to WiFi '%s' and open http://%s/settings\n", wifiManager.getAPSSID(), wifiManager.getIP().toString().c_str()); } // Create FreeRTOS tasks logCapture.println("[Setup] Creating FreeRTOS tasks..."); // 1-Wire Task - Highest priority, Core 1 (dedicated) logCapture.println("[Setup] Creating OneWire task on Core 1..."); Serial.flush(); // Ensure output before task starts xTaskCreatePinnedToCore( oneWireTask, "OneWire", TASK_STACK_ONEWIRE, NULL, TASK_PRIORITY_ONEWIRE, &oneWireTaskHandle, TASK_CORE_ONEWIRE); // Give OneWire task time to initialize before continuing vTaskDelay(pdMS_TO_TICKS(100)); logCapture.println("[Setup] OneWire task created"); // MQTT Task - Medium priority, Core 0 if (mqttSettings.enabled && strlen(mqttSettings.server) > 0) { xTaskCreatePinnedToCore( mqttTask, "MQTT", TASK_STACK_MQTT, NULL, TASK_PRIORITY_MQTT, &mqttTaskHandle, TASK_CORE_MQTT); } else { logCapture.println("[Setup] MQTT disabled or not configured"); } logCapture.println("[Setup] All tasks created"); // Mark OTA firmware as valid — all init succeeded confirmOtaIfNeeded(); digitalWrite(STATUS_LED_PIN, HIGH); logCapture.println("[Setup] Complete! LED should be ON"); } void loop() { // Process captive portal DNS requests if active webConfig.loop(); // Process WiFi manager events wifiManager.loop(); // Check reset button checkResetButton(); // Dynamic start of MQTT task if requested if (mqttShouldStart && mqttTaskHandle == NULL) { startMqttTask(); mqttShouldStart = false; } // Periodic debug log update - captures system state for crash analysis if (millis() - lastDebugUpdate >= DEBUG_UPDATE_INTERVAL) { lastDebugUpdate = millis(); // Get stack high water marks if tasks exist uint16_t hwmOnewire = oneWireTaskHandle ? uxTaskGetStackHighWaterMark(oneWireTaskHandle) : 0; uint16_t hwmMqtt = mqttTaskHandle ? uxTaskGetStackHighWaterMark(mqttTaskHandle) : 0; debugLog.updateAliveStatus(hwmOnewire, hwmMqtt); } // Staleness check - mark sensors as stale if no updates for too long if (sensorSettings.staleTimeoutMinutes > 0 && (millis() - lastStalenessCheck >= STALENESS_CHECK_INTERVAL_MS)) { lastStalenessCheck = millis(); unsigned long staleThresholdMs = (unsigned long)sensorSettings.staleTimeoutMinutes * 60 * 1000; unsigned long now = millis(); for (int i = 0; i < NUM_SENSORS; i++) { if (sensorStates[i].enabled && sensorStates[i].status == SensorStatus::ACTIVE && (now - sensorStates[i].lastUpdateMs) > staleThresholdMs) { // Send SET_STALE command SensorCommand_t cmd; cmd.type = SensorCommandType::SET_STALE; cmd.sensorIndex = i; xQueueSend(sensorCommandQueue, &cmd, 0); logCapture.printf("[Stale] Sensor %d marked as stale (no update for %lu min)\n", i, (now - sensorStates[i].lastUpdateMs) / 60000); } } } // Periodic NVS save for temperatures (with debounce) if (temperaturesChanged && (millis() - lastNvsSave >= NVS_SAVE_INTERVAL_MS)) { lastNvsSave = millis(); temperaturesChanged = false; // Save current temperatures to NVS if (xSemaphoreTake(tempMutex, pdMS_TO_TICKS(100)) == pdTRUE) { logCapture.print("[NVS] Saving temperatures: "); for (int i = 0; i < NUM_SENSORS; i++) { sensorSettings.lastTemperatures[i] = temperatures[i]; sensorSettings.sensorEnabled[i] = sensorStates[i].enabled; logCapture.printf("%.1f ", temperatures[i]); } logCapture.println(); xSemaphoreGive(tempMutex); settingsManager.saveSensorSettings(sensorSettings); } else { logCapture.println("[NVS] WARNING: Could not save temperatures (mutex timeout)"); } } // Small delay to prevent watchdog issues vTaskDelay(pdMS_TO_TICKS(10)); } // ============================================ // Factory Reset Button Check // ============================================ void checkResetButton() { bool buttonPressed = (digitalRead(RESET_BUTTON_PIN) == LOW); if (buttonPressed) { if (resetButtonPressStart == 0) { resetButtonPressStart = millis(); logCapture.println("[Reset] Button pressed..."); } else if (!resetInProgress) { unsigned long holdTime = millis() - resetButtonPressStart; // Blink LED to indicate progress if ((holdTime / 500) % 2 == 0) { digitalWrite(STATUS_LED_PIN, LOW); } else { digitalWrite(STATUS_LED_PIN, HIGH); } // Check if held long enough for reset if (holdTime >= RESET_HOLD_TIME_MS) { resetInProgress = true; logCapture.println("[Reset] Factory reset triggered!"); // Fast blink to indicate reset for (int i = 0; i < 10; i++) { digitalWrite(STATUS_LED_PIN, i % 2); delay(100); } // Perform factory reset settingsManager.factoryReset(); logCapture.println("[Reset] Rebooting..."); delay(500); safeRestart(); } } } else { // Button released if (resetButtonPressStart > 0 && !resetInProgress) { unsigned long holdTime = millis() - resetButtonPressStart; logCapture.printf("[Reset] Button released after %lu ms\n", holdTime); digitalWrite(STATUS_LED_PIN, HIGH); } resetButtonPressStart = 0; } }