CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-littlefs-python

A Python wrapper for LittleFS filesystem designed for embedded systems with minimal RAM and flash requirements

Pending
Overview
Eval results
Files

contexts.mddocs/

Context Classes

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.

Capabilities

UserContext Class

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."""

UserContextWinDisk Class

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 Integration

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 library

Usage Examples

Memory Context Usage

from 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)

Windows Disk Context Usage

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}")

Disk Image File Context

# 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()

Custom Context Implementation

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')

Context Buffer Analysis

# 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 = None

Performance Context

import 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

docs

cli.md

contexts.md

file-operations.md

filesystem.md

index.md

low-level-api.md

tile.json