25 de outubro de 2021

Do bit Ao Byte

Embarcados, Linux e programação

Sistema de arquivos no ESP8266

arrays dinâmicos em C++ | Interagir com ponteiros | Alocação de memória | Ponteiros em C/C++ | Socket server com ESP8266 | Socket server com ESP32 | Socket server com Python | Socket client com ESP32 | Sistema de arquivos no ESP8266

Sabe quando você começa a tratar de um assunto e de repente está em outro completamente diferente? Bem, eu iniciei esse artigo para tratar de outro assunto. Quando percebi, estava totalmente voltado a sistema de arquivos no ESP8266, então mudei esse primeiro parágrafo para condizer com o conteúdo.

Sistema de arquivos do ESP8266 e ESP32

O sistema de arquivos principal desde o ESP8266 tem sido o SPIFFS, mas atualmente está sendo descontinuado e deixará de funcionar em alguma nova versão do IDF.

O SPIFFS não tem suporte a diretórios reais e tem um baixo overhead. Particularmente, diretórios reais nunca foram um problema para mim, mas certamente deve haver casos em que vocês precisaram do recurso e se depararam com essa limitação.

O LittleFS foi adicionado recentemente. É tão simples de se utilizar quanto o SPIFFS, tem melhor performance e suporte a diretórios, mas assim como os sistemas de arquivos tradicionais para X86, o overhead para arquivos é de 4K, contra 256Bytes de alocação para o SPIFFS. Isso significa que se você criar um arquivo vazio no LittleFS, 4K já foram pro espaço.

O comportamento de File e Dir são o mesmo para ambos. A inicialização do sistema de arquivos também só muda o prefixo, que invés de SPIFFS.begin() deverá ser ser LittleFS.begin(). O resto do código permanece igual, portanto é fácil portar.

Para usar FAT é necessário usar a biblioteca SD.h.

Resumindo: O uso de “/” em qualquer lugar do arquivo no sistema de arquivos SPIFFS fará parte do nome do arquivo, enquanto no LittleFS indica nível de diretório.

Outra limitação do SPIFFS é o tamanho do nome de arquivo, que não deve exceder 31 Bytes. Isso é facilmente atingido quando presumimos a utilização de pseudo-níveis de diretório. No LittleFS também temos a limitação de 31 Bytes para nomes de arquivos, porém podemos criar tantos níveis de diretórios quanto couberem.

Para adicionar o uploader do LittleFS à IDE do Arduino, siga os passos descritos adiante:

  • Baixe o arquivo jar do LittleFS uploader.
  • Crie (caso não exista) o diretório tools no diretório de sketchbooks do Arduino.
  • Extraia o conteúdo do arquivo baixado dentro do diretório tools.
  • Reinicie a IDE do Arduino, caso esteja aberta.
  • Abra ou crie um sketch.
  • Crie um diretório chamado data dentro do diretório de seu novo sketch salvo.
  • Use Tools > ESP8266 Sketch Data Upload. A mensagem de upload deve aparecer no status bar.

Configurando o sistema de arquivos

Podemos (e certamente devemos) preparar o sistema de arquivos antes de acessá-lo. Para torná-lo disponível, o procedimento é semelhante aos sistemas de arquivos no Linux, onde podemos obtê-los através da chamada “montagem”. Claro que não basta reservar uma área para o sistema de arquivos; a região dedicada deve conter o sistema de arquivos propriamente dito, portanto é necessário fazer a formatação. Para iniciar um sistema de arquivos, utilizamos xxxConfig, onde xxx pode ser SPIFFS, SDFS ou LittleFS:

LittleFSConfig cfg;
cfg.setAutoFormat(true);
LittleFS.setConfig(cfg);

Se a configuração não for feita no SPIFFS, o comportamento padrão fará uma autoformatação antes de montá-lo. Já o mesmo não acontece com o SDFS e, confesso, não experimentei fazer de outro modo com o LittleFS. Se já contiver um sistema de arquivos e chamar outro em cima para tentar atualizar sua estrutura, tenha em mente que os dados contidos no sistema de arquivos original serão perdidos.

Após a configuração, podemos então iniciar o sistema de arquivos:

LittleFs.begin();

O mesmo para SPIFFS.

Assim como para iniciar, podemos também fechar o sistema de arquivos, utilizando:

LittleFS.end();

O sistema de arquivos será desmontado e é necessário fazê-lo antes de atualizar um sistema de arquivos usando OTA. Grande dica essa.

Formatando o sistema de arquivos

Supondo que temos montes de arquivos gerados no sistema de arquivos e queremos reiniciar a estrutura. Invés de escrever código para fazer um loop na raiz e apagar os arquivos, podemos simplesmente chamar:

LittleFS.format();

E nem precisamos subir um novo sketch, basta ter uma função pronta que possa ser provocada através de um meio qualquer, seja uma interface web, um socket, um broker MQTT, etc.

