-
Notifications
You must be signed in to change notification settings - Fork 3
FileSystem
Raft mounts a flash-resident file system (LittleFS or SPIFFS) and, optionally, an SD card. The same
FileSystemobject is used by every SysMod, by the WebServer for static-asset delivery, by the DataLogger for autonomous CSV/JSONL capture and by FileManager to expose REST endpoints. This page covers how data gets into a build, how it is mounted at runtime, and which API to call from C++.
The code lives at components/core/FileSystem/ in RaftCore. The single global FileSystem fileSystem; instance is created by RaftCoreApp and configured by the FileManager SysMod using values from your SysType JSON.
A Raft device exposes up to two file systems, identified by name:
| FS name | Mount point | Backing storage | Notes |
|---|---|---|---|
local |
/local |
LittleFS or SPIFFS in the fs partition |
Always present unless explicitly disabled. Alias spiffs accepted for legacy callers. |
sd |
/sd |
FAT-on-SPI SD card | Optional; requires RAFT_ENABLE_SD and pin configuration. |
When a REST API or C++ call passes an empty file-system name, getDefaultFSRoot() picks one. The default is local, but if DefaultSD is set in FileManager configuration and an SD card is present at boot, default operations resolve to sd instead.
The local partition is always labelled fs (with spiffs as a fallback label for older partition tables) — see Partitions and Flash Layout for the layout details.
Raft supports both, selected at build time by the SysType's FS_TYPE value (in features.cmake):
set(FS_TYPE "littlefs") # or "spiffs"This single value drives three things in the build:
-
Which IDF component is linked.
RaftCore/CMakeLists.txtrequireslittlefs(and definesFILE_SYSTEM_SUPPORTS_LITTLEFS) forlittlefs, orspiffs(and definesRAFT_FILESYSTEM_HAS_SPIFFS) otherwise. -
Which image generator is used.
RaftGenFSImage.cmakecallslittlefs_create_partition_image(fs … FLASH_IN_PROJECT)orspiffs_create_partition_image(fs … FLASH_IN_PROJECT)accordingly. -
Which subtype your
partitions.csvmust declare for thefspartition:0x83for LittleFS,spiffsfor SPIFFS. Mismatches cause mount failure on first boot — and a re-format ifLocalFsFormatIfCorruptis set.
Recommendation: prefer LittleFS for new projects. It tolerates power loss, supports nested directories and is the default in current Raft examples. Keep SPIFFS only for legacy partition layouts that cannot be reformatted in the field.
At runtime, FileSystem::localFileSystemSetup() actually probes both — if LittleFS support is compiled in, it tries to mount LittleFS first regardless of LocalFsDefault, and only falls back to SPIFFS if the partition does not look like LittleFS. This means a device that originally shipped with SPIFFS can be migrated to LittleFS by switching FS_TYPE and reflashing.
Anything you want pre-loaded into local at first boot goes into a file-system image that is built and flashed alongside the firmware.
The Raft pipeline accepts content from two places, merged together by CopyFSAndWebUI:
+--------------------------+ +------------------------------+
| FS_IMAGE_PATH (your data | | UI_SOURCE_PATH (Web UI src) |
| files: configs, scripts, | | parcel/npm bundle output |
| audio, calibration .json | | gzipped .html/.js/.css |
| …) | | |
+------------+-------------+ +--------------+---------------+
\\ /
\\ CopyFSAndWebUI /
\\______________________________/
|
v
build/<sys>/raft/FSImage/
|
v
littlefs_create_partition_image(fs FSImage/ FLASH_IN_PROJECT)
|
v
fs.bin (flashed with the rest of the project)
In your SysType's features.cmake:
set(FS_TYPE "littlefs")
set(FS_IMAGE_PATH "../Common/FSImage") # source folder, relative to SysType
set(UI_SOURCE_PATH "../Common/WebUI") # optional; see WebUIBuildPipelineAnything under systypes/Common/FSImage/ (or whatever path you set) is copied verbatim into the file-system image. Typical contents:
systypes/Common/FSImage/
├── config/
│ └── default-pipeline.json ← read by a SysMod at boot
├── certs/
│ └── ca.pem ← MQTT TLS root cert
└── tunes/
├── startup.wav
└── alarm.wav
After flashing, those appear at /local/config/default-pipeline.json, /local/certs/ca.pem, etc.
If a SysType has no Web UI and no data files, leave FS_IMAGE_PATH unset. RaftGenFSImage.cmake is still useful (the partition is created empty) but the fs.bin will be tiny.
To skip image creation entirely, drop the include(${raftcore_SOURCE_DIR}/scripts/RaftGenFSImage.cmake) line from your project's top-level CMakeLists.txt — the partition will be left blank on flash, and the device will format it on first boot if LocalFsFormatIfCorrupt is set.
The FS image must fit the fs partition or the build fails. Typical sizes:
- 256 KB — a few configs, no Web UI.
- 512 KB — the parcel-built React/MUI starter Web UI.
- 1–4 MB — Web UI plus audio/image assets.
See Partitions and Flash Layout to grow the partition.
Set RAFT_ENABLE_SD (a project-level CMake variable, often added to your top-level CMakeLists.txt or features.cmake) to pull in the fatfs and sdmmc ESP-IDF components and define RAFT_FILESYSTEM_HAS_FATSD:
set(RAFT_ENABLE_SD ON CACHE BOOL "" FORCE)Then enable and configure SD pins in your SysType's SysTypes.json:
Pin values can be GPIO numbers ("5") or symbolic pin names resolved by ConfigPinMap (e.g. "SD_CS"). With SDEnabled set the FileManager calls FileSystem::setup(...) with SD parameters; the FileSystem then mounts FAT on the card and exposes it as /sd. Set DefaultSD to make file ops with no FS-name argument resolve to the SD card.
The FileSystem mount sequence happens during the FileManager SysMod's setup(). The sequence is:
-
FileSystem::setup(localFsType, formatIfCorrupt, enableSD, …)is called with SysType-derived parameters. - Local FS is probed:
- LittleFS first (if compiled in). On success,
_localFsType = LOCAL_FS_LITTLEFSand the partition is mounted at/local. - SPIFFS next (if compiled in and LittleFS failed).
- If both fail, the FS is formatted (if
LocalFsFormatIfCorruptis true) and re-tried. - If everything fails,
_localFsType = LOCAL_FS_DISABLEandlocalbecomes unavailable.
- LittleFS first (if compiled in). On success,
- SD is initialised if
SDEnabled. Mount failure is logged but never fatal. - The cache is primed (10 service iterations) so the first
filelistcall returns immediately.
The cache (enabled with CacheFileSysInfo) is critical for serving Web UI listings on resource-constrained boards — getFilesJSON() uses it directly when the request is for the FS root.
All APIs go through the global fileSystem object. The most useful entry points:
#include "FileSystem.h"
// Pass "" as fileSystemStr to use the default FS
// One-shot read (returns a heap-allocated, NUL-terminated buffer; caller frees)
uint8_t* pBuf = fileSystem.getFileContents("", "config/default.json");
if (pBuf) {
// … use as char* …
free(pBuf);
}
// One-shot write
String contents = "key=value";
fileSystem.setFileContents("local", "settings.txt", contents);
// Existence and metadata
uint32_t size = 0;
if (fileSystem.getFileInfo("", "tunes/startup.wav", size)) { … }
// Streamed I/O — preferred for anything that might be large
FILE* pFile = fileSystem.fileOpen("", "log.csv", /*write=*/true, /*seekToPos=*/0, /*seekFromEnd=*/true);
if (pFile) {
fileSystem.fileWrite(pFile, (const uint8_t*)line, lineLen);
fileSystem.fileClose(pFile, "", "log.csv", /*fileModified=*/true);
}
// Reformat (destructive)
String resp;
fileSystem.reformat("local", resp, /*force=*/false);Use FileSystemChunker when reading or writing in fixed-size blocks — the OTA path, the file-download protocol and the DataLogger all use it. The chunker keeps the FILE* open across calls and serialises mutex acquisition on its own.
A few invariants worth knowing:
-
Single mutex.
FileSystemserialises every operation with_fileSysMutex. Long reads or writes block other callers, so chunk large operations rather than holding the file open across manyloop()iterations. -
getFileContentsallocates from PSRAM if available. It returnsnullptrif the file is larger than ~⅓ of the largest available allocation block. For files larger than that, use the streamed API or aFileSystemChunker. -
Filenames may include the FS name.
"/local/foo.txt","local/foo.txt"and"foo.txt"(withfileSystemStr="local") all resolve to the same path.
The FileManager SysMod exposes a thin wrapper for HTTP/RICREST clients. See Built-in REST Endpoints for the full list — filelist, fileread, filedelete, fileupload, reformatfs. Folder paths use ~ instead of / so they survive URL parsing.
| Symptom | Likely cause | Fix |
|---|---|---|
localFileSystemSetup no file system found at boot |
Partition subtype mismatch (spiffs vs 0x83) or partition table not flashed |
Match FS_TYPE to the fs partition subtype, then idf.py erase-flash flash
|
LittleFS image creation failed: too large |
FSImage/ exceeds the fs partition size |
Grow fs in partitions.csv or trim assets |
getContents … too big to read |
File larger than the in-memory threshold | Use fileOpen/fileRead or FileSystemChunker instead |
| First boot wipes the FS image | Mount failed and LocalFsFormatIfCorrupt reformatted |
Verify partition subtype; flash the FS image with idf.py flash (not just app-flash) |
unknownfs sd from filelist
|
SD not enabled or pins wrong | Enable RAFT_ENABLE_SD, set SDEnabled and SPI pins, check card |
fileupload succeeds but file missing |
Cache returned stale listing | Either set CacheFileSysInfo: 0 while debugging, or re-fetch — uploads call markFileCacheDirty automatically |
-
WebUI Build Pipeline — how Web UI assets are merged with
FS_IMAGE_PATHcontent. -
Partitions and Flash Layout — defining the
fspartition. - FileManager SysMod — config keys and REST surface.
-
Built-in REST Endpoints —
filelist,fileread,filedelete,fileupload,reformatfs. - File Download Protocol (OKTO) — outbound file streaming.
- DataLogger — autonomous CSV/JSONL logging onto the same FS.
Getting Started
- Quick Start
- Architecture at a Glance
- Writing Your First SysMod
- Adding a Comms Channel
- Adding an I2C Device Type
- PlatformIO / Arduino
Scaffolding & Building
- Raft CLI
- SysTypes
- Top-Level SysType
- Build Process
- WebUI Build Pipeline
- File System
- Partitions & Flash
- Local Dev Libraries
- Library Developer Guide
Architecture
Built-in SysMods
- NetworkManager
- BLEManager
- WebServer
- MQTTManager
- SerialConsole
- CommandSerial
- CommandSocket
- CommandFile
- FileManager
- LogManager
- ESPOTAUpdate
- StatePublisher
- Remote Logging
- Data Source Registration
Comms & Protocols
- Stack Overview
- Comms Channels
- ProtocolExchange
- RICREST Protocol
- Real-Time Streams
- Adding REST Endpoints
- Built-in REST Endpoints
- File Download (OKTO)
- OTA Update Flow
Devices & Buses
- DeviceManager
- Device Manager REST API
- Device Factory & Classes
- Device Type Records
- Adding an I2C Device Type
- Device Data Publishing
- Data Logger
- I2C Bus
- I2C Device Scanning
- I2C ID & Polling
- MotorControl Overview
- MotorControl Config
- MotorControl Commands
Helpers
Reference
{ "name": "FileManager", "LocalFsDefault": "littlefs", "SDEnabled": 1, "SDMOSI": "23", "SDMISO": "19", "SDCLK": "18", "SDCS": "5", "DefaultSD": 0, "CacheFileSysInfo": 1 }