Merge branch 'feature/enable-datetime-modification-fatfs' into 'master'

fatfsgen.py: enabled date and time for fatfs

Closes IDF-4092

See merge request espressif/esp-idf!17519
This commit is contained in:
Martin Gano 2022-04-08 05:24:09 +08:00
commit d612c71be5
18 changed files with 347 additions and 116 deletions

View File

@ -3,13 +3,14 @@
# SPDX-License-Identifier: Apache-2.0
import os
from datetime import datetime
from typing import Any, List, Optional
from fatfsgen_utils.fat import FAT
from fatfsgen_utils.fatfs_parser import FATFSParser
from fatfsgen_utils.fatfs_state import FATFSState
from fatfsgen_utils.fs_object import Directory
from fatfsgen_utils.utils import (BYTES_PER_DIRECTORY_ENTRY, FAT32, generate_4bytes_random,
from fatfsgen_utils.utils import (BYTES_PER_DIRECTORY_ENTRY, FAT32, FATFS_INCEPTION, generate_4bytes_random,
get_args_for_partition_generator, pad_string, read_filesystem)
@ -29,6 +30,7 @@ class FATFS:
sectors_per_fat: int = 1,
hidden_sectors: int = 0,
long_names_enabled: bool = False,
use_default_datetime: bool = True,
entry_size: int = 32,
num_heads: int = 0xff,
oem_name: str = 'MSDOS5.0',
@ -60,7 +62,8 @@ class FATFS:
sec_per_track=sec_per_track,
long_names_enabled=long_names_enabled,
volume_label=volume_label,
oem_name=oem_name)
oem_name=oem_name,
use_default_datetime=use_default_datetime)
binary_image: bytes = bytearray(
read_filesystem(binary_image_path) if binary_image_path else self.create_empty_fatfs())
self.state.binary_image = binary_image
@ -74,17 +77,28 @@ class FATFS:
fatfs_state=self.state)
self.root_directory.init_directory()
def create_file(self, name: str, extension: str = '', path_from_root: Optional[List[str]] = None) -> None:
def create_file(self, name: str,
extension: str = '',
path_from_root: Optional[List[str]] = None,
object_timestamp_: datetime = FATFS_INCEPTION) -> None:
# when path_from_root is None the dir is root
self.root_directory.new_file(name=name, extension=extension, path_from_root=path_from_root)
self.root_directory.new_file(name=name,
extension=extension,
path_from_root=path_from_root,
object_timestamp_=object_timestamp_)
def create_directory(self, name: str, path_from_root: Optional[List[str]] = None) -> None:
def create_directory(self, name: str,
path_from_root: Optional[List[str]] = None,
object_timestamp_: datetime = FATFS_INCEPTION) -> None:
# when path_from_root is None the dir is root
parent_dir = self.root_directory
if path_from_root:
parent_dir = self.root_directory.recursive_search(path_from_root, self.root_directory)
self.root_directory.new_directory(name=name, parent=parent_dir, path_from_root=path_from_root)
self.root_directory.new_directory(name=name,
parent=parent_dir,
path_from_root=path_from_root,
object_timestamp_=object_timestamp_)
def write_content(self, path_from_root: List[str], content: bytes) -> None:
"""
@ -140,16 +154,23 @@ class FATFS:
normal_path = os.path.normpath(folder_relative_path)
split_path = normal_path.split(os.sep)
object_timestamp = datetime.fromtimestamp(os.path.getctime(real_path))
if os.path.isfile(real_path):
with open(real_path, 'rb') as file:
content = file.read()
file_name, extension = os.path.splitext(split_path[-1])
extension = extension[1:] # remove the dot from the extension
self.create_file(name=file_name, extension=extension, path_from_root=split_path[1:-1] or None)
self.create_file(name=file_name,
extension=extension,
path_from_root=split_path[1:-1] or None,
object_timestamp_=object_timestamp)
self.write_content(split_path[1:], content)
elif os.path.isdir(real_path):
if not is_dir:
self.create_directory(split_path[-1], split_path[1:-1])
self.create_directory(name=split_path[-1],
path_from_root=split_path[1:-1],
object_timestamp_=object_timestamp)
# sorting files for better testability
dir_content = list(sorted(os.listdir(real_path)))
@ -171,7 +192,8 @@ def main() -> None:
size=args.partition_size,
root_entry_count=args.root_entry_count,
explicit_fat_type=args.fat_type,
long_names_enabled=args.long_name_support)
long_names_enabled=args.long_name_support,
use_default_datetime=args.use_default_datetime)
fatfs.generate(args.input_directory)
fatfs.write_filesystem(args.output_file)

View File

@ -7,7 +7,8 @@ from construct import Const, Int8ul, Int16ul, Int32ul, PaddedString, Struct
from .exceptions import LowerCaseException, TooLongNameException
from .fatfs_state import FATFSState
from .utils import MAX_EXT_SIZE, MAX_NAME_SIZE, is_valid_fatfs_name, pad_string
from .utils import (DATETIME, FATFS_INCEPTION, MAX_EXT_SIZE, MAX_NAME_SIZE, SHORT_NAMES_ENCODING, build_date_entry,
build_time_entry, is_valid_fatfs_name, pad_string)
class Entry:
@ -36,18 +37,22 @@ class Entry:
SHORT_ENTRY: int = -1
SHORT_ENTRY_LN: int = 0
# The 1st January 1980 00:00:00
DEFAULT_DATE: DATETIME = (FATFS_INCEPTION.year, FATFS_INCEPTION.month, FATFS_INCEPTION.day)
DEFAULT_TIME: DATETIME = (FATFS_INCEPTION.hour, FATFS_INCEPTION.minute, FATFS_INCEPTION.second)
ENTRY_FORMAT_SHORT_NAME = Struct(
'DIR_Name' / PaddedString(MAX_NAME_SIZE, 'utf-8'),
'DIR_Name_ext' / PaddedString(MAX_EXT_SIZE, 'utf-8'),
'DIR_Name' / PaddedString(MAX_NAME_SIZE, SHORT_NAMES_ENCODING),
'DIR_Name_ext' / PaddedString(MAX_EXT_SIZE, SHORT_NAMES_ENCODING),
'DIR_Attr' / Int8ul,
'DIR_NTRes' / Const(b'\x00'),
'DIR_CrtTimeTenth' / Const(b'\x00'),
'DIR_CrtTime' / Const(b'\x00\x00'),
'DIR_CrtDate' / Const(b'\x21\x00'),
'DIR_LstAccDate' / Const(b'\x00\x00'),
'DIR_CrtTimeTenth' / Const(b'\x00'), # ignored by esp-idf fatfs library
'DIR_CrtTime' / Int16ul, # ignored by esp-idf fatfs library
'DIR_CrtDate' / Int16ul, # ignored by esp-idf fatfs library
'DIR_LstAccDate' / Int16ul, # must be same as DIR_WrtDate
'DIR_FstClusHI' / Const(b'\x00\x00'),
'DIR_WrtTime' / Const(b'\x00\x00'),
'DIR_WrtDate' / Const(b'\x21\x00'),
'DIR_WrtTime' / Int16ul,
'DIR_WrtDate' / Int16ul,
'DIR_FstClusLO' / Int16ul,
'DIR_FileSize' / Int32ul,
)
@ -117,6 +122,8 @@ class Entry:
entity_type: int,
entity_extension: str = '',
size: int = 0,
date: DATETIME = DEFAULT_DATE,
time: DATETIME = DEFAULT_TIME,
lfn_order: int = SHORT_ENTRY,
lfn_names: Optional[List[bytes]] = None,
lfn_checksum_: int = 0,
@ -126,6 +133,8 @@ class Entry:
:param entity_name: name recorded in the entry
:param entity_extension: extension recorded in the entry
:param size: size of the content of the file
:param date: denotes year (actual year minus 1980), month number day of the month (minimal valid is (0, 1, 1))
:param time: denotes hour, minute and second with granularity 2 seconds (sec // 2)
:param entity_type: type of the entity (file [0x20] or directory [0x10])
:param lfn_order: if long names support is enabled, defines order in long names entries sequence (-1 for short)
:param lfn_names: if the entry is dedicated for long names the lfn_names contains
@ -137,11 +146,17 @@ class Entry:
:raises LowerCaseException: In case when long_names_enabled is set to False and filename exceeds 8 chars
for name or 3 chars for extension the exception is raised
:raises TooLongNameException: When long_names_enabled is set to False and name doesn't fit to 8.3 filename
an exception is raised
"""
valid_full_name: bool = is_valid_fatfs_name(entity_name) and is_valid_fatfs_name(entity_extension)
if not (valid_full_name or lfn_order >= 0):
raise LowerCaseException('Lower case is not supported in short name entry, use upper case.')
if self.fatfs_state.use_default_datetime:
date = self.DEFAULT_DATE
time = self.DEFAULT_TIME
# clean entry before allocation
self._clean_entry()
self._is_empty = False
@ -154,17 +169,25 @@ class Entry:
raise TooLongNameException(
'Maximal length of the object name is {} characters and {} characters for extension!'.format(
MAX_NAME_SIZE, MAX_EXT_SIZE
))
)
)
start_address = self.entry_address
end_address = start_address + self.fatfs_state.entry_size
if lfn_order in (self.SHORT_ENTRY, self.SHORT_ENTRY_LN):
date_entry_: int = build_date_entry(*date)
time_entry: int = build_time_entry(*time)
self.fatfs_state.binary_image[start_address: end_address] = self._build_entry(
DIR_Name=pad_string(object_name, size=MAX_NAME_SIZE),
DIR_Name_ext=pad_string(object_extension, size=MAX_EXT_SIZE),
DIR_Attr=entity_type,
DIR_FstClusLO=first_cluster_id,
DIR_FileSize=size
DIR_FileSize=size,
DIR_CrtDate=date_entry_, # ignored by esp-idf fatfs library
DIR_LstAccDate=date_entry_, # must be same as DIR_WrtDate
DIR_WrtDate=date_entry_,
DIR_CrtTime=time_entry, # ignored by esp-idf fatfs library
DIR_WrtTime=time_entry
)
else:
assert lfn_names is not None

