Relatório Técnico — Módulo de Storage (Armazenamento Local Tolerante a Falhas)
MÓDULO DE STORAGE DO ARES
Section titled “MÓDULO DE STORAGE DO ARES”OBJETIVO
Section titled “OBJETIVO”O módulo de storage é o componente responsável por manter em custódia as mensagens que circulam pela rede do ARES. Como a rede opera segundo o modelo store-carry-forward, um nó nunca pode presumir que existe, no instante em que recebe uma mensagem, um caminho contínuo até o destino. Cabe ao storage guardar essa mensagem de forma durável, controlar a situação em que ela se encontra e entregá-la ao despachante de rádio quando surgir uma janela de contato com o próximo salto. Este documento descreve como esse módulo é construído e como cada uma de suas partes funciona dentro do firmware do nó.
PLATAFORMA E MEIO DE ARMAZENAMENTO
Section titled “PLATAFORMA E MEIO DE ARMAZENAMENTO”Todos os nós utilizam o microcontrolador ESP32-S3, o mesmo já empregado na prova de conceito First Breath. Esse chip reúne processador, memória, rádio Wi-Fi e os periféricos necessários para conversar com outros componentes, o que permite que o hardware seja praticamente idêntico em todos os papéis da rede e que a diferença entre eles resida no firmware (ESPRESSIF SYSTEMS, 2024a). Todo o código do storage, incluindo a lógica de fila, a gravação e a leitura, é executado dentro do próprio ESP32-S3. Não existe nenhum chip ou servidor de banco de dados separado.
Os dados são gravados em um cartão microSD ligado ao ESP32-S3 por um módulo leitor. O cartão não executa lógica alguma: funciona como um arquivo passivo, em que o firmware escreve e do qual lê. A escolha do cartão, em vez da memória flash interna do microcontrolador, separa fisicamente os dados do firmware, oferece capacidade da ordem de gigabytes e permite retirar o cartão para auditoria em um computador comum. A capacidade, inclusive, não representa limitação de projeto: com cerca de 260 bytes por mensagem, conforme a especificação de requisitos do sistema (ARES, 2026a), um buffer de 500.000 mensagens ocupa aproximadamente 130 MB.
A ligação entre o ESP32-S3 e o cartão é feita pelo protocolo SPI, que utiliza quatro fios: um relógio gerado pelo microcontrolador, uma linha de dados de ida, uma linha de dados de volta e uma linha de seleção que indica ao cartão quando ele está sendo endereçado. O cartão ocupa um barramento SPI exclusivo, de modo que não disputa o canal com o módulo de rádio LoRa, que também se comunica por SPI. Existe ainda a alternativa do modo SDMMC, mais veloz, mas que exige pinos específicos do chip (ESPRESSIF SYSTEMS, 2024b); o modo SPI foi preferido por ser aceito pelos módulos leitores comuns e pela biblioteca de gravação utilizada.
FORMATO DE GRAVAÇÃO
Section titled “FORMATO DE GRAVAÇÃO”Os dados são gravados em arquivos no formato SQLite por meio da biblioteca Sqlite µLogger. Essa biblioteca é uma implementação reduzida, pensada para microcontroladores, que escreve registros em arquivos compatíveis com o SQLite padrão usando apenas uma página de memória (512 bytes ou mais) e um pouco de pilha, e que permite recuperar o arquivo após uma queda de energia durante a gravação (RAMANATHAN, 2019a). Como o resultado é um arquivo SQLite convencional, qualquer computador consegue abri-lo depois com ferramentas comuns, o que atende à exigência de registro auditável do sistema.
Essa biblioteca possui uma característica que determina todo o desenho do módulo: ela apenas acrescenta registros ao final do arquivo e não executa comandos SQL. Não é possível, portanto, ordenar as mensagens por prioridade com uma consulta nem alterar o estado de uma linha já gravada. Cada banco, além disso, aceita uma única tabela (RAMANATHAN, 2019a). O armazenamento foi organizado para funcionar dentro dessas condições, a partir do princípio do registro antecipado de escrita, em que o histórico de mudanças é acrescentado sequencialmente e o estado corrente é obtido a partir dele (SQLITE, 2024).
ORGANIZAÇÃO DOS DADOS
Section titled “ORGANIZAÇÃO DOS DADOS”O storage mantém dois conjuntos de arquivos e uma estrutura em memória. O primeiro conjunto é o registro de mensagens, no qual cada mensagem recebida é gravada uma única vez e nunca mais modificada. A tabela contém o identificador único da mensagem, o identificador do nó de origem, a prioridade de 0 a 4, o carimbo de tempo de emissão, o instante de recepção medido pelo relógio local do nó, o número de saltos restantes, a posição geográfica, o texto da mensagem e a assinatura que comprova sua autenticidade. Os campos seguem a estrutura do payload JSON já definida na especificação do ARES, o que mantém coerência entre o que viaja pela rede e o que é guardado.
O segundo conjunto é o registro de eventos, que existe justamente porque o registro de mensagens não pode ser alterado. Toda vez que a situação de uma mensagem muda, um novo registro é acrescentado a esse arquivo contendo o identificador da mensagem, o novo estado, o motivo (quando se trata de um descarte) e o instante em que ocorreu. Os estados persistidos são a custódia aceita, a entrega concluída, a expiração e o descarte. O estado atual de qualquer mensagem é, portanto, o do último evento associado a ela. O estado transitório de mensagem em transmissão não é gravado: se o nó reiniciar no meio de um envio, a mensagem simplesmente volta à fila e é reenviada, o que não causa problema porque a rede foi projetada para tolerar duplicatas.
A terceira peça é o índice em memória RAM. Ele guarda, para cada mensagem em custódia, o identificador, o arquivo e a posição em que ela foi gravada, a prioridade, o estado e o instante de expiração. É esse índice que responde, sem tocar no cartão, se uma mensagem já é conhecida e qual é a próxima a ser enviada. Ele é reconstruído sempre que o nó liga, a partir da leitura dos dois registros, de modo que nenhuma informação importante depende de ter sobrevivido apenas na RAM.
SEPARAÇÃO POR PRIORIDADE
Section titled “SEPARAÇÃO POR PRIORIDADE”O registro de mensagens é dividido em arquivos distintos para cada nível de prioridade, de P0 a P4, e cada arquivo é encerrado e substituído por outro quando atinge um tamanho limite. Essa divisão resolve o problema do descarte. Como o registro só admite acréscimo, apagar uma mensagem isolada não é viável, mas apagar um arquivo inteiro é uma operação simples. Quando todas as mensagens de um arquivo já estão em estado terminal, isto é, entregues ou expiradas, o arquivo é removido, e a remoção é anotada no registro de eventos.
Quando o espaço livre do cartão cai abaixo de um limite de segurança, o descarte começa pelos arquivos de prioridade P4 e avança progressivamente para P3, P2 e P1, sempre do mais antigo ao mais recente. Cada mensagem eliminada dessa forma recebe um evento de descarte com o motivo registrado, de modo que nenhuma mensagem desapareça sem rastro, como exige o requisito RS-04 do sistema. Os arquivos de prioridade P0, que contêm os pedidos de socorro e alertas críticos, nunca são removidos por falta de espaço e só deixam de existir quando suas mensagens são entregues ou expiram. Se, ainda assim, não houver espaço para uma nova mensagem P0, a situação é registrada e sinalizada, mas esse é um caso extremo diante da folga de capacidade descrita acima.
FLUXO DE UMA MENSAGEM
Section titled “FLUXO DE UMA MENSAGEM”Quando um pacote chega pelo rádio, a função de callback do ESP-NOW é acionada. Essa função é executada no contexto da pilha Wi-Fi do ESP-IDF e não pode realizar operações demoradas, pois isso comprometeria a própria recepção de rádio (ESPRESSIF SYSTEMS, 2024c). Foi exatamente esse o problema encontrado no POC-01, quando uma requisição HTTP feita dentro do callback derrubou o gateway (ARES, 2026b). Por isso o callback faz apenas o mínimo: verifica o tamanho do pacote, copia seu conteúdo para um item de tamanho fixo e o coloca em uma fila do FreeRTOS, sem esperar (BARRY, 2016).
Uma tarefa dedicada ao storage retira os itens dessa fila e é a única parte do firmware autorizada a escrever no cartão, o que evita acessos simultâneos ao arquivo e ao barramento SPI. Para cada pacote, a tarefa consulta o índice pelo identificador da mensagem. Se a mensagem já é conhecida, um evento de duplicata é registrado e o pacote é descartado, garantindo a deduplicação exigida pelo requisito RS-09. Se for nova, a tarefa a acrescenta ao arquivo correspondente à sua prioridade, acrescenta o evento de custódia aceita ao registro de eventos e atualiza o índice. Ao final de cada lote de escritas, a tarefa finaliza o registro e força a descarga dos dados para o cartão, porque há relatos de que registros finalizados sem esse passo podem não persistir após um reinício (RAMANATHAN, 2023).
A fila de entrada tem capacidade finita, e se uma rajada de pacotes chegar enquanto o cartão está ocupando uma escrita lenta, os pacotes excedentes são perdidos. Essas perdas são contadas e registradas, e o tamanho da fila é dimensionado para absorver rajadas realistas, já que perder uma mensagem sem registro contraria o requisito de ausência de descarte silencioso.
ENTREGA E EXPIRAÇÃO
Section titled “ENTREGA E EXPIRAÇÃO”O despachante de saída consulta o índice e seleciona a mensagem pendente de maior prioridade e, dentro dessa prioridade, a mais antiga. Quando o rádio informa que existe um vizinho ao alcance, a mensagem é enviada. Ao receber a confirmação de custódia pelo próximo nó, ou a confirmação de entrega pelo gateway, a tarefa de storage registra o evento correspondente e a mensagem deixa de integrar a fila. A entrega é do tipo ao menos uma vez, o que significa que uma mensagem pode chegar mais de uma vez ao destino, e a idempotência do gateway se encarrega de produzir um único registro final.
O instante de expiração de cada mensagem é calculado a partir do momento em que ela foi recebida pelo nó, e não a partir do carimbo de tempo do emissor. Nós sem fixação de sinal GNSS possuem relógios com deriva, e usar a hora declarada pelo emissor tornaria a expiração imprevisível. Quando o prazo se esgota, a mensagem recebe o evento de expiração e é retirada da fila de saída, mas continua registrada para fins de auditoria.
INICIALIZAÇÃO E RECUPERAÇÃO
Section titled “INICIALIZAÇÃO E RECUPERAÇÃO”Como os nós podem ser desligados abruptamente por esgotamento de bateria ou por falha, o módulo parte do princípio de que cada inicialização pode ser uma recuperação. Ao ligar, o firmware executa a rotina de recuperação da biblioteca sobre os arquivos que não foram finalizados corretamente (RAMANATHAN, 2019a), percorre todos os arquivos do registro de mensagens e reaplica o registro de eventos para determinar o estado de cada mensagem. Com isso, o índice é reconstruído e o nó retoma a operação exatamente do ponto em que estava, inclusive retransmitindo as mensagens que não chegaram a ser confirmadas.
DIMENSIONAMENTO DA MEMÓRIA
Section titled “DIMENSIONAMENTO DA MEMÓRIA”O índice em RAM é o único componente cujo tamanho cresce com a quantidade de mensagens. Estimando cerca de 24 bytes por entrada, ele ocupa aproximadamente 48 KB para 2.000 mensagens, meta dos nós de usuário, valor que cabe na RAM interna do ESP32-S3. Para 50.000 mensagens, meta dos drones, o índice chega a cerca de 1,2 MB, o que exige uma variante do chip com memória PSRAM adicional (ESPRESSIF SYSTEMS, 2024d). Para 500.000 mensagens, meta do gateway, a estimativa chega a aproximadamente 12 MB, o que indica que o gateway precisará de hardware de maior capacidade ou de um índice parcial mantido no cartão. Esses números são estimativas feitas a partir da estrutura descrita neste documento e precisam ser confirmados por medição.
PONTOS A VALIDAR EM BANCADA
Section titled “PONTOS A VALIDAR EM BANCADA”Alguns aspectos só poderão ser confirmados com o hardware em mãos. A biblioteca Sqlite µLogger declara testes em placas ESP32 com cartão SD, sem mencionar o ESP32-S3 explicitamente, de modo que sua compilação e funcionamento nesse chip devem ser verificados (RAMANATHAN, 2019a). Também é necessário medir o tempo de escrita no cartão e seu efeito sobre a fila de entrada, tendo em vista a meta de 500 ms para mensagens P0, bem como o tempo de reconstrução do índice na inicialização e o comportamento do arquivo após um corte de energia durante uma gravação.
Há ainda uma questão a resolver no formato da mensagem. A estimativa de 260 bytes ultrapassa o limite de 250 bytes por pacote da versão 1.0 do ESP-NOW. A versão 2.0, disponível a partir do ESP-IDF 5.4, aceita até 1470 bytes (ESPRESSIF SYSTEMS, 2024c), de modo que é preciso verificar qual versão do ESP-IDF acompanha o núcleo Arduino-ESP32 em uso, ou então reduzir ou fragmentar a mensagem.
REFERÊNCIAS
Section titled “REFERÊNCIAS”ARES. Especificação de Requisitos do Sistema, versão 0.1, Seção 8.2 (dimensionamento de buffer). Documento interno, 2026a.
ARES. Documentação do POC-01 “Operação First Breath” e relatório do incidente ESP-NOW/HTTP de 24 de agosto de 2026. Documento interno, 2026b.
BARRY, R. Mastering the FreeRTOS Real Time Kernel. 2016. Disponível em: https://www.freertos.org/Documentation/RTOS_book.html
ESPRESSIF SYSTEMS. ESP32-S3 Series Datasheet. 2024a. Disponível em: https://www.espressif.com/sites/default/files/documentation/esp32-s3_datasheet_en.pdf
ESPRESSIF SYSTEMS. SD/SDIO/MMC Driver. ESP-IDF Programming Guide, ESP32-S3. 2024b. Disponível em: https://docs.espressif.com/projects/esp-idf/en/latest/esp32s3/api-reference/storage/sdmmc.html
ESPRESSIF SYSTEMS. ESP-NOW. ESP-IDF Programming Guide, v5.4, ESP32-S3. 2024c. Disponível em: https://docs.espressif.com/projects/esp-idf/en/release-v5.4/esp32s3/api-reference/network/esp_now.html
ESPRESSIF SYSTEMS. ESP32-S3 Series Datasheet (variantes com PSRAM). 2024d. Disponível em: https://www.espressif.com/sites/default/files/documentation/esp32-s3_datasheet_en.pdf
RAMANATHAN, A. Sqlite µLogger for Arduino. GitHub, 2019a. Disponível em: https://github.com/siara-cc/sqlite_micro_logger_arduino
RAMANATHAN, A. Issue #10: dados não persistidos após dblog_finalize sem fechamento do arquivo. Repositório sqlite_micro_logger_arduino, 2023. Disponível em: https://github.com/siara-cc/sqlite_micro_logger_arduino/issues/10
SQLITE. Write-Ahead Logging. 2024. Disponível em: https://sqlite.org/wal.html