/** @file system.cpp @brief system library @author Miroslav Pivovarsky Contact: miroslav.pivovarsky@gmail.com @bug: no know bug */ #include "system.h" /** @brief Function for init system library @param none @return none */ void System_Init() { SystemLog.AddEvent(LogLevel_Info, F("Init system lib")); /* show last reset status */ String reason_simple = System_printMcuResetReasonSimple(); SystemLog.AddEvent(LogLevel_Warning, "CPU reset reason: " + reason_simple); String reason_core0 = System_PrintMcuResetReason(rtc_get_reset_reason(0)); String reason_core1 = System_PrintMcuResetReason(rtc_get_reset_reason(1)); SystemLog.AddEvent(LogLevel_Warning, "CPU0 reset reason: " + reason_core0); SystemLog.AddEvent(LogLevel_Warning, "CPU1 reset reason: " + reason_core1); SystemLog.AddEvent(LogLevel_Info, "MCU Temperature: " + String(temperatureRead()) + " *C"); SystemLog.AddEvent(LogLevel_Info, "Internal Total heap: " + String(ESP.getHeapSize()) + " ,internal Free Heap: " + String(ESP.getFreeHeap())); SystemLog.AddEvent(LogLevel_Info, "SPIRam Total heap: " + String(ESP.getPsramSize()) + " ,SPIRam Free Heap: " + String(ESP.getFreePsram())); SystemLog.AddEvent(LogLevel_Info, "ChipRevision: " + String(ESP.getChipRevision()) + " ,Cpu Freq: " + String(ESP.getCpuFreqMHz()) + " ,SDK Version: " + String(ESP.getSdkVersion())); SystemLog.AddEvent(LogLevel_Info, "Flash Size: " + String(ESP.getFlashChipSize()) + " ,Flash Speed " + String(ESP.getFlashChipSpeed())); System_CheckIfPsramIsUsed(); } /** @brief Function for load configuration from EEPROM @param none @return none */ void System_LoadCfg() { } /** @brief Function for check if PSRAM is used @param none @return none */ void System_CheckIfPsramIsUsed() { if (psramFound()) { SystemLog.AddEvent(LogLevel_Info, F("PSRAM is used.")); void *ptr = malloc(100); if (ptr != NULL) { if (esp_ptr_external_ram(ptr)) { SystemLog.AddEvent(LogLevel_Info, F("malloc/new is using SPIRAM")); } else { SystemLog.AddEvent(LogLevel_Info, F("malloc/new is not using SPIRAM")); } free(ptr); } else { SystemLog.AddEvent(LogLevel_Info, F("Failed to allocate memory")); } } else { SystemLog.AddEvent(LogLevel_Info, F("PSRAM is not used.")); } } /** @brief Function for init update functions from file @param none @return none */ void System_UpdateInit() { Update.onProgress([](int progress, size_t total) { /* update from file */ SystemCamera.SetFlashStatus(true); digitalWrite(FW_STATUS_LED_PIN, FW_STATUS_LED_LEVEL_ON); uint8_t updateProgress = (progress * 100) / FirmwareUpdate.FirmwareSize; SystemLog.AddEvent(LogLevel_Info, "Updating: " + String(FirmwareUpdate.FirmwareSize) + "/" + String(progress) + " -> " + String(updateProgress) + "%"); FirmwareUpdate.PercentProcess = updateProgress; FirmwareUpdate.TransferedBytes = progress; delay(10); digitalWrite(FW_STATUS_LED_PIN, !FW_STATUS_LED_LEVEL_ON); }); } /** @brief Main function for system lib @param none @return none */ void System_Main() { /* check new FW version */ if (false == FirmwareUpdate.CheckNewVersionAfterBoot) { System_CheckNewVersion(); } /* task for download and flash FW from server */ System_OtaCloudUpdate(); } /** @brief Function for check FW version on the WEB server https://docs.github.com/en/rest/releases/releases?apiVersion=2022-11-28 @param none @return none */ void System_CheckNewVersion() { if (WL_CONNECTED == WiFi.status()) { SystemLog.AddEvent(LogLevel_Info, F("Check new FW version from OTA")); FirmwareUpdate.CheckNewVersionAfterBoot = true; WiFiClientSecure client; client.setCACert(root_CAs_ota); //client.setInsecure(); FirmwareUpdate.CheckNewVersionFwStatus = F("N/A"); FirmwareUpdate.NewVersionFw = F("Unknown"); FirmwareUpdate.OtaUpdateFwUrl = ""; FirmwareUpdate.OtaUpdateFwAvailable = false; /* connect to server and get json */ if (!client.connect(OTA_UPDATE_API_SERVER, 443)) { FirmwareUpdate.CheckNewVersionFwStatus = F("Failed connect to OTA server!"); SystemLog.AddEvent(LogLevel_Info, FirmwareUpdate.CheckNewVersionFwStatus); } else { SystemLog.AddEvent(LogLevel_Verbose, F("Connected to server!")); client.println("GET https://" + String(OTA_UPDATE_API_SERVER) + String(OTA_UPDATE_API_URL) + " HTTP/1.0"); client.println("Host: " + String(OTA_UPDATE_API_SERVER)); client.println("User-Agent: " + String(DEVICE_HOSTNAME)); client.println("Connection: close"); client.println(); while (client.connected()) { String line = client.readStringUntil('\n'); if (line == "\r") { SystemLog.AddEvent(LogLevel_Verbose, "headers received: " + line); break; } } /* wait for data, and read it */ delay(350); String Data = ""; while (client.connected()) { while (client.available()) { Data += (char)client.read(); } } SystemLog.AddEvent(LogLevel_Verbose, Data); client.stop(); /* json analyzed */ JsonDocument jsonDoc; DeserializationError error = deserializeJson(jsonDoc, Data); if (error) { FirmwareUpdate.CheckNewVersionFwStatus = F("Failed to parse JSON from OTA server!"); SystemLog.AddEvent(LogLevel_Warning, FirmwareUpdate.CheckNewVersionFwStatus); SystemLog.AddEvent(LogLevel_Warning, Data); } else { const char *firmwareVersion = jsonDoc["tag_name"]; if (firmwareVersion) { FirmwareUpdate.CheckNewVersionFwStatus = F("Download successful"); FirmwareUpdate.NewVersionFw = firmwareVersion; SystemLog.AddEvent(LogLevel_Info, "Available OTA firmware: " + FirmwareUpdate.NewVersionFw); /* get assets */ JsonArray assets = jsonDoc["assets"]; int assetsCount = assets.size(); SystemLog.AddEvent(LogLevel_Info, "Assets count: " + String(assetsCount)); for(int i = 0; i < assetsCount; i++) { JsonObject asset = assets[i]; const char* name = asset["name"]; SystemLog.AddEvent(LogLevel_Info, "Assets[" + String(i) + "]: " + String(name)); /* get FW file and URL */ if (strcmp_P(name, OTA_UPDATE_FW_FILE) == 0) { /* get download URL */ const char* download_url = asset["browser_download_url"]; FirmwareUpdate.OtaUpdateFwUrl = download_url; SystemLog.AddEvent(LogLevel_Info, "Found FW file: " + String(name) + " URL: " + FirmwareUpdate.OtaUpdateFwUrl); FirmwareUpdate.OtaUpdateFwAvailable = true; } } } else { FirmwareUpdate.CheckNewVersionFwStatus = F("JSON key 'tag_name' from OTA server not found!"); SystemLog.AddEvent(LogLevel_Warning, FirmwareUpdate.CheckNewVersionFwStatus); } } } } } /** @brief Function for check start OTA update from WEB server @param none @return none */ void System_OtaCloudUpdate() { if (true == FirmwareUpdate.StartOtaUpdate) { FirmwareUpdate.Processing = true; FirmwareUpdate.StartOtaUpdate = false; FirmwareUpdate.UpdatingStatus = F("Sync NTP time..."); SystemWifiMngt.SyncNtpTime(); FirmwareUpdate.UpdatingStatus = F("Start updating"); System_OtaUpdateStart(); } } /** @brief Function for downloading FW and flash new FW to the memmory https://github.com/espressif/arduino-esp32/blob/master/libraries/HTTPUpdate/src/HTTPUpdate.h @param none @return bool - true if update is done */ bool System_OtaUpdateStart() { bool b_ret = false; WiFiClientSecure client; /* check if new FW version is available */ if (FirmwareUpdate.OtaUpdateFwAvailable == false) { FirmwareUpdate.UpdatingStatus = SYSTEM_MSG_UPDATE_NO_FW; SystemLog.AddEvent(LogLevel_Info, FirmwareUpdate.UpdatingStatus); FirmwareUpdate.Processing = false; return b_ret; } /* reset wdg */ esp_task_wdt_reset(); /* set certificat for secure connection, set timeout and follow redirect */ client.setCACert(root_CAs_ota); // client.setInsecure(); client.setTimeout(12000 / SECOND_TO_MILISECOND); /* timeout argument is defined in seconds for setTimeout */ httpUpdate.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); /* HTTPC_FORCE_FOLLOW_REDIRECTS */ /* set callback function */ httpUpdate.onStart(System_OtaUpdateStartCB); httpUpdate.onEnd(System_OtaUpdateEndCB); httpUpdate.onError(System_OtaUpdateErrorCB); httpUpdate.onProgress(System_OtaUpdateProgressCB); /* mcu configuration */ httpUpdate.rebootOnUpdate(false); FirmwareUpdate.UpdatingStatus = SYSTEM_MSG_UPDATE_PROCESS; httpUpdate.setLedPin(FW_STATUS_LED_PIN, FW_STATUS_LED_LEVEL_ON); SystemLog.AddEvent(LogLevel_Info, F("Start OTA update URL: "), FirmwareUpdate.OtaUpdateFwUrl + ";"); /* start update */ t_httpUpdate_return ret = httpUpdate.update(client, FirmwareUpdate.OtaUpdateFwUrl.c_str()); /* check result */ switch (ret) { case HTTP_UPDATE_FAILED: b_ret = true; FirmwareUpdate.UpdatingStatus = String(SYSTEM_MSG_UPDATE_FAIL) + "[" + String(httpUpdate.getLastError()) + "]: " + httpUpdate.getLastErrorString(); break; case HTTP_UPDATE_NO_UPDATES: FirmwareUpdate.UpdatingStatus = F("No updates"); break; case HTTP_UPDATE_OK: FirmwareUpdate.UpdatingStatus = SYSTEM_MSG_UPDATE_DONE; break; } FirmwareUpdate.Processing = false; SystemLog.AddEvent(LogLevel_Info, F("OTA update DONE. "), FirmwareUpdate.UpdatingStatus); return b_ret; } /** @brief CB function OTA update process @param int - current bytes @param int - total bytes @return none */ void System_OtaUpdateProgressCB(int cur, int total) { /* OTA update from server */ FirmwareUpdate.FirmwareSize = total; uint8_t updateProgress = (cur * 100) / FirmwareUpdate.FirmwareSize; FirmwareUpdate.PercentProcess = updateProgress; FirmwareUpdate.TransferedBytes = cur; SystemLog.AddEvent(LogLevel_Info, "Downloaded: " + String(cur) + "/" + String(FirmwareUpdate.FirmwareSize) + " -> " + String(FirmwareUpdate.PercentProcess) + "%"); } /** @brief CB function start OTA update @param none @return none */ void System_OtaUpdateStartCB() { SystemLog.AddEvent(LogLevel_Info, F("Start OTA update")); } /** @brief CB function start OTA update @param none @return none */ void System_OtaUpdateEndCB() { SystemLog.AddEvent(LogLevel_Info, F("OTA update done")); } /** @brief CB function error OTA update @param int - error code @return none */ void System_OtaUpdateErrorCB(int error) { SystemLog.AddEvent(LogLevel_Info, "OTA update error: " + String(error)); } /** @brief Function for print reset reason @param int - reset reason @return String - reset reason */ String System_PrintMcuResetReason(int reason) { String ret = ""; switch (reason) { case 1: /**<1, Vbat power on reset*/ ret = F("POWERON_RESET"); break; case 3: /**<3, Software reset digital core*/ ret = F("SW_RESET"); break; case 4: /**<4, Legacy watch dog reset digital core*/ ret = F("OWDT_RESET"); break; case 5: /**<5, Deep Sleep reset digital core*/ ret = F("DEEPSLEEP_RESET"); break; case 6: /**<6, Reset by SLC module, reset digital core*/ ret = F("SDIO_RESET"); break; case 7: /**<7, Timer Group0 Watch dog reset digital core*/ ret = F("TG0WDT_SYS_RESET"); break; case 8: /**<8, Timer Group1 Watch dog reset digital core*/ ret = F("TG1WDT_SYS_RESET"); break; case 9: /**<9, RTC Watch dog Reset digital core*/ ret = F("RTCWDT_SYS_RESET"); break; case 10: /**<10, Instrusion tested to reset CPU*/ ret = F("INTRUSION_RESET"); break; case 11: /**<11, Time Group reset CPU*/ ret = F("TGWDT_CPU_RESET"); break; case 12: /**<12, Software reset CPU*/ ret = F("SW_CPU_RESET"); break; case 13: /**<13, RTC Watch dog Reset CPU*/ ret = F("RTCWDT_CPU_RESET"); break; case 14: /**<14, for APP CPU, reseted by PRO CPU*/ ret = F("EXT_CPU_RESET"); break; case 15: /**<15, Reset when the vdd voltage is not stable*/ ret = F("RTCWDT_BROWN_OUT_RESET"); break; case 16: /**<16, RTC Watch dog reset digital core and rtc module*/ ret = F("RTCWDT_RTC_RESET"); break; default: ret = F("NO_MEAN"); } return ret; } /** @brief Function for print reset reason @param none @return String - reset reason */ String System_printMcuResetReasonSimple() { String ret = ""; esp_reset_reason_t reason = esp_reset_reason(); switch (reason) { case ESP_RST_UNKNOWN: ret = F("Reset reason can not be determined"); break; case ESP_RST_POWERON: ret = F("Reset due to power-on event"); break; case ESP_RST_EXT: ret = F("Reset by external pin (not applicable for ESP32)"); break; case ESP_RST_SW: ret = F("Software reset via esp_restart"); break; case ESP_RST_PANIC: ret = F("Software reset due to exception/panic"); break; case ESP_RST_INT_WDT: ret = F("Reset (software or hardware) due to interrupt watchdog"); break; case ESP_RST_TASK_WDT: ret = F("Reset due to task watchdog"); break; case ESP_RST_WDT: ret = F("Reset due to other watchdogs"); break; case ESP_RST_DEEPSLEEP: ret = F("Reset after exiting deep sleep mode"); break; case ESP_RST_BROWNOUT: ret = F("Brownout reset (software or hardware)"); break; case ESP_RST_SDIO: ret = F("Reset over SDIO"); break; default: ret = F("N/A"); break; } return ret; } /** @brief Function for WiFi management system task @param void *pvParameters @return none */ void System_TaskWifiManagement(void *pvParameters) { SystemLog.AddEvent(LogLevel_Info, F("Task Wifi Management. core: "), String(xPortGetCoreID())); TickType_t xLastWakeTime = xTaskGetTickCount(); while (1) { /* wifi management. Enable/disable AP_STA mode and STA mode*/ SystemWifiMngt.WifiManagement(); McuTemperature.TemperatureCelsius = temperatureRead(); /* wifi reconnect after signal lost */ SystemWifiMngt.WiFiReconnect(); SystemLog.AddEvent(LogLevel_Info, "Free RAM: " + String(ESP.getFreeHeap()) + " B"); SystemLog.AddEvent(LogLevel_Info, "Free SPIRAM: " + String(ESP.getFreePsram()) + " B"); SystemLog.AddEvent(LogLevel_Info, "Temperature: " + String(McuTemperature.TemperatureCelsius) + " *C"); SystemLog.AddEvent(LogLevel_Verbose, F("WiFiManagement task. Stack free size: "), String(uxTaskGetStackHighWaterMark(NULL)) + "B"); SystemLog.AddEvent(LogLevel_Verbose, F("WiFi status: "), String(WiFi.status())); /* reset wdg */ esp_task_wdt_reset(); /* next start task */ vTaskDelayUntil(&xLastWakeTime, TASK_WIFI / portTICK_PERIOD_MS); } } /** * @brief Function for main system task * * @param void *pvParameters * @return none */ void System_TaskMain(void *pvParameters) { SystemLog.AddEvent(LogLevel_Info, F("System task. core: "), String(xPortGetCoreID())); TickType_t xLastWakeTime = xTaskGetTickCount(); while (1) { /* for ota update */ esp_task_wdt_reset(); System_Main(); SystemLog.AddEvent(LogLevel_Verbose, F("System task. Stack free size: "), String(uxTaskGetStackHighWaterMark(NULL)) + "B"); /* reset wdg */ esp_task_wdt_reset(); /* next start task */ vTaskDelayUntil(&xLastWakeTime, TASK_SYSTEM / portTICK_PERIOD_MS); } } /** * @brief Function for capture and send photo task * * @param void *pvParameters * @return none */ void System_TaskCaptureAndSendPhoto(void *pvParameters) { SystemLog.AddEvent(LogLevel_Info, F("Task photo processing. core: "), String(xPortGetCoreID())); TickType_t xLastWakeTime = xTaskGetTickCount(); while (1) { if (Connect.CheckSendingIntervalExpired()) { Connect.SetSendingIntervalCounter(0); /* send network information to backend */ if ((WL_CONNECTED == WiFi.status()) && (false == FirmwareUpdate.Processing)) { SystemLog.AddEvent(LogLevel_Verbose, F("Task photo processing. Start sending info")); esp_task_wdt_reset(); Connect.SendInfoToBackend(); } /* send photo to backend*/ if ((WL_CONNECTED == WiFi.status()) && (false == FirmwareUpdate.Processing)) { SystemLog.AddEvent(LogLevel_Verbose, F("Task photo processing. Start sending photo")); esp_task_wdt_reset(); Connect.TakePictureAndSendToBackend(); } } else { /* update counter */ Connect.IncreaseSendingIntervalCounter(); } SystemLog.AddEvent(LogLevel_Verbose, F("Photo processing task. Stack free size: "), String(uxTaskGetStackHighWaterMark(NULL)) + "B"); /* reset wdg */ esp_task_wdt_reset(); /* next start task */ vTaskDelayUntil(&xLastWakeTime, TASK_PHOTO_SEND / portTICK_PERIOD_MS); } } /** * @brief Function for micro SD card check task * * @param void *pvParameters * @return none */ void System_TaskSdCardCheck(void *pvParameters) { SystemLog.AddEvent(LogLevel_Info, F("MicroSdCard check task. core: "), String(xPortGetCoreID())); TickType_t xLastWakeTime = xTaskGetTickCount(); while (1) { esp_task_wdt_reset(); /* check micro SD card */ if ((true == SystemLog.GetCardDetectAfterBoot()) && (false == SystemLog.GetCardDetectedStatus())) { SystemLog.LogCloseFile(); SystemLog.ReinitCard(); SystemLog.LogOpenFile(); SystemLog.AddEvent(LogLevel_Warning, F("Reinit micro SD card done!")); } /* check card free space */ if (true == SystemLog.GetCardDetectedStatus()) { SystemLog.AddEvent(LogLevel_Verbose, F("Check card free space")); SystemLog.CheckCardSpace(); } /* check maximum log file size */ if (true == SystemLog.GetCardDetectedStatus()) { SystemLog.AddEvent(LogLevel_Verbose, F("Check maximum log file size")); SystemLog.CheckMaxLogFileSize(); } /* check if log file is opened */ if (true == SystemLog.GetCardDetectedStatus()) { SystemLog.LogCheckOpenedFile(); if (false == SystemLog.GetLogFileOpened()) { SystemLog.LogOpenFile(); SystemLog.AddEvent(LogLevel_Warning, F("Log file is not opened!")); } } SystemLog.AddEvent(LogLevel_Info, "CardStatus: " + String(SystemLog.GetCardDetectedStatus()) + " FileStatus: " + String(SystemLog.GetLogFileOpened())); SystemLog.AddEvent(LogLevel_Verbose, F("MicroSdCard task. Stack free size: "), String(uxTaskGetStackHighWaterMark(NULL)) + "B"); /* reset wdg */ esp_task_wdt_reset(); /* next start task */ vTaskDelayUntil(&xLastWakeTime, TASK_SDCARD / portTICK_PERIOD_MS); } } /** * @brief Function for serial configuration task * * @param void *pvParameters * @return none */ void System_TaskSerialCfg(void *pvParameters) { SystemLog.AddEvent(LogLevel_Info, F("SerialCg task. core: "), String(xPortGetCoreID())); TickType_t xLastWakeTime = xTaskGetTickCount(); while (1) { esp_task_wdt_reset(); SystemSerialCfg.ProcessIncommingData(); SystemLog.AddEvent(LogLevel_Verbose, F("SerialCfg task. Stack free size: "), String(uxTaskGetStackHighWaterMark(NULL)) + "B"); /* reset wdg */ esp_task_wdt_reset(); /* next start task */ vTaskDelayUntil(&xLastWakeTime, TASK_SERIAL_CFG / portTICK_PERIOD_MS); } } /** * @brief Function for stream telemetry task * * @param void *pvParameters * @return none */ void System_TaskStreamTelemetry(void *pvParameters) { SystemLog.AddEvent(LogLevel_Info, F("StreamTelemetry task. core: "), String(xPortGetCoreID())); TickType_t xLastWakeTime = xTaskGetTickCount(); while (1) { esp_task_wdt_reset(); SystemLog.AddEvent(LogLevel_Verbose, F("StreamTelemetry task. Stack free size: "), String(uxTaskGetStackHighWaterMark(NULL)) + "B"); if (SystemCamera.GetStreamStatus()) { char buf[80] = { '\0' }; sprintf(buf, "Stream, average data in %dsec. FPS: %.1f, Size: %uKB", (TASK_STREAM_TELEMETRY / SECOND_TO_MILISECOND), SystemCamera.StreamGetFrameAverageFps(), SystemCamera.StreamGetFrameAverageSize()); SystemLog.AddEvent(LogLevel_Info, buf); SystemCamera.StreamClearFrameData(); } /* reset wdg */ esp_task_wdt_reset(); /* next start task */ vTaskDelayUntil(&xLastWakeTime, TASK_STREAM_TELEMETRY / portTICK_PERIOD_MS); } } /** * @brief Function for system led task * * @param void *pvParameters * @return none */ void System_TaskSysLed(void *pvParameters) { SystemLog.AddEvent(LogLevel_Info, F("SystemLed task. core: "), String(xPortGetCoreID())); TickType_t xLastWakeTime = xTaskGetTickCount(); while (1) { system_led.toggle(); /* reset wdg */ esp_task_wdt_reset(); SystemLog.AddEvent(LogLevel_Verbose, F("SystemLed task. Stack free size: "), String(uxTaskGetStackHighWaterMark(NULL)) + "B"); /* next start task */ vTaskDelayUntil(&xLastWakeTime, system_led.getTimer() / portTICK_PERIOD_MS); } } /** * @brief Function for WiFi watchdog task * * @param void *pvParameters * @return none */ void System_TaskWiFiWatchdog(void *pvParameters) { SystemLog.AddEvent(LogLevel_Info, F("WiFiWatchdog task. core: "), String(xPortGetCoreID())); TickType_t xLastWakeTime = xTaskGetTickCount(); while (1) { esp_task_wdt_reset(); SystemWifiMngt.WiFiWatchdog(); SystemLog.AddEvent(LogLevel_Verbose, F("WiFiWatchdog task. Stack free size: "), String(uxTaskGetStackHighWaterMark(NULL)) + "B"); /* reset wdg */ esp_task_wdt_reset(); /* next start task */ vTaskDelayUntil(&xLastWakeTime, TASK_WIFI_WATCHDOG / portTICK_PERIOD_MS); } } /** * @brief Function for micro SD card remove files task * * @param void *pvParameters * @return none */ void System_TaskSdCardRemove(void *pvParameters) { SystemLog.AddEvent(LogLevel_Info, F("TaskSdCardRemove. core: "), String(xPortGetCoreID())); TickType_t xLastWakeTime = xTaskGetTickCount(); SdCardRemoveTime = TASK_SDCARD_FILE_REMOVE; while (1) { esp_task_wdt_reset(); if (0 != StartRemoveSdCard) { if (1 == StartRemoveSdCard) { SdCardRemoveTime = 5000; SystemLog.AddEvent(LogLevel_Info, F("Start remove timelaps photo")); uint16_t file_count = SystemLog.CountFilesInDir(SD_MMC, TIMELAPS_PHOTO_FOLDER); SystemLog.AddEvent(LogLevel_Info, F("Files in dir: "), String(file_count)); esp_task_wdt_reset(); StartRemoveSdCard = 2; } if ( false == SystemLog.RemoveFilesInDir(SD_MMC, TIMELAPS_PHOTO_FOLDER, FILE_REMOVE_MAX_COUNT)) { SystemLog.AddEvent(LogLevel_Info, F("Remove files in dir done")); StartRemoveSdCard = 0; SdCardRemoveTime = TASK_SDCARD_FILE_REMOVE; } } SystemLog.AddEvent(LogLevel_Verbose, F("MicroSdCard task. Stack free size: "), String(uxTaskGetStackHighWaterMark(NULL)) + "B"); /* reset wdg */ esp_task_wdt_reset(); /* next start task */ vTaskDelayUntil(&xLastWakeTime, SdCardRemoveTime / portTICK_PERIOD_MS); } } /* EOF */