/** @file micro_sd.cpp @brief library for communication with micro-SD card @author Miroslav Pivovarsky Contact: miroslav.pivovarsky@gmail.com @bug: no know bug */ #include "micro_sd.h" /** @brief Constructor @param none @return none */ MicroSd::MicroSd() { CardDetected = false; DetectAfterBoot = false; sdCardMutex = xSemaphoreCreateMutex(); } /** @brief Reinit micro SD card @param none @return none */ void MicroSd::ReinitCard() { Serial.println(F("Reinit micro SD card!")); Serial.println(F("Deinit micro SD card")); SD_MMC.end(); delay(50); Serial.println(F("Init micro SD card")); InitSdCard(); } /** * @brief Open file * * @param i_file - file * @param i_path - path and file name * @return true * @return false */ bool MicroSd::OpenFile(File *i_file, String i_path) { bool status = false; if (true == CardDetected) { #if (true == CONSOLE_VERBOSE_DEBUG) Serial.println("Opening file: " + i_path); #endif if (SD_MMC.cardType() == CARD_NONE) { Serial.println("No SD card detected"); CardDetected = false; } else { *i_file = SD_MMC.open(i_path.c_str(), FILE_APPEND); if (!*i_file) { #if (true == CONSOLE_VERBOSE_DEBUG) Serial.println("Failed to open file"); #endif CardDetected = false; } else { status = true; #if (true == CONSOLE_VERBOSE_DEBUG) Serial.println("File opened"); #endif } } } return status; } /** * @brief Close file * * @param i_file - file */ void MicroSd::CloseFile(File *i_file) { if (*i_file) { i_file->close(); } } /** * @brief Check if file is opened * * @param i_file - file * @return true * @return false */ bool MicroSd::CheckOpenFile(File *i_file) { if (!*i_file) { #if (true == CONSOLE_VERBOSE_DEBUG) Serial.println(F("File not opened!")); #endif return false; } else { return true; } } /** @brief Init SD card. And check, if is SD card inserted @param none @return none */ void MicroSd::InitSdCard() { /* Start INIT Micro SD card */ Serial.println(F("Start init micro-SD Card")); /* set SD card to 1-line/1-bit mode. GPIO 4 is used for LED and for microSD card. But communication is slower. */ /* https://github.com/espressif/arduino-esp32/blob/master/libraries/SD_MMC/src/SD_MMC.h */ SD_MMC.setPins(SD_PIN_CLK, SD_PIN_CMD, SD_PIN_DATA0); if (!SD_MMC.begin("/sdcard", true)) { Serial.println(F("SD Card Mount Failed")); CardDetected = false; CardSizeMB = 0; return; } /* check microSD card and card type */ uint8_t cardType = SD_MMC.cardType(); if (cardType == CARD_NONE) { Serial.println(F("No SD_MMC card attached")); CardDetected = false; CardSizeMB = 0; return; } /* print card type */ Serial.print(F("Found card. Card Type: ")); if (cardType == CARD_MMC) { Serial.println(F("MMC")); } else if (cardType == CARD_SD) { Serial.println(F("SDSC")); } else if (cardType == CARD_SDHC) { Serial.println(F("SDHC")); } else { Serial.println(F("UNKNOWN")); } CardDetected = true; DetectAfterBoot = true; /* calculation card size */ CheckCardUsedStatus(); } /** @brief List directory on the micro SD card @param fs::FS - card @param String - Directory name @param uint8_t - levels @return none */ void MicroSd::ListDir(fs::FS &fs, String DirName, uint8_t levels) { if (true == CardDetected) { Serial.printf("Listing directory: %s\n", DirName.c_str()); File root = fs.open(DirName.c_str()); if (!root) { Serial.println(F("Failed to open directory")); return; } if (!root.isDirectory()) { Serial.println(F("Not a directory")); return; } File file = root.openNextFile(); while (file) { if (file.isDirectory()) { Serial.print(F(" DIR : ")); Serial.println(file.name()); if (levels) { ListDir(fs, file.path(), levels - 1); } } else { Serial.print(F(" FILE: ")); Serial.print(file.name()); Serial.print(F(" SIZE: ")); Serial.println(file.size()); } file = root.openNextFile(); } } } /** @brief Check directory on the micro SD card @param fs::FS - card @param String - dir name @return bool - status */ bool MicroSd::CheckDir(fs::FS &fs, String path) { bool status = false; if (true == CardDetected) { #if (true == CONSOLE_VERBOSE_DEBUG) Serial.printf("Checking Dir: %s... ", path.c_str()); #endif if (fs.exists(path.c_str())) { status = true; } } return status; } /** @brief List directory on the micro SD card @param fs::FS - card @param String - dir name @return bool - status */ bool MicroSd::CreateDir(fs::FS &fs, String path) { bool status = false; if (true == CardDetected) { #if (true == CONSOLE_VERBOSE_DEBUG) Serial.printf("Creating Dir: %s... ", path.c_str()); #endif if (fs.mkdir(path.c_str())) { status = true; } #if (true == CONSOLE_VERBOSE_DEBUG) Serial.println((status == true) ? "Created" : "Failed"); #endif } return status; } /** @brief remove directory on the micro SD card @param fs::FS - card @param String - dir name @return bool - status */ bool MicroSd::RemoveDir(fs::FS &fs, String path) { bool status = false; if (true == CardDetected) { #if (true == CONSOLE_VERBOSE_DEBUG) Serial.printf("Removing Dir: %s... ", path.c_str()); #endif if (fs.rmdir(path.c_str())) { status = true; } #if (true == CONSOLE_VERBOSE_DEBUG) Serial.println((status == true) ? "Removed" : "Failed"); #endif } return status; } /** @brief Read file and print data to console @param fs::FS - card @param String - file name @return none */ void MicroSd::ReadFileConsole(fs::FS &fs, String path) { if (true == CardDetected) { Serial.printf("Reading file: %s\n", path.c_str()); File file = fs.open(path.c_str()); if (!file) { Serial.println(F("Failed to open file for reading")); return; } Serial.print(F("Read from file: ")); while (file.available()) { Serial.write(file.read()); } } } /** @brief Write message to file @param fs::FS - card @param String - file name @param String - message @return bool - status */ bool MicroSd::WriteFile(fs::FS &fs, String path, String message) { bool status = false; if (true == CardDetected) { #if (true == CONSOLE_VERBOSE_DEBUG) Serial.printf("Writing file: %s... ", path.c_str()); #endif File file = fs.open(path.c_str(), FILE_WRITE); if (!file) { #if (true == CONSOLE_VERBOSE_DEBUG) Serial.printf("Failed to open file for writing"); #endif } else { if (file.print(message.c_str())) { status = true; } #if (true == CONSOLE_VERBOSE_DEBUG) Serial.println((status == true) ? "File written" : "Write Failed"); #endif } } return status; } /** @brief Added text to end of file @param fs::FS - card @param String - file name @param String - message @return bool - status */ bool MicroSd::AppendFile(fs::FS &fs, String path, String message) { bool status = false; if (true == CardDetected) { #if (true == CONSOLE_VERBOSE_DEBUG) Serial.printf("Appending to file: %s... ", path.c_str()); #endif File file = fs.open(path.c_str(), FILE_APPEND); if (!file) { #if (true == CONSOLE_VERBOSE_DEBUG) Serial.println("Failed to open file for appending"); #endif CardDetected = false; } else { if (file.print(message.c_str())) { status = true; } #if (true == CONSOLE_VERBOSE_DEBUG) Serial.println((status == true) ? "Message appended" : "Append Failed"); #endif } } return status; } /** @brief Added text to end of file @param File - file @param String - message @return bool - status */ bool MicroSd::AppendFile(File *i_file, String *i_msg) { /* take mutex */ xSemaphoreTake(sdCardMutex, portMAX_DELAY); bool status = false; /* check if card is corrupted */ if (false == isCardCorrupted()) { xSemaphoreGive(sdCardMutex); return false; } /* check if card is detected */ if (true == CardDetected) { #if (true == CONSOLE_VERBOSE_DEBUG) Serial.printf("Appending to file:"); #endif /* check if file is opened */ if (!*i_file) { Serial.println("File not opened"); CardDetected = false; } else { /* write to file */ if (i_file->print(i_msg->c_str())) { if (*i_file) { i_file->flush(); /* check if write was OK */ if (!i_file->getWriteError()) { #if (true == CONSOLE_VERBOSE_DEBUG) Serial.println("Write OK"); #endif status = true; } else { Serial.println(F("Failed write to file")); } } else { Serial.println(F("File not opened!")); } } else { Serial.println(F("Failed write to file!")); } #if (true == CONSOLE_VERBOSE_DEBUG) Serial.println((status == true) ? "Message appended" : "Append Failed"); #endif } } /* give mutex */ xSemaphoreGive(sdCardMutex); return status; } /** @brief Rename file on the SD card @param fs::FS - card @param String - origin file name @param String - new file name @return bool - status */ bool MicroSd::RenameFile(fs::FS &fs, String path1, String path2) { bool status = false; if (true == CardDetected) { #if (true == CONSOLE_VERBOSE_DEBUG) Serial.printf("Renaming file %s to %s... ", path1.c_str(), path2.c_str()); #endif if (fs.rename(path1.c_str(), path2.c_str())) { status = true; } #if (true == CONSOLE_VERBOSE_DEBUG) Serial.println((status == true) ? "File renamed" : "Rename Failed"); #endif } return status; } /** @brief Delete file on the SD card @param fs::FS - card @param String - file name @return bool - status */ bool MicroSd::DeleteFile(fs::FS &fs, String path) { bool status = false; if (true == CardDetected) { #if (true == CONSOLE_VERBOSE_DEBUG) Serial.printf("Deleting file: %s... ", path.c_str()); #endif if (fs.remove(path.c_str())) { status = true; } #if (true == CONSOLE_VERBOSE_DEBUG) Serial.println((status == true) ? "File deleted" : "Delete Failed"); #endif } return status; } /** @brief Get file size in the kb @param fs::FS - card @param String - file name @return uint32_t - size */ uint32_t MicroSd::GetFileSize(fs::FS &fs, String path) { uint32_t ret = 0; if (true == CardDetected) { #if (true == CONSOLE_VERBOSE_DEBUG) Serial.printf("Getting file size: %s... ", path.c_str()); #endif File file = fs.open(path.c_str(), FILE_APPEND); if (!file) { #if (true == CONSOLE_VERBOSE_DEBUG) Serial.println("Failed to open file for appending"); #endif return 0; } ret = file.size() / 1024; /* convert from bytes to kb */ #if (true == CONSOLE_VERBOSE_DEBUG) Serial.printf(" File size: %d\n ", ret); #endif } return ret; /* kb*/ } /** @brief Check file count with partial match @param fs::FS - card @param String - dir name @param String - file name @return int16_t - count */ uint16_t MicroSd::FileCount(fs::FS &fs, String DirName, String FileName) { uint16_t FileCount = 0; if (true == CardDetected) { #if (true == CONSOLE_VERBOSE_DEBUG) Serial.printf("File name count: %s\n", DirName.c_str()); #endif File root = fs.open(DirName.c_str()); if (!root) { Serial.println(F("Failed to open directory")); return 0; } if (!root.isDirectory()) { Serial.println(F("Not a directory")); return 0; } File file = root.openNextFile(); while (file) { if (!file.isDirectory()) { #if (true == CONSOLE_VERBOSE_DEBUG) Serial.print(" FILE: "); Serial.print(file.name()); Serial.print(" SIZE: "); Serial.print(file.size()); #endif if (String(file.name()).indexOf(FileName) != -1) { FileCount++; #if (true == CONSOLE_VERBOSE_DEBUG) Serial.print(" - MATCH"); #endif } #if (true == CONSOLE_VERBOSE_DEBUG) Serial.println(""); #endif } file = root.openNextFile(); } } return FileCount; } /** @brief Remove files in directory @param fs::FS - card @param String - dir name @param int - max files @return bool - status */ bool MicroSd::RemoveFilesInDir(fs::FS &fs, String path, int maxFiles) { bool ret = false; File dir = fs.open(path.c_str()); if (!dir) { return ret; } int fileCount = 0; File file = dir.openNextFile(); while (file) { ret = true; String fileName = path + "/" + file.name(); fs.remove(fileName.c_str()); Serial.printf("Removing file: %s\n", fileName.c_str()); fileCount++; if (fileCount >= maxFiles) { break; } file = dir.openNextFile(); } return ret; } int MicroSd::CountFilesInDir(fs::FS &fs, String path) { uint16_t file_count = FileCount(fs, path, ""); return file_count; } /** @brief Check card used status @param none @return bool - status */ void MicroSd::CheckCardUsedStatus() { CardSizeMB = SD_MMC.cardSize() / (1024 * 1024); CardTotalMB = SD_MMC.totalBytes() / (1024 * 1024); CardUsedMB = SD_MMC.usedBytes() / (1024 * 1024); CardFreeMB = CardSizeMB - CardUsedMB; FreeSpacePercent = (CardFreeMB * 100) / CardSizeMB; UsedSpacePercent = 100 - FreeSpacePercent; #if (true == CONSOLE_VERBOSE_DEBUG) Serial.printf("Card size: %d MB, Total: %d MB, Used: %d MB, Free: %d GB, Free: %d %% \n", CardSizeMB, CardTotalMB, CardUsedMB, CardFreeMB, FreeSpacePercent); #endif } /** * @brief Function to check if card is corrupted * * @return true * @return false */ bool MicroSd::isCardCorrupted() { bool ret = true; if (true == CardDetected) { #if (true == CONSOLE_VERBOSE_DEBUG) //Serial.println(F("Checking card...")); #endif /* check card size */ uint64_t use = SD_MMC.usedBytes(); uint64_t size = 0; if (use != 0) { size = SD_MMC.cardSize(); } #if (true == CONSOLE_VERBOSE_DEBUG) Serial.printf("Card size: %llu, Used: %llu\n", size, use); #endif /* check space on the card */ if (size == use) { Serial.println(F("No space left on device!")); CardDetected = false; ret = false; } /* check another error */ if ((size <= 0 ) || (size == 0) || (use <= 0) || (use == 0)) { Serial.println(F("No card detected!")); CardDetected = false; ret = false; } } else { ret = false; } return ret; } /** @brief Write picture to the SD card @param fs::FS - card @param String - file name @return String - data */ bool MicroSd::WritePicture(String i_PhotoName, uint8_t *i_PhotoData, size_t i_PhotoLen) { #if (true == CONSOLE_VERBOSE_DEBUG) Serial.println(f("WritePicture")); #endif bool ret_stat = false; File file = SD_MMC.open(i_PhotoName, FILE_WRITE); if (file) { size_t ret = 0; ret = file.write(i_PhotoData, i_PhotoLen); if (ret != i_PhotoLen) { Serial.println(F("Failed. Error while writing to file")); } else { Serial.printf("Saved as %s\n", i_PhotoName.c_str()); ret_stat = true; } file.close(); } else { Serial.printf("Failed. Could not open file: %s\n", i_PhotoName.c_str()); } return ret_stat; } /** @brief Write picture to the SD card with EXIF data @param fs::FS - card @param String - file name @param uint8_t - data @param size_t - data length @param const uint8_t - EXIF data @param size_t - EXIF data length @return bool - status */ bool MicroSd::WritePicture(String i_PhotoName, uint8_t *i_PhotoData, size_t i_PhotoLen, const uint8_t *i_PtohoExif, size_t i_PhotoExifLen) { #if (true == CONSOLE_VERBOSE_DEBUG) Serial.println(F("WritePicture EXIF")); #endif bool ret_stat = false; File file = SD_MMC.open(i_PhotoName, FILE_WRITE); if (file) { size_t ret = 0; ret = file.write(i_PtohoExif, i_PhotoExifLen); ret += file.write(i_PhotoData, i_PhotoLen); if (ret != (i_PhotoLen + i_PhotoExifLen)) { #if (true == CONSOLE_VERBOSE_DEBUG) Serial.println(F("Failed. Error while writing to file")); #endif ret_stat = false; } else { #if (true == CONSOLE_VERBOSE_DEBUG) Serial.printf("Saved as %s\n", i_PhotoName.c_str()); #endif ret_stat = true; } file.close(); } else { #if (true == CONSOLE_VERBOSE_DEBUG) Serial.printf("Failed. Could not open file: %s\n", i_PhotoName.c_str()); #endif ret_stat = false; } return ret_stat; } /** @brief Get card detected status @param none @return bool - status */ bool MicroSd::GetCardDetectedStatus() { return CardDetected; } /** @brief Get card size @param none @return uint16_t - size */ uint16_t MicroSd::GetCardSizeMB() { return CardSizeMB; } /** @brief Get card detect after boot @param none @return bool - status */ bool MicroSd::GetCardDetectAfterBoot() { return DetectAfterBoot; } /** @brief Get card total MB @param none @return uint16_t - size */ uint16_t MicroSd::GetCardTotalMB() { return CardTotalMB; } /** @brief Get card used MB @param none @return uint16_t - size */ uint16_t MicroSd::GetCardUsedMB() { return CardUsedMB; } /** @brief Get card free MB @param none @return uint16_t - size */ uint16_t MicroSd::GetCardFreeMB() { return CardFreeMB; } /** @brief Get free space percent @param none @return uint8_t - percent */ uint8_t MicroSd::GetFreeSpacePercent() { return FreeSpacePercent; } /** @brief Get used space percent @param none @return uint8_t - percent */ uint8_t MicroSd::GetUsedSpacePercent() { return UsedSpacePercent; } /* EOF */