• Система автоматизации с открытым исходным кодом на базе esp8266/esp32 микроконтроллеров и приложения IoT Manager. Наша группа в Telegram

ESP32 LittleFS как получить данные о файловой системе

Доброго времени суток. Некоторое время назад узнал тут на форуме, что файлы на есп можно загружать не только на фтп. Сейчас немного дошли руки. Вроде как есть хорошая документация по этому вопросу от самого еспресиф: https://arduino-esp8266.readthedocs.io/en/latest/filesystem.html#uploading-files-to-file-system
Но что-то кроме двух команд ничего не получается.
Код:
if(LittleFS.begin()) Serial.println("initialised OK");
if(LittleFS.exists("/")){Serial.println("yes");}
Вроде куча примеров, но не работают. Правда пробую на есп32, а в справочнике примеры для есп8266, по идее не должно быть разницы.
Например, пробую перебрать каталоги:
Код:
Dir dir = LittleFS.openDir("/data");
while (dir.next()) {
    Serial.print(dir.fileName());
    if(dir.fileSize()) {
        File f = dir.openFile("r");
        Serial.println(f.size());
    }
}
Ругается, что
Код:
'Dir' was not declared in this scope
 Dir dir = LittleFS.openDir("/data");
 ^~~
 
Или пробую получить данные о структуре файловой системы:
Код:
FSInfo fs_info;
LittleFS.info(fs_info);

Serial.println(fs_info.totalBytes);
Получаю кучу ошибок:
Код:
error: 'FSInfo' does not name a type
 FSInfo fs_info;
 ^~~~~~
In function 'void setup()':
error: 'class fs::LittleFSFS' has no member named 'info'
 LittleFS.info(fs_info);
          ^~~~
error: 'fs_info' was not declared in this scope
 LittleFS.info(fs_info);
               ^~~~~~~
note: suggested alternative: 'isinff'
 LittleFS.info(fs_info);
               ^~~~~~~
               isinff
exit status 1
'FSInfo' does not name a type
 

aZholtikov

Active member
Примеры LittleFS для ESP8266 и ESP32 "мягко говоря" ОЧЕНЬ сильно различаются...
Используйте пример для нужной платформы.
 

aZholtikov

Active member
Есть. И огромная!

Код:
#include <Arduino.h>
#include "FS.h"
#include <LittleFS.h>

/* You only need to format LittleFS the first time you run a
   test or else use the LITTLEFS plugin to create a partition
   https://github.com/lorol/arduino-esp32littlefs-plugin
  
   If you test two partitions, you need to use a custom
   partition.csv file, see in the sketch folder */

//#define TWOPART

#define FORMAT_LITTLEFS_IF_FAILED true

void listDir(fs::FS &fs, const char * dirname, uint8_t levels){
    Serial.printf("Listing directory: %s\r\n", dirname);

    File root = fs.open(dirname);
    if(!root){
        Serial.println("- failed to open directory");
        return;
    }
    if(!root.isDirectory()){
        Serial.println(" - not a directory");
        return;
    }

    File file = root.openNextFile();
    while(file){
        if(file.isDirectory()){
            Serial.print("  DIR : ");
            Serial.println(file.name());
            if(levels){
                listDir(fs, file.path(), levels -1);
            }
        } else {
            Serial.print("  FILE: ");
            Serial.print(file.name());
            Serial.print("\tSIZE: ");
            Serial.println(file.size());
        }
        file = root.openNextFile();
    }
}

void createDir(fs::FS &fs, const char * path){
    Serial.printf("Creating Dir: %s\n", path);
    if(fs.mkdir(path)){
        Serial.println("Dir created");
    } else {
        Serial.println("mkdir failed");
    }
}

void removeDir(fs::FS &fs, const char * path){
    Serial.printf("Removing Dir: %s\n", path);
    if(fs.rmdir(path)){
        Serial.println("Dir removed");
    } else {
        Serial.println("rmdir failed");
    }
}