View File

@ -2,8 +2,8 @@
# SPDX-License-Identifier: Apache-2.0
from construct import Const, Int8ul, Int16ul, Int32ul, PaddedString, Struct
from .utils import (BYTES_PER_DIRECTORY_ENTRY, get_fatfs_type, get_non_data_sectors_cnt, number_of_clusters,
read_filesystem)
from .utils import (BYTES_PER_DIRECTORY_ENTRY, SHORT_NAMES_ENCODING, get_fatfs_type, get_non_data_sectors_cnt,
number_of_clusters, read_filesystem)
class FATFSParser:
@ -16,7 +16,7 @@ class FATFSParser:
BOOT_SECTOR_HEADER = Struct(
'BS_jmpBoot' / Const(b'\xeb\xfe\x90'),
'BS_OEMName' / PaddedString(MAX_OEM_NAME_SIZE, 'utf-8'),
'BS_OEMName' / PaddedString(MAX_OEM_NAME_SIZE, SHORT_NAMES_ENCODING),
'BPB_BytsPerSec' / Int16ul,
'BPB_SecPerClus' / Int8ul,
'BPB_RsvdSecCnt' / Int16ul,
@ -33,8 +33,8 @@ class FATFSParser:
'BS_Reserved1' / Const(b'\x00'),
'BS_BootSig' / Const(b'\x29'),
'BS_VolID' / Int32ul,
'BS_VolLab' / PaddedString(MAX_VOL_LAB_SIZE, 'utf-8'),
'BS_FilSysType' / PaddedString(MAX_FS_TYPE_SIZE, 'utf-8'),
'BS_VolLab' / PaddedString(MAX_VOL_LAB_SIZE, SHORT_NAMES_ENCODING),
'BS_FilSysType' / PaddedString(MAX_FS_TYPE_SIZE, SHORT_NAMES_ENCODING),
'BS_EMPTY' / Const(448 * b'\x00'),
'Signature_word' / Const(b'\x55\xAA')
)

View File

