JFS (Jumperless FileSystem)

The jfs module is basically like MicroPython's vfs and parts of os, but kinda written in a style that's probably more familiar to Arduino-style C++ people (me). It uses the almost standardized API shared by FatFS (the one Jumperless actually uses), LittleFS, and SDFS, but still has been Pythonified to use types that are easier to work with in MicroPython.

Quick Reference

File Operations:

  • jfs.open(path, mode) - Opens file, returns file handle
  • jfs.read(file, size=1024) - Read from file
  • jfs.write(file, data) - Write to file
  • jfs.close(file) - Close file
  • jfs.seek(file, position, whence=0) - Seek in file
  • jfs.tell(file) - Get current position
  • jfs.size(file) - Get file size
  • jfs.available(file) - Get bytes available

File Object Methods (after f = jfs.open(...)):

  • f.print(data) - Print to the file (like print() but writes to the file, adds a newline)
  • f.flush() - Flush buffered data to file
  • f.position() - Alias for f.tell()
  • f.name() - Get file name

Directory Operations:

  • jfs.exists(path) - Check if path exists (returns True/False)
  • jfs.listdir(path) - List directory contents (returns list)
  • jfs.mkdir(path) - Create directory
  • jfs.rmdir(path) - Remove directory
  • jfs.remove(path) - Remove file
  • jfs.rename(from, to) - Rename/move file
  • jfs.stat(path) - Get file/directory status info

Filesystem Info:

  • jfs.info() - Returns (total, used, free) tuple

Usage

import jfs  #you don't actually need this, jfs is imported globally by default

# List files in the root directory
files = jfs.listdir('/')   # returns a python list
print(files)
Output:
['config.txt', 'slots/', 'python_scripts/']

(Directories come back with a trailing /. Older filesystems may also show legacy nodeFileSlot*.txt files and a net_colors/ directory at the root.)

If you want to make this print with subdirectories
files = jfs.listdir('/')

for file in range(len(files)):

    print(files[file])

    if files[file].endswith("/"):
        subdir = jfs.listdir(files[file])

        for i in range(len(subdir)):
            print("     - " + subdir[i])
Output:
config.txt
slots/
     - slot0.yaml
     - slot1.yaml
     - slot2.yaml
python_scripts/
     - history.txt
     - examples/
     - lib/
     - script_1.py
     - script_2.py
     - log.txt

File API

File objects returned by jfs.open() support method calls directly on the object:

# Object-oriented file operations

# Write-only mode
f = jfs.open('hello.txt', 'w')
f.write('Hello, Jumperless!')
f.close()

# Read from the file (need to reopen or use w+/r+ mode)
f = jfs.open('hello.txt', 'r')
content = f.read()             # Read from file object
size = f.size()                # Get file size
f.close()

# Read-write mode (truncates file)
f = jfs.open('hello.txt', 'w+')
f.write('Hello, Jumperless!')
f.seek(0)                      # Seek to beginning to read what we wrote
content = f.read()             # Now this works!
f.close()

# Context manager support (automatically closes file)
with jfs.open('data.txt', 'w+') as f:
    f.write('This file will be automatically closed')
    f.seek(0)                  # Reset to beginning
    content = f.read()         # Read back what we wrote
    pos = f.tell()             # Get current position
    name = f.name()            # Get file name

Using f.print() for logging

The f.print() method works like Python's print() but writes to the file. It automatically converts arguments to strings, joins multiple arguments with spaces, and adds a newline - perfect for logging.

Note: f.print() does not flush after each call (a FatFS flush takes ~2 seconds, so auto-flushing would make logging loops crawl). Data is committed when you f.close() or f.seek() - if you're logging data you can't afford to lose to a crash or reset, call f.flush() yourself at checkpoints:

# Great for logging - auto-converts types and adds newlines
with jfs.open('log.txt', 'w+') as f:
    f.print("Starting test...")
    voltage = adc_get(0)
    f.print("Voltage:", voltage, "V")    # Multiple args work
    f.print("Test complete!")
    f.flush()                            # Commit to flash at a checkpoint

# Unlike f.write(), f.print() handles non-strings automatically
f = jfs.open('data.txt', 'w')
f.print(123)           # OK - converts int to string
f.print(3.14)          # OK - converts float to string
f.print("mixed", 42)   # OK - multiple args joined with spaces
f.close()

Note: f.print() is a file object method only. There is no jfs.print() module-level function.

Other ways to do the same thing ### 2. Module-Level Functions You can also use module-level functions with file handles:
f = jfs.open('hello.txt', 'w')
jfs.write(f, 'Hello, Jumperless!')  # Module-level function
jfs.seek(f, 0)                      # Module-level function  
content = jfs.read(f)               # Module-level function
jfs.close(f)                        # Module-level function
### 3. Direct String Operations (For simple cases) The whole-file path-based helpers live in the `jumperless` module (available globally), not in `jfs`:
# Write/read entire files at once (no file handles needed)
fs_write('notes.txt', 'key=value\nother=setting')
content = fs_read('notes.txt')