void readFile(fs::FS &fs, const char * path){
    Serial.printf("Reading file: %s\r\n", path);

    File file = fs.open(path);
    if(!file || file.isDirectory()){
        Serial.println("- failed to open file for reading");
        return;
    }

    Serial.println("- read from file:");
    while(file.available()){
        Serial.write(file.read());
    }
    file.close();
}

void writeFile(fs::FS &fs, const char * path, const char * message){
    Serial.printf("Writing file: %s\r\n", path);

    File file = fs.open(path, FILE_WRITE);
    if(!file){
        Serial.println("- failed to open file for writing");
        return;
    }
    if(file.print(message)){
        Serial.println("- file written");
    } else {
        Serial.println("- write failed");
    }
    file.close();
}

void appendFile(fs::FS &fs, const char * path, const char * message){
    Serial.printf("Appending to file: %s\r\n", path);

    File file = fs.open(path, FILE_APPEND);
    if(!file){
        Serial.println("- failed to open file for appending");
        return;
    }
    if(file.print(message)){
        Serial.println("- message appended");
    } else {
        Serial.println("- append failed");
    }
    file.close();
}

void renameFile(fs::FS &fs, const char * path1, const char * path2){
    Serial.printf("Renaming file %s to %s\r\n", path1, path2);
    if (fs.rename(path1, path2)) {
        Serial.println("- file renamed");
    } else {
        Serial.println("- rename failed");
    }
}

void deleteFile(fs::FS &fs, const char * path){
    Serial.printf("Deleting file: %s\r\n", path);
    if(fs.remove(path)){
        Serial.println("- file deleted");
    } else {
        Serial.println("- delete failed");
    }
}

// SPIFFS-like write and delete file, better use #define CONFIG_LITTLEFS_SPIFFS_COMPAT 1

void writeFile2(fs::FS &fs, const char * path, const char * message){
    if(!fs.exists(path)){
        if (strchr(path, '/')) {
            Serial.printf("Create missing folders of: %s\r\n", path);
            char *pathStr = strdup(path);
            if (pathStr) {
                char *ptr = strchr(pathStr, '/');
                while (ptr) {
                    *ptr = 0;
                    fs.mkdir(pathStr);
                    *ptr = '/';
                    ptr = strchr(ptr+1, '/');
                }
            }
            free(pathStr);
        }
    }

    Serial.printf("Writing file to: %s\r\n", path);
    File file = fs.open(path, FILE_WRITE);
    if(!file){
        Serial.println("- failed to open file for writing");
        return;
    }
    if(file.print(message)){
        Serial.println("- file written");
    } else {
        Serial.println("- write failed");
    }
    file.close();
}

void deleteFile2(fs::FS &fs, const char * path){
    Serial.printf("Deleting file and empty folders on path: %s\r\n", path);

    if(fs.remove(path)){
        Serial.println("- file deleted");
    } else {
        Serial.println("- delete failed");
    }

    char *pathStr = strdup(path);
    if (pathStr) {
        char *ptr = strrchr(pathStr, '/');
        if (ptr) {
            Serial.printf("Removing all empty folders on path: %s\r\n", path);
        }
        while (ptr) {
            *ptr = 0;
            fs.rmdir(pathStr);
            ptr = strrchr(pathStr, '/');
        }
        free(pathStr);
    }
}

void testFileIO(fs::FS &fs, const char * path){
    Serial.printf("Testing file I/O with %s\r\n", path);

    static uint8_t buf[512];
    size_t len = 0;
    File file = fs.open(path, FILE_WRITE);
    if(!file){
        Serial.println("- failed to open file for writing");
        return;
    }

    size_t i;
    Serial.print("- writing" );
    uint32_t start = millis();
    for(i=0; i<2048; i++){
        if ((i & 0x001F) == 0x001F){
          Serial.print(".");
        }
        file.write(buf, 512);
    }
    Serial.println("");
    uint32_t end = millis() - start;
    Serial.printf(" - %u bytes written in %u ms\r\n", 2048 * 512, end);
    file.close();

    file = fs.open(path);
    start = millis();
    end = start;
    i = 0;
    if(file && !file.isDirectory()){
        len = file.size();
        size_t flen = len;
        start = millis();
        Serial.print("- reading" );
        while(len){
            size_t toRead = len;
            if(toRead > 512){
                toRead = 512;
            }
            file.read(buf, toRead);
            if ((i++ & 0x001F) == 0x001F){
              Serial.print(".");
            }
            len -= toRead;
        }
        Serial.println("");
        end = millis() - start;
        Serial.printf("- %u bytes read in %u ms\r\n", flen, end);
        file.close();
    } else {
        Serial.println("- failed to open file for reading");
    }
}