@ -29,6 +29,7 @@ class FATFSState:
num_heads: int,
hidden_sectors: int,
file_sys_type: str,
use_default_datetime: bool,
explicit_fat_type: int = None,
long_names_enabled: bool = False):
@ -50,6 +51,7 @@ class FATFSState:
self.size: int = size
self.sectors_per_fat_cnt: int = sectors_per_fat
self.sectors_per_cluster: int = sectors_per_cluster
self.use_default_datetime: bool = use_default_datetime
if self.clusters in (FAT12_MAX_CLUSTERS, FAT16_MAX_CLUSTERS):
print('WARNING: It is not recommended to create FATFS with bounding '

View File

@ -2,6 +2,7 @@
# SPDX-License-Identifier: Apache-2.0
import os
from datetime import datetime
from typing import List, Optional, Tuple, Union
from .entry import Entry
@ -11,8 +12,8 @@ from .fatfs_state import FATFSState
from .long_filename_utils import (build_lfn_full_name, build_lfn_unique_entry_name_order,
get_required_lfn_entries_count, split_name_to_lfn_entries,
split_name_to_lfn_entry_blocks)
from .utils import (MAX_EXT_SIZE, MAX_NAME_SIZE, build_lfn_short_entry_name, lfn_checksum, required_clusters_count,
split_content_into_sectors, split_to_name_and_extension)
from .utils import (DATETIME, MAX_EXT_SIZE, MAX_NAME_SIZE, build_lfn_short_entry_name, lfn_checksum,
required_clusters_count, split_content_into_sectors, split_to_name_and_extension)
class File:
@ -180,8 +181,10 @@ class Directory:
extension,
target_dir,
free_cluster,
entity_type):
# type: (Entry, str, str, Directory, Cluster, int) -> Tuple[Cluster, Entry, Directory]
entity_type,
date,
time):
# type: (Entry, str, str, Directory, Cluster, int, DATETIME, DATETIME) -> Tuple[Cluster, Entry, Directory]
lfn_full_name: str = build_lfn_full_name(name, extension)
lfn_unique_entry_order: int = build_lfn_unique_entry_name_order(target_dir.entities, name)
lfn_short_entry_name: str = build_lfn_short_entry_name(name, extension, lfn_unique_entry_order)
@ -207,15 +210,18 @@ class Directory:
entity_name=lfn_short_entry_name[:MAX_NAME_SIZE],
entity_extension=lfn_short_entry_name[MAX_NAME_SIZE:],
entity_type=entity_type,
lfn_order=Entry.SHORT_ENTRY_LN)
lfn_order=Entry.SHORT_ENTRY_LN,
date=date,
time=time)
return free_cluster, free_entry, target_dir
def allocate_object(self,
name,
entity_type,
object_timestamp_,
path_from_root=None,
extension=''):
# type: (str, int, Optional[List[str]], str) -> Tuple[Cluster, Entry, Directory]
# type: (str, int, datetime, Optional[List[str]], str) -> Tuple[Cluster, Entry, Directory]
"""
Method finds the target directory in the path
and allocates cluster (both the record in FAT and cluster in the data region)
@ -226,10 +232,16 @@ class Directory:
free_entry: Entry = target_dir.find_free_entry() or target_dir.chain_directory()
name_fits_short_struct: bool = len(name) <= MAX_NAME_SIZE and len(extension) <= MAX_EXT_SIZE
fatfs_date_ = (object_timestamp_.year, object_timestamp_.month, object_timestamp_.day)
fatfs_time_ = (object_timestamp_.hour, object_timestamp_.minute, object_timestamp_.second)
if not self.fatfs_state.long_names_enabled or name_fits_short_struct:
free_entry.allocate_entry(first_cluster_id=free_cluster.id,
entity_name=name,
entity_extension=extension,
date=fatfs_date_,
time=fatfs_time_,
entity_type=entity_type)
return free_cluster, free_entry, target_dir
return self.allocate_long_name_object(free_entry=free_entry,
@ -237,13 +249,20 @@ class Directory:
extension=extension,
target_dir=target_dir,
free_cluster=free_cluster,
entity_type=entity_type)
entity_type=entity_type,
date=fatfs_date_,
time=fatfs_time_)
def new_file(self, name: str, extension: str, path_from_root: Optional[List[str]]) -> None:
def new_file(self,
name: str,
extension: str,
path_from_root: Optional[List[str]],
object_timestamp_: datetime) -> None:
free_cluster, free_entry, target_dir = self.allocate_object(name=name,
extension=extension,
entity_type=Directory.ATTR_ARCHIVE,
path_from_root=path_from_root)
path_from_root=path_from_root,
object_timestamp_=object_timestamp_)
file: File = File(name=name,
fat=self.fat,
@ -253,11 +272,12 @@ class Directory:
file.first_cluster = free_cluster
target_dir.entities.append(file)
def new_directory(self, name, parent, path_from_root):
# type: (str, Directory, Optional[List[str]]) -> None
def new_directory(self, name, parent, path_from_root, object_timestamp_):
# type: (str, Directory, Optional[List[str]], datetime) -> None
free_cluster, free_entry, target_dir = self.allocate_object(name=name,
entity_type=Directory.ATTR_DIRECTORY,
path_from_root=path_from_root)
path_from_root=path_from_root,
object_timestamp_=object_timestamp_)
directory: Directory = Directory(name=name,
fat=self.fat,

View File

@ -5,9 +5,10 @@ import argparse
import binascii
import os
import uuid
from datetime import datetime
from typing import List, Optional, Tuple
from construct import Int16ul
from construct import BitsInteger, BitStruct, Int16ul
FAT12_MAX_CLUSTERS: int = 4085
FAT16_MAX_CLUSTERS: int = 65525
@ -18,9 +19,12 @@ BYTES_PER_DIRECTORY_ENTRY: int = 32
UINT32_MAX: int = (1 << 32) - 1
MAX_NAME_SIZE: int = 8
MAX_EXT_SIZE: int = 3
DATETIME = Tuple[int, int, int]
FATFS_INCEPTION: datetime = datetime(1980, 1, 1, 0, 0, 0, 0)
# long names are encoded to two bytes in utf-16
LONG_NAMES_ENCODING: str = 'utf-16'
SHORT_NAMES_ENCODING: str = 'utf-8'
def crc32(input_values: List[int], crc: int) -> int:
@ -99,7 +103,7 @@ def is_valid_fatfs_name(string: str) -> bool:
return string == string.upper()
def split_by_half_byte_12_bit_little_endian(value: int) -> Tuple[int, int, int]:
def split_by_half_byte_12_bit_little_endian(value: int) -> DATETIME:
value_as_bytes: bytes = Int16ul.build(value)
return value_as_bytes[0] & 0x0f, value_as_bytes[0] >> 4, value_as_bytes[1] & 0x0f
@ -159,6 +163,10 @@ def get_args_for_partition_generator(desc: str) -> argparse.Namespace:
parser.add_argument('--long_name_support',
action='store_true',
help='Set flag to enable long names support.')
parser.add_argument('--use_default_datetime',
action='store_true',
help='For test purposes. If the flag is set the files are created with '
'the default timestamp that is the 1st of January 1980')
parser.add_argument('--fat_type',
default=0,
type=int,
@ -180,3 +188,45 @@ def get_args_for_partition_generator(desc: str) -> argparse.Namespace:
def read_filesystem(path: str) -> bytearray:
with open(path, 'rb') as fs_file:
return bytearray(fs_file.read())
DATE_ENTRY = BitStruct(
'year' / BitsInteger(7),
'month' / BitsInteger(4),
'day' / BitsInteger(5))
TIME_ENTRY = BitStruct(
'hour' / BitsInteger(5),
'minute' / BitsInteger(6),
'second' / BitsInteger(5),
)
def build_date_entry(year: int, mon: int, mday: int) -> int:
"""
:param year: denotes year starting from 1980 (0 ~ 1980, 1 ~ 1981, etc), valid values are 1980 + 0..127 inclusive
thus theoretically 1980 - 2107
:param mon: denotes number of month of year in common order (1 ~ January, 2 ~ February, etc.),
valid values: 1..12 inclusive
:param mday: denotes number of day in month, valid values are 1..31 inclusive
:returns: 16 bit integer number (7 bits for year, 4 bits for month and 5 bits for day of the month)
"""
assert year in range(1980, 2107)
assert mon in range(1, 13)
assert mday in range(1, 32)
return int.from_bytes(DATE_ENTRY.build(dict(year=year - 1980, month=mon, day=mday)), 'big')
def build_time_entry(hour: int, minute: int, sec: int) -> int:
"""
:param hour: denotes number of hour, valid values are 0..23 inclusive
:param minute: denotes minutes, valid range 0..59 inclusive
:param sec: denotes seconds with granularity 2 seconds (e.g. 1 ~ 2, 29 ~ 58), valid range 0..29 inclusive
:returns: 16 bit integer number (5 bits for hour, 6 bits for minute and 5 bits for second)
"""
assert hour in range(0, 23)
assert minute in range(0, 60)
assert sec in range(0, 60)
return int.from_bytes(TIME_ENTRY.build(dict(hour=hour, minute=minute, second=sec // 2)), 'big')

View File

@ -3,7 +3,7 @@
# Create a fatfs image of the specified directory on the host during build and optionally
# have the created image flashed using `idf.py flash`
function(fatfs_create_partition_image partition base_dir)
set(options FLASH_IN_PROJECT WL_INIT)
set(options FLASH_IN_PROJECT WL_INIT PRESERVE_TIME)
cmake_parse_arguments(arg "${options}" "" "${multi}" "${ARGN}")
@ -16,6 +16,12 @@ function(fatfs_create_partition_image partition base_dir)
set(fatfsgen_py ${python} ${idf_path}/components/fatfs/fatfsgen.py)
endif()
if(arg_PRESERVE_TIME)
set(default_datetime_option)
else()
set(default_datetime_option --use_default_datetime)
endif()
if("${CONFIG_FATFS_SECTORS_PER_CLUSTER_1}")
set(sectors_per_cluster 1)
elseif("${CONFIG_FATFS_SECTORS_PER_CLUSTER_2}")
@ -60,6 +66,8 @@ function(fatfs_create_partition_image partition base_dir)
set(fatfs_explicit_type 16)
endif()
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")
@ -71,6 +79,7 @@ function(fatfs_create_partition_image partition base_dir)
add_custom_target(fatfs_${partition}_bin ALL
COMMAND ${fatfsgen_py} ${base_dir_full_path}
${fatfs_long_names_option}
${default_datetime_option}
--partition_size ${size}
--output_file ${image_file}
--sector_size "${fatfs_sector_size}"
@ -102,21 +111,39 @@ endfunction()
function(fatfs_create_rawflash_image partition base_dir)
set(options FLASH_IN_PROJECT)
set(options FLASH_IN_PROJECT PRESERVE_TIME)
cmake_parse_arguments(arg "${options}" "" "${multi}" "${ARGN}")
if(arg_FLASH_IN_PROJECT)
fatfs_create_partition_image(${partition} ${base_dir} FLASH_IN_PROJECT)
if(arg_PRESERVE_TIME)
fatfs_create_partition_image(${partition} ${base_dir} FLASH_IN_PROJECT PRESERVE_TIME)
else()
fatfs_create_partition_image(${partition} ${base_dir} FLASH_IN_PROJECT)
endif()
else()
fatfs_create_partition_image(${partition} ${base_dir})
if(arg_PRESERVE_TIME)
fatfs_create_partition_image(${partition} ${base_dir} PRESERVE_TIME)
else()
fatfs_create_partition_image(${partition} ${base_dir})
endif()
endif()
endfunction()
function(fatfs_create_spiflash_image partition base_dir)
set(options FLASH_IN_PROJECT)
set(options FLASH_IN_PROJECT PRESERVE_TIME)
cmake_parse_arguments(arg "${options}" "" "${multi}" "${ARGN}")
if(arg_FLASH_IN_PROJECT)
fatfs_create_partition_image(${partition} ${base_dir} FLASH_IN_PROJECT WL_INIT)
if(arg_PRESERVE_TIME)
fatfs_create_partition_image(${partition} ${base_dir} FLASH_IN_PROJECT WL_INIT PRESERVE_TIME)
else()
fatfs_create_partition_image(${partition} ${base_dir} FLASH_IN_PROJECT WL_INIT)
endif()
else()
fatfs_create_partition_image(${partition} ${base_dir} WL_INIT)
if(arg_PRESERVE_TIME)
fatfs_create_partition_image(${partition} ${base_dir} WL_INIT PRESERVE_TIME)
else()
fatfs_create_partition_image(${partition} ${base_dir} WL_INIT)
endif()
endif()
endfunction()

View File

@ -217,7 +217,7 @@ class FatFSGen(unittest.TestCase):
file_system = read_filesystem(CFG['output_file'])
self.assertEqual(file_system[0x2000:0x2010], b'TESTFOLD \x10\x00\x00\x00\x00')
self.assertEqual(file_system[0x2010:0x2020], b'!\x00\x00\x00\x00\x00\x00\x00\x21\x00\x02\x00\x00\x00\x00\x00')
self.assertEqual(file_system[0x2010:0x2020], b'!\x00!\x00\x00\x00\x00\x00!\x00\x02\x00\x00\x00\x00\x00')
self.assertEqual(file_system[0x6040:0x6050], b'TESTFOLD \x10\x00\x00\x00\x00')
self.assertEqual(file_system[0x6040:0x6050], b'TESTFOLD \x10\x00\x00\x00\x00')
@ -231,7 +231,7 @@ class FatFSGen(unittest.TestCase):
fatfs.write_filesystem(CFG['output_file'])
file_system = read_filesystem(CFG['output_file'])
self.assertEqual(file_system[0x6060:0x6070], b'TESTFIL2 \x00\x00\x00\x00')
self.assertEqual(file_system[0x6070:0x6080], b'!\x00\x00\x00\x00\x00\x00\x00\x21\x00\x05\x00\x0b\x00\x00\x00')
self.assertEqual(file_system[0x6070:0x6080], b'!\x00!\x00\x00\x00\x00\x00!\x00\x05\x00\x0b\x00\x00\x00')
self.assertEqual(file_system[0x7040:0x7050], b'LASTFILE \x00\x00\x00\x00')
self.assertEqual(file_system[0x8000:0x8010], b'deeptest\n\x00\x00\x00\x00\x00\x00\x00')
self.assertEqual(file_system[0x9000:0x9010], b'thisistest\n\x00\x00\x00\x00\x00')
@ -294,7 +294,7 @@ class FatFSGen(unittest.TestCase):
fatfs.create_file('HELLO', extension='TXT')
fatfs.write_filesystem(CFG['output_file'])
file_system = read_filesystem(CFG['output_file'])
self.assertEqual(file_system[0x2000: 0x2019], b'HELLO TXT \x00\x00\x00\x00!\x00\x00\x00\x00\x00\x00\x00!')
self.assertEqual(file_system[0x2000: 0x2019], b'HELLO TXT \x00\x00\x00\x00!\x00!\x00\x00\x00\x00\x00!')
def test_lfn_short_name(self) -> None:
fatfs = fatfsgen.FATFS(long_names_enabled=True)
@ -303,7 +303,7 @@ class FatFSGen(unittest.TestCase):
fatfs.write_filesystem(CFG['output_file'])
file_system = read_filesystem(CFG['output_file'])
self.assertEqual(file_system[0x2000: 0x2010], b'HELLO TXT \x00\x00\x00\x00')
self.assertEqual(file_system[0x2010: 0x2020], b'!\x00\x00\x00\x00\x00\x00\x00!\x00\x02\x00\x0e\x00\x00\x00')
self.assertEqual(file_system[0x2010: 0x2020], b'!\x00!\x00\x00\x00\x00\x00!\x00\x02\x00\x0e\x00\x00\x00')
self.assertEqual(file_system[0x6000: 0x6010], b'this is a test\x00\x00')
def test_lfn_empty_name(self) -> None:
@ -316,7 +316,7 @@ class FatFSGen(unittest.TestCase):
self.assertEqual(file_system[0x2020: 0x2030], b'\x01h\x00e\x00l\x00l\x00o\x00\x0f\x00\xb3h\x00')
self.assertEqual(file_system[0x2030: 0x2040], b'e\x00l\x00l\x00o\x00h\x00\x00\x00e\x00l\x00')
self.assertEqual(file_system[0x2040: 0x2050], b'HELLOH~1TXT \x00\x00\x00\x00')
self.assertEqual(file_system[0x2050: 0x2060], b'!\x00\x00\x00\x00\x00\x00\x00!\x00\x02\x00\x00\x00\x00\x00')
self.assertEqual(file_system[0x2050: 0x2060], b'!\x00!\x00\x00\x00\x00\x00!\x00\x02\x00\x00\x00\x00\x00')
def test_lfn_plain_name(self) -> None:
fatfs = fatfsgen.FATFS(long_names_enabled=True)
@ -329,7 +329,7 @@ class FatFSGen(unittest.TestCase):
self.assertEqual(file_system[0x2020: 0x2030], b'\x01h\x00e\x00l\x00l\x00o\x00\x0f\x00\xb3h\x00')
self.assertEqual(file_system[0x2030: 0x2040], b'e\x00l\x00l\x00o\x00h\x00\x00\x00e\x00l\x00')
self.assertEqual(file_system[0x2040: 0x2050], b'HELLOH~1TXT \x00\x00\x00\x00')
self.assertEqual(file_system[0x2050: 0x2060], b'!\x00\x00\x00\x00\x00\x00\x00!\x00\x02\x00\x0e\x00\x00\x00')
self.assertEqual(file_system[0x2050: 0x2060], b'!\x00!\x00\x00\x00\x00\x00!\x00\x02\x00\x0e\x00\x00\x00')
self.assertEqual(file_system[0x6000: 0x6010], b'this is a test\x00\x00')
def test_lfn_plain_name_no_ext(self) -> None:
@ -343,7 +343,7 @@ class FatFSGen(unittest.TestCase):
self.assertEqual(file_system[0x2020: 0x2030], b'\x01h\x00e\x00l\x00l\x00o\x00\x0f\x00Vh\x00')
self.assertEqual(file_system[0x2030: 0x2040], b'e\x00l\x00l\x00o\x00h\x00\x00\x00e\x00l\x00')
self.assertEqual(file_system[0x2040: 0x2050], b'HELLOH~1 \x00\x00\x00\x00')
self.assertEqual(file_system[0x2050: 0x2060], b'!\x00\x00\x00\x00\x00\x00\x00!\x00\x02\x00\x0e\x00\x00\x00')
self.assertEqual(file_system[0x2050: 0x2060], b'!\x00!\x00\x00\x00\x00\x00!\x00\x02\x00\x0e\x00\x00\x00')
self.assertEqual(file_system[0x6000: 0x6010], b'this is a test\x00\x00')
def test_lfn_short_entry_exception(self) -> None:
@ -361,14 +361,14 @@ class FatFSGen(unittest.TestCase):
self.assertEqual(file_system[0x2020: 0x2030], b'\x01v\x00e\x00r\x00y\x00l\x00\x0f\x00\xa6o\x00')
self.assertEqual(file_system[0x2030: 0x2040], b'n\x00g\x00t\x00e\x00s\x00\x00\x00t\x00f\x00')
self.assertEqual(file_system[0x2040: 0x2050], b'VERYLO~1 \x10\x00\x00\x00\x00')
self.assertEqual(file_system[0x2050: 0x2060], b'!\x00\x00\x00\x00\x00\x00\x00!\x00\x02\x00\x00\x00\x00\x00')
self.assertEqual(file_system[0x2050: 0x2060], b'!\x00!\x00\x00\x00\x00\x00!\x00\x02\x00\x00\x00\x00\x00')
self.assertEqual(file_system[0x6000: 0x6010], b'. \x10\x00\x00\x00\x00')
self.assertEqual(file_system[0x6012: 0x6020], b'\x00\x00\x00\x00\x00\x00!\x00\x02\x00\x00\x00\x00\x00')
self.assertEqual(file_system[0x6012: 0x6020], b'!\x00\x00\x00\x00\x00!\x00\x02\x00\x00\x00\x00\x00')
self.assertEqual(file_system[0x6020: 0x6030], b'.. \x10\x00\x00\x00\x00')
self.assertEqual(file_system[0x6030: 0x6040], b'!\x00\x00\x00\x00\x00\x00\x00!\x00\x01\x00\x00\x00\x00\x00')
self.assertEqual(file_system[0x6030: 0x6040], b'!\x00!\x00\x00\x00\x00\x00!\x00\x01\x00\x00\x00\x00\x00')
self.assertEqual(file_system[0x6040: 0x6050], b'HELLO TXT \x00\x00\x00\x00')
self.assertEqual(file_system[0x6050: 0x6060], b'!\x00\x00\x00\x00\x00\x00\x00!\x00\x03\x00\x00\x00\x00\x00')
self.assertEqual(file_system[0x6050: 0x6060], b'!\x00!\x00\x00\x00\x00\x00!\x00\x03\x00\x00\x00\x00\x00')
def test_lfn_nested_long_empty(self) -> None:
fatfs: fatfsgen.FATFS = fatfsgen.FATFS(long_names_enabled=True)
@ -382,12 +382,12 @@ class FatFSGen(unittest.TestCase):
self.assertEqual(file_system[0x2020: 0x2030], b'\x01v\x00e\x00r\x00y\x00l\x00\x0f\x00\x10o\x00')
self.assertEqual(file_system[0x2030: 0x2040], b'n\x00g\x00t\x00e\x00s\x00\x00\x00t\x00f\x00')
self.assertEqual(file_system[0x2040: 0x2050], b'verylo~1 \x10\x00\x00\x00\x00')
self.assertEqual(file_system[0x2050: 0x2060], b'!\x00\x00\x00\x00\x00\x00\x00!\x00\x02\x00\x00\x00\x00\x00')
self.assertEqual(file_system[0x2050: 0x2060], b'!\x00!\x00\x00\x00\x00\x00!\x00\x02\x00\x00\x00\x00\x00')
self.assertEqual(file_system[0x6000: 0x6010], b'. \x10\x00\x00\x00\x00')
self.assertEqual(file_system[0x6012: 0x6020], b'\x00\x00\x00\x00\x00\x00!\x00\x02\x00\x00\x00\x00\x00')
self.assertEqual(file_system[0x6012: 0x6020], b'!\x00\x00\x00\x00\x00!\x00\x02\x00\x00\x00\x00\x00')
self.assertEqual(file_system[0x6020: 0x6030], b'.. \x10\x00\x00\x00\x00')
self.assertEqual(file_system[0x6030: 0x6040], b'!\x00\x00\x00\x00\x00\x00\x00!\x00\x01\x00\x00\x00\x00\x00')
self.assertEqual(file_system[0x6030: 0x6040], b'!\x00!\x00\x00\x00\x00\x00!\x00\x01\x00\x00\x00\x00\x00')
self.assertEqual(file_system[0x6040: 0x6050], b'Bl\x00o\x00.\x00t\x00x\x00\x0f\x00\xd5t\x00')
self.assertEqual(file_system[0x6050: 0x6060],
b'\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\xff\xff\xff\xff')
@ -404,14 +404,14 @@ class FatFSGen(unittest.TestCase):
self.assertEqual(file_system[0x2020: 0x2030], b'\x01v\x00e\x00r\x00y\x00l\x00\x0f\x00\xa6o\x00')
self.assertEqual(file_system[0x2030: 0x2040], b'n\x00g\x00t\x00e\x00s\x00\x00\x00t\x00f\x00')
self.assertEqual(file_system[0x2040: 0x2050], b'VERYLO~1 \x10\x00\x00\x00\x00')
self.assertEqual(file_system[0x2050: 0x2060], b'!\x00\x00\x00\x00\x00\x00\x00!\x00\x02\x00\x00\x00\x00\x00')
self.assertEqual(file_system[0x2050: 0x2060], b'!\x00!\x00\x00\x00\x00\x00!\x00\x02\x00\x00\x00\x00\x00')
self.assertEqual(file_system[0x6000: 0x6010], b'. \x10\x00\x00\x00\x00')
self.assertEqual(file_system[0x6012: 0x6020], b'\x00\x00\x00\x00\x00\x00!\x00\x02\x00\x00\x00\x00\x00')
self.assertEqual(file_system[0x6012: 0x6020], b'!\x00\x00\x00\x00\x00!\x00\x02\x00\x00\x00\x00\x00')
self.assertEqual(file_system[0x6020: 0x6030], b'.. \x10\x00\x00\x00\x00')
self.assertEqual(file_system[0x6030: 0x6040], b'!\x00\x00\x00\x00\x00\x00\x00!\x00\x01\x00\x00\x00\x00\x00')
self.assertEqual(file_system[0x6030: 0x6040], b'!\x00!\x00\x00\x00\x00\x00!\x00\x01\x00\x00\x00\x00\x00')
self.assertEqual(file_system[0x6040: 0x6050], b'HELLO TXT \x00\x00\x00\x00')
self.assertEqual(file_system[0x6050: 0x6060], b'!\x00\x00\x00\x00\x00\x00\x00!\x00\x03\x00\x0e\x00\x00\x00')
self.assertEqual(file_system[0x6050: 0x6060], b'!\x00!\x00\x00\x00\x00\x00!\x00\x03\x00\x0e\x00\x00\x00')
self.assertEqual(file_system[0x7000: 0x7010], b'this is a test\x00\x00')
@ -452,12 +452,12 @@ class FatFSGen(unittest.TestCase):
self.assertEqual(file_system[0x2020: 0x2030], b'\x01v\x00e\x00r\x00y\x00l\x00\x0f\x00\xa6o\x00')
self.assertEqual(file_system[0x2030: 0x2040], b'n\x00g\x00t\x00e\x00s\x00\x00\x00t\x00f\x00')
self.assertEqual(file_system[0x2040: 0x2050], b'VERYLO~1 \x10\x00\x00\x00\x00')
self.assertEqual(file_system[0x2050: 0x2060], b'!\x00\x00\x00\x00\x00\x00\x00!\x00\x02\x00\x00\x00\x00\x00')
self.assertEqual(file_system[0x2050: 0x2060], b'!\x00!\x00\x00\x00\x00\x00!\x00\x02\x00\x00\x00\x00\x00')
self.assertEqual(file_system[0x6000: 0x6010], b'. \x10\x00\x00\x00\x00')
self.assertEqual(file_system[0x6011: 0x6020], b'\x00\x00\x00\x00\x00\x00\x00!\x00\x02\x00\x00\x00\x00\x00')
self.assertEqual(file_system[0x6011: 0x6020], b'\x00!\x00\x00\x00\x00\x00!\x00\x02\x00\x00\x00\x00\x00')
self.assertEqual(file_system[0x6020: 0x6030], b'.. \x10\x00\x00\x00\x00')
self.assertEqual(file_system[0x6030: 0x6040], b'!\x00\x00\x00\x00\x00\x00\x00!\x00\x01\x00\x00\x00\x00\x00')
self.assertEqual(file_system[0x6030: 0x6040], b'!\x00!\x00\x00\x00\x00\x00!\x00\x01\x00\x00\x00\x00\x00')
self.assertEqual(file_system[0x6040: 0x6050], b'Bl\x00o\x00o\x00o\x00o\x00\x0f\x00\xb3o\x00')
self.assertEqual(file_system[0x6050: 0x6060], b'o\x00o\x00.\x00t\x00x\x00\x00\x00t\x00\x00\x00')
@ -465,7 +465,7 @@ class FatFSGen(unittest.TestCase):
self.assertEqual(file_system[0x6060: 0x6070], b'\x01h\x00e\x00l\x00l\x00o\x00\x0f\x00\xb3h\x00')
self.assertEqual(file_system[0x6070: 0x6080], b'e\x00l\x00l\x00o\x00h\x00\x00\x00e\x00l\x00')
self.assertEqual(file_system[0x6080: 0x6090], b'HELLOH~1TXT \x00\x00\x00\x00')
self.assertEqual(file_system[0x6090: 0x60a0], b'!\x00\x00\x00\x00\x00\x00\x00!\x00\x03\x00\x10\x00\x00\x00')
self.assertEqual(file_system[0x6090: 0x60a0], b'!\x00!\x00\x00\x00\x00\x00!\x00\x03\x00\x10\x00\x00\x00')
self.assertEqual(file_system[0x60a0: 0x60b0], b'Bl\x00o\x00o\x00o\x00o\x00\x0f\x00\x93o\x00')
self.assertEqual(file_system[0x60b0: 0x60c0], b'o\x00b\x00.\x00t\x00x\x00\x00\x00t\x00\x00\x00')

View File

@ -134,7 +134,7 @@ class WLFatFSGen(unittest.TestCase):
file_system = bytearray(fs_file.read())
self.assertEqual(file_system[0x7060:0x7070], b'TESTFIL2 \x00\x00\x00\x00')
self.assertEqual(file_system[0x7070:0x7080], b'!\x00\x00\x00\x00\x00\x00\x00\x21\x00\x05\x00\x0b\x00\x00\x00')
self.assertEqual(file_system[0x7070:0x7080], b'!\x00!\x00\x00\x00\x00\x00!\x00\x05\x00\x0b\x00\x00\x00')
self.assertEqual(file_system[0x8040:0x8050], b'LASTFILE \x00\x00\x00\x00')
self.assertEqual(file_system[0x9000:0x9010], b'deeptest\n\x00\x00\x00\x00\x00\x00\x00')
self.assertEqual(file_system[0xa000:0xa010], b'thisistest\n\x00\x00\x00\x00\x00')

View File

@ -60,6 +60,7 @@ class WLFATFS:
sec_per_track: int = 0x3f,
volume_label: str = 'Espressif',
file_sys_type: str = 'FAT',
use_default_datetime: bool = True,
version: int = 2,
temp_buff_size: int = 32,
update_rate: int = 16,
@ -101,6 +102,7 @@ class WLFATFS:
long_names_enabled=long_names_enabled,
entry_size=entry_size,
num_heads=num_heads,
use_default_datetime=use_default_datetime,
oem_name=oem_name,
sec_per_track=sec_per_track,
volume_label=volume_label,
@ -194,7 +196,8 @@ if __name__ == '__main__':
size=args.partition_size,
root_entry_count=args.root_entry_count,
explicit_fat_type=args.fat_type,
long_names_enabled=args.long_name_support)
long_names_enabled=args.long_name_support,
use_default_datetime=args.use_default_datetime)
wl_fatfs.wl_generate(args.input_directory)
wl_fatfs.init_wl()

View File

@ -1,5 +1,8 @@
# SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: CC0-1.0
import os
from datetime import datetime
from pathlib import Path
from typing import Optional
import ttfw_idf
@ -7,53 +10,85 @@ import ttfw_idf
@ttfw_idf.idf_example_test(env_tag='Example_GENERIC')
def test_examples_fatfsgen(env: ttfw_idf.TinyFW.Env, _: Optional[list]) -> None:
dut = env.get_dut('fatfsgen', 'examples/storage/fatfsgen', app_config_name='test_read_write_partition_gen')
dut.start_app()
dut.expect_all('example: Mounting FAT filesystem',
'example: Opening file',
'example: File written',
'example: Reading file',
'example: Read from file: \'This is written by the device\'',
'example: Reading file',
'example: Read from file: \'This is generated on the host\'',
'example: Unmounting FAT filesystem',
'example: Done',
timeout=20)
env.close_dut(dut.name)
tag = 'fatfsgen'
test_path = 'examples/storage/fatfsgen'
timeout = 20
filename_ln = 'sublongnames/testlongfilenames.txt'
filename_sn = 'sub/test.txt'
dir_ln = os.path.join(os.path.dirname(__file__), 'fatfs_long_name_image')
dir_sn = os.path.join(os.path.dirname(__file__), 'fatfs_image')
Path(os.path.join(dir_ln, filename_ln)).touch()
Path(os.path.join(dir_sn, filename_sn)).touch()
date_modified = datetime.today().strftime('%Y-%m-%d')
date_default = '1980-01-01'
dut = env.get_dut('fatfsgen', 'examples/storage/fatfsgen', app_config_name='test_read_only_partition_gen')
dut.start_app()
dut.expect_all('example: Mounting FAT filesystem',
'example: Reading file',
'example: Read from file: \'this is test\'',
'example: Unmounting FAT filesystem',
'example: Done',
timeout=20)
env.close_dut(dut.name)
for test_name in ['test_read_write_partition_gen', 'test_read_write_partition_gen_default_dt']:
filename = filename_sn
filename_expected = f'/spiflash/{filename}'
date_ = date_default if test_name == 'test_read_write_partition_gen_default_dt' else date_modified
dut = env.get_dut(tag, test_path, app_config_name=test_name)
dut.start_app()
dut.expect_all('example: Mounting FAT filesystem',
'example: Opening file',
'example: File written',
'example: Reading file',
'example: Read from file: \'This is written by the device\'',
'example: Reading file',
f'The file \'{filename_expected}\' was modified at date: {date_}',
'example: Read from file: \'This is generated on the host\'',
'example: Unmounting FAT filesystem',
'example: Done',
timeout=timeout)
env.close_dut(dut.name)
dut = env.get_dut('fatfsgen', 'examples/storage/fatfsgen', app_config_name='test_read_write_partition_gen_ln')
dut.start_app()
dut.expect_all('example: Mounting FAT filesystem',
'example: Opening file',
'example: File written',
'example: Reading file',
'example: Read from file: \'This is written by the device\'',
'example: Reading file',
'example: Read from file: \'This is generated on the host; long name it has\'',
'example: Unmounting FAT filesystem',
'example: Done',
timeout=20)
env.close_dut(dut.name)
for test_name in ['test_read_only_partition_gen', 'test_read_only_partition_gen_default_dt']:
filename = filename_sn
filename_expected = f'/spiflash/{filename}'
date_ = date_default if test_name == 'test_read_only_partition_gen_default_dt' else date_modified
dut = env.get_dut(tag, test_path, app_config_name=test_name)
dut.start_app()
dut.expect_all('example: Mounting FAT filesystem',
'example: Reading file',
f'The file \'{filename_expected}\' was modified at date: {date_}',
'example: Read from file: \'this is test\'',
'example: Unmounting FAT filesystem',
'example: Done',
timeout=timeout)
env.close_dut(dut.name)
dut = env.get_dut('fatfsgen', 'examples/storage/fatfsgen', app_config_name='test_read_only_partition_gen_ln')
dut.start_app()
dut.expect_all('example: Mounting FAT filesystem',
'example: Reading file',
'example: Read from file: \'this is test; long name it has\'',
'example: Unmounting FAT filesystem',
'example: Done',
timeout=20)
env.close_dut(dut.name)
for test_name in ['test_read_write_partition_gen_ln', 'test_read_write_partition_gen_ln_default_dt']:
filename = filename_ln
filename_expected = f'/spiflash/{filename}'
date_ = date_default if test_name == 'test_read_write_partition_gen_ln_default_dt' else date_modified
dut = env.get_dut(tag, test_path, app_config_name=test_name)
dut.start_app()
dut.expect_all('example: Mounting FAT filesystem',
'example: Opening file',
'example: File written',
'example: Reading file',
'example: Read from file: \'This is written by the device\'',
'example: Reading file',
f'The file \'{filename_expected}\' was modified at date: {date_}',
'example: Read from file: \'This is generated on the host; long name it has\'',
'example: Unmounting FAT filesystem',
'example: Done',
timeout=timeout)
env.close_dut(dut.name)
for test_name in ['test_read_only_partition_gen_ln', 'test_read_only_partition_gen_ln_default_dt']:
filename = filename_ln
filename_expected = f'/spiflash/{filename}'
date_ = date_default if test_name == 'test_read_only_partition_gen_ln_default_dt' else date_modified
dut = env.get_dut(tag, test_path, app_config_name=test_name)
dut.start_app()
dut.expect_all('example: Mounting FAT filesystem',
'example: Reading file',
f'The file \'{filename_expected}\' was modified at date: {date_}',
'example: Read from file: \'this is test; long name it has\'',
'example: Unmounting FAT filesystem',
'example: Done',
timeout=timeout)
env.close_dut(dut.name)
if __name__ == '__main__':

View File

@ -16,7 +16,15 @@ else()
endif()
if(CONFIG_EXAMPLE_FATFS_MODE_READ_ONLY)
fatfs_create_rawflash_image(storage ${image} FLASH_IN_PROJECT)
if(CONFIG_EXAMPLE_FATFS_DEFAULT_DATETIME)
fatfs_create_rawflash_image(storage ${image} FLASH_IN_PROJECT)
else()
fatfs_create_rawflash_image(storage ${image} FLASH_IN_PROJECT PRESERVE_TIME)
endif()
else()
fatfs_create_spiflash_image(storage ${image} FLASH_IN_PROJECT)
if(CONFIG_EXAMPLE_FATFS_DEFAULT_DATETIME)
fatfs_create_spiflash_image(storage ${image} FLASH_IN_PROJECT)
else()
fatfs_create_spiflash_image(storage ${image} FLASH_IN_PROJECT PRESERVE_TIME)
endif()
endif()

View File

@ -7,4 +7,11 @@ menu "Example Configuration"
If read-only mode is set, the generated fatfs image will be raw (without wear levelling support).
Otherwise it will support wear levelling that enables read-write mounting.
config EXAMPLE_FATFS_DEFAULT_DATETIME
bool "Default modification date and time for generated FATFS image"
default n
help
If default datetime is set, all files created in the generated FATFS partition have default time
equal to FATFS origin time (1 January 1980)
endmenu

View File

@ -106,6 +106,20 @@ void app_main(void)
host_filename2 = "/spiflash/hello.txt";
}
struct stat info;
struct tm timeinfo;
char buffer[32];
if(stat(host_filename1, &info) < 0){
ESP_LOGE(TAG, "Failed to read file stats");
return;
}
localtime_r(&info.st_mtime, &timeinfo);
strftime(buffer, sizeof(buffer), "%Y-%m-%d", &timeinfo);
ESP_LOGI(TAG, "The file '%s' was modified at date: %s", host_filename1, buffer);
if (EXAMPLE_FATFS_MODE_READ_ONLY){
f = fopen(host_filename1, "rb");
} else {

View File

@ -0,0 +1,5 @@
CONFIG_EXAMPLE_FATFS_MODE_READ_ONLY=y
CONFIG_FATFS_LFN_HEAP=n
CONFIG_FATFS_LFN_STACK=n
CONFIG_FATFS_LFN_NONE=y
CONFIG_EXAMPLE_FATFS_DEFAULT_DATETIME=y

View File

@ -0,0 +1,5 @@
CONFIG_EXAMPLE_FATFS_MODE_READ_ONLY=y
CONFIG_FATFS_LFN_HEAP=y
CONFIG_FATFS_LFN_NONE=n
CONFIG_FATFS_LFN_STACK=n
CONFIG_EXAMPLE_FATFS_DEFAULT_DATETIME=y

View File

@ -0,0 +1,5 @@
CONFIG_EXAMPLE_FATFS_MODE_READ_ONLY=n
CONFIG_FATFS_LFN_HEAP=n
CONFIG_FATFS_LFN_STACK=n
CONFIG_FATFS_LFN_NONE=y
CONFIG_EXAMPLE_FATFS_DEFAULT_DATETIME=y

View File

@ -0,0 +1,5 @@
CONFIG_EXAMPLE_FATFS_MODE_READ_ONLY=n
CONFIG_FATFS_LFN_HEAP=y
CONFIG_FATFS_LFN_NONE=n
CONFIG_FATFS_LFN_STACK=n
CONFIG_EXAMPLE_FATFS_DEFAULT_DATETIME=y