diff --git a/components/fatfs/fatfsgen.py b/components/fatfs/fatfsgen.py index 38fd973eff..959b281c49 100755 --- a/components/fatfs/fatfsgen.py +++ b/components/fatfs/fatfsgen.py @@ -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) diff --git a/components/fatfs/fatfsgen_utils/entry.py b/components/fatfs/fatfsgen_utils/entry.py index df71f38a42..a15279cafc 100644 --- a/components/fatfs/fatfsgen_utils/entry.py +++ b/components/fatfs/fatfsgen_utils/entry.py @@ -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 diff --git a/components/fatfs/fatfsgen_utils/fatfs_parser.py b/components/fatfs/fatfsgen_utils/fatfs_parser.py index 90b417cf28..4801c35c7a 100644 --- a/components/fatfs/fatfsgen_utils/fatfs_parser.py +++ b/components/fatfs/fatfsgen_utils/fatfs_parser.py @@ -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') ) diff --git a/components/fatfs/fatfsgen_utils/fatfs_state.py b/components/fatfs/fatfsgen_utils/fatfs_state.py index 097b6686ad..c0578f50b0 100644 --- a/components/fatfs/fatfsgen_utils/fatfs_state.py +++ b/components/fatfs/fatfsgen_utils/fatfs_state.py @@ -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 ' diff --git a/components/fatfs/fatfsgen_utils/fs_object.py b/components/fatfs/fatfsgen_utils/fs_object.py index 8ce3f9274d..a2719028f7 100644 --- a/components/fatfs/fatfsgen_utils/fs_object.py +++ b/components/fatfs/fatfsgen_utils/fs_object.py @@ -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, diff --git a/components/fatfs/fatfsgen_utils/utils.py b/components/fatfs/fatfsgen_utils/utils.py index d547ffa440..16867d4680 100644 --- a/components/fatfs/fatfsgen_utils/utils.py +++ b/components/fatfs/fatfsgen_utils/utils.py @@ -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') diff --git a/components/fatfs/project_include.cmake b/components/fatfs/project_include.cmake index 9052fa44c6..e0837ad5e7 100644 --- a/components/fatfs/project_include.cmake +++ b/components/fatfs/project_include.cmake @@ -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() diff --git a/components/fatfs/test_fatfsgen/test_fatfsgen.py b/components/fatfs/test_fatfsgen/test_fatfsgen.py index 751d3029c2..c8ef4eb957 100755 --- a/components/fatfs/test_fatfsgen/test_fatfsgen.py +++ b/components/fatfs/test_fatfsgen/test_fatfsgen.py @@ -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') diff --git a/components/fatfs/test_fatfsgen/test_wl_fatfsgen.py b/components/fatfs/test_fatfsgen/test_wl_fatfsgen.py index f1659b74ce..0f379b66d3 100755 --- a/components/fatfs/test_fatfsgen/test_wl_fatfsgen.py +++ b/components/fatfs/test_fatfsgen/test_wl_fatfsgen.py @@ -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') diff --git a/components/fatfs/wl_fatfsgen.py b/components/fatfs/wl_fatfsgen.py index 0f7fdf4765..085713520e 100755 --- a/components/fatfs/wl_fatfsgen.py +++ b/components/fatfs/wl_fatfsgen.py @@ -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() diff --git a/examples/storage/fatfsgen/fatfsgen_example_test.py b/examples/storage/fatfsgen/fatfsgen_example_test.py index b6a995d2f6..fbcb9acc87 100644 --- a/examples/storage/fatfsgen/fatfsgen_example_test.py +++ b/examples/storage/fatfsgen/fatfsgen_example_test.py @@ -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__': diff --git a/examples/storage/fatfsgen/main/CMakeLists.txt b/examples/storage/fatfsgen/main/CMakeLists.txt index 3f9669aae4..306e4a14d8 100644 --- a/examples/storage/fatfsgen/main/CMakeLists.txt +++ b/examples/storage/fatfsgen/main/CMakeLists.txt @@ -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() diff --git a/examples/storage/fatfsgen/main/Kconfig.projbuild b/examples/storage/fatfsgen/main/Kconfig.projbuild index 99611cdbb7..31c7c5f3d9 100644 --- a/examples/storage/fatfsgen/main/Kconfig.projbuild +++ b/examples/storage/fatfsgen/main/Kconfig.projbuild @@ -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 diff --git a/examples/storage/fatfsgen/main/fatfsgen_example_main.c b/examples/storage/fatfsgen/main/fatfsgen_example_main.c index a6d6dae8b9..57497ab165 100644 --- a/examples/storage/fatfsgen/main/fatfsgen_example_main.c +++ b/examples/storage/fatfsgen/main/fatfsgen_example_main.c @@ -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 { diff --git a/examples/storage/fatfsgen/sdkconfig.ci.test_read_only_partition_gen_default_dt b/examples/storage/fatfsgen/sdkconfig.ci.test_read_only_partition_gen_default_dt new file mode 100644 index 0000000000..007bef1e37 --- /dev/null +++ b/examples/storage/fatfsgen/sdkconfig.ci.test_read_only_partition_gen_default_dt @@ -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 diff --git a/examples/storage/fatfsgen/sdkconfig.ci.test_read_only_partition_gen_ln_default_dt b/examples/storage/fatfsgen/sdkconfig.ci.test_read_only_partition_gen_ln_default_dt new file mode 100644 index 0000000000..1fdc304e4f --- /dev/null +++ b/examples/storage/fatfsgen/sdkconfig.ci.test_read_only_partition_gen_ln_default_dt @@ -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 diff --git a/examples/storage/fatfsgen/sdkconfig.ci.test_read_write_partition_gen_default_dt b/examples/storage/fatfsgen/sdkconfig.ci.test_read_write_partition_gen_default_dt new file mode 100644 index 0000000000..f0135f8216 --- /dev/null +++ b/examples/storage/fatfsgen/sdkconfig.ci.test_read_write_partition_gen_default_dt @@ -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 diff --git a/examples/storage/fatfsgen/sdkconfig.ci.test_read_write_partition_gen_ln_default_dt b/examples/storage/fatfsgen/sdkconfig.ci.test_read_write_partition_gen_ln_default_dt new file mode 100644 index 0000000000..ea48850fb4 --- /dev/null +++ b/examples/storage/fatfsgen/sdkconfig.ci.test_read_write_partition_gen_ln_default_dt @@ -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