[{"element_type":"markdown","content":"\u003ch1\u003eRunning Two Programs on a Single CircuitPython Device\u003c/h1\u003e\n\n\u003cp\u003eI have a nice e-ink display on my shelf running a CircuitPython\nprogram that updates the display every fifteen minutes. Recently\nI wrote a second program that would also just be ideal for this display.\u003c/p\u003e\n\n\u003cp\u003eThinking about my options, I could either\u003c/p\u003e\n\n\u003cul\u003e\n\u003cli\u003ecopy the relevant program to the MCU everytime I want to switch\u003c/li\u003e\n\u003cli\u003ewrite a wrapper application for both programs\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003cp\u003eThe first option was not attractive, because I want to switch on a\nregular basis. The second option also has it's problems. Both existing\nprograms are valid stand-alone and are maintained in their own\nGithub-repositories, so adding a wrapper would make changes to the software\nmuch more complicated.\u003c/p\u003e\n\n\u003cp\u003eSo I was thinking about a sort of \"boot-switcher\". Users running Linux\non their PCs often have a boot-manager that allows them to switch to\nmore obscure operating systems once in a while, and I wondered if\nthere is a simple way to use this concept also for a CircuitPython device.\u003c/p\u003e\n\n\u003ch2\u003eHardware Setup\u003c/h2\u003e\n\n\u003cp\u003eSince a MCU neither has a BIOS that can start a boot-manager before the\nreal OS, nor (usually) a mouse or keyboard attached for the selection of\nthe target program, this solution needs a free GPIO and a slider or button.\u003c/p\u003e\n\n\u003cp\u003eOur \"boot-manager\" is the code in \u003ccode class=\"inline\"\u003eboot.py\u003c/code\u003e. This code reads the state\nof the GPIO and starts one of the programs depending on the value. Using\na slider will result in a \"sticky\" behavior: unless you flip it again,\neach reset will start the same program. The button is different, since\nyou have to keep it pressed during power-on/reset to start the second\nprogram. So the second program is more of an exception while the first\nprogram runs as the default.\u003c/p\u003e\n\n\u003ch2\u003eSoftware Setup\u003c/h2\u003e\n\n\u003cp\u003eFor the boot-switcher to work, the programs can't be deployed to the\ntop-level directory of the CIRCUITPY-drive. Instead, the layout on the\ndrive is like this:\u003c/p\u003e\n\n\u003cp\u003e\u003cimg src=\"https://github.com/bablokb/cp-bootswitcher/blob/main/directories.png?raw=true\" alt=\"\"\u003e\u003c/p\u003e\n\n\u003cp\u003eSo every application moves one level down into its own directory.\u003c/p\u003e\n\n\u003cp\u003eYou can find the necessary \u003ccode class=\"inline\"\u003eboot.py\u003c/code\u003e in the Github-repo\n\u003ca href=\"https://github.com/bablokb/cp-bootswitcher\"\u003ehttps://github.com/bablokb/cp-bootswitcher\u003c/a\u003e. In this file, you have to\nconfigure your setup, e.g.\u003c/p\u003e\n\u003ccode class=\"\"\u003e\u003cpre\u003ePIN_SWITCH = board.D1\nAPP_NAMES  = [\"_app0\",\"_app1\"]\nSHARED_FILES = [\"boot.py\"]+APP_NAMES\n\u003c/pre\u003e\u003c/code\u003e\n\u003cp\u003eThe names of the application directories don't matter, but you have to\nadd the names to \u003ccode class=\"inline\"\u003eAPP_NAMES\u003c/code\u003e. You can also add more files/directories\nto \u003ccode class=\"inline\"\u003eSHARED_FILES\u003c/code\u003e, but all of these entries must be top-level. One\nuse case for this is if your applications share the lib-folder.\u003c/p\u003e\n\n\u003ch2\u003eHow does it Work?\u003c/h2\u003e\n\n\u003cp\u003eThe implementation of the boot-switcher turned out to be simple and\nduring power-on or reset you won't notice a delay. The code in\n\u003ccode class=\"inline\"\u003eboot.py\u003c/code\u003e only checks the state of the GPIO and then moves all files\nfrom the top-level back to the appropriate application folder and then\nall files from the other application folder to the toplevel. Moving is\na fast operation, since it only changes the entry in the\ndirectory-table (FAT), so moving a very large lib-folder won't be slower\nthan moving \u003ccode class=\"inline\"\u003ecode.py\u003c/code\u003e.\u003c/p\u003e\n\n\u003ch2\u003eOther Use-Cases\u003c/h2\u003e\n\n\u003cp\u003eThere are a number of other use-cases for the boot-switcher, e.g.\u003c/p\u003e\n\n\u003cul\u003e\n\u003cli\u003eproviding two versions of an application, e.g. a beta version in\naddition to a productive version\u003c/li\u003e\n\u003cli\u003eproviding a special diagnostic application in addition to the normal\napplication\u003c/li\u003e\n\u003cli\u003erunning the same application with two different configurations\n(e.g. within two different WLAN-networks)\u003c/li\u003e\n\u003cli\u003ehaving two sets of libraries e.g. for CP 8.x and 9.x\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch2\u003eFinal Notes\u003c/h2\u003e\n\n\u003cp\u003eIf you only want to start different top-level programs and otherwise\nshare the rest of your code and all settings, you don't need\nthe boot-switcher. You can do this with a few lines of code, e.g.:\u003c/p\u003e\n\u003ccode class=\"\"\u003e\u003cpre\u003eimport supervisor\n...\nsupervisor.set_next_code_file(\"admin.py\",sticky_on_reload=True)\nsupervisor.reload()\n\u003c/pre\u003e\u003c/code\u003e\n\u003cp\u003eCode within \u003ccode class=\"inline\"\u003eboot.py\u003c/code\u003e can do many things for you. For example, you can\nmake the CIRCUITPY drive writable for your program or hide it for\nthe PC. Of course you can also shoot yourself in the foot, so reading\nthe documentation is recommended.\u003c/p\u003e\n","metadata":{"markdown":"Running Two Programs on a Single CircuitPython Device\n=====================================================\n\nI have a nice e-ink display on my shelf running a CircuitPython\nprogram that updates the display every fifteen minutes. Recently\nI wrote a second program that would also just be ideal for this display.\n\nThinking about my options, I could either\n\n  - copy the relevant program to the MCU everytime I want to switch\n  - write a wrapper application for both programs\n\nThe first option was not attractive, because I want to switch on a\nregular basis. The second option also has it's problems. Both existing\nprograms are valid stand-alone and are maintained in their own\nGithub-repositories, so adding a wrapper would make changes to the software\nmuch more complicated.\n\nSo I was thinking about a sort of \"boot-switcher\". Users running Linux\non their PCs often have a boot-manager that allows them to switch to\nmore obscure operating systems once in a while, and I wondered if\nthere is a simple way to use this concept also for a CircuitPython device.\n\n\nHardware Setup\n--------------\n\nSince a MCU neither has a BIOS that can start a boot-manager before the\nreal OS, nor (usually) a mouse or keyboard attached for the selection of\nthe target program, this solution needs a free GPIO and a slider or button.\n\nOur \"boot-manager\" is the code in `boot.py`. This code reads the state\nof the GPIO and starts one of the programs depending on the value. Using\na slider will result in a \"sticky\" behavior: unless you flip it again,\neach reset will start the same program. The button is different, since\nyou have to keep it pressed during power-on/reset to start the second\nprogram. So the second program is more of an exception while the first\nprogram runs as the default.\n\n\nSoftware Setup\n--------------\n\nFor the boot-switcher to work, the programs can't be deployed to the\ntop-level directory of the CIRCUITPY-drive. Instead, the layout on the\ndrive is like this:\n\n![](https://github.com/bablokb/cp-bootswitcher/blob/main/directories.png?raw=true)\n\nSo every application moves one level down into its own directory.\n\nYou can find the necessary `boot.py` in the Github-repo\n\u0026lt;https://github.com/bablokb/cp-bootswitcher\u0026gt;. In this file, you have to\nconfigure your setup, e.g.\n\n    PIN_SWITCH = board.D1\n    APP_NAMES  = [\"_app0\",\"_app1\"]\n    SHARED_FILES = [\"boot.py\"]+APP_NAMES\n\nThe names of the application directories don't matter, but you have to\nadd the names to `APP_NAMES`. You can also add more files/directories\nto `SHARED_FILES`, but all of these entries must be top-level. One\nuse case for this is if your applications share the lib-folder.\n\n\nHow does it Work?\n-----------------\n\nThe implementation of the boot-switcher turned out to be simple and\nduring power-on or reset you won't notice a delay. The code in\n`boot.py` only checks the state of the GPIO and then moves all files\nfrom the top-level back to the appropriate application folder and then\nall files from the other application folder to the toplevel. Moving is\na fast operation, since it only changes the entry in the\ndirectory-table (FAT), so moving a very large lib-folder won't be slower\nthan moving `code.py`.\n\n\nOther Use-Cases\n---------------\n\nThere are a number of other use-cases for the boot-switcher, e.g.\n\n  - providing two versions of an application, e.g. a beta version in\n    addition to a productive version\n  - providing a special diagnostic application in addition to the normal\n    application\n  - running the same application with two different configurations\n    (e.g. within two different WLAN-networks)\n  - having two sets of libraries e.g. for CP 8.x and 9.x\n\n\nFinal Notes\n-----------\n\nIf you only want to start different top-level programs and otherwise\nshare the rest of your code and all settings, you don't need\nthe boot-switcher. You can do this with a few lines of code, e.g.:\n\n    import supervisor\n    ...\n    supervisor.set_next_code_file(\"admin.py\",sticky_on_reload=True)\n    supervisor.reload()\n\nCode within `boot.py` can do many things for you. For example, you can\nmake the CIRCUITPY drive writable for your program or hide it for\nthe PC. Of course you can also shoot yourself in the foot, so reading\nthe documentation is recommended.\n"}}]