void setup(){
    Serial.begin(115200);

#ifdef TWOPART
    if(!LittleFS.begin(FORMAT_LITTLEFS_IF_FAILED, "/lfs2", 5, "part2")){
    Serial.println("part2 Mount Failed");
    return;
    }
    appendFile(LittleFS, "/hello0.txt", "World0!\r\n");
    readFile(LittleFS, "/hello0.txt");
    LittleFS.end();

    Serial.println( "Done with part2, work with the first lfs partition..." );
#endif

    if(!LittleFS.begin(FORMAT_LITTLEFS_IF_FAILED)){
        Serial.println("LittleFS Mount Failed");
        return;
    }
    Serial.println( "SPIFFS-like write file to new path and delete it w/folders" );
    writeFile2(LittleFS, "/new1/new2/new3/hello3.txt", "Hello3");
    listDir(LittleFS, "/", 3);
    deleteFile2(LittleFS, "/new1/new2/new3/hello3.txt");
    
    listDir(LittleFS, "/", 3);
    createDir(LittleFS, "/mydir");
    writeFile(LittleFS, "/mydir/hello2.txt", "Hello2");
    listDir(LittleFS, "/", 1);
    deleteFile(LittleFS, "/mydir/hello2.txt");
    removeDir(LittleFS, "/mydir");
    listDir(LittleFS, "/", 1);
    writeFile(LittleFS, "/hello.txt", "Hello ");
    appendFile(LittleFS, "/hello.txt", "World!\r\n");
    readFile(LittleFS, "/hello.txt");
    renameFile(LittleFS, "/hello.txt", "/foo.txt");
    readFile(LittleFS, "/foo.txt");
    deleteFile(LittleFS, "/foo.txt");
    testFileIO(LittleFS, "/test.txt");
    deleteFile(LittleFS, "/test.txt");
    
    Serial.println( "Test complete" );
}

void loop(){

}
 
Есть. И огромная!
В этом примере я вроде как не нашел пример как посчитать выделенный объем, и возможно тут используется другая библиотека LittleFS.h, не такая, как в моем случае. Но видимо ошибки опираются именно на ваш вариант, и видимо сейчас принудительно используется библиотека, которая устанавливалась вместе с плагином загрузки файлов на есп32 с файловой системой LittleFS. Я этот плагин тоже поставил, но особо не смотрел внутрь, хотя у меня он почему-то не заработал (IDE 1.8.19 специально для совместимости с плагином).
Но и с тем примером, что у вас в сообщении у меня ничего не получается. Вырезал кусок для просмотра каталогов:
Код:
void listDir(fs::FS &fs, const char * dirname, uint8_t levels){
    Serial.printf("Listing directory: %s\r\n", dirname);

    File root = fs.open(dirname);
    if(!root){
        Serial.println("- failed to open directory");
        return;
    }
    if(!root.isDirectory()){
        Serial.println(" - not a directory");
        return;
    }

    File file = root.openNextFile();
    while(file){
        if(file.isDirectory()){
            Serial.print("  DIR : ");
            Serial.println(file.name());
            if(levels){
                listDir(fs, file.path(), levels -1);
            }
        } else {
            Serial.print("  FILE: ");
            Serial.print(file.name());
            Serial.print("\tSIZE: ");
            Serial.println(file.size());
        }
        file = root.openNextFile();
    }
}
Но честно говоря так и не понял, что тут за параметры вызываются (fs::FS &fs, const char * dirname, uint8_t levels)
И посмотрел ту библиотеку, которая с примером идет. Там есть функция для получения инфы про общий объем:
Код:
size_t LittleFSFS::totalBytes()
{
    size_t total,used;
    if(esp_littlefs_info(partitionLabel_, &total, &used)){
        return 0;
    }
    return total;
}

