Running Two Programs on a Single CircuitPython Device
I have a nice e-ink display on my shelf running a CircuitPython program that updates the display every fifteen minutes. Recently I wrote a second program that would also just be ideal for this display.
Thinking about my options, I could either
- copy the relevant program to the MCU everytime I want to switch
- write a wrapper application for both programs
The first option was not attractive, because I want to switch on a regular basis. The second option also has it's problems. Both existing programs are valid stand-alone and are maintained in their own Github-repositories, so adding a wrapper would make changes to the software much more complicated.
So I was thinking about a sort of "boot-switcher". Users running Linux on their PCs often have a boot-manager that allows them to switch to more obscure operating systems once in a while, and I wondered if there is a simple way to use this concept also for a CircuitPython device.
Hardware Setup
Since a MCU neither has a BIOS that can start a boot-manager before the real OS, nor (usually) a mouse or keyboard attached for the selection of the target program, this solution needs a free GPIO and a slider or button.
Our "boot-manager" is the code in boot.py
. This code reads the state
of the GPIO and starts one of the programs depending on the value. Using
a slider will result in a "sticky" behavior: unless you flip it again,
each reset will start the same program. The button is different, since
you have to keep it pressed during power-on/reset to start the second
program. So the second program is more of an exception while the first
program runs as the default.
Software Setup
For the boot-switcher to work, the programs can't be deployed to the top-level directory of the CIRCUITPY-drive. Instead, the layout on the drive is like this:
So every application moves one level down into its own directory.
You can find the necessary boot.py
in the Github-repo
https://github.com/bablokb/cp-bootswitcher. In this file, you have to
configure your setup, e.g.
PIN_SWITCH = board.D1
APP_NAMES = ["_app0","_app1"]
SHARED_FILES = ["boot.py"]+APP_NAMES
The names of the application directories don't matter, but you have to
add the names to APP_NAMES
. You can also add more files/directories
to SHARED_FILES
, but all of these entries must be top-level. One
use case for this is if your applications share the lib-folder.
How does it Work?
The implementation of the boot-switcher turned out to be simple and
during power-on or reset you won't notice a delay. The code in
boot.py
only checks the state of the GPIO and then moves all files
from the top-level back to the appropriate application folder and then
all files from the other application folder to the toplevel. Moving is
a fast operation, since it only changes the entry in the
directory-table (FAT), so moving a very large lib-folder won't be slower
than moving code.py
.
Other Use-Cases
There are a number of other use-cases for the boot-switcher, e.g.
- providing two versions of an application, e.g. a beta version in addition to a productive version
- providing a special diagnostic application in addition to the normal application
- running the same application with two different configurations (e.g. within two different WLAN-networks)
- having two sets of libraries e.g. for CP 8.x and 9.x
Final Notes
If you only want to start different top-level programs and otherwise share the rest of your code and all settings, you don't need the boot-switcher. You can do this with a few lines of code, e.g.:
import supervisor
...
supervisor.set_next_code_file("admin.py",sticky_on_reload=True)
supervisor.reload()
Code within boot.py
can do many things for you. For example, you can
make the CIRCUITPY drive writable for your program or hide it for
the PC. Of course you can also shoot yourself in the foot, so reading
the documentation is recommended.