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 handlejfs.read(file, size=1024)- Read from filejfs.write(file, data)- Write to filejfs.close(file)- Close filejfs.seek(file, position, whence=0)- Seek in filejfs.tell(file)- Get current positionjfs.size(file)- Get file sizejfs.available(file)- Get bytes available
File Object Methods (after f = jfs.open(...)):
f.print(data)- Print to the file (likeprint()but writes to the file, adds a newline)f.flush()- Flush buffered data to filef.position()- Alias forf.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 directoryjfs.rmdir(path)- Remove directoryjfs.remove(path)- Remove filejfs.rename(from, to)- Rename/move filejfs.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)
['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])
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
# 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
Trueif it exists,Falseotherwise.
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)