Já escrevi DIVERSOS artigos sobre sistemas de arquivos e deixei MUITO código pronto para todos os acessos descritos na documentação oficial, então a partir desse ponto deixo as referências, como o acesso ao sistema de arquivos com ESP-IDF, Esse outro bem detalhado sobre sistema de arquivos no ESP32, esse outro importantíssimo de como ler um arquivo para uma variável, esse aqui, muito legal sobre como transferir uma imagem jpg pela serial para o ESP8266 e ESP32, e basta, hum?

Código para usar LittleFS no ESP8266 e ESP32

Para que não haja a necessidade de adaptações no código dos artigos anteriores, afim de lhes poupar trabalho, deixo um código completo e funcional:

#include <Arduino.h>
#include "FS.h"
#include "LittleFS.h"

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

    File root = fs.open(dirname,"r");
    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.name(), levels -1);
            }
        } else {
            Serial.print("  FILE: ");
            Serial.print(file.name());
            Serial.print("  SIZE: ");
            Serial.println(file.size());
        }
        file = root.openNextFile();
    }
}

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

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

    Serial.print("Read from file: ");
    while(file.available()){
        Serial.write(file.read());
    }
}

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

    File file = fs.open(path, "w");
    if(!file){
        Serial.println("Failed to open file for writing");
        return;
    }
    if(file.print(message)){
        Serial.println("File written");
    } else {
        Serial.println("Write failed");
    }
}

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

    File file = fs.open(path, "a");
    if(!file){
        Serial.println("Failed to open file for appending");
        return;
    }
    if(file.print(message)){
        Serial.println("Message appended");
    } else {
        Serial.println("Append failed");
    }
}

void renameFile(fs::FS &fs, const char * path1, const char * path2){
    Serial.printf("Renaming file %s to %s\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\n", path);
    if(fs.remove(path)){
        Serial.println("File deleted");
    } else {
        Serial.println("Delete failed");
    }
}

void testFileIO(fs::FS &fs, const char * path){
    File file = fs.open(path,"r");
    static uint8_t buf[512];
    size_t len = 0;
    uint32_t start = millis();
    uint32_t end = start;
    if(file && !file.isDirectory()){
        len = file.size();
        size_t flen = len;
        start = millis();
        while(len){
            size_t toRead = len;
            if(toRead > 512){
                toRead = 512;
            }
            file.read(buf, toRead);
            len -= toRead;
        }
        end = millis() - start;
        Serial.printf("%u bytes read for %u ms\n", flen, end);
        file.close();
    } else {
        Serial.println("Failed to open file for reading");
    }


    file = fs.open(path, "w");
    if(!file){
        Serial.println("Failed to open file for writing");
        return;
    }

    size_t i;
    start = millis();
    for(i=0; i<2048; i++){
        file.write(buf, 512);
    }
    end = millis() - start;
    Serial.printf("%u bytes written for %u ms\n", 2048 * 512, end);
    file.close();
}

void setup(){
    LittleFSConfig cfg;
    cfg.setAutoFormat(true);
    LittleFS.setConfig(cfg);

    Serial.begin(9600);
    delay(3000);
    if(!LittleFS.begin()){
        Serial.println("LittleFS Mount Failed");
        return;
    }
    
    listDir(LittleFS, "/", 0);
    writeFile(LittleFS, "/hello.txt", "Hello ");
    appendFile(LittleFS, "/hello.txt", "World!\n");
    readFile(LittleFS, "/hello.txt");
    deleteFile(LittleFS, "/foo.txt");
    renameFile(LittleFS, "/hello.txt", "/foo.txt");
    readFile(LittleFS, "/foo.txt");
    testFileIO(LittleFS, "/test.txt");
}

void loop(){

}

Na primeira inicialização só haverá uma mensagem de escrita, que pode ser vista na serial:

E desconectando e reconectando o ESP, após 3 segundos será mostrado isso:

Espero que tenha gostado do artigo e, por gentileza, inscreva-se em nosso canal DobitaobyteBrasil no Youtube.

O link direto para o vídeo relacionado a esse artigo é esse.

Com todas essas referências não há mais razão para não utilizar esse recurso, certo?

Onde comprar ESP8266?

Claro que sempre vou recomendar a loja de um dos parceiros do blog, não só pela parceria, que nos proporcionaram até o final de 2020 mais de 800 artigos, apreciados já quase 2 milhões de vezes pelos leitores, mas também pela confiabilidade de durante esses anos nunca ter observado uma ocorrência negativa com eles. Então, precisa de um ESP8266 ou ESP32? Ou, precisa de alguma coisa para seus projetos? Verifique a lista de parceiros à direita do nosso blog.

Até a próxima!

 

Revisão: Ricardo Amaral de Andrade