├── .gitignore ├── README.md ├── download_fs.py ├── include └── README ├── lib └── README ├── platformio.ini ├── project_task.png ├── src └── main.cpp └── test └── README /.gitignore: -------------------------------------------------------------------------------- 1 | .pio 2 | .vscode/.browse.c_cpp.db* 3 | .vscode/c_cpp_properties.json 4 | .vscode/launch.json 5 | .vscode/ipch 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ESP32 & ESP8266 Filesystem Downloader Plugin 2 | 3 | ## Description 4 | 5 | This script provides an extension to the available PlatformIO targets / project tasks to download the filesystem (SPIFFS or LittleFS) from a running ESP32 / ESP8266 over the serial bootloader using esptool.py, and mklittlefs / mkspiffs for extracting. 6 | 7 | See original topic at https://community.platformio.org/t/download-or-view-esp8266-file-system/17749. 8 | 9 | The lack of this feature in the core is also tracked in the issue https://github.com/platformio/platform-espressif8266/issues/238 (and related in the ESP32 platform). 10 | 11 | ## Limitations 12 | 13 | * only works with ESP8266 and ESP32 chips. 14 | * only works over the serial bootloader too, downloading the flash content via a JTAG adapter and OpenOCD is not implemented. 15 | 16 | ## Usage 17 | 18 | The extractor can be run by either using the VSCode task "Custom" -> "Download Filesystem". 19 | 20 | ![project task](project_task.png) 21 | 22 | Alternatively one can execute 23 | 24 | ``` 25 | pio run -t downloadfs 26 | ``` 27 | (with optional `-e `) from a [PIO commandline](https://docs.platformio.org/en/latest/integration/ide/vscode.html#platformio-core-cli). 28 | 29 | ## Configuration 30 | The output will be saved, by default, in the "unpacked_fs" of the project. 31 | This folder can be changed by writing `custom_unpack_dir = some_other_dir` in the corresponding platformio.ini environment. 32 | 33 | The paramaters needed for extraction of the file system binary and further decoding are automatically deduced from the settings of the currently selected environment. That means that e.g. for the ESP8266, the settings `board_build.filesystem` and `board_build.ldscript` are respected as normal, and the filesystem type as well as start and end address of the filesystem in flash is extracted from it. For the ESP32, the partition table is also respected. This means that if you have a working project that writes some things to the filesystem, no further configuration of the script should be needed. 34 | 35 | ## Using in a different project 36 | 37 | To add this extractor task functionality to your project, simply copy the `download_fs.py` of this repository to the root of your project (same as platformio.ini) and add 38 | 39 | ```ini 40 | extra_scripts = download_fs.py 41 | ``` 42 | to the `platformio.ini`. After a VSCode restart, the new task should appear. 43 | 44 | ## This Test Firmware 45 | 46 | This repository contains a test firwmare for both ESP8266 and ESP32 chips and the LittleFS and SPIFFS filesystem flavors. The PlatformIO environments 47 | 48 | * `esp8266_spiffs`: ESP8266 (NodeMCUv2) + SPIFFS 49 | * `esp8266_littlefs`: ESP8266 (NodeMCUv2) + LittleFS 50 | * `esp32dev`: ESP32 (ESP32 Dev Module C) + SPIFFS 51 | * `esp32dev`: ESP32 (DOIT ESP32 DEVKIT V1) + LittleFS 52 | 53 | are implemented. The `main.cpp` automatically recognizes which environment it is run in and interacts with the correct filesystem automatically. 54 | 55 | The test firwmare programatically writes the file `hello.txt` in the filesystem with the content `Hello World!` and a suffix depending on the filesystem. 56 | 57 | For testing of this script, please once upload the correct test firmware for your environment to the chip. 58 | 59 | ## Example execution 60 | 61 | As an example, an ESP8266 NodeMCUv2 board is connected to the PC. Commands are executed from the commandline. 62 | 63 | In my case, to have a clean initial setup, I completely erase the flash once by manually doing 64 | 65 | ``` 66 | python C:\Users\\.platformio\packages\tool-esptoolpy\esptool.py erase_flash 67 | ``` 68 | 69 | Then, the test firmware for the ESP8266 + LittleFS combination is uploaded and monitored. 70 | 71 | ``` 72 | pio run -e esp8266_littlefs -t upload -t monitor 73 | ``` 74 | 75 | (alternatively: Execute "Upload and Monitor" task in VSCode in correct environment) 76 | 77 | The output at the bottom should say 78 | 79 | ``` 80 | Wrote 294720 bytes (216563 compressed) at 0x00000000 in 5.7 seconds (effective 415.2 kbit/s)... 81 | 82 | [..] 83 | ================================ [SUCCESS] Took 15.89 seconds ================================ 84 | --- Available filters and text transformations: colorize, debug, default, direct, esp8266_exception_decoder, hexlify, log2file, nocontrol, printable, send_on_enter, time 85 | --- More details at http://bit.ly/pio-monitor-filters 86 | --- Miniterm on COM13 74880,8,N,1 --- 87 | --- Quit: Ctrl+C | Menu: Ctrl+T | Help: Ctrl+T followed by Ctrl+H --- 88 | 89 | ets Jan 8 2013,rst cause:2, boot mode:(3,7) 90 | 91 | load 0x4010f000, len 3584, room 16 92 | tail 0 93 | chksum 0xb0 94 | csum 0xb0 95 | v2843a5ac 96 | ~ld 97 | 98 | Using LittleFS. 99 | Writing file: /hello.txt 100 | - file written 101 | ``` 102 | 103 | Press Ctrl+C to abort the monitor. 104 | 105 | Let us now retrieve the file by doing 106 | 107 | ``` 108 | pio run -e esp8266_littlefs -t downloadfs 109 | ``` 110 | 111 | (alternatively: execute "Custom" -> "Download Filesystem" in VSCode in correct environment) 112 | 113 | Which should output 114 | 115 | ``` 116 | [...] 117 | Entrypoint 118 | Retrieving filesystem info for ESP8266. 119 | FS_START: 0x300000 120 | FS_END: 0x3fa000 121 | FS_PAGE: 0x100 122 | FS_BLOCK: 0x2000 123 | Recognized LittleFS filesystem. 124 | Parsed FS info: FS type FSType.LITTLEFS Start 0x300000 Len 1024000 Page size 256 Block size 8192 Tool: C:\Users\Max\.platformio\packages\tool-mklittlefs\mklittlefs 125 | Executing flash download command. 126 | "c:\users\max\appdata\local\programs\python\python38\python.exe" "C:\Users\Max\.platformio\packages\tool-esptoolpy@1.20800.0\esptool.py" --chip esp8266 --port "COM13" --baud 400000 --before default_reset --after hard_reset read_flash 0x300000 0xfa000 "C:\Users\Max\Documents\PlatformIO\Projects\esp32ftdi\downloaded_fs_0x300000_0xfa000.bin" 127 | esptool.py v2.8 128 | Serial port COM13 129 | Connecting.... 130 | Chip is ESP8266EX 131 | Features: WiFi 132 | Crystal is 26MHz 133 | MAC: 2c:3a:e8:06:17:b1 134 | Uploading stub... 135 | Running stub... 136 | Stub running... 137 | Changing baud rate to 400000 138 | Changed. 139 | 1024000 (100 %) 140 | 1024000 (100 %) 141 | 142 | Read 1024000 bytes at 0x300000 in 27.4 seconds (299.0 kbit/s)... 143 | Hard resetting via RTS pin... 144 | Downloaded filesystem binary. 145 | Download was okay: True. File at: C:\Users\Max\Documents\PlatformIO\Projects\esp32ftdi\downloaded_fs_0x300000_0xfa000.bin 146 | Executing extraction command: "C:\Users\Max\.platformio\packages\tool-mklittlefs\mklittlefs" -b 8192 -p 256 --unpack "unpacked_esp8266_littlefs" "C:\Users\Max\Documents\PlatformIO\Projects\esp32ftdi\downloaded_fs_0x300000_0xfa000.bin" 147 | hello.txt > ./unpacked_esp8266_littlefs/hello.txt size: 27 Bytes 148 | Unpacked filesystem. 149 | Extracted 1 file(s) from filesystem. 150 | ``` 151 | 152 | The file `unpacked_esp8266_littlefs/hello.txt` was recovered and saved in the project folder. 153 | 154 | The content of the file is 155 | 156 | ```text 157 | > type .\unpacked_esp8266_littlefs\hello.txt 158 | Hello from ESP8266 LittleFS 159 | ``` 160 | 161 | Just as the firmware has written it (see `main.cpp`). 162 | -------------------------------------------------------------------------------- /download_fs.py: -------------------------------------------------------------------------------- 1 | # Written by Maximilian Gerhardt 2 | # 29th December 2020 3 | # and Christian Baars, Johann Obermeier 4 | # 2023 / 2024 5 | # License: Apache 6 | # Expanded from functionality provided by PlatformIO's espressif32 and espressif8266 platforms, credited below. 7 | # This script provides functions to download the filesystem (LittleFS) from a running ESP32 / ESP8266 8 | # over the serial bootloader using esptool.py, and mklittlefs for extracting. 9 | # run by either using the VSCode task "Custom" -> "Download Filesystem" 10 | # or by doing 'pio run -t downloadfs' (with optional '-e ') from the commandline. 11 | # output will be saved, by default, in the "unpacked_fs" of the project. 12 | # this folder can be changed by writing 'custom_unpack_dir = some_other_dir' in the corresponding platformio.ini 13 | # environment. 14 | import re 15 | import sys 16 | from os.path import isfile, join 17 | from enum import Enum 18 | import os 19 | import subprocess 20 | import shutil 21 | 22 | Import("env") 23 | platform = env.PioPlatform() 24 | board = env.BoardConfig() 25 | mcu = board.get("build.mcu", "esp32") 26 | 27 | 28 | class FSType(Enum): 29 | LITTLEFS="littlefs" 30 | FATFS="fatfs" 31 | 32 | class FSInfo: 33 | def __init__(self, fs_type, start, length, page_size, block_size): 34 | self.fs_type = fs_type 35 | self.start = start 36 | self.length = length 37 | self.page_size = page_size 38 | self.block_size = block_size 39 | def __repr__(self): 40 | return f"FS type {self.fs_type} Start {hex(self.start)} Len {self.length} Page size {self.page_size} Block size {self.block_size}" 41 | # extract command supposed to be implemented by subclasses 42 | def get_extract_cmd(self, input_file, output_dir): 43 | raise NotImplementedError() 44 | 45 | class FS_Info(FSInfo): 46 | def __init__(self, start, length, page_size, block_size): 47 | self.tool = env["MKFSTOOL"] 48 | self.tool = join(platform.get_package_dir("tool-mklittlefs"), self.tool) 49 | super().__init__(FSType.LITTLEFS, start, length, page_size, block_size) 50 | def __repr__(self): 51 | return f"{self.fs_type} Start {hex(self.start)} Len {hex(self.length)} Page size {hex(self.page_size)} Block size {hex(self.block_size)}" 52 | def get_extract_cmd(self, input_file, output_dir): 53 | return f'"{self.tool}" -b {self.block_size} -s {self.length} -p {self.page_size} --unpack "{output_dir}" "{input_file}"' 54 | 55 | # SPIFFS helpers copied from ESP32, https://github.com/platformio/platform-espressif32/blob/develop/builder/main.py 56 | # Copyright 2014-present PlatformIO 57 | # Licensed under the Apache License, Version 2.0 (the "License"); 58 | 59 | def _parse_size(value): 60 | if isinstance(value, int): 61 | return value 62 | elif value.isdigit(): 63 | return int(value) 64 | elif value.startswith("0x"): 65 | return int(value, 16) 66 | elif value[-1].upper() in ("K", "M"): 67 | base = 1024 if value[-1].upper() == "K" else 1024 * 1024 68 | return int(value[:-1]) * base 69 | return value 70 | 71 | ## FS helpers for ESP8266 72 | # copied from https://github.com/platformio/platform-espressif8266/blob/develop/builder/main.py 73 | # Copyright 2014-present PlatformIO 74 | # Licensed under the Apache License, Version 2.0 (the "License"); 75 | 76 | def _parse_ld_sizes(ldscript_path): 77 | assert ldscript_path 78 | result = {} 79 | # get flash size from LD script path 80 | match = re.search(r"\.flash\.(\d+[mk]).*\.ld", ldscript_path) 81 | if match: 82 | result['flash_size'] = _parse_size(match.group(1)) 83 | 84 | appsize_re = re.compile( 85 | r"irom0_0_seg\s*:.+len\s*=\s*(0x[\da-f]+)", flags=re.I) 86 | filesystem_re = re.compile( 87 | r"PROVIDE\s*\(\s*_%s_(\w+)\s*=\s*(0x[\da-f]+)\s*\)" % "FS" 88 | if "arduino" in env.subst("$PIOFRAMEWORK") 89 | else "SPIFFS", 90 | flags=re.I, 91 | ) 92 | with open(ldscript_path) as fp: 93 | for line in fp.readlines(): 94 | line = line.strip() 95 | if not line or line.startswith("/*"): 96 | continue 97 | match = appsize_re.search(line) 98 | if match: 99 | result['app_size'] = _parse_size(match.group(1)) 100 | continue 101 | match = filesystem_re.search(line) 102 | if match: 103 | result['fs_%s' % match.group(1)] = _parse_size( 104 | match.group(2)) 105 | return result 106 | 107 | def esp8266_fetch_fs_size(env): 108 | ldsizes = _parse_ld_sizes(env.GetActualLDScript()) 109 | for key in ldsizes: 110 | if key.startswith("fs_"): 111 | env[key.upper()] = ldsizes[key] 112 | 113 | assert all([ 114 | k in env 115 | for k in ["FS_START", "FS_END", "FS_PAGE", "FS_BLOCK"] 116 | ]) 117 | 118 | # esptool flash starts from 0 119 | for k in ("FS_START", "FS_END"): 120 | _value = 0 121 | if env[k] < 0x40300000: 122 | _value = env[k] & 0xFFFFF 123 | elif env[k] < 0x411FB000: 124 | _value = env[k] & 0xFFFFFF 125 | _value -= 0x200000 # correction 126 | else: 127 | _value = env[k] & 0xFFFFFF 128 | _value += 0xE00000 # correction 129 | 130 | env[k] = _value 131 | 132 | ## Script interface functions 133 | def parse_partition_table(content): 134 | entries = [e for e in content.split(b'\xaaP') if len(e) > 0] 135 | #print("Partition data:") 136 | for entry in entries: 137 | type = entry[1] 138 | if type in [0x82,0x83]: # SPIFFS or LITTLEFS 139 | offset = int.from_bytes(entry[2:5], byteorder='little', signed=False) 140 | size = int.from_bytes(entry[6:9], byteorder='little', signed=False) 141 | #print("type:",hex(type)) 142 | #print("address:",hex(offset)) 143 | #print("size:",hex(size)) 144 | env["FS_START"] = offset 145 | env["FS_SIZE"] = size 146 | env["FS_PAGE"] = int("0x100", 16) 147 | env["FS_BLOCK"] = int("0x1000", 16) 148 | 149 | def get_partition_table(): 150 | esptoolpy = join(platform.get_package_dir("tool-esptoolpy") or "", "esptool.py") 151 | upload_port = join(env.get("UPLOAD_PORT", "none")) 152 | download_speed = join(str(board.get("download.speed", "115200"))) 153 | if "none" in upload_port: 154 | env.AutodetectUploadPort() 155 | upload_port = join(env.get("UPLOAD_PORT", "none")) 156 | fs_file = join(env["PROJECT_DIR"], "partition_table_from_flash.bin") 157 | esptoolpy_flags = [ 158 | "--chip", mcu, 159 | "--port", upload_port, 160 | "--baud", download_speed, 161 | "--before", "default_reset", 162 | "--after", "hard_reset", 163 | "read_flash", 164 | "0x8000", 165 | "0x1000", 166 | fs_file 167 | ] 168 | esptoolpy_cmd = [env["PYTHONEXE"], esptoolpy] + esptoolpy_flags 169 | try: 170 | returncode = subprocess.call(esptoolpy_cmd, shell=False) 171 | except subprocess.CalledProcessError as exc: 172 | print("Downloading failed with " + str(exc)) 173 | with open(fs_file, mode="rb") as file: 174 | content = file.read() 175 | parse_partition_table(content) 176 | 177 | def get_fs_type_start_and_length(): 178 | platform = env["PIOPLATFORM"] 179 | if platform == "espressif32": 180 | print(f"Retrieving filesystem info for {mcu}.") 181 | get_partition_table() 182 | return FS_Info(env["FS_START"], env["FS_SIZE"], env["FS_PAGE"], env["FS_BLOCK"]) 183 | elif platform == "espressif8266": 184 | print("Retrieving filesystem info for ESP8266.") 185 | filesystem = board.get("build.filesystem", "littlefs") 186 | if filesystem not in ("littlefs"): 187 | print("Unrecognized board_build.filesystem option '" + str(filesystem) + "'.") 188 | env.Exit(1) 189 | # fetching sizes is the same for all filesystems 190 | esp8266_fetch_fs_size(env) 191 | #print("FS_START: " + hex(env["FS_START"])) 192 | #print("FS_SIZE: " + hex(env["FS_END"] - env["FS_START"])) 193 | #print("FS_PAGE: " + hex(env["FS_PAGE"])) 194 | #print("FS_BLOCK: " + hex(env["FS_BLOCK"])) 195 | if filesystem == "littlefs": 196 | print("Recognized LittleFS filesystem.") 197 | return FS_Info(env["FS_START"], env["FS_END"] - env["FS_START"], env["FS_PAGE"], env["FS_BLOCK"]) 198 | else: 199 | print("Unrecongized configuration.") 200 | pass 201 | 202 | def download_fs(fs_info: FSInfo): 203 | print(fs_info) 204 | esptoolpy = join(platform.get_package_dir("tool-esptoolpy") or "", "esptool.py") 205 | upload_port = join(env.get("UPLOAD_PORT", "none")) 206 | download_speed = join(str(board.get("download.speed", "115200"))) 207 | if "none" in upload_port: 208 | env.AutodetectUploadPort() 209 | upload_port = join(env.get("UPLOAD_PORT", "none")) 210 | fs_file = join(env.subst("$BUILD_DIR"), f"downloaded_fs_{hex(fs_info.start)}_{hex(fs_info.length)}.bin") 211 | esptoolpy_flags = [ 212 | "--chip", mcu, 213 | "--port", upload_port, 214 | "--baud", download_speed, 215 | "--before", "default_reset", 216 | "--after", "hard_reset", 217 | "read_flash", 218 | hex(fs_info.start), 219 | hex(fs_info.length), 220 | fs_file 221 | ] 222 | esptoolpy_cmd = [env["PYTHONEXE"], esptoolpy] + esptoolpy_flags 223 | print("Download filesystem image") 224 | try: 225 | returncode = subprocess.call(esptoolpy_cmd, shell=False) 226 | return (True, fs_file) 227 | except subprocess.CalledProcessError as exc: 228 | print("Downloading failed with " + str(exc)) 229 | return (False, "") 230 | 231 | def unpack_fs(fs_info: FSInfo, downloaded_file: str): 232 | # by writing custom_unpack_dir = some_dir in the platformio.ini, one can 233 | # control the unpack directory 234 | unpack_dir = env.GetProjectOption("custom_unpack_dir", "unpacked_fs") 235 | if not os.path.exists(downloaded_file): 236 | print(f"ERROR: {downloaded_file} with filesystem not found, maybe download failed due to download_speed setting being too high.") 237 | assert(0) 238 | try: 239 | if os.path.exists(unpack_dir): 240 | shutil.rmtree(unpack_dir) 241 | except Exception as exc: 242 | print("Exception while attempting to remove the folder '" + str(unpack_dir) + "': " + str(exc)) 243 | if not os.path.exists(unpack_dir): 244 | os.makedirs(unpack_dir) 245 | 246 | cmd = fs_info.get_extract_cmd(downloaded_file, unpack_dir) 247 | print("Unpack files from filesystem image") 248 | try: 249 | returncode = subprocess.call(cmd, shell=True) 250 | return (True, unpack_dir) 251 | except subprocess.CalledProcessError as exc: 252 | print("Unpacking filesystem failed with " + str(exc)) 253 | return (False, "") 254 | 255 | def display_fs(extracted_dir): 256 | # extract command already nicely lists all extracted files. 257 | # no need to display that ourselves. just display a summary 258 | file_count = sum([len(files) for r, d, files in os.walk(extracted_dir)]) 259 | print("Extracted " + str(file_count) + " file(s) from filesystem.") 260 | 261 | def command_download_fs(*args, **kwargs): 262 | info = get_fs_type_start_and_length() 263 | download_ok, downloaded_file = download_fs(info) 264 | unpack_ok, unpacked_dir = unpack_fs(info, downloaded_file) 265 | if unpack_ok is True: 266 | display_fs(unpacked_dir) 267 | 268 | 269 | env.AddCustomTarget( 270 | name="downloadfs", 271 | dependencies=None, 272 | actions=[ 273 | command_download_fs 274 | ], 275 | title="Download Filesystem", 276 | description="Downloads and displays files stored in the target ESP32/ESP8266" 277 | ) 278 | -------------------------------------------------------------------------------- /include/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for project header files. 3 | 4 | A header file is a file containing C declarations and macro definitions 5 | to be shared between several project source files. You request the use of a 6 | header file in your project source file (C, C++, etc) located in `src` folder 7 | by including it, with the C preprocessing directive `#include'. 8 | 9 | ```src/main.c 10 | 11 | #include "header.h" 12 | 13 | int main (void) 14 | { 15 | ... 16 | } 17 | ``` 18 | 19 | Including a header file produces the same results as copying the header file 20 | into each source file that needs it. Such copying would be time-consuming 21 | and error-prone. With a header file, the related declarations appear 22 | in only one place. If they need to be changed, they can be changed in one 23 | place, and programs that include the header file will automatically use the 24 | new version when next recompiled. The header file eliminates the labor of 25 | finding and changing all the copies as well as the risk that a failure to 26 | find one copy will result in inconsistencies within a program. 27 | 28 | In C, the usual convention is to give header files names that end with `.h'. 29 | It is most portable to use only letters, digits, dashes, and underscores in 30 | header file names, and at most one dot. 31 | 32 | Read more about using header files in official GCC documentation: 33 | 34 | * Include Syntax 35 | * Include Operation 36 | * Once-Only Headers 37 | * Computed Includes 38 | 39 | https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html 40 | -------------------------------------------------------------------------------- /lib/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for project specific (private) libraries. 3 | PlatformIO will compile them to static libraries and link into executable file. 4 | 5 | The source code of each library should be placed in a an own separate directory 6 | ("lib/your_library_name/[here are source files]"). 7 | 8 | For example, see a structure of the following two libraries `Foo` and `Bar`: 9 | 10 | |--lib 11 | | | 12 | | |--Bar 13 | | | |--docs 14 | | | |--examples 15 | | | |--src 16 | | | |- Bar.c 17 | | | |- Bar.h 18 | | | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html 19 | | | 20 | | |--Foo 21 | | | |- Foo.c 22 | | | |- Foo.h 23 | | | 24 | | |- README --> THIS FILE 25 | | 26 | |- platformio.ini 27 | |--src 28 | |- main.c 29 | 30 | and a contents of `src/main.c`: 31 | ``` 32 | #include 33 | #include 34 | 35 | int main (void) 36 | { 37 | ... 38 | } 39 | 40 | ``` 41 | 42 | PlatformIO Library Dependency Finder will find automatically dependent 43 | libraries scanning project source files. 44 | 45 | More information about PlatformIO Library Dependency Finder 46 | - https://docs.platformio.org/page/librarymanager/ldf.html 47 | -------------------------------------------------------------------------------- /platformio.ini: -------------------------------------------------------------------------------- 1 | [platformio] 2 | ;default_envs = esp32dev 3 | ;default_envs = esp8266_spiffs 4 | default_envs = esp8266_littlefs 5 | 6 | [env:esp8266_spiffs] 7 | platform = espressif8266 8 | board = nodemcuv2 9 | framework = arduino 10 | monitor_speed = 74880 11 | upload_speed = 400000 12 | board_build.filesystem = spiffs 13 | extra_scripts = download_fs.py 14 | 15 | [env:esp8266_littlefs] 16 | platform = espressif8266 17 | board = nodemcuv2 18 | framework = arduino 19 | monitor_speed = 74880 20 | upload_speed = 400000 21 | board_build.filesystem = littlefs 22 | ; use linker script for 4MB flash, of which 2MB is filesystem 23 | ;board_build.ldscript = eagle.flash.4m2m.ld 24 | ; or default, if commented. 25 | build_flags = -D DO_LITTLEFS 26 | extra_scripts = download_fs.py 27 | ; test extra option for script 28 | custom_unpack_dir = unpacked_esp8266_littlefs 29 | 30 | [env:esp32dev] 31 | platform = espressif32 32 | board = esp32dev 33 | framework = arduino 34 | monitor_speed = 115200 35 | ;board_build.partitions = default.csv 36 | board_build.partitions = min_spiffs.csv 37 | extra_scripts = download_fs.py 38 | ; by default, SPIFFS is assumed. 39 | ; one can still manually use LittleFS by doing a 40 | ;lib_deps = 41 | ; LittleFS_esp32 42 | ; and using these library functions, however, this is not recognized. 43 | ; will be added once LittleFS is in mainstream ESP32 core / library and PlatformIO buildsystem -------------------------------------------------------------------------------- /project_task.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxgerhardt/pio-esp32-esp8266-filesystem-downloader/04032716c14220acc1aa2facf557cc17c8b06577/project_task.png -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #ifdef ARDUINO_ARCH_ESP32 2 | #include "FS.h" 3 | #include "SPIFFS.h" 4 | 5 | /* You only need to format SPIFFS the first time you run a 6 | test or else use the SPIFFS plugin to create a partition 7 | https://github.com/me-no-dev/arduino-esp32fs-plugin */ 8 | #define FORMAT_SPIFFS_IF_FAILED true 9 | 10 | void listDir(fs::FS &fs, const char *dirname, uint8_t levels) 11 | { 12 | Serial.printf("Listing directory: %s\r\n", dirname); 13 | 14 | File root = fs.open(dirname); 15 | if (!root) 16 | { 17 | Serial.println("- failed to open directory"); 18 | return; 19 | } 20 | if (!root.isDirectory()) 21 | { 22 | Serial.println(" - not a directory"); 23 | return; 24 | } 25 | 26 | File file = root.openNextFile(); 27 | while (file) 28 | { 29 | if (file.isDirectory()) 30 | { 31 | Serial.print(" DIR : "); 32 | Serial.println(file.name()); 33 | if (levels) 34 | { 35 | listDir(fs, file.name(), levels - 1); 36 | } 37 | } 38 | else 39 | { 40 | Serial.print(" FILE: "); 41 | Serial.print(file.name()); 42 | Serial.print("\tSIZE: "); 43 | Serial.println(file.size()); 44 | } 45 | file = root.openNextFile(); 46 | } 47 | } 48 | 49 | void readFile(fs::FS &fs, const char *path) 50 | { 51 | Serial.printf("Reading file: %s\r\n", path); 52 | 53 | File file = fs.open(path); 54 | if (!file || file.isDirectory()) 55 | { 56 | Serial.println("- failed to open file for reading"); 57 | return; 58 | } 59 | 60 | Serial.println("- read from file:"); 61 | while (file.available()) 62 | { 63 | Serial.write(file.read()); 64 | } 65 | } 66 | 67 | void writeFile(fs::FS &fs, const char *path, const char *message) 68 | { 69 | Serial.printf("Writing file: %s\r\n", path); 70 | 71 | File file = fs.open(path, FILE_WRITE); 72 | if (!file) 73 | { 74 | Serial.println("- failed to open file for writing"); 75 | return; 76 | } 77 | if (file.print(message)) 78 | { 79 | Serial.println("- file written"); 80 | } 81 | else 82 | { 83 | Serial.println("- frite failed"); 84 | } 85 | } 86 | 87 | void appendFile(fs::FS &fs, const char *path, const char *message) 88 | { 89 | Serial.printf("Appending to file: %s\r\n", path); 90 | 91 | File file = fs.open(path, FILE_APPEND); 92 | if (!file) 93 | { 94 | Serial.println("- failed to open file for appending"); 95 | return; 96 | } 97 | if (file.print(message)) 98 | { 99 | Serial.println("- message appended"); 100 | } 101 | else 102 | { 103 | Serial.println("- append failed"); 104 | } 105 | } 106 | 107 | void renameFile(fs::FS &fs, const char *path1, const char *path2) 108 | { 109 | Serial.printf("Renaming file %s to %s\r\n", path1, path2); 110 | if (fs.rename(path1, path2)) 111 | { 112 | Serial.println("- file renamed"); 113 | } 114 | else 115 | { 116 | Serial.println("- rename failed"); 117 | } 118 | } 119 | 120 | void deleteFile(fs::FS &fs, const char *path) 121 | { 122 | Serial.printf("Deleting file: %s\r\n", path); 123 | if (fs.remove(path)) 124 | { 125 | Serial.println("- file deleted"); 126 | } 127 | else 128 | { 129 | Serial.println("- delete failed"); 130 | } 131 | } 132 | 133 | void testFileIO(fs::FS &fs, const char *path) 134 | { 135 | Serial.printf("Testing file I/O with %s\r\n", path); 136 | 137 | static uint8_t buf[512]; 138 | size_t len = 0; 139 | File file = fs.open(path, FILE_WRITE); 140 | if (!file) 141 | { 142 | Serial.println("- failed to open file for writing"); 143 | return; 144 | } 145 | 146 | size_t i; 147 | Serial.print("- writing"); 148 | uint32_t start = millis(); 149 | for (i = 0; i < 2048; i++) 150 | { 151 | if ((i & 0x001F) == 0x001F) 152 | { 153 | Serial.print("."); 154 | } 155 | file.write(buf, 512); 156 | } 157 | Serial.println(""); 158 | uint32_t end = millis() - start; 159 | Serial.printf(" - %u bytes written in %u ms\r\n", 2048 * 512, end); 160 | file.close(); 161 | 162 | file = fs.open(path); 163 | start = millis(); 164 | end = start; 165 | i = 0; 166 | if (file && !file.isDirectory()) 167 | { 168 | len = file.size(); 169 | size_t flen = len; 170 | start = millis(); 171 | Serial.print("- reading"); 172 | while (len) 173 | { 174 | size_t toRead = len; 175 | if (toRead > 512) 176 | { 177 | toRead = 512; 178 | } 179 | file.read(buf, toRead); 180 | if ((i++ & 0x001F) == 0x001F) 181 | { 182 | Serial.print("."); 183 | } 184 | len -= toRead; 185 | } 186 | Serial.println(""); 187 | end = millis() - start; 188 | Serial.printf("- %u bytes read in %u ms\r\n", flen, end); 189 | file.close(); 190 | } 191 | else 192 | { 193 | Serial.println("- failed to open file for reading"); 194 | } 195 | } 196 | 197 | void setup() 198 | { 199 | Serial.begin(115200); 200 | if (!SPIFFS.begin(FORMAT_SPIFFS_IF_FAILED)) 201 | { 202 | Serial.println("SPIFFS Mount Failed"); 203 | return; 204 | } 205 | 206 | listDir(SPIFFS, "/", 0); 207 | writeFile(SPIFFS, "/hello.txt", "Hello "); 208 | appendFile(SPIFFS, "/hello.txt", "World!\r\n"); 209 | //readFile(SPIFFS, "/hello.txt"); 210 | //renameFile(SPIFFS, "/hello.txt", "/foo.txt"); 211 | //readFile(SPIFFS, "/foo.txt"); 212 | //deleteFile(SPIFFS, "/foo.txt"); 213 | //testFileIO(SPIFFS, "/test.txt"); 214 | //deleteFile(SPIFFS, "/test.txt"); 215 | Serial.println("Test complete"); 216 | } 217 | 218 | void loop() {} 219 | #else /* esp8266 */ 220 | #include 221 | #include "FS.h" 222 | #ifdef DO_LITTLEFS 223 | #include 224 | #define USED_FS LittleFS 225 | #else 226 | #define USED_FS SPIFFS 227 | #endif 228 | void writeFile(fs::FS &fs, const char *path, const char *message) 229 | { 230 | Serial.printf("Writing file: %s\r\n", path); 231 | 232 | File file = fs.open(path, "w+"); 233 | if (!file) 234 | { 235 | Serial.println("- failed to open file for writing"); 236 | return; 237 | } 238 | if (file.print(message)) 239 | { 240 | Serial.println("- file written"); 241 | } 242 | else 243 | { 244 | Serial.println("- frite failed"); 245 | } 246 | } 247 | 248 | void setup() 249 | { 250 | Serial.begin(74880); 251 | Serial.println(); 252 | 253 | #ifdef DO_LITTLEFS 254 | Serial.println("Using LittleFS."); 255 | #else 256 | Serial.println("Using SPIFFS."); 257 | #endif 258 | 259 | if (!USED_FS.begin()) 260 | { 261 | Serial.println("Filesystem Mount Failed"); 262 | if (!USED_FS.format()) 263 | { 264 | Serial.println("FS format failed, too.."); 265 | } 266 | else 267 | { 268 | Serial.println("FS format OK"); 269 | } 270 | } 271 | 272 | #ifdef DO_LITTLEFS 273 | writeFile(USED_FS, "/hello.txt", "Hello from ESP8266 LittleFS"); 274 | #else 275 | writeFile(USED_FS, "/hello.txt", "Hello from ESP8266 SPIFFS"); 276 | #endif 277 | } 278 | void loop() {} 279 | #endif 280 | -------------------------------------------------------------------------------- /test/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for PlatformIO Unit Testing and project tests. 3 | 4 | Unit Testing is a software testing method by which individual units of 5 | source code, sets of one or more MCU program modules together with associated 6 | control data, usage procedures, and operating procedures, are tested to 7 | determine whether they are fit for use. Unit testing finds problems early 8 | in the development cycle. 9 | 10 | More information about PlatformIO Unit Testing: 11 | - https://docs.platformio.org/page/plus/unit-testing.html 12 | --------------------------------------------------------------------------------