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 # SPDX-License-Identifier: Apache-2.0
import os import os
from datetime import datetime
from typing import Any, List, Optional from typing import Any, List, Optional
from fatfsgen_utils.fat import FAT from fatfsgen_utils.fat import FAT
from fatfsgen_utils.fatfs_parser import FATFSParser from fatfsgen_utils.fatfs_parser import FATFSParser
from fatfsgen_utils.fatfs_state import FATFSState from fatfsgen_utils.fatfs_state import FATFSState
from fatfsgen_utils.fs_object import Directory 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) get_args_for_partition_generator, pad_string, read_filesystem)
@ -29,6 +30,7 @@ class FATFS:
sectors_per_fat: int = 1, sectors_per_fat: int = 1,
hidden_sectors: int = 0, hidden_sectors: int = 0,
long_names_enabled: bool = False, long_names_enabled: bool = False,
use_default_datetime: bool = True,
entry_size: int = 32, entry_size: int = 32,
num_heads: int = 0xff, num_heads: int = 0xff,
oem_name: str = 'MSDOS5.0', oem_name: str = 'MSDOS5.0',
@ -60,7 +62,8 @@ class FATFS:
sec_per_track=sec_per_track, sec_per_track=sec_per_track,
long_names_enabled=long_names_enabled, long_names_enabled=long_names_enabled,
volume_label=volume_label, volume_label=volume_label,
oem_name=oem_name) oem_name=oem_name,
use_default_datetime=use_default_datetime)
binary_image: bytes = bytearray( binary_image: bytes = bytearray(
read_filesystem(binary_image_path) if binary_image_path else self.create_empty_fatfs()) read_filesystem(binary_image_path) if binary_image_path else self.create_empty_fatfs())
self.state.binary_image = binary_image self.state.binary_image = binary_image
@ -74,17 +77,28 @@ class FATFS:
fatfs_state=self.state) fatfs_state=self.state)
self.root_directory.init_directory() 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 # 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 # when path_from_root is None the dir is root
parent_dir = self.root_directory parent_dir = self.root_directory
if path_from_root: if path_from_root:
parent_dir = self.root_directory.recursive_search(path_from_root, self.root_directory) 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: 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) normal_path = os.path.normpath(folder_relative_path)
split_path = normal_path.split(os.sep) split_path = normal_path.split(os.sep)
object_timestamp = datetime.fromtimestamp(os.path.getctime(real_path))
if os.path.isfile(real_path): if os.path.isfile(real_path):
with open(real_path, 'rb') as file: with open(real_path, 'rb') as file:
content = file.read() content = file.read()
file_name, extension = os.path.splitext(split_path[-1]) file_name, extension = os.path.splitext(split_path[-1])
extension = extension[1:] # remove the dot from the extension 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) self.write_content(split_path[1:], content)
elif os.path.isdir(real_path): elif os.path.isdir(real_path):
if not is_dir: 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 # sorting files for better testability
dir_content = list(sorted(os.listdir(real_path))) dir_content = list(sorted(os.listdir(real_path)))
@ -171,7 +192,8 @@ def main() -> None:
size=args.partition_size, size=args.partition_size,
root_entry_count=args.root_entry_count, root_entry_count=args.root_entry_count,
explicit_fat_type=args.fat_type, 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.generate(args.input_directory)
fatfs.write_filesystem(args.output_file) 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 .exceptions import LowerCaseException, TooLongNameException
from .fatfs_state import FATFSState 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: class Entry:
@ -36,18 +37,22 @@ class Entry:
SHORT_ENTRY: int = -1 SHORT_ENTRY: int = -1
SHORT_ENTRY_LN: int = 0 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( ENTRY_FORMAT_SHORT_NAME = Struct(
'DIR_Name' / PaddedString(MAX_NAME_SIZE, 'utf-8'), 'DIR_Name' / PaddedString(MAX_NAME_SIZE, SHORT_NAMES_ENCODING),
'DIR_Name_ext' / PaddedString(MAX_EXT_SIZE, 'utf-8'), 'DIR_Name_ext' / PaddedString(MAX_EXT_SIZE, SHORT_NAMES_ENCODING),
'DIR_Attr' / Int8ul, 'DIR_Attr' / Int8ul,
'DIR_NTRes' / Const(b'\x00'), 'DIR_NTRes' / Const(b'\x00'),
'DIR_CrtTimeTenth' / Const(b'\x00'), 'DIR_CrtTimeTenth' / Const(b'\x00'), # ignored by esp-idf fatfs library
'DIR_CrtTime' / Const(b'\x00\x00'), 'DIR_CrtTime' / Int16ul, # ignored by esp-idf fatfs library
'DIR_CrtDate' / Const(b'\x21\x00'), 'DIR_CrtDate' / Int16ul, # ignored by esp-idf fatfs library
'DIR_LstAccDate' / Const(b'\x00\x00'), 'DIR_LstAccDate' / Int16ul, # must be same as DIR_WrtDate
'DIR_FstClusHI' / Const(b'\x00\x00'), 'DIR_FstClusHI' / Const(b'\x00\x00'),
'DIR_WrtTime' / Const(b'\x00\x00'), 'DIR_WrtTime' / Int16ul,
'DIR_WrtDate' / Const(b'\x21\x00'), 'DIR_WrtDate' / Int16ul,
'DIR_FstClusLO' / Int16ul, 'DIR_FstClusLO' / Int16ul,
'DIR_FileSize' / Int32ul, 'DIR_FileSize' / Int32ul,
) )
@ -117,6 +122,8 @@ class Entry:
entity_type: int, entity_type: int,
entity_extension: str = '', entity_extension: str = '',
size: int = 0, size: int = 0,
date: DATETIME = DEFAULT_DATE,
time: DATETIME = DEFAULT_TIME,
lfn_order: int = SHORT_ENTRY, lfn_order: int = SHORT_ENTRY,
lfn_names: Optional[List[bytes]] = None, lfn_names: Optional[List[bytes]] = None,
lfn_checksum_: int = 0, lfn_checksum_: int = 0,
@ -126,6 +133,8 @@ class Entry:
:param entity_name: name recorded in the entry :param entity_name: name recorded in the entry
:param entity_extension: extension recorded in the entry :param entity_extension: extension recorded in the entry
:param size: size of the content of the file :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 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_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 :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 :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 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) 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): if not (valid_full_name or lfn_order >= 0):
raise LowerCaseException('Lower case is not supported in short name entry, use upper case.') 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 # clean entry before allocation
self._clean_entry() self._clean_entry()
self._is_empty = False self._is_empty = False
@ -154,17 +169,25 @@ class Entry:
raise TooLongNameException( raise TooLongNameException(
'Maximal length of the object name is {} characters and {} characters for extension!'.format( 'Maximal length of the object name is {} characters and {} characters for extension!'.format(
MAX_NAME_SIZE, MAX_EXT_SIZE MAX_NAME_SIZE, MAX_EXT_SIZE
)) )
)
start_address = self.entry_address start_address = self.entry_address
end_address = start_address + self.fatfs_state.entry_size end_address = start_address + self.fatfs_state.entry_size
if lfn_order in (self.SHORT_ENTRY, self.SHORT_ENTRY_LN): 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( self.fatfs_state.binary_image[start_address: end_address] = self._build_entry(
DIR_Name=pad_string(object_name, size=MAX_NAME_SIZE), DIR_Name=pad_string(object_name, size=MAX_NAME_SIZE),
DIR_Name_ext=pad_string(object_extension, size=MAX_EXT_SIZE), DIR_Name_ext=pad_string(object_extension, size=MAX_EXT_SIZE),
DIR_Attr=entity_type, DIR_Attr=entity_type,
DIR_FstClusLO=first_cluster_id, 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: else:
assert lfn_names is not None assert lfn_names is not None

View File

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

View File

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

View File

@ -2,6 +2,7 @@
# SPDX-License-Identifier: Apache-2.0 # SPDX-License-Identifier: Apache-2.0
import os import os
from datetime import datetime
from typing import List, Optional, Tuple, Union from typing import List, Optional, Tuple, Union
from .entry import Entry 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, 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, get_required_lfn_entries_count, split_name_to_lfn_entries,
split_name_to_lfn_entry_blocks) 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, from .utils import (DATETIME, MAX_EXT_SIZE, MAX_NAME_SIZE, build_lfn_short_entry_name, lfn_checksum,
split_content_into_sectors, split_to_name_and_extension) required_clusters_count, split_content_into_sectors, split_to_name_and_extension)
class File: class File:
@ -180,8 +181,10 @@ class Directory:
extension, extension,
target_dir, target_dir,
free_cluster, free_cluster,
entity_type): entity_type,
# type: (Entry, str, str, Directory, Cluster, int) -> Tuple[Cluster, Entry, Directory] 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_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_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) 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_name=lfn_short_entry_name[:MAX_NAME_SIZE],
entity_extension=lfn_short_entry_name[MAX_NAME_SIZE:], entity_extension=lfn_short_entry_name[MAX_NAME_SIZE:],
entity_type=entity_type, 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 return free_cluster, free_entry, target_dir
def allocate_object(self, def allocate_object(self,
name, name,
entity_type, entity_type,
object_timestamp_,
path_from_root=None, path_from_root=None,
extension=''): 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 Method finds the target directory in the path
and allocates cluster (both the record in FAT and cluster in the data region) 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() 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 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: if not self.fatfs_state.long_names_enabled or name_fits_short_struct:
free_entry.allocate_entry(first_cluster_id=free_cluster.id, free_entry.allocate_entry(first_cluster_id=free_cluster.id,
entity_name=name, entity_name=name,
entity_extension=extension, entity_extension=extension,
date=fatfs_date_,
time=fatfs_time_,
entity_type=entity_type) entity_type=entity_type)
return free_cluster, free_entry, target_dir return free_cluster, free_entry, target_dir
return self.allocate_long_name_object(free_entry=free_entry, return self.allocate_long_name_object(free_entry=free_entry,
@ -237,13 +249,20 @@ class Directory:
extension=extension, extension=extension,
target_dir=target_dir, target_dir=target_dir,
free_cluster=free_cluster, 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, free_cluster, free_entry, target_dir = self.allocate_object(name=name,
extension=extension, extension=extension,
entity_type=Directory.ATTR_ARCHIVE, 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, file: File = File(name=name,
fat=self.fat, fat=self.fat,
@ -253,11 +272,12 @@ class Directory:
file.first_cluster = free_cluster file.first_cluster = free_cluster
target_dir.entities.append(file) target_dir.entities.append(file)
def new_directory(self, name, parent, path_from_root): def new_directory(self, name, parent, path_from_root, object_timestamp_):
# type: (str, Directory, Optional[List[str]]) -> None # type: (str, Directory, Optional[List[str]], datetime) -> None
free_cluster, free_entry, target_dir = self.allocate_object(name=name, free_cluster, free_entry, target_dir = self.allocate_object(name=name,
entity_type=Directory.ATTR_DIRECTORY, 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, directory: Directory = Directory(name=name,
fat=self.fat, fat=self.fat,

View File

@ -5,9 +5,10 @@ import argparse
import binascii import binascii
import os import os
import uuid import uuid
from datetime import datetime
from typing import List, Optional, Tuple from typing import List, Optional, Tuple
from construct import Int16ul from construct import BitsInteger, BitStruct, Int16ul
FAT12_MAX_CLUSTERS: int = 4085 FAT12_MAX_CLUSTERS: int = 4085
FAT16_MAX_CLUSTERS: int = 65525 FAT16_MAX_CLUSTERS: int = 65525
@ -18,9 +19,12 @@ BYTES_PER_DIRECTORY_ENTRY: int = 32
UINT32_MAX: int = (1 << 32) - 1 UINT32_MAX: int = (1 << 32) - 1
MAX_NAME_SIZE: int = 8 MAX_NAME_SIZE: int = 8
MAX_EXT_SIZE: int = 3 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 are encoded to two bytes in utf-16
LONG_NAMES_ENCODING: str = 'utf-16' LONG_NAMES_ENCODING: str = 'utf-16'
SHORT_NAMES_ENCODING: str = 'utf-8'
def crc32(input_values: List[int], crc: int) -> int: 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() 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) value_as_bytes: bytes = Int16ul.build(value)
return value_as_bytes[0] & 0x0f, value_as_bytes[0] >> 4, value_as_bytes[1] & 0x0f 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', parser.add_argument('--long_name_support',
action='store_true', action='store_true',
help='Set flag to enable long names support.') 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', parser.add_argument('--fat_type',
default=0, default=0,
type=int, type=int,
@ -180,3 +188,45 @@ def get_args_for_partition_generator(desc: str) -> argparse.Namespace:
def read_filesystem(path: str) -> bytearray: def read_filesystem(path: str) -> bytearray:
with open(path, 'rb') as fs_file: with open(path, 'rb') as fs_file:
return bytearray(fs_file.read()) 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 # Create a fatfs image of the specified directory on the host during build and optionally
# have the created image flashed using `idf.py flash` # have the created image flashed using `idf.py flash`
function(fatfs_create_partition_image partition base_dir) 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}") 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) set(fatfsgen_py ${python} ${idf_path}/components/fatfs/fatfsgen.py)
endif() 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}") if("${CONFIG_FATFS_SECTORS_PER_CLUSTER_1}")
set(sectors_per_cluster 1) set(sectors_per_cluster 1)
elseif("${CONFIG_FATFS_SECTORS_PER_CLUSTER_2}") elseif("${CONFIG_FATFS_SECTORS_PER_CLUSTER_2}")
@ -60,6 +66,8 @@ function(fatfs_create_partition_image partition base_dir)
set(fatfs_explicit_type 16) set(fatfs_explicit_type 16)
endif() endif()
get_filename_component(base_dir_full_path ${base_dir} ABSOLUTE) 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(size "--partition-name ${partition}" "size")
partition_table_get_partition_info(offset "--partition-name ${partition}" "offset") 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 add_custom_target(fatfs_${partition}_bin ALL
COMMAND ${fatfsgen_py} ${base_dir_full_path} COMMAND ${fatfsgen_py} ${base_dir_full_path}
${fatfs_long_names_option} ${fatfs_long_names_option}
${default_datetime_option}
--partition_size ${size} --partition_size ${size}
--output_file ${image_file} --output_file ${image_file}
--sector_size "${fatfs_sector_size}" --sector_size "${fatfs_sector_size}"
@ -102,21 +111,39 @@ endfunction()
function(fatfs_create_rawflash_image partition base_dir) 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}") cmake_parse_arguments(arg "${options}" "" "${multi}" "${ARGN}")
if(arg_FLASH_IN_PROJECT) 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() 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() endif()
endfunction() endfunction()
function(fatfs_create_spiflash_image partition base_dir) 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}") cmake_parse_arguments(arg "${options}" "" "${multi}" "${ARGN}")
if(arg_FLASH_IN_PROJECT) 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() 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() endif()
endfunction() endfunction()

View File

@ -217,7 +217,7 @@ class FatFSGen(unittest.TestCase):
file_system = read_filesystem(CFG['output_file']) file_system = read_filesystem(CFG['output_file'])
self.assertEqual(file_system[0x2000:0x2010], b'TESTFOLD \x10\x00\x00\x00\x00') 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')
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']) fatfs.write_filesystem(CFG['output_file'])
file_system = read_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[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[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[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') 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.create_file('HELLO', extension='TXT')
fatfs.write_filesystem(CFG['output_file']) fatfs.write_filesystem(CFG['output_file'])
file_system = read_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: def test_lfn_short_name(self) -> None:
fatfs = fatfsgen.FATFS(long_names_enabled=True) fatfs = fatfsgen.FATFS(long_names_enabled=True)
@ -303,7 +303,7 @@ class FatFSGen(unittest.TestCase):
fatfs.write_filesystem(CFG['output_file']) fatfs.write_filesystem(CFG['output_file'])
file_system = read_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[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') self.assertEqual(file_system[0x6000: 0x6010], b'this is a test\x00\x00')
def test_lfn_empty_name(self) -> None: 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[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[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[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: def test_lfn_plain_name(self) -> None:
fatfs = fatfsgen.FATFS(long_names_enabled=True) 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[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[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[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') self.assertEqual(file_system[0x6000: 0x6010], b'this is a test\x00\x00')
def test_lfn_plain_name_no_ext(self) -> None: 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[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[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[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') self.assertEqual(file_system[0x6000: 0x6010], b'this is a test\x00\x00')
def test_lfn_short_entry_exception(self) -> None: 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[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[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[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[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[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[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: def test_lfn_nested_long_empty(self) -> None:
fatfs: fatfsgen.FATFS = fatfsgen.FATFS(long_names_enabled=True) 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[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[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[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[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[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[0x6040: 0x6050], b'Bl\x00o\x00.\x00t\x00x\x00\x0f\x00\xd5t\x00')
self.assertEqual(file_system[0x6050: 0x6060], self.assertEqual(file_system[0x6050: 0x6060],
b'\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\xff\xff\xff\xff') 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[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[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[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[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[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[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') 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[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[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[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[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[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[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') 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[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[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[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[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') 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()) file_system = bytearray(fs_file.read())
self.assertEqual(file_system[0x7060:0x7070], b'TESTFIL2 \x00\x00\x00\x00') 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[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[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') 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, sec_per_track: int = 0x3f,
volume_label: str = 'Espressif', volume_label: str = 'Espressif',
file_sys_type: str = 'FAT', file_sys_type: str = 'FAT',
use_default_datetime: bool = True,
version: int = 2, version: int = 2,
temp_buff_size: int = 32, temp_buff_size: int = 32,
update_rate: int = 16, update_rate: int = 16,
@ -101,6 +102,7 @@ class WLFATFS:
long_names_enabled=long_names_enabled, long_names_enabled=long_names_enabled,
entry_size=entry_size, entry_size=entry_size,
num_heads=num_heads, num_heads=num_heads,
use_default_datetime=use_default_datetime,
oem_name=oem_name, oem_name=oem_name,
sec_per_track=sec_per_track, sec_per_track=sec_per_track,
volume_label=volume_label, volume_label=volume_label,
@ -194,7 +196,8 @@ if __name__ == '__main__':
size=args.partition_size, size=args.partition_size,
root_entry_count=args.root_entry_count, root_entry_count=args.root_entry_count,
explicit_fat_type=args.fat_type, 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.wl_generate(args.input_directory)
wl_fatfs.init_wl() wl_fatfs.init_wl()

View File

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

View File

@ -16,7 +16,15 @@ else()
endif() endif()
if(CONFIG_EXAMPLE_FATFS_MODE_READ_ONLY) 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() 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() 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). 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. 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 endmenu

View File

@ -106,6 +106,20 @@ void app_main(void)
host_filename2 = "/spiflash/hello.txt"; 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){ if (EXAMPLE_FATFS_MODE_READ_ONLY){
f = fopen(host_filename1, "rb"); f = fopen(host_filename1, "rb");
} else { } 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