A Python wrapper for LittleFS filesystem designed for embedded systems with minimal RAM and flash requirements
—
Storage backend implementations that handle block-level read, program, erase, and sync operations for different storage targets. Context classes provide the abstraction layer between LittleFS and physical storage, enabling support for memory buffers, disk devices, and custom storage implementations.
Basic memory-based context implementation that stores filesystem data in a Python bytearray. This is the default context used for most applications and provides a simple in-memory filesystem suitable for creating filesystem images.
class UserContext:
def __init__(self, buffsize: int) -> None:
"""
Initialize memory-based storage context.
Parameters:
- buffsize: int, size of memory buffer in bytes
The buffer is initialized with 0xFF bytes (erased flash state).
"""
def read(self, cfg: LFSConfig, block: int, off: int, size: int) -> bytearray:
"""
Read data from storage.
Parameters:
- cfg: LFSConfig, filesystem configuration
- block: int, block number to read from
- off: int, offset within block
- size: int, number of bytes to read
Returns:
bytearray: Data read from storage
"""
def prog(self, cfg: LFSConfig, block: int, off: int, data: bytes) -> int:
"""
Program (write) data to storage.
Parameters:
- cfg: LFSConfig, filesystem configuration
- block: int, block number to write to
- off: int, offset within block
- data: bytes, data to write
Returns:
int: 0 on success
"""
def erase(self, cfg: LFSConfig, block: int) -> int:
"""
Erase a block (set all bytes to 0xFF).
Parameters:
- cfg: LFSConfig, filesystem configuration
- block: int, block number to erase
Returns:
int: 0 on success
"""
def sync(self, cfg: LFSConfig) -> int:
"""
Sync cached data to storage.
Parameters:
- cfg: LFSConfig, filesystem configuration
Returns:
int: 0 on success
For memory context, this is a no-op since data is immediately available.
"""
@property
def buffer(self) -> bytearray:
"""Access to the underlying memory buffer."""Windows disk-based context implementation that provides direct access to Windows disk devices. This context enables LittleFS operations on physical drives, partitions, or disk image files using Windows Win32 APIs.
class UserContextWinDisk(UserContext):
def __init__(self, disk_path: str) -> None:
"""
Initialize Windows disk-based storage context.
Parameters:
- disk_path: str, Windows device path (e.g., '\\\\.\\PhysicalDrive0', '\\\\.\\C:', 'image.bin')
Requires:
- Windows platform
- pywin32 package installed
- Appropriate permissions for disk access
Raises:
ImportError: If pywin32 is not available
IOError: If disk cannot be opened
"""
def read(self, cfg: LFSConfig, block: int, off: int, size: int) -> bytearray:
"""
Read data from Windows disk device.
Uses Win32 SetFilePointer and ReadFile APIs for direct disk access.
"""
def prog(self, cfg: LFSConfig, block: int, off: int, data: bytes) -> int:
"""
Program data to Windows disk device.
Uses Win32 SetFilePointer and WriteFile APIs for direct disk access.
"""
def erase(self, cfg: LFSConfig, block: int) -> int:
"""
Erase block on Windows disk device by writing 0xFF bytes.
Uses Win32 SetFilePointer and WriteFile APIs.
"""
def sync(self, cfg: LFSConfig) -> int:
"""
Sync data to Windows disk device.
Uses Win32 FlushFileBuffers API to ensure data is written to storage.
"""
def __del__(self):
"""Cleanup Windows handle on destruction."""Context objects are integrated with LFSConfig and automatically used by filesystem operations:
class LFSConfig:
@property
def user_context(self) -> UserContext:
"""Access to the associated user context."""
# Context is automatically used for all filesystem operations
# through callback functions registered with the C libraryfrom littlefs import LittleFS, UserContext
# Create custom-sized memory context
context = UserContext(buffsize=2*1024*1024) # 2MB buffer
# Use with LittleFS
fs = LittleFS(context=context, block_size=4096, block_count=512)
# Perform filesystem operations
with fs.open('test.txt', 'w') as f:
f.write('Hello, World!')
# Access raw buffer data
raw_data = context.buffer
print(f"Buffer size: {len(raw_data)} bytes")
# Export filesystem image
with open('filesystem.bin', 'wb') as f:
f.write(raw_data)from littlefs import LittleFS, UserContextWinDisk
# Access physical drive (requires admin privileges)
try:
disk_context = UserContextWinDisk('\\\\.\\PhysicalDrive1')
fs = LittleFS(context=disk_context, block_size=4096, mount=False)
# Format the drive with LittleFS
fs.format()
fs.mount()
# Use filesystem normally
fs.mkdir('data')
with fs.open('data/readme.txt', 'w') as f:
f.write('LittleFS on physical drive!')
except ImportError:
print("pywin32 not available")
except IOError as e:
print(f"Cannot access disk: {e}")# Create disk image file first
with open('disk_image.bin', 'wb') as f:
f.write(b'\\xff' * (1024 * 1024)) # 1MB blank image
# Use Windows context with file path
disk_context = UserContextWinDisk('disk_image.bin')
fs = LittleFS(context=disk_context, block_size=512, block_count=2048)
# Use filesystem
with fs.open('config.json', 'w') as f:
f.write('{"version": "1.0"}')
# Changes are written directly to disk_image.bin
fs.unmount()import logging
class LoggingContext(UserContext):
"""Context that logs all operations for debugging."""
def __init__(self, buffsize: int):
super().__init__(buffsize)
self.logger = logging.getLogger(__name__)
def read(self, cfg, block, off, size):
self.logger.debug(f"READ: block={block}, off={off}, size={size}")
return super().read(cfg, block, off, size)
def prog(self, cfg, block, off, data):
self.logger.debug(f"PROG: block={block}, off={off}, size={len(data)}")
return super().prog(cfg, block, off, data)
def erase(self, cfg, block):
self.logger.debug(f"ERASE: block={block}")
return super().erase(cfg, block)
def sync(self, cfg):
self.logger.debug("SYNC")
return super().sync(cfg)
# Use custom context
logging.basicConfig(level=logging.DEBUG)
ctx = LoggingContext(64*1024) # 64KB with logging
fs = LittleFS(context=ctx, block_size=512, block_count=128)
# All operations will be logged
with fs.open('debug.txt', 'w') as f:
f.write('This will generate read/prog/erase logs')# Analyze raw filesystem data
fs = LittleFS(block_size=512, block_count=256)
# Create some files
with fs.open('file1.txt', 'w') as f:
f.write('Content of file 1')
with fs.open('file2.txt', 'w') as f:
f.write('Content of file 2')
# Examine raw buffer
buffer = fs.context.buffer
print(f"Total buffer size: {len(buffer)} bytes")
# Find used vs unused space
used_blocks = fs.used_block_count
total_blocks = fs.block_count
block_size = 512
print(f"Used blocks: {used_blocks}/{total_blocks}")
print(f"Used space: {used_blocks * block_size} bytes")
print(f"Free space: {(total_blocks - used_blocks) * block_size} bytes")
# Look for filesystem signatures (first few bytes often contain metadata)
header = buffer[:64]
print(f"Filesystem header: {header[:16].hex()}")
# Find erased regions (0xFF bytes indicate unused flash)
erased_start = None
for i, byte in enumerate(buffer):
if byte == 0xff:
if erased_start is None:
erased_start = i
else:
if erased_start is not None:
size = i - erased_start
if size > 512: # Only report significant erased regions
print(f"Erased region: {erased_start}-{i} ({size} bytes)")
erased_start = Noneimport time
class PerformanceContext(UserContext):
"""Context that measures performance metrics."""
def __init__(self, buffsize: int):
super().__init__(buffsize)
self.read_count = 0
self.write_count = 0
self.erase_count = 0
self.read_bytes = 0
self.write_bytes = 0
self.read_time = 0.0
self.write_time = 0.0
def read(self, cfg, block, off, size):
start = time.time()
result = super().read(cfg, block, off, size)
self.read_time += time.time() - start
self.read_count += 1
self.read_bytes += size
return result
def prog(self, cfg, block, off, data):
start = time.time()
result = super().prog(cfg, block, off, data)
self.write_time += time.time() - start
self.write_count += 1
self.write_bytes += len(data)
return result
def erase(self, cfg, block):
self.erase_count += 1
return super().erase(cfg, block)
def get_stats(self):
return {
'reads': self.read_count,
'writes': self.write_count,
'erases': self.erase_count,
'read_bytes': self.read_bytes,
'write_bytes': self.write_bytes,
'read_time': self.read_time,
'write_time': self.write_time,
'read_throughput': self.read_bytes / self.read_time if self.read_time > 0 else 0,
'write_throughput': self.write_bytes / self.write_time if self.write_time > 0 else 0
}
# Measure filesystem performance
perf_ctx = PerformanceContext(512*1024) # 512KB
fs = LittleFS(context=perf_ctx, block_size=4096, block_count=128)
# Perform operations
start_time = time.time()
for i in range(100):
with fs.open(f'file_{i:03d}.txt', 'w') as f:
f.write(f'Content for file {i}\\n' * 10)
total_time = time.time() - start_time
# Report performance metrics
stats = perf_ctx.get_stats()
print(f"Total time: {total_time:.3f}s")
print(f"Operations: {stats['reads']} reads, {stats['writes']} writes, {stats['erases']} erases")
print(f"Data: {stats['read_bytes']} bytes read, {stats['write_bytes']} bytes written")
print(f"Throughput: {stats['read_throughput']:.0f} B/s read, {stats['write_throughput']:.0f} B/s write")
print(f"Average: {100/total_time:.1f} files/second")Install with Tessl CLI
npx tessl i tessl/pypi-littlefs-python