Belay - Interface with hardware from your PC from within a single Python program

Belay lets you write and run Python code on a host like PC and execute pieces of that code down on a connected microcontroller from inside your host Python session.  You write python code and mark up which code will run on the microcontroller (MCU).  The rest of the code runs on the host.  At runtime Belay will transfer the MCU targeted code down to the microcontroller.  The host Python code can then call that downloaded MCU based code as if it were running locally. 

MicroPython CircuitPython and host Python

Belay runs on Python3 on the host. It can download and interact with MicroPython or CircuitPython runtimes on the microcontroller.

Code on both the Host and the Microcontroller

All the Python code starts on the PC on the left. For this example, everything is in one file like the led flasher above. Both PC and MCU code is in the same Python source file. We copy any libraries from the PC to the RP2040 /pydrive.  This can be done externally or using the drive replicator in Belay.

The @device.task functions are downloaded to the microcontroller and remote executers are built, one for each downloaded function. The PC/Mac/Linux-based Python code actually calls a remote executer that acts as a proxy for and is paired with the function that was sent to the microcontroller. Host side code can be any mix of local and remote calls.


There is a communication speed limit between the host and the microcontroller. This means you have to be judicious about the data you send back in forth especially if there are any highly timing critical functions.  You may need to move more autonomy to the MCU in those cases.

Using example/01_led 
  1. main.py holds all the code for both the host and microcontroller in a single file.
  2. There is a main.py python program that we will execute on the PC.  
  3. All of the functions tagged with @Belay.Task are downloaded to the PC and executers are created.
  4. The @Belay.Setup method is run creating the global context
  5. The code in main.py outside the @Belay tags runs on the PC sometimes pausing to call the @Belay.Task functions that are deployed  microcontroller

Blinky Light Code Example

This is the Belay version of the blinking light.
  1. This sample downloads the set_led() method to the microcontroller. 
  2. It then runs setup() which runs that code on the microcontroller to create the led variable. 
  3. Finally, it loops on the host invoking set_led() in an alternating on/off pattern. set_led() is on the MCU so each set_led() call runs remotely over the connected REPL session.

 Setup the connection with the micropython board.
# This also executes a few common imports on-device.
device = belay.Device(args.port)

# This runs in a global scope on the MCU making the variables available
@device.setup
def setup():  # The function name doesn't matter, but is "setup" by convention.
    from machine import Pin
    led = Pin(25, Pin.OUT)

# This sends the function's code over to the board.
# Calling the local ``set_led`` function will execute it on-device.
@device.task
def set_led(state):
    # Configuration for a Pi Pico board.
    print(f"Printing from device; turning LED to {state}.")
    led.value(state)

setup()
# Calls set_led() that is actually running on the microcontroller
while True:
    set_led(True)
    time.sleep(0.5)
    set_led(False)
    time.sleep(0.5)

Segregating Host/PC and Microcontroller functionality

I'm kind of OCD so I like to have segregated my host-based code from the Python code that will be sent to the MCU.  This results in a main.py targeted at the host PC with mcu bound code all in one or more class definitions.

You can put all your mcu targeted code into a class marking each remote bound method with the @Belay.Task tag. The code will be sent to the microcontroller when the class is instantiated on the host. The main.py just imports and creates that class.  It can then run like any normal Python environment other than some of the calls end up executing remotely to the microcontroller.



This is actually taken from the 08B example in the Belay repository and it contains two files for each test. 
  1. There is a python file with a class definition in it that holds all the code we want to run on the Microcontroller.
  2. There is a main.py python program that we will execute on the PC.  
  3. The main.py imports the tagged python file with the class Definition.
  4. All of the functions in the class that are tagged with @Belay.Task are downloaded to the PC and executers are created.
  5. The @Belay.Setup() method is run creating a global context.
  6. All the code in main.py runs on the PC sometimes pausing to call the @Belay.Task functions that are deployed  microcontroller 

Video

More Video

General Execution Flow

This gives a more graphical explanation of how it works with a little handwaving.  The top two swimlanes both run on the PC/Mac/Server/Pi or whatever. The bottom swimlane runs on a microcontroller like a SAMD21 or the Pico 2040.


Revision History

Created 2023-02


Comments

Popular posts from this blog

Understanding your WSL2 RAM and swap - Changing the default 50%-25%

Installing the RNDIS driver on Windows 11 to use USB Raspberry Pi as network attached

DNS for Azure Point to Site (P2S) VPN - getting the internal IPs