size_t LittleFSFS::usedBytes()
{
    size_t total,used;
    if(esp_littlefs_info(partitionLabel_, &total, &used)){
        return 0;
    }
    return used;
}
Но тоже не понял, как они работают
 
Задается на стадии компилирования программы в настройках IDE.
Это понятно и это везде пишется, в том числе и то, что если параметры не заданы, то устанавливаются по умолчанию. Но если вдруг забыл, что и сколько на какие разделы было выделено? Или нашел на улице плату и решил проверить, что ж там за файловая система )))
 

aZholtikov

Active member
Это понятно и это везде пишется, в том числе и то, что если параметры не заданы, то устанавливаются по умолчанию. Но если вдруг забыл, что и сколько на какие разделы было выделено? Или нашел на улице плату и решил проверить, что ж там за файловая система )))
Даже создатели ESP вряд ли помогут... Гугл в помощь!
 

aZholtikov

Active member
Ну или считать дамп flash и вручную пройтись по адресам. "Силенок" хватит? :)
 

aZholtikov

Active member
Flash в ESP это всего лишь "внешняя железка" которую "виртуально" размечают перед прошивкой. Понять как она размечена "после нахождении ESP на улице" можно только "ручками".
Разбираем скаченный с Flash бинарник и ищем "знакомые буквы".
 
Даже создатели ESP вряд ли помогут
Что вы такое говорите? Я же не зря привел описание файловой системы на оф сайте. Там все это есть. Только у меня примеры не работают. Да и видел я такую реализацию, вернее результат. Все прекрасно выдает в json (видимо для целей автора так было удобней)
 
Разбираем скаченный с Flash бинарник и ищем "знакомые буквы".
Вот, сразу взял пример результата считывания:
Код:
{"type":"LittleFS", "isOk":"true", "totalBytes":"2072576", "usedBytes":"491520","unsupportedFiles":""}
Как видите, тут показывает какая файловая система, сколько выделено и сколько занято.
 

aZholtikov

Active member
Это я знаю. Это работает тогда, И ТОЛЬКО ТОГДА, когда Flash уже размечен и IDE знает как!
Узнать размер выделенный под FS на ESP "найденной на улице" невозможно (простым и не очень пользователями).
 

aZholtikov

Active member
А теперь вопрос - если взять ВАШ код по проверке размера FS... Куда он "зальется"? Не будет ли нарушения FS? Как разметил Flash предыдущий создатель??? В каких областях памяти что где находится???
 
А теперь вопрос - если взять ВАШ код по проверке размера FS... Куда он "зальется"? Не будет ли нарушения FS? Как разметил Flash предыдущий создатель??? В каких областях памяти что где находится???
Конечно зальется во флэш и затрет часть инфы, но как минимум сможет показать файловую систему и общий размер. Разве при заливке скетча форматируется флэш? Даже функция стирания есть с условиями типа только скетч вытереть или только файлы. И когда я перезаливаю скетч, то залитые до этого файлы не стираются.
В любом случае, если бы это была маленькая функция, то я бы заливал её с каждым скетчем плюс название скетча, плюс перечень файлов на флэши, чтоб выводило в монитор порта. Не наклейки же цеплять с надписями, что и где.
 

aZholtikov

Active member
FS как отдельное ВЫДЕЛЕННОЕ И НЕПРИКОСНОВЕННОЕ пространство в ESP нет.
Если бы было просто считать Flash, "развернуть" записанную программу, посмотреть что есть в памяти устройства то не существовали бы "очень отдельные услуги" с "очень отдельной стоимостью" по Reverse engineering.
Ну или китайцев или индусов наймите человек несколько... :)
 
Сверху Снизу