mirror of
https://github.com/espressif/esp-idf.git
synced 2024-10-05 20:47:46 -04:00
Merge branch 'feature/spiffs_image_generator' into 'master'
SPIFFS Image Generator See merge request idf/esp-idf!4156
This commit is contained in:
commit
d52ecb71d6
45
components/spiffs/Makefile.projbuild
Normal file
45
components/spiffs/Makefile.projbuild
Normal file
@ -0,0 +1,45 @@
|
||||
SPIFFSGEN_PY:=$(COMPONENT_PATH)/spiffsgen.py
|
||||
SPIFFSGEN_FLASH_IN_PROJECT=
|
||||
|
||||
ifdef CONFIG_SPIFFS_USE_MAGIC
|
||||
USE_MAGIC = "--use-magic"
|
||||
else
|
||||
USE_MAGIC = ""
|
||||
endif
|
||||
|
||||
ifdef CONFIG_SPIFFS_USE_MAGIC_LENGTH
|
||||
USE_MAGIC_LEN = "--use-magic-len"
|
||||
else
|
||||
USE_MAGIC_LEN = ""
|
||||
endif
|
||||
|
||||
# spiffs_create_partition_image
|
||||
#
|
||||
# Create a spiffs image of the specified directory on the host during build and optionally
|
||||
# have the created image flashed using `make flash`
|
||||
define spiffs_create_partition_image
|
||||
|
||||
|
||||
$(1)_bin: $(PARTITION_TABLE_BIN) | check_python_dependencies
|
||||
partition_size=`$(GET_PART_INFO) --partition-name $(1) \
|
||||
--partition-table-file $(PARTITION_TABLE_BIN) \
|
||||
get_partition_info --info size`; \
|
||||
$(PYTHON) $(SPIFFSGEN_PY) $$$$partition_size $(2) $(BUILD_DIR_BASE)/$(1).bin \
|
||||
--page-size=$(CONFIG_SPIFFS_PAGE_SIZE) \
|
||||
--obj-name-len=$(CONFIG_SPIFFS_OBJ_NAME_LEN) \
|
||||
--meta-len=$(CONFIG_SPIFFS_META_LENGTH) \
|
||||
$(USE_MAGIC) \
|
||||
$(USE_MAGIC_LEN)
|
||||
|
||||
all_binaries: $(1)_bin
|
||||
print_flash_cmd: $(1)_bin
|
||||
|
||||
# Append the created binary to esptool_py args if FLASH_IN_PROJECT is set
|
||||
ifeq ($(3), FLASH_IN_PROJECT)
|
||||
SPIFFSGEN_FLASH_IN_PROJECT += $(1)
|
||||
endif
|
||||
endef
|
||||
|
||||
ESPTOOL_ALL_FLASH_ARGS += $(foreach partition,$(SPIFFSGEN_FLASH_IN_PROJECT), \
|
||||
$(shell $(GET_PART_INFO) --partition-name $(partition) \
|
||||
--partition-table-file $(PARTITION_TABLE_BIN) get_partition_info --info offset) $(BUILD_DIR_BASE)/$(partition).bin)
|
46
components/spiffs/project_include.cmake
Normal file
46
components/spiffs/project_include.cmake
Normal file
@ -0,0 +1,46 @@
|
||||
# spiffs_create_partition_image
|
||||
#
|
||||
# Create a spiffs image of the specified directory on the host during build and optionally
|
||||
# have the created image flashed using `idf.py flash`
|
||||
function(spiffs_create_partition_image partition base_dir)
|
||||
set(options FLASH_IN_PROJECT)
|
||||
cmake_parse_arguments(arg "${options}" "" "" "${ARGN}")
|
||||
|
||||
set(spiffsgen_py ${PYTHON} ${IDF_PATH}/components/spiffs/spiffsgen.py)
|
||||
|
||||
get_filename_component(base_dir_full_path ${base_dir} ABSOLUTE)
|
||||
|
||||
partition_table_get_partition_info(size "--partition-name ${partition}" "size")
|
||||
partition_table_get_partition_info(offset "--partition-name ${partition}" "offset")
|
||||
|
||||
set(image_file ${CMAKE_BINARY_DIR}/${partition}.bin)
|
||||
|
||||
if(CONFIG_SPIFFS_USE_MAGIC)
|
||||
set(use_magic "--use-magic")
|
||||
endif()
|
||||
|
||||
if(CONFIG_SPIFFS_USE_MAGIC_LENGTH)
|
||||
set(use_magic_len "--use-magic-len")
|
||||
endif()
|
||||
|
||||
# Execute SPIFFS image generation; this always executes as there is no way to specify for CMake to watch for
|
||||
# contents of the base dir changing.
|
||||
add_custom_target(spiffs_${partition}_bin ALL
|
||||
COMMAND ${spiffsgen_py} ${size} ${base_dir_full_path} ${image_file}
|
||||
--page-size=${CONFIG_SPIFFS_PAGE_SIZE}
|
||||
--obj-name-len=${CONFIG_SPIFFS_OBJ_NAME_LEN}
|
||||
--meta-len=${CONFIG_SPIFFS_META_LENGTH}
|
||||
${use_magic}
|
||||
${use_magic_len}
|
||||
)
|
||||
|
||||
set_property(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" APPEND PROPERTY
|
||||
ADDITIONAL_MAKE_CLEAN_FILES
|
||||
${image_file})
|
||||
|
||||
if(arg_FLASH_IN_PROJECT)
|
||||
esptool_py_flash_project_args(${partition} ${offset} ${image_file} FLASH_IN_PROJECT)
|
||||
else()
|
||||
esptool_py_flash_project_args(${partition} ${offset} ${image_file})
|
||||
endif()
|
||||
endfunction()
|
527
components/spiffs/spiffsgen.py
Executable file
527
components/spiffs/spiffsgen.py
Executable file
@ -0,0 +1,527 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# spiffsgen is a tool used to generate a spiffs image from a directory
|
||||
#
|
||||
# Copyright 2019 Espressif Systems (Shanghai) PTE LTD
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http:#www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from __future__ import division
|
||||
import os
|
||||
import io
|
||||
import math
|
||||
import struct
|
||||
import argparse
|
||||
import ctypes
|
||||
|
||||
SPIFFS_PH_FLAG_USED_FINAL_INDEX = 0xF8
|
||||
SPIFFS_PH_FLAG_USED_FINAL = 0xFC
|
||||
|
||||
SPIFFS_PH_FLAG_LEN = 1
|
||||
SPIFFS_PH_IX_SIZE_LEN = 4
|
||||
SPIFFS_PH_IX_OBJ_TYPE_LEN = 1
|
||||
SPIFFS_TYPE_FILE = 1
|
||||
|
||||
# Based on typedefs under spiffs_config.h
|
||||
SPIFFS_OBJ_ID_LEN = 2 # spiffs_obj_id
|
||||
SPIFFS_SPAN_IX_LEN = 2 # spiffs_span_ix
|
||||
SPIFFS_PAGE_IX_LEN = 2 # spiffs_page_ix
|
||||
SPIFFS_BLOCK_IX_LEN = 2 # spiffs_block_ix
|
||||
|
||||
|
||||
class SpiffsBuildConfig():
|
||||
def __init__(self, page_size, page_ix_len, block_size,
|
||||
block_ix_len, meta_len, obj_name_len, obj_id_len,
|
||||
span_ix_len, packed, aligned, endianness, use_magic, use_magic_len):
|
||||
if block_size % page_size != 0:
|
||||
raise RuntimeError("block size should be a multiple of page size")
|
||||
|
||||
self.page_size = page_size
|
||||
self.block_size = block_size
|
||||
self.obj_id_len = obj_id_len
|
||||
self.span_ix_len = span_ix_len
|
||||
self.packed = packed
|
||||
self.aligned = aligned
|
||||
self.obj_name_len = obj_name_len
|
||||
self.meta_len = meta_len
|
||||
self.page_ix_len = page_ix_len
|
||||
self.block_ix_len = block_ix_len
|
||||
self.endianness = endianness
|
||||
self.use_magic = use_magic
|
||||
self.use_magic_len = use_magic_len
|
||||
|
||||
self.PAGES_PER_BLOCK = self.block_size // self.page_size
|
||||
self.OBJ_LU_PAGES_PER_BLOCK = int(math.ceil(self.block_size / self.page_size * self.obj_id_len / self.page_size))
|
||||
self.OBJ_USABLE_PAGES_PER_BLOCK = self.PAGES_PER_BLOCK - self.OBJ_LU_PAGES_PER_BLOCK
|
||||
|
||||
self.OBJ_LU_PAGES_OBJ_IDS_LIM = self.page_size // self.obj_id_len
|
||||
|
||||
self.OBJ_DATA_PAGE_HEADER_LEN = self.obj_id_len + self.span_ix_len + SPIFFS_PH_FLAG_LEN
|
||||
|
||||
pad = 4 - (4 if self.OBJ_DATA_PAGE_HEADER_LEN % 4 == 0 else self.OBJ_DATA_PAGE_HEADER_LEN % 4)
|
||||
|
||||
self.OBJ_DATA_PAGE_HEADER_LEN_ALIGNED = self.OBJ_DATA_PAGE_HEADER_LEN + pad
|
||||
self.OBJ_DATA_PAGE_HEADER_LEN_ALIGNED_PAD = pad
|
||||
self.OBJ_DATA_PAGE_CONTENT_LEN = self.page_size - self.OBJ_DATA_PAGE_HEADER_LEN
|
||||
|
||||
self.OBJ_INDEX_PAGES_HEADER_LEN = (self.OBJ_DATA_PAGE_HEADER_LEN_ALIGNED + SPIFFS_PH_IX_SIZE_LEN +
|
||||
SPIFFS_PH_IX_OBJ_TYPE_LEN + self.obj_name_len + self.meta_len)
|
||||
self.OBJ_INDEX_PAGES_OBJ_IDS_HEAD_LIM = (self.page_size - self.OBJ_INDEX_PAGES_HEADER_LEN) // self.block_ix_len
|
||||
self.OBJ_INDEX_PAGES_OBJ_IDS_LIM = (self.page_size - self.OBJ_DATA_PAGE_HEADER_LEN_ALIGNED) / self.block_ix_len
|
||||
|
||||
|
||||
class SpiffsFullError(RuntimeError):
|
||||
def __init__(self, message=None):
|
||||
super(SpiffsFullError, self).__init__(message)
|
||||
|
||||
|
||||
class SpiffsPage():
|
||||
_endianness_dict = {
|
||||
"little": "<",
|
||||
"big": ">"
|
||||
}
|
||||
|
||||
_len_dict = {
|
||||
1: "B",
|
||||
2: "H",
|
||||
4: "I",
|
||||
8: "Q"
|
||||
}
|
||||
|
||||
_type_dict = {
|
||||
1: ctypes.c_ubyte,
|
||||
2: ctypes.c_ushort,
|
||||
4: ctypes.c_uint,
|
||||
8: ctypes.c_ulonglong
|
||||
}
|
||||
|
||||
def __init__(self, bix, build_config):
|
||||
self.build_config = build_config
|
||||
self.bix = bix
|
||||
|
||||
|
||||
class SpiffsObjLuPage(SpiffsPage):
|
||||
def __init__(self, bix, build_config):
|
||||
SpiffsPage.__init__(self, bix, build_config)
|
||||
|
||||
self.obj_ids_limit = self.build_config.OBJ_LU_PAGES_OBJ_IDS_LIM
|
||||
self.obj_ids = list()
|
||||
|
||||
def _calc_magic(self, blocks_lim):
|
||||
# Calculate the magic value mirrorring computation done by the macro SPIFFS_MAGIC defined in
|
||||
# spiffs_nucleus.h
|
||||
magic = 0x20140529 ^ self.build_config.page_size
|
||||
if self.build_config.use_magic_len:
|
||||
magic = magic ^ (blocks_lim - self.bix)
|
||||
magic = SpiffsPage._type_dict[self.build_config.obj_id_len](magic)
|
||||
return magic.value
|
||||
|
||||
def register_page(self, page):
|
||||
if not self.obj_ids_limit > 0:
|
||||
raise SpiffsFullError()
|
||||
|
||||
obj_id = (page.obj_id, page.__class__)
|
||||
self.obj_ids.append(obj_id)
|
||||
self.obj_ids_limit -= 1
|
||||
|
||||
def to_binary(self):
|
||||
global test
|
||||
img = b""
|
||||
|
||||
for (obj_id, page_type) in self.obj_ids:
|
||||
if page_type == SpiffsObjIndexPage:
|
||||
obj_id ^= (1 << ((self.build_config.obj_id_len * 8) - 1))
|
||||
img += struct.pack(SpiffsPage._endianness_dict[self.build_config.endianness] +
|
||||
SpiffsPage._len_dict[self.build_config.obj_id_len], obj_id)
|
||||
|
||||
assert(len(img) <= self.build_config.page_size)
|
||||
|
||||
img += b"\xFF" * (self.build_config.page_size - len(img))
|
||||
|
||||
return img
|
||||
|
||||
def magicfy(self, blocks_lim):
|
||||
# Only use magic value if no valid obj id has been written to the spot, which is the
|
||||
# spot taken up by the last obj id on last lookup page. The parent is responsible
|
||||
# for determining which is the last lookup page and calling this function.
|
||||
remaining = self.obj_ids_limit
|
||||
empty_obj_id_dict = {
|
||||
1: 0xFF,
|
||||
2: 0xFFFF,
|
||||
4: 0xFFFFFFFF,
|
||||
8: 0xFFFFFFFFFFFFFFFF
|
||||
}
|
||||
if (remaining >= 2):
|
||||
for i in range(remaining):
|
||||
if i == remaining - 2:
|
||||
self.obj_ids.append((self._calc_magic(blocks_lim), SpiffsObjDataPage))
|
||||
break
|
||||
else:
|
||||
self.obj_ids.append((empty_obj_id_dict[self.build_config.obj_id_len], SpiffsObjDataPage))
|
||||
self.obj_ids_limit -= 1
|
||||
|
||||
|
||||
class SpiffsObjIndexPage(SpiffsPage):
|
||||
def __init__(self, obj_id, span_ix, size, name, build_config):
|
||||
SpiffsPage.__init__(self, 0, build_config)
|
||||
self.obj_id = obj_id
|
||||
self.span_ix = span_ix
|
||||
self.name = name
|
||||
self.size = size
|
||||
|
||||
if self.span_ix == 0:
|
||||
self.pages_lim = self.build_config.OBJ_INDEX_PAGES_OBJ_IDS_HEAD_LIM
|
||||
else:
|
||||
self.pages_lim = self.build_config.OBJ_INDEX_PAGES_OBJ_IDS_LIM
|
||||
|
||||
self.pages = list()
|
||||
|
||||
def register_page(self, page):
|
||||
if not self.pages_lim > 0:
|
||||
raise SpiffsFullError
|
||||
|
||||
self.pages.append(page.offset)
|
||||
self.pages_lim -= 1
|
||||
|
||||
def to_binary(self):
|
||||
obj_id = self.obj_id ^ (1 << ((self.build_config.obj_id_len * 8) - 1))
|
||||
img = struct.pack(SpiffsPage._endianness_dict[self.build_config.endianness] +
|
||||
SpiffsPage._len_dict[self.build_config.obj_id_len] +
|
||||
SpiffsPage._len_dict[self.build_config.span_ix_len] +
|
||||
SpiffsPage._len_dict[SPIFFS_PH_FLAG_LEN],
|
||||
obj_id,
|
||||
self.span_ix,
|
||||
SPIFFS_PH_FLAG_USED_FINAL_INDEX)
|
||||
|
||||
# Add padding before the object index page specific information
|
||||
img += b"\xFF" * self.build_config.OBJ_DATA_PAGE_HEADER_LEN_ALIGNED_PAD
|
||||
|
||||
# If this is the first object index page for the object, add filname, type
|
||||
# and size information
|
||||
if self.span_ix == 0:
|
||||
img += struct.pack(SpiffsPage._endianness_dict[self.build_config.endianness] +
|
||||
SpiffsPage._len_dict[SPIFFS_PH_IX_SIZE_LEN] +
|
||||
SpiffsPage._len_dict[SPIFFS_PH_FLAG_LEN],
|
||||
self.size,
|
||||
SPIFFS_TYPE_FILE)
|
||||
|
||||
img += self.name.encode() + (b"\x00" * ((self.build_config.obj_name_len - len(self.name)) + self.build_config.meta_len))
|
||||
|
||||
# Finally, add the page index of daa pages
|
||||
for page in self.pages:
|
||||
page = page >> int(math.log(self.build_config.page_size, 2))
|
||||
img += struct.pack(SpiffsPage._endianness_dict[self.build_config.endianness] +
|
||||
SpiffsPage._len_dict[self.build_config.page_ix_len], page)
|
||||
|
||||
assert(len(img) <= self.build_config.page_size)
|
||||
|
||||
img += b"\xFF" * (self.build_config.page_size - len(img))
|
||||
|
||||
return img
|
||||
|
||||
|
||||
class SpiffsObjDataPage(SpiffsPage):
|
||||
def __init__(self, offset, obj_id, span_ix, contents, build_config):
|
||||
SpiffsPage.__init__(self, 0, build_config)
|
||||
self.obj_id = obj_id
|
||||
self.span_ix = span_ix
|
||||
self.contents = contents
|
||||
self.offset = offset
|
||||
|
||||
def to_binary(self):
|
||||
img = struct.pack(SpiffsPage._endianness_dict[self.build_config.endianness] +
|
||||
SpiffsPage._len_dict[self.build_config.obj_id_len] +
|
||||
SpiffsPage._len_dict[self.build_config.span_ix_len] +
|
||||
SpiffsPage._len_dict[SPIFFS_PH_FLAG_LEN],
|
||||
self.obj_id,
|
||||
self.span_ix,
|
||||
SPIFFS_PH_FLAG_USED_FINAL)
|
||||
|
||||
img += self.contents
|
||||
|
||||
assert(len(img) <= self.build_config.page_size)
|
||||
|
||||
img += b"\xFF" * (self.build_config.page_size - len(img))
|
||||
|
||||
return img
|
||||
|
||||
|
||||
class SpiffsBlock():
|
||||
def _reset(self):
|
||||
self.cur_obj_index_span_ix = 0
|
||||
self.cur_obj_data_span_ix = 0
|
||||
self.cur_obj_id = 0
|
||||
self.cur_obj_idx_page = None
|
||||
|
||||
def __init__(self, bix, blocks_lim, build_config):
|
||||
self.build_config = build_config
|
||||
self.offset = bix * self.build_config.block_size
|
||||
self.remaining_pages = self.build_config.OBJ_USABLE_PAGES_PER_BLOCK
|
||||
self.pages = list()
|
||||
self.bix = bix
|
||||
|
||||
lu_pages = list()
|
||||
for i in range(self.build_config.OBJ_LU_PAGES_PER_BLOCK):
|
||||
page = SpiffsObjLuPage(self.bix, self.build_config)
|
||||
lu_pages.append(page)
|
||||
|
||||
self.pages.extend(lu_pages)
|
||||
|
||||
self.lu_page_iter = iter(lu_pages)
|
||||
self.lu_page = next(self.lu_page_iter)
|
||||
|
||||
self._reset()
|
||||
|
||||
def _register_page(self, page):
|
||||
if isinstance(page, SpiffsObjDataPage):
|
||||
self.cur_obj_idx_page.register_page(page) # can raise SpiffsFullError
|
||||
|
||||
try:
|
||||
self.lu_page.register_page(page)
|
||||
except SpiffsFullError:
|
||||
self.lu_page = next(self.lu_page_iter)
|
||||
try:
|
||||
self.lu_page.register_page(page)
|
||||
except AttributeError: # no next lookup page
|
||||
# Since the amount of lookup pages is pre-computed at every block instance,
|
||||
# this should never occur
|
||||
raise RuntimeError("invalid attempt to add page to a block when there is no more space in lookup")
|
||||
|
||||
self.pages.append(page)
|
||||
|
||||
def begin_obj(self, obj_id, size, name, obj_index_span_ix=0, obj_data_span_ix=0):
|
||||
if not self.remaining_pages > 0:
|
||||
raise SpiffsFullError()
|
||||
self._reset()
|
||||
|
||||
self.cur_obj_id = obj_id
|
||||
self.cur_obj_index_span_ix = obj_index_span_ix
|
||||
self.cur_obj_data_span_ix = obj_data_span_ix
|
||||
|
||||
page = SpiffsObjIndexPage(obj_id, self.cur_obj_index_span_ix, size, name, self.build_config)
|
||||
self._register_page(page)
|
||||
|
||||
self.cur_obj_idx_page = page
|
||||
|
||||
self.remaining_pages -= 1
|
||||
self.cur_obj_index_span_ix += 1
|
||||
|
||||
def update_obj(self, contents):
|
||||
if not self.remaining_pages > 0:
|
||||
raise SpiffsFullError()
|
||||
page = SpiffsObjDataPage(self.offset + (len(self.pages) * self.build_config.page_size),
|
||||
self.cur_obj_id, self.cur_obj_data_span_ix, contents, self.build_config)
|
||||
|
||||
self._register_page(page)
|
||||
|
||||
self.cur_obj_data_span_ix += 1
|
||||
self.remaining_pages -= 1
|
||||
|
||||
def end_obj(self):
|
||||
self._reset()
|
||||
|
||||
def is_full(self):
|
||||
return self.remaining_pages <= 0
|
||||
|
||||
def to_binary(self, blocks_lim):
|
||||
img = b""
|
||||
|
||||
if self.build_config.use_magic:
|
||||
for (idx, page) in enumerate(self.pages):
|
||||
if idx == self.build_config.OBJ_LU_PAGES_PER_BLOCK - 1:
|
||||
page.magicfy(blocks_lim)
|
||||
img += page.to_binary()
|
||||
else:
|
||||
for page in self.pages:
|
||||
img += page.to_binary()
|
||||
|
||||
assert(len(img) <= self.build_config.block_size)
|
||||
|
||||
img += b"\xFF" * (self.build_config.block_size - len(img))
|
||||
return img
|
||||
|
||||
|
||||
class SpiffsFS():
|
||||
def __init__(self, img_size, build_config):
|
||||
if img_size % build_config.block_size != 0:
|
||||
raise RuntimeError("image size should be a multiple of block size")
|
||||
|
||||
self.img_size = img_size
|
||||
self.build_config = build_config
|
||||
|
||||
self.blocks = list()
|
||||
self.blocks_lim = self.img_size // self.build_config.block_size
|
||||
self.remaining_blocks = self.blocks_lim
|
||||
self.cur_obj_id = 1 # starting object id
|
||||
|
||||
def _create_block(self):
|
||||
if self.is_full():
|
||||
raise SpiffsFullError("the image size has been exceeded")
|
||||
|
||||
block = SpiffsBlock(len(self.blocks), self.blocks_lim, self.build_config)
|
||||
self.blocks.append(block)
|
||||
self.remaining_blocks -= 1
|
||||
return block
|
||||
|
||||
def is_full(self):
|
||||
return self.remaining_blocks <= 0
|
||||
|
||||
def create_file(self, img_path, file_path):
|
||||
contents = None
|
||||
|
||||
if len(img_path) > self.build_config.obj_name_len:
|
||||
raise RuntimeError("object name '%s' too long" % img_path)
|
||||
|
||||
name = img_path
|
||||
|
||||
with open(file_path, "rb") as obj:
|
||||
contents = obj.read()
|
||||
|
||||
stream = io.BytesIO(contents)
|
||||
|
||||
try:
|
||||
block = self.blocks[-1]
|
||||
block.begin_obj(self.cur_obj_id, len(contents), name)
|
||||
except (IndexError, SpiffsFullError):
|
||||
block = self._create_block()
|
||||
block.begin_obj(self.cur_obj_id, len(contents), name)
|
||||
|
||||
contents_chunk = stream.read(self.build_config.OBJ_DATA_PAGE_CONTENT_LEN)
|
||||
|
||||
while contents_chunk:
|
||||
try:
|
||||
block = self.blocks[-1]
|
||||
try:
|
||||
# This can fail because either (1) all the pages in block have been
|
||||
# used or (2) object index has been exhausted.
|
||||
block.update_obj(contents_chunk)
|
||||
except SpiffsFullError:
|
||||
# If its (1), use the outer exception handler
|
||||
if block.is_full():
|
||||
raise SpiffsFullError
|
||||
# If its (2), write another object index page
|
||||
block.begin_obj(self.cur_obj_id, len(contents), name,
|
||||
obj_index_span_ix=block.cur_obj_index_span_ix,
|
||||
obj_data_span_ix=block.cur_obj_data_span_ix)
|
||||
continue
|
||||
except (IndexError, SpiffsFullError):
|
||||
# All pages in the block have been exhausted. Create a new block, copying
|
||||
# the previous state of the block to a new one for the continuation of the
|
||||
# current object
|
||||
prev_block = block
|
||||
block = self._create_block()
|
||||
block.cur_obj_id = prev_block.cur_obj_id
|
||||
block.cur_obj_idx_page = prev_block.cur_obj_idx_page
|
||||
block.cur_obj_data_span_ix = prev_block.cur_obj_data_span_ix
|
||||
block.cur_obj_index_span_ix = prev_block.cur_obj_index_span_ix
|
||||
continue
|
||||
|
||||
contents_chunk = stream.read(self.build_config.OBJ_DATA_PAGE_CONTENT_LEN)
|
||||
|
||||
block.end_obj()
|
||||
|
||||
self.cur_obj_id += 1
|
||||
|
||||
def to_binary(self):
|
||||
img = b""
|
||||
for block in self.blocks:
|
||||
img += block.to_binary(self.blocks_lim)
|
||||
bix = len(self.blocks)
|
||||
if self.build_config.use_magic:
|
||||
# Create empty blocks with magic numbers
|
||||
while self.remaining_blocks > 0:
|
||||
block = SpiffsBlock(bix, self.blocks_lim, self.build_config)
|
||||
img += block.to_binary(self.blocks_lim)
|
||||
self.remaining_blocks -= 1
|
||||
bix += 1
|
||||
else:
|
||||
# Just fill remaining spaces FF's
|
||||
img += "\xFF" * (self.img_size - len(img))
|
||||
return img
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="SPIFFS Image Generator",
|
||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
||||
|
||||
parser.add_argument("image_size",
|
||||
help="Size of the created image")
|
||||
|
||||
parser.add_argument("base_dir",
|
||||
help="Path to directory from which the image will be created")
|
||||
|
||||
parser.add_argument("output_file",
|
||||
help="Created image output file path")
|
||||
|
||||
parser.add_argument("--page-size",
|
||||
help="Logical page size. Set to value same as CONFIG_SPIFFS_PAGE_SIZE.",
|
||||
type=int,
|
||||
default=256)
|
||||
|
||||
parser.add_argument("--block-size",
|
||||
help="Logical block size. Set to the same value as the flash chip's sector size (g_rom_flashchip.sector_size).",
|
||||
type=int,
|
||||
default=4096)
|
||||
|
||||
parser.add_argument("--obj-name-len",
|
||||
help="File full path maximum length. Set to value same as CONFIG_SPIFFS_OBJ_NAME_LEN.",
|
||||
type=int,
|
||||
default=32)
|
||||
|
||||
parser.add_argument("--meta-len",
|
||||
help="File metadata length. Set to value same as CONFIG_SPIFFS_META_LENGTH.",
|
||||
type=int,
|
||||
default=4)
|
||||
|
||||
parser.add_argument("--use-magic",
|
||||
help="Use magic number to create an identifiable SPIFFS image. Specify if CONFIG_SPIFFS_USE_MAGIC.",
|
||||
action="store_true",
|
||||
default=True)
|
||||
|
||||
parser.add_argument("--use-magic-len",
|
||||
help="Use position in memory to create different magic numbers for each block. Specify if CONFIG_SPIFFS_USE_MAGIC_LENGTH.",
|
||||
action="store_true",
|
||||
default=True)
|
||||
|
||||
parser.add_argument("--big-endian",
|
||||
help="Specify if the target architecture is big-endian. If not specified, little-endian is assumed.",
|
||||
action="store_true",
|
||||
default=False)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if not os.path.exists(args.base_dir):
|
||||
raise RuntimeError("given base directory %s does not exist" % args.base_dir)
|
||||
|
||||
with open(args.output_file, "wb") as image_file:
|
||||
image_size = int(args.image_size, 0)
|
||||
spiffs_build_default = SpiffsBuildConfig(args.page_size, SPIFFS_PAGE_IX_LEN,
|
||||
args.block_size, SPIFFS_BLOCK_IX_LEN, args.meta_len,
|
||||
args.obj_name_len, SPIFFS_OBJ_ID_LEN, SPIFFS_SPAN_IX_LEN,
|
||||
True, True, "big" if args.big_endian else "little",
|
||||
args.use_magic, args.use_magic_len)
|
||||
|
||||
spiffs = SpiffsFS(image_size, spiffs_build_default)
|
||||
|
||||
for root, dirs, files in os.walk(args.base_dir):
|
||||
for f in files:
|
||||
full_path = os.path.join(root, f)
|
||||
spiffs.create_file("/" + os.path.relpath(full_path, args.base_dir), full_path)
|
||||
|
||||
image = spiffs.to_binary()
|
||||
|
||||
image_file.write(image)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -68,7 +68,7 @@ $(BUILD_DIR)/$(COMPONENT_LIB): $(OBJ_FILES) $(SDKCONFIG)
|
||||
clean:
|
||||
$(MAKE) -C $(STUBS_LIB_DIR) clean
|
||||
$(MAKE) -C $(SPI_FLASH_SIM_DIR) clean
|
||||
rm -f $(OBJ_FILES) $(TEST_OBJ_FILES) $(TEST_PROGRAM) $(COMPONENT_LIB) partition_table.bin
|
||||
rm -f $(OBJ_FILES) $(TEST_OBJ_FILES) $(TEST_PROGRAM) $(COMPONENT_LIB) partition_table.bin image.bin
|
||||
|
||||
lib: $(BUILD_DIR)/$(COMPONENT_LIB)
|
||||
|
||||
@ -86,7 +86,11 @@ TEST_OBJ_FILES = $(filter %.o, $(TEST_SOURCE_FILES:.cpp=.o) $(TEST_SOURCE_FILES:
|
||||
$(TEST_PROGRAM): lib $(TEST_OBJ_FILES) $(SPI_FLASH_SIM_BUILD_DIR)/$(SPI_FLASH_SIM_LIB) $(STUBS_LIB_BUILD_DIR)/$(STUBS_LIB) partition_table.bin $(SDKCONFIG)
|
||||
g++ $(LDFLAGS) $(CXXFLAGS) -o $@ $(TEST_OBJ_FILES) -L$(BUILD_DIR) -l:$(COMPONENT_LIB) -L$(SPI_FLASH_SIM_BUILD_DIR) -l:$(SPI_FLASH_SIM_LIB) -L$(STUBS_LIB_BUILD_DIR) -l:$(STUBS_LIB)
|
||||
|
||||
test: $(TEST_PROGRAM)
|
||||
# Use spiffs source directory as the test image
|
||||
spiffs_image: ../spiffs $(shell find ../spiffs -type d) $(shell find ../spiffs -type -f -name '*')
|
||||
../spiffsgen.py 2097152 ../spiffs image.bin
|
||||
|
||||
test: $(TEST_PROGRAM) spiffs_image
|
||||
./$(TEST_PROGRAM)
|
||||
|
||||
# Create other necessary targets
|
||||
|
@ -1,6 +1,9 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <sys/mman.h>
|
||||
#include <dirent.h>
|
||||
#include <limits.h>
|
||||
|
||||
#include "esp_partition.h"
|
||||
#include "spiffs.h"
|
||||
@ -11,19 +14,18 @@
|
||||
|
||||
extern "C" void init_spi_flash(const char* chip_size, size_t block_size, size_t sector_size, size_t page_size, const char* partition_bin);
|
||||
|
||||
TEST_CASE("format disk, open file, write and read file", "[spiffs]")
|
||||
static void init_spiffs(spiffs *fs, uint32_t max_files)
|
||||
{
|
||||
init_spi_flash(CONFIG_ESPTOOLPY_FLASHSIZE, CONFIG_WL_SECTOR_SIZE * 16, CONFIG_WL_SECTOR_SIZE, CONFIG_WL_SECTOR_SIZE, "partition_table.bin");
|
||||
|
||||
spiffs fs;
|
||||
spiffs_config cfg;
|
||||
s32_t spiffs_res;
|
||||
|
||||
const esp_partition_t *partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_SPIFFS, "storage");
|
||||
REQUIRE(partition);
|
||||
|
||||
// Configure objects needed by SPIFFS
|
||||
esp_spiffs_t esp_user_data;
|
||||
esp_user_data.partition = partition;
|
||||
fs.user_data = (void*)&esp_user_data;
|
||||
esp_spiffs_t *user_data = (esp_spiffs_t*) calloc(1, sizeof(*user_data));
|
||||
user_data->partition = partition;
|
||||
fs->user_data = (void*)user_data;
|
||||
|
||||
cfg.hal_erase_f = spiffs_api_erase;
|
||||
cfg.hal_read_f = spiffs_api_read;
|
||||
@ -34,34 +36,131 @@ TEST_CASE("format disk, open file, write and read file", "[spiffs]")
|
||||
cfg.phys_erase_block = CONFIG_WL_SECTOR_SIZE;
|
||||
cfg.phys_size = partition->size;
|
||||
|
||||
uint32_t max_files = 5;
|
||||
uint32_t work_sz = cfg.log_page_size * 2;
|
||||
uint8_t *work = (uint8_t*) malloc(work_sz);
|
||||
|
||||
uint32_t fds_sz = max_files * sizeof(spiffs_fd);
|
||||
uint32_t work_sz = cfg.log_page_size * 2;
|
||||
uint8_t *fds = (uint8_t*) malloc(fds_sz);
|
||||
|
||||
#if CONFIG_SPIFFS_CACHE
|
||||
uint32_t cache_sz = sizeof(spiffs_cache) + max_files * (sizeof(spiffs_cache_page)
|
||||
+ cfg.log_page_size);
|
||||
|
||||
uint8_t *work = (uint8_t*) malloc(work_sz);
|
||||
uint8_t *fds = (uint8_t*) malloc(fds_sz);
|
||||
uint8_t *cache = (uint8_t*) malloc(cache_sz);
|
||||
|
||||
s32_t spiffs_res;
|
||||
uint8_t *cache = (uint8_t*) malloc(cache_sz);
|
||||
#else
|
||||
uint32_t cache_sz = 0;
|
||||
uint8_t cache = NULL;
|
||||
#endif
|
||||
|
||||
// Special mounting procedure: mount, format, mount as per
|
||||
// https://github.com/pellepl/spiffs/wiki/Using-spiffs
|
||||
spiffs_res = SPIFFS_mount(&fs, &cfg, work, fds, fds_sz,
|
||||
spiffs_res = SPIFFS_mount(fs, &cfg, work, fds, fds_sz,
|
||||
cache, cache_sz, spiffs_api_check);
|
||||
REQUIRE(spiffs_res == SPIFFS_ERR_NOT_A_FS);
|
||||
|
||||
spiffs_res = SPIFFS_format(&fs);
|
||||
REQUIRE(spiffs_res >= SPIFFS_OK);
|
||||
if (spiffs_res == SPIFFS_ERR_NOT_A_FS) {
|
||||
spiffs_res = SPIFFS_format(fs);
|
||||
REQUIRE(spiffs_res >= SPIFFS_OK);
|
||||
|
||||
spiffs_res = SPIFFS_mount(fs, &cfg, work, fds, fds_sz,
|
||||
cache, cache_sz, spiffs_api_check);
|
||||
}
|
||||
|
||||
spiffs_res = SPIFFS_mount(&fs, &cfg, work, fds, fds_sz,
|
||||
cache, cache_sz, spiffs_api_check);
|
||||
REQUIRE(spiffs_res >= SPIFFS_OK);
|
||||
}
|
||||
|
||||
static void deinit_spiffs(spiffs *fs)
|
||||
{
|
||||
SPIFFS_unmount(fs);
|
||||
|
||||
free(fs->work);
|
||||
free(fs->user_data);
|
||||
free(fs->fd_space);
|
||||
|
||||
#if CONFIG_SPIFFS_CACHE
|
||||
free(fs->cache);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void check_spiffs_files(spiffs *fs, const char *base_path, char* cur_path)
|
||||
{
|
||||
DIR *dir;
|
||||
struct dirent *entry;
|
||||
size_t len = strlen(cur_path);
|
||||
|
||||
if (len == 0) {
|
||||
strcpy(cur_path, base_path);
|
||||
len = strlen(base_path);
|
||||
}
|
||||
|
||||
dir = opendir(cur_path);
|
||||
REQUIRE(dir != 0);
|
||||
|
||||
while ((entry = readdir(dir)) != NULL) {
|
||||
char *name = entry->d_name;
|
||||
if (entry->d_type == DT_DIR) {
|
||||
if (!strcmp(name, ".") || !strcmp(name, ".."))
|
||||
continue;
|
||||
cur_path[len] = '/';
|
||||
strcpy(cur_path + len + 1, name);
|
||||
check_spiffs_files(fs, base_path, cur_path);
|
||||
cur_path[len] = '\0';
|
||||
} else {
|
||||
char path[PATH_MAX];
|
||||
|
||||
// Read the file from host FS
|
||||
strcpy(path, cur_path);
|
||||
strcat(path, "/");
|
||||
strcat(path, name);
|
||||
|
||||
FILE* f = fopen(path , "r");
|
||||
REQUIRE(f);
|
||||
fseek(f, 0, SEEK_END);
|
||||
long sz = ftell(f);
|
||||
fseek(f, 0, SEEK_SET);
|
||||
|
||||
char *f_contents = (char*) malloc(sz);
|
||||
fread(f_contents, 1, sz, f);
|
||||
fclose(f);
|
||||
|
||||
s32_t spiffs_res;
|
||||
|
||||
// Read the file from SPIFFS
|
||||
char *spiffs_path = path + strlen(base_path);
|
||||
spiffs_res = SPIFFS_open(fs, spiffs_path, SPIFFS_RDONLY, 0);
|
||||
REQUIRE(spiffs_res > SPIFFS_OK);
|
||||
|
||||
spiffs_file fd = spiffs_res;
|
||||
|
||||
spiffs_stat stat;
|
||||
spiffs_res = SPIFFS_stat(fs, spiffs_path, &stat);
|
||||
|
||||
char *spiffs_f_contents = (char*) malloc(stat.size);
|
||||
spiffs_res = SPIFFS_read(fs, fd, spiffs_f_contents, stat.size);
|
||||
REQUIRE(spiffs_res == stat.size);
|
||||
|
||||
// Compare the contents
|
||||
REQUIRE(sz == stat.size);
|
||||
|
||||
bool same = memcmp(f_contents, spiffs_f_contents, sz) == 0;
|
||||
REQUIRE(same);
|
||||
|
||||
free(f_contents);
|
||||
free(spiffs_f_contents);
|
||||
}
|
||||
}
|
||||
closedir(dir);
|
||||
}
|
||||
|
||||
TEST_CASE("format disk, open file, write and read file", "[spiffs]")
|
||||
{
|
||||
init_spi_flash(CONFIG_ESPTOOLPY_FLASHSIZE, CONFIG_WL_SECTOR_SIZE * 16, CONFIG_WL_SECTOR_SIZE, CONFIG_WL_SECTOR_SIZE, "partition_table.bin");
|
||||
|
||||
spiffs fs;
|
||||
s32_t spiffs_res;
|
||||
|
||||
init_spiffs(&fs, 5);
|
||||
|
||||
// Open test file
|
||||
spiffs_res = SPIFFS_open(&fs, "test.txt", SPIFFS_O_CREAT | SPIFFS_O_RDWR, 0);
|
||||
spiffs_res = SPIFFS_open(&fs, "test.txt", SPIFFS_O_CREAT | SPIFFS_O_RDWR, 0);
|
||||
REQUIRE(spiffs_res >= SPIFFS_OK);
|
||||
|
||||
// Generate data
|
||||
@ -77,17 +176,15 @@ TEST_CASE("format disk, open file, write and read file", "[spiffs]")
|
||||
*((uint32_t*)(data + i)) = i;
|
||||
}
|
||||
|
||||
s32_t bw;
|
||||
|
||||
// Write data to file
|
||||
spiffs_res = SPIFFS_write(&fs, file, (void*)data, data_size);
|
||||
spiffs_res = SPIFFS_write(&fs, file, (void*)data, data_size);
|
||||
REQUIRE(spiffs_res >= SPIFFS_OK);
|
||||
REQUIRE(spiffs_res == data_size);
|
||||
|
||||
// Set the file object pointer to the beginning
|
||||
spiffs_res = SPIFFS_lseek(&fs, file, 0, SPIFFS_SEEK_SET);
|
||||
REQUIRE(spiffs_res >= SPIFFS_OK);
|
||||
|
||||
|
||||
// Read the file
|
||||
spiffs_res = SPIFFS_read(&fs, file, (void*)read, data_size);
|
||||
REQUIRE(spiffs_res >= SPIFFS_OK);
|
||||
@ -99,9 +196,53 @@ TEST_CASE("format disk, open file, write and read file", "[spiffs]")
|
||||
|
||||
REQUIRE(memcmp(data, read, data_size) == 0);
|
||||
|
||||
// Unmount
|
||||
SPIFFS_unmount(&fs);
|
||||
deinit_spiffs(&fs);
|
||||
|
||||
free(read);
|
||||
free(data);
|
||||
}
|
||||
|
||||
TEST_CASE("can read spiffs image", "[spiffs]")
|
||||
{
|
||||
init_spi_flash(CONFIG_ESPTOOLPY_FLASHSIZE, CONFIG_WL_SECTOR_SIZE * 16, CONFIG_WL_SECTOR_SIZE, CONFIG_WL_SECTOR_SIZE, "partition_table.bin");
|
||||
|
||||
spiffs fs;
|
||||
s32_t spiffs_res;
|
||||
|
||||
const esp_partition_t *partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_SPIFFS, "storage");
|
||||
|
||||
// Write the contents of the image file to partition
|
||||
FILE* img_file = fopen("image.bin", "r");
|
||||
REQUIRE(img_file);
|
||||
|
||||
fseek(img_file, 0, SEEK_END);
|
||||
long img_size = ftell(img_file);
|
||||
fseek(img_file, 0, SEEK_SET);
|
||||
|
||||
char *img = (char*) malloc(img_size);
|
||||
fread(img, 1, img_size, img_file);
|
||||
fclose(img_file);
|
||||
|
||||
REQUIRE(partition->size == img_size);
|
||||
|
||||
esp_partition_erase_range(partition, 0, partition->size);
|
||||
esp_partition_write(partition, 0, img, img_size);
|
||||
|
||||
free(img);
|
||||
|
||||
// Mount the spiffs partition and init filesystem, using the contents of
|
||||
// the image file
|
||||
init_spiffs(&fs, 1024);
|
||||
|
||||
// Check spiffs consistency
|
||||
spiffs_res = SPIFFS_check(&fs);
|
||||
REQUIRE(spiffs_res == SPIFFS_OK);
|
||||
|
||||
char path_buf[PATH_MAX];
|
||||
|
||||
// The image is created from the spiffs source directory. Compare the files in that
|
||||
// directory to the files read from the SPIFFS image.
|
||||
check_spiffs_files(&fs, "../spiffs", path_buf);
|
||||
|
||||
deinit_spiffs(&fs);
|
||||
}
|
@ -10,15 +10,74 @@ It supports wear leveling, file system consistency checks and more.
|
||||
Notes
|
||||
-----
|
||||
|
||||
- Presently, spiffs does not support directories. It produces a flat structure. If SPIFFS is mounted under ``/spiffs`` creating a file with path ``/spiffs/tmp/myfile.txt`` will create a file called ``/tmp/myfile.txt`` in SPIFFS, instead of ``myfile.txt`` under directory ``/spiffs/tmp``.
|
||||
- Currently, SPIFFS does not support directories. It produces a flat structure. If SPIFFS is mounted under ``/spiffs``, then creating a file with path ``/spiffs/tmp/myfile.txt`` will create a file called ``/tmp/myfile.txt`` in SPIFFS, instead of ``myfile.txt`` under directory ``/spiffs/tmp``.
|
||||
- It is not a realtime stack. One write operation might last much longer than another.
|
||||
- Presently, it does not detect or handle bad blocks.
|
||||
- Currently, it does not detect or handle bad blocks.
|
||||
|
||||
Tools
|
||||
-----
|
||||
|
||||
Host-Side tools for creating SPIFS partition images exist and one such tool is `mkspiffs <https://github.com/igrr/mkspiffs>`_.
|
||||
You can use it to create image from a given folder and then flash that image with ``esptool.py``
|
||||
spiffsgen.py
|
||||
^^^^^^^^^^^^
|
||||
|
||||
:component_file:`spiffsgen.py<spiffs/spiffsgen.py>` is a write-only Python SPIFFS implementation used to create filesystem
|
||||
images from the contents of a host folder. To use ``spiffsgen.py``, simply invoke it from your favorite terminal::
|
||||
|
||||
python spiffsgen.py <image_size> <base_dir> <output_file>
|
||||
|
||||
- image_size: size of the partition on which the created SPIFFS image will be flashed to
|
||||
- base_dir: directory to create the SPIFFS image of
|
||||
- output_file: SPIFFS image output file
|
||||
|
||||
Besides the three required arguments: *image_size*, *base_dir* and *output_file*, there are other arguments
|
||||
that control image generation. Documentation on these arguments exist in the tool's help::
|
||||
|
||||
python spiffsgen.py --help
|
||||
|
||||
These optional arguments correspond to possible SPIFFS build configuration.
|
||||
User should make sure that the image is generated with the same arguments/configuration as
|
||||
SPIFFS was built with, else the user ends up with an invalid image. As a guide, the help output indicates the SPIFFS
|
||||
build configuration the argument corresponds to. In cases when these arguments
|
||||
are not specified, the default values shown in the help output are used.
|
||||
|
||||
Once the image has been created, it can be flashed using ``esptool.py`` or ``parttool.py``.
|
||||
|
||||
Aside from invoking ``spiffsgen.py`` standalone, it is also possible to use it directly from the build system by calling
|
||||
``spiffs_create_partition_image``.
|
||||
|
||||
Make::
|
||||
|
||||
$(eval $(call spiffs_create_partition_image,<partition>,<base_dir>,[FLASH_IN_PROJECT]))
|
||||
|
||||
CMake::
|
||||
|
||||
spiffs_create_partition_image(<partition> <base_dir> [FLASH_IN_PROJECT])
|
||||
|
||||
This is more convenient as the build configuration is automatically passed to the tool,
|
||||
ensuring that the image generated is valid for that build. An example of this is while the *image_size* is required
|
||||
for the standalone invocation, only the *partition* name is required when using ``spiffs_create_partition_image`` --
|
||||
the image size is automatically obtained from the project's partition table.
|
||||
It is important to note that due to the differences in structure between the two build systems,
|
||||
when using Make, ``spiffs_create_partition_image`` must be called from the project Makefile;
|
||||
for CMake, it should be called from one of the component CMakeLists.txt. For both build systems, the image will be created in the build directory
|
||||
with filename *partition*.bin.
|
||||
|
||||
Optionally, user can opt to have the image automatically flashed together with the app binaries, partition tables, etc. on
|
||||
``idf.py flash`` or ``make flash`` by specifying ``FLASH_IN_PROJECT``. For example::
|
||||
|
||||
spiffs_create_partition_image(my_spiffs_partition my_folder FLASH_IN_PROJECT)
|
||||
|
||||
If FLASH_IN_PROJECT is not specified, the image is still generated,
|
||||
but user has to flash it manually using ``esptool.py``, ``parttool.py`` or a custom build system target.
|
||||
|
||||
For an example, see :example:`examples/build_system/spiffsgen>`.
|
||||
|
||||
|
||||
mkspiffs
|
||||
^^^^^^^^
|
||||
|
||||
Another tool for creating SPIFS partition images is `mkspiffs <https://github.com/igrr/mkspiffs>`_.
|
||||
Like ``spiffsgen.py``, it can be used to create image from a given folder and then flash that image with ``esptool.py``
|
||||
|
||||
To do that you need to obtain some parameters:
|
||||
|
||||
@ -35,6 +94,27 @@ To flash the image to ESP32 at offset 0x110000::
|
||||
|
||||
python esptool.py --chip esp32 --port [port] --baud [baud] write_flash -z 0x110000 spiffs.bin
|
||||
|
||||
|
||||
Notes on which SPIFFS tool to use
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The two tools presented above offer very similar functionality. There are, however, reasons to prefer one
|
||||
over the other depending on the use case.
|
||||
|
||||
If the intent is to simply generate a SPIFFS image during build, ``spiffsgen.py`` makes it very convenient
|
||||
by providing functions/commands from the build system itself. This makes it easy to generate SPIFFS images
|
||||
that match the build configuration and can be flashed together with the application.
|
||||
Another case for choosing ``spiffsgen.py`` is when the host has no C/C++ compiler available, since ``mkspiffs``
|
||||
requires compilation.
|
||||
|
||||
On the other hand, ``mkspiffs`` offers unpacking SPIFFS images in addition to image generation. This is not
|
||||
possible with ``spiffsgen.py``, at least not yet. There might also be environments where a Python interpreter
|
||||
is not available, but a host compiler is or a pre-compiled ``mkspiffs`` binary
|
||||
can do the job. However, there is no build system integration for ``mkspiffs`` and the user has to
|
||||
do the corresponding work: compiling ``mkspiffs`` during build (if a pre-compiled binary is not used), creating build rules/targets
|
||||
for the output files, passing proper parameters to the tool, etc.
|
||||
|
||||
|
||||
See also
|
||||
--------
|
||||
|
||||
|
6
examples/build_system/spiffsgen/CMakeLists.txt
Normal file
6
examples/build_system/spiffsgen/CMakeLists.txt
Normal file
@ -0,0 +1,6 @@
|
||||
# The following lines of boilerplate have to be in your project's CMakeLists
|
||||
# in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(spiffsgen)
|
15
examples/build_system/spiffsgen/Makefile
Normal file
15
examples/build_system/spiffsgen/Makefile
Normal file
@ -0,0 +1,15 @@
|
||||
|
||||
#
|
||||
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
|
||||
# project subdirectory.
|
||||
#
|
||||
|
||||
PROJECT_NAME := spiffsgen
|
||||
|
||||
include $(IDF_PATH)/make/project.mk
|
||||
|
||||
# Create a SPIFFS image from the contents of the 'spiffs_image' directory
|
||||
# that fits the partition named 'storage'. FLASH_IN_PROJECT indicates that
|
||||
# the generated image should be flashed when the entire project is flashed to
|
||||
# the target with 'make flash'.
|
||||
$(eval $(call spiffs_create_partition_image,storage,spiffs_image,FLASH_IN_PROJECT))
|
59
examples/build_system/spiffsgen/README.md
Normal file
59
examples/build_system/spiffsgen/README.md
Normal file
@ -0,0 +1,59 @@
|
||||
# SPIFFS Image Generation on Build Example
|
||||
|
||||
(See the README.md file in the upper level 'examples' directory for more information about examples.)
|
||||
|
||||
This example demonstrates how to use the SPIFFS image generation tool [spiffsgen.py](../../../components/spiffs/spiffsgen.py) to automatically create a SPIFFS
|
||||
filesystem image from the contents of a host folder during build, with an option of
|
||||
automatically flashing the created image on invocation of `idf.py flash` or `make flash`.
|
||||
For more information, see description of `spiffsgen.py` on the ESP-IDF Programming Guide under API Reference > Storage > SPIFFS Filesystem.
|
||||
|
||||
The following gives an overview of the example:
|
||||
|
||||
1. There is a directory `spiffs_image` from which the SPIFFS filesystem image will be created.
|
||||
|
||||
2. The function `spiffs_create_partition_image` is used to specify that a SPIFFS image
|
||||
should be created during build for the `storage` partition. For CMake, it is called from [the main component's CMakeLists.txt](./main/CMakeLists.txt);
|
||||
for Make, from the [project Makefile](./Makefile). `FLASH_IN_PROJECT` specifies that the created image
|
||||
should be flashed on invocation of `idf.py flash` or `make flash` together with app, bootloader, partition table, etc.
|
||||
For both build systems, the image is created on the example's build directory with the output filename `storage.bin`.
|
||||
|
||||
3. Upon invocation of `idf.py flash monitor` or `make flash monitor`, application loads and
|
||||
finds there is already a valid SPIFFS filesystem in the `storage` partition with files same as those in `spiffs_image` directory. The application is then
|
||||
able to read those files.
|
||||
|
||||
## How to use example
|
||||
|
||||
### Build and flash
|
||||
|
||||
To run the example, type the following command:
|
||||
|
||||
```Makefile
|
||||
# Make
|
||||
make flash monitor
|
||||
```
|
||||
or
|
||||
```CMake
|
||||
# CMake
|
||||
idf.py flash monitor
|
||||
```
|
||||
|
||||
(To exit the serial monitor, type ``Ctrl-]``.)
|
||||
|
||||
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
|
||||
|
||||
## Example output
|
||||
|
||||
Here is the example's console output:
|
||||
|
||||
```
|
||||
...
|
||||
I (10) example: Initializing SPIFFS
|
||||
I (110) example: Partition size: total: 896321, used: 171935
|
||||
I (110) example: Reading hello.txt
|
||||
I (110) example: Read from hello.txt: Hello World!
|
||||
I (110) example: Computing alice.txt MD5 hash
|
||||
I (330) example: Computed MD5 hash of alice.txt: deeb71f585cbb3ae5f7976d5127faf2a
|
||||
I (330) example: SPIFFS unmounted
|
||||
```
|
||||
|
||||
The logic of the example is contained in a [single source file](./main/spiffsgen_example_main.c), and it should be relatively simple to match points in its execution with the log outputs above.
|
34
examples/build_system/spiffsgen/example_test.py
Normal file
34
examples/build_system/spiffsgen/example_test.py
Normal file
@ -0,0 +1,34 @@
|
||||
from __future__ import print_function
|
||||
import os
|
||||
import sys
|
||||
import hashlib
|
||||
|
||||
try:
|
||||
import IDF
|
||||
except ImportError:
|
||||
test_fw_path = os.getenv('TEST_FW_PATH')
|
||||
if test_fw_path and test_fw_path not in sys.path:
|
||||
sys.path.insert(0, test_fw_path)
|
||||
import IDF
|
||||
|
||||
|
||||
@IDF.idf_example_test(env_tag='Example_WIFI')
|
||||
def test_examples_spiffsgen(env, extra_data):
|
||||
# Test with default build configurations
|
||||
dut = env.get_dut('spiffsgen', 'examples/build_system/spiffsgen')
|
||||
dut.start_app()
|
||||
|
||||
base_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'spiffs_image')
|
||||
|
||||
# Expect hello.txt is read successfully
|
||||
with open(os.path.join(base_dir, 'hello.txt'), 'r') as hello_txt:
|
||||
dut.expect('Read from hello.txt: ' + hello_txt.read())
|
||||
|
||||
# Expect alice.txt MD5 hash is computed accurately
|
||||
with open(os.path.join(base_dir, 'sub', 'alice.txt'), 'rb') as alice_txt:
|
||||
alice_md5 = hashlib.md5(alice_txt.read()).hexdigest()
|
||||
dut.expect('Computed MD5 hash of alice.txt: ' + alice_md5)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
test_examples_spiffsgen()
|
10
examples/build_system/spiffsgen/main/CMakeLists.txt
Normal file
10
examples/build_system/spiffsgen/main/CMakeLists.txt
Normal file
@ -0,0 +1,10 @@
|
||||
set(COMPONENT_SRCS "spiffsgen_example_main.c")
|
||||
set(COMPONENT_ADD_INCLUDEDIRS ".")
|
||||
|
||||
register_component()
|
||||
|
||||
# Create a SPIFFS image from the contents of the 'spiffs_image' directory
|
||||
# that fits the partition named 'storage'. FLASH_IN_PROJECT indicates that
|
||||
# the generated image should be flashed when the entire project is flashed to
|
||||
# the target with 'idf.py flash'.
|
||||
spiffs_create_partition_image(storage ../spiffs_image FLASH_IN_PROJECT)
|
4
examples/build_system/spiffsgen/main/component.mk
Normal file
4
examples/build_system/spiffsgen/main/component.mk
Normal file
@ -0,0 +1,4 @@
|
||||
#
|
||||
# "main" pseudo-component makefile.
|
||||
#
|
||||
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
|
130
examples/build_system/spiffsgen/main/spiffsgen_example_main.c
Normal file
130
examples/build_system/spiffsgen/main/spiffsgen_example_main.c
Normal file
@ -0,0 +1,130 @@
|
||||
/* SPIFFS Image Generation on Build Example
|
||||
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <sys/unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include "esp_err.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_spiffs.h"
|
||||
#include "esp32/rom/md5_hash.h"
|
||||
|
||||
static const char *TAG = "example";
|
||||
|
||||
static void read_hello_txt()
|
||||
{
|
||||
ESP_LOGI(TAG, "Reading hello.txt");
|
||||
|
||||
// Open for reading hello.txt
|
||||
FILE* f = fopen("/spiffs/hello.txt", "r");
|
||||
if (f == NULL) {
|
||||
ESP_LOGE(TAG, "Failed to open hello.txt");
|
||||
return;
|
||||
}
|
||||
|
||||
char buf[64];
|
||||
memset(buf, 0, sizeof(buf));
|
||||
fread(buf, 1, sizeof(buf), f);
|
||||
fclose(f);
|
||||
|
||||
// Display the read contents from the file
|
||||
ESP_LOGI(TAG, "Read from hello.txt: %s", buf);
|
||||
}
|
||||
|
||||
static void compute_alice_txt_md5()
|
||||
{
|
||||
ESP_LOGI(TAG, "Computing alice.txt MD5 hash");
|
||||
|
||||
// The file alice.txt lives under a subdirectory, though SPIFFS itself is flat
|
||||
FILE* f = fopen("/spiffs/sub/alice.txt", "r");
|
||||
if (f == NULL) {
|
||||
ESP_LOGE(TAG, "Failed to open alice.txt");
|
||||
return;
|
||||
}
|
||||
|
||||
// Read file and compute the digest chunk by chunk
|
||||
#define MD5_MAX_LEN 16
|
||||
|
||||
char buf[64];
|
||||
struct MD5Context ctx;
|
||||
unsigned char digest[MD5_MAX_LEN];
|
||||
|
||||
MD5Init(&ctx);
|
||||
|
||||
size_t read;
|
||||
|
||||
do {
|
||||
read = fread((void*) buf, 1, sizeof(buf), f);
|
||||
MD5Update(&ctx, (unsigned const char*) buf, read);
|
||||
} while(read == sizeof(buf));
|
||||
|
||||
MD5Final(digest, &ctx);
|
||||
|
||||
// Create a string of the digest
|
||||
char digest_str[MD5_MAX_LEN * 2];
|
||||
|
||||
for (int i = 0; i < MD5_MAX_LEN; i++) {
|
||||
sprintf(&digest_str[i * 2], "%02x", (unsigned int)digest[i]);
|
||||
}
|
||||
|
||||
// For reference, MD5 should be deeb71f585cbb3ae5f7976d5127faf2a
|
||||
ESP_LOGI(TAG, "Computed MD5 hash of alice.txt: %s", digest_str);
|
||||
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
ESP_LOGI(TAG, "Initializing SPIFFS");
|
||||
|
||||
esp_vfs_spiffs_conf_t conf = {
|
||||
.base_path = "/spiffs",
|
||||
.partition_label = NULL,
|
||||
.max_files = 5,
|
||||
.format_if_mount_failed = false
|
||||
};
|
||||
|
||||
// Use settings defined above to initialize and mount SPIFFS filesystem.
|
||||
// Note: esp_vfs_spiffs_register is an all-in-one convenience function.
|
||||
esp_err_t ret = esp_vfs_spiffs_register(&conf);
|
||||
|
||||
if (ret != ESP_OK) {
|
||||
if (ret == ESP_FAIL) {
|
||||
ESP_LOGE(TAG, "Failed to mount or format filesystem");
|
||||
} else if (ret == ESP_ERR_NOT_FOUND) {
|
||||
ESP_LOGE(TAG, "Failed to find SPIFFS partition");
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Failed to initialize SPIFFS (%s)", esp_err_to_name(ret));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
size_t total = 0, used = 0;
|
||||
ret = esp_spiffs_info(NULL, &total, &used);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to get SPIFFS partition information (%s)", esp_err_to_name(ret));
|
||||
} else {
|
||||
ESP_LOGI(TAG, "Partition size: total: %d, used: %d", total, used);
|
||||
}
|
||||
|
||||
/* The following calls demonstrate reading files from the generated SPIFFS
|
||||
* image. The images should contain the same files and contents as the spiffs_image directory.
|
||||
*/
|
||||
|
||||
// Read and display the contents of a small text file (hello.txt)
|
||||
read_hello_txt();
|
||||
|
||||
// Compute and display the MD5 hash of a large text file (alice.txt)
|
||||
compute_alice_txt_md5();
|
||||
|
||||
// All done, unmount partition and disable SPIFFS
|
||||
esp_vfs_spiffs_unregister(NULL);
|
||||
ESP_LOGI(TAG, "SPIFFS unmounted");
|
||||
}
|
6
examples/build_system/spiffsgen/partitions_example.csv
Normal file
6
examples/build_system/spiffsgen/partitions_example.csv
Normal file
@ -0,0 +1,6 @@
|
||||
# Name, Type, SubType, Offset, Size, Flags
|
||||
# Note: if you change the phy_init or app partition offset, make sure to change the offset in Kconfig.projbuild
|
||||
nvs, data, nvs, 0x9000, 0x6000,
|
||||
phy_init, data, phy, 0xf000, 0x1000,
|
||||
factory, app, factory, 0x10000, 1M,
|
||||
storage, data, spiffs, , 0xF0000,
|
|
5
examples/build_system/spiffsgen/sdkconfig.defaults
Normal file
5
examples/build_system/spiffsgen/sdkconfig.defaults
Normal file
@ -0,0 +1,5 @@
|
||||
CONFIG_PARTITION_TABLE_CUSTOM=y
|
||||
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions_example.csv"
|
||||
CONFIG_PARTITION_TABLE_CUSTOM_APP_BIN_OFFSET=0x10000
|
||||
CONFIG_PARTITION_TABLE_FILENAME="partitions_example.csv"
|
||||
CONFIG_APP_OFFSET=0x10000
|
1
examples/build_system/spiffsgen/spiffs_image/hello.txt
Normal file
1
examples/build_system/spiffsgen/spiffs_image/hello.txt
Normal file
@ -0,0 +1 @@
|
||||
Hello World!
|
3736
examples/build_system/spiffsgen/spiffs_image/sub/alice.txt
Normal file
3736
examples/build_system/spiffsgen/spiffs_image/sub/alice.txt
Normal file
File diff suppressed because it is too large
Load Diff
@ -13,6 +13,7 @@ components/app_update/gen_empty_partition.py
|
||||
components/app_update/otatool.py
|
||||
components/partition_table/test_gen_esp32part_host/gen_esp32part_tests.py
|
||||
components/ulp/esp32ulp_mapgen.py
|
||||
components/spiffs/spiffsgen.py
|
||||
docs/check_doc_warnings.sh
|
||||
docs/check_lang_folder_sync.sh
|
||||
docs/gen-kconfig-doc.py
|
||||
|
Loading…
x
Reference in New Issue
Block a user