File Modes

When using jfs.open(path, mode), the following modes are supported:

Mode Description Read Write Create
'r' Read only
'w' Write only
'a' Append only
'r+' Read + Write
'w+' Read + Write
'a+' Read + Append

Important: You cannot read from a file opened in write-only mode ('w' or 'a') - reads just come back empty. Use 'w+', 'r+', or 'a+' if you need both read and write access.

You can also add 'b' to any mode (e.g. 'rb', 'wb') to open in binary mode: reads return bytes and writes accept bytes/bytearray/memoryview.

Open a file for writing

f = jfs.open('hello.txt', 'w')
f.write('Hello, Jumperless!')  # Now works with object-oriented API!
f.close()

Directory Operations

jfs.listdir(path)

Returns a list containing the names of the entries in the directory given by path.

  • path (str): The path to the directory.

Example:

# List contents of the root directory
print(jfs.listdir('/'))

# List contents of a subdirectory
jfs.mkdir('/my_dir')
print(jfs.listdir('/my_dir'))

jfs.mkdir(path)

Create a new directory.

  • path (str): The path of the new directory.

jfs.rmdir(path)

Remove an empty directory.

  • path (str): The path of the directory to remove.

jfs.remove(path)

Remove a file.

  • path (str): The path of the file to remove.

jfs.rename(old_path, new_path)

Rename a file or directory.

  • old_path (str): The current path.
  • new_path (str): The new path.

jfs.exists(path)

Check if a file or directory exists.

  • path (str): The path to check.
  • Returns True if it exists, False otherwise.

jfs.stat(path)

Get status of a file or directory.

  • path (str): The path of the file or directory.
  • Returns a 10-element tuple in the same order as os.stat(). Only the size field (index 6) is really populated - mode and timestamps are placeholders.

Filesystem Information

jfs.info()

Get information about the filesystem.

  • Returns a tuple (total_bytes, used_bytes, free_bytes).

Example:

total, used, free = jfs.info()
print("Filesystem Size: " + str(total / 1024) + " KB")
print("Used: " + str(used / 1024) + " KB")
print("Free: " + str(free / 1024) + " KB")

File I/O

The jfs module supports standard file opening and handling using jfs.open() and file objects, including support for the with statement for automatic resource management.

jfs.open(path, mode='r')

Open a file and return a corresponding file object.

  • path (str): The path to the file.
  • mode (str, optional): The mode in which the file is opened. Defaults to 'r'.
    • 'r': Read (default).
    • 'w': Write (creates a new file or truncates an existing one).
    • 'a': Append.
    • 'r+': Read and write.
    • 'w+': Write and read (creates/truncates).
    • 'a+': Append and read.

Example:

# Open a file for reading
f = jfs.open('config.txt', 'r')
content = f.read()
f.close()

# Use 'with' for automatic closing
with jfs.open('data.log', 'a') as log_file:
    log_file.write('New log entry.\\n')

File Object Methods

The file object returned by jfs.open() has the following methods:

file.read([size])

Read size bytes from the file. If size is omitted, reads from the current position to the end of the file. Reads are capped at 8192 bytes per call, so loop if you're reading a file bigger than 8 KB.

file.write(data)

Write the given string or bytes data to the file. Returns the number of bytes written.

file.close()

Close the file. A closed file cannot be read or written to.

file.seek(offset, [whence])

Change the stream position. * offset: The byte offset. * whence (optional): * 0: Seek from the start of the stream (default). Use jfs.SEEK_SET. * 1: Seek from the current position. Use jfs.SEEK_CUR. * 2: Seek from the end of the stream. Use jfs.SEEK_END.

file.tell()

Return the current stream position. * Aliases: file.position()

file.size()

Return the total size of the file in bytes.

file.available()

Return the number of bytes available to be read from the current position to the end of the file.

file.name()

Returns the name of the file. (It's a method call, not an attribute - f.name gives you the bound method, not the string.)


Module-Level File Operations

For convenience, the jfs module also provides functions that operate directly on file handles returned by jfs.open(). This can be useful in some scripting scenarios but using file object methods is generally preferred for clarity.

  • jfs.read(file_handle, [size])
  • jfs.write(file_handle, data)
  • jfs.close(file_handle)
  • jfs.seek(file_handle, offset, [whence])
  • jfs.tell(file_handle)
  • jfs.size(file_handle)
  • jfs.available(file_handle)

Example:

file_handle = jfs.open('temp.txt', 'w')
jfs.write(file_handle, 'some data')
jfs.close(file_handle)