Storage

Sometimes you need to store useful information. Such information is stored as data: representation of information (in a digital form when stored on computers). If you store data on a computer it should persist, even if you switch the device off and on again.

Happily MicroPython on the micro:bit allows you to do this with a very simple file system. Because of memory constraints there is approximately 30k of storage available on the file system.

Note

The micropython file system should not be confused with the micro:bit mass storage mode which presents the device as a USB drive. Mass storage mode is only intended for copying across a HEX file, so you won’t see files you create using the file system appearing on the MICROBIT drive.

What is a file system?

It’s a means of storing and organising data in a persistent manner - any data stored in a file system should survive restarts of the device. As the name suggests, data stored on a file system is organised into files.

../_images/files.jpg

A computer file is a named digital resource that’s stored on a file system. Such resources contain useful information as data. This is exactly how a paper file works. It’s a sort of named container that contains useful information. Usually, both paper and digital files are named to indicate what they contain. On computers it is common to end a file with a .something suffix. Usually, the “something” indicates what type of data is used to represent the information. For example, .txt indicates a text file, .jpg a JPEG image and .mp3 sound data encoded as MP3.

Some file systems (such as the one found on your laptop or PC) allow you to organise your files into directories: named containers that group related files and sub-directories together. However, the file system provided by MicroPython is a flat file system. A flat file system does not have directories - all your files are just stored in the same place.

The Python programming language contains easy to use and powerful ways in which to work with a computer’s file system. MicroPython on the micro:bit implements a useful subset of these features to make it easy to read and write files on the device, while also providing consistency with other versions of Python.

Warning

Flashing your micro:bit will DESTROY ALL YOUR DATA since it re-writes all the flash memory used by the device and the file system is stored in the flash memory.

However, if you switch off your device the data will remain intact until you either delete it or re-flash the device.

Open Sesame

Reading and writing a file on the file system is achieved by the open function. Once a file is opened you can do stuff with it until you close it (analogous with the way we use paper files). It is essential you close a file so MicroPython knows you’ve finished with it.

The best way to make sure of this is to use the with statement like this:

with open('story.txt') as my_file:
    content = my_file.read()
print(content)

The with statement uses the open function to open a file and assign it to an object. In the example above, the open function opens the file called story.txt (obviously a text file containing a story of some sort). The object that’s used to represent the file in the Python code is called my_file. Subsequently, in the code block indented underneath the with statement, the my_file object is used to read() the content of the file and assign it to the content object.

Here’s the important point, the next line containing the print statement is not indented. The code block associated with the with statement is only the single line that reads the file. Once the code block associated with the with statement is closed then Python (and MicroPython) will automatically close the file for you. This is called context handling and the open function creates objects that are context handlers for files.

Put simply, the scope of your interaction with a file is defined by the code block associated with the with statement that opens the file.

Confused?

Don’t be. I’m simply saying your code should look like this:

with open('some_file') as some_object:
    # Do stuff with some_object in this block of code
    # associated with the with statement.

# When the block is finished then MicroPython
# automatically closes the file for you.

Just like a paper file, a digital file is opened for two reasons: to read its content (as demonstrated above) or to write something to the file. The default mode is to read the file. If you want to write to a file you need to tell the open function in the following way:

with open('hello.txt', 'w') as my_file:
    my_file.write("Hello, World!")

Notice the 'w' argument is used to set the my_file object into write mode. You could also pass an 'r' argument to set the file object to read mode, but since this is the default, it’s often left off.

Writing data to the file is done with the (you guessed it) write method that takes the string you want to write to the file as an argument. In the example above, I write the text “Hello, World!” to a file called “hello.txt”.

Simple!

Note

When you open a file and write (perhaps several times while the file is in an open state) you will be writing OVER the content of the file if it already exists.

If you want to append data to a file you should first read it, store the content somewhere, close it, append your data to the content and then open it to write again with the revised content.

While this is the case in MicroPython, “normal” Python can open files to write in “append” mode. That we can’t do this on the micro:bit is a result of the simple implementation of the file system.

OS SOS

As well as reading and writing files, Python can manipulate them. You certainly need to know what files are on the file system and sometimes you need to delete them too.

On a regular computer, it is the role of the operating system (like Windows, OSX or Linux) to manage this on Python’s behalf. Such functionality is made available in Python via a module called os. Since MicroPython is the operating system we’ve decided to keep the appropriate functions in the os module for consistency so you’ll know where to find them when you use “regular” Python on a device like a laptop or Raspberry Pi.

Essentially, you can do three operations related to the file system: list the files, remove a file and ask for the size of a file.

To list the files on your file system use the listdir function. It returns a list of strings indicating the file names of the files on the file system:

import os
my_files = os.listdir()

To delete a file use the remove function. It takes a string representing the file name of the file you want to delete as an argument, like this:

import os
os.remove('filename.txt')

Finally, sometimes it’s useful to know how big a file is before reading from it. To achieve this use the size function. Like the remove function, it takes a string representing the file name of the file whose size you want to know. It returns an integer (whole number) telling you the number of bytes the file takes up:

import os
file_size = os.size('a_big_file.txt')

It’s all very well having a file system, but what if we want to put or get files on or off the device?

Just use the microfs utility!

File Transfer

If you have Python installed on the computer you use to program your BBC micro:bit then you can use a special utility called microfs (shortened to ufs when using it in the command line). Full instructions for installing and using all the features of microfs can be found in its documentation.

Nevertheless it’s possible to do most of the things you need with just four simple commands:

$ ufs ls
story.txt

The ls sub-command lists the files on the file system (it’s named after the common Unix command, ls, that serves the same function).

$ ufs get story.txt

The get sub-command gets a file from the connected micro:bit and saves it into your current location on your computer (it’s named after the get command that’s part of the common file transfer protocol [FTP] that serves the same function).

$ ufs rm story.txt

The rm sub-command removes the named file from the file system on the connected micro:bit (it’s named after the common Unix command, rm, that serves the same function).

$ ufs put story2.txt

Finally, the put sub-command puts a file from your computer onto the connected device (it’s named after the put command that’s part of FTP that serves the same function).

Note

The ufs get and put commands only operate on one file at a time.

Mainly main.py

The file system also has an interesting property: if you just flashed the MicroPython runtime onto the device then when it starts it’s simply waiting for something to do. However, if you copy a special file called main.py onto the file system, upon restarting the device, MicroPython will run the contents of the main.py file.

Furthermore, if you copy other Python files onto the file system then you can import them as you would any other Python module. For example, if you had a hello.py file that contained the following simple code:

def say_hello(name="World"):
    return "Hello, {}!".format(name)

…you could import and use the say_hello function like this:

from microbit import display
from hello import say_hello

display.scroll(say_hello())

Of course, it results in the text “Hello, World!” scrolling across the display. The important point is that such an example is split between two Python modules and the import statement is used to share code.

Note

If you have flashed a script onto the device in addition to the MicroPython runtime, then MicroPython will ignore main.py and run your embedded script instead.

To flash just the MicroPython runtime, simply make sure the script you may have written in your editor has zero characters in it. Once flashed you’ll be able to copy over a main.py file.