Python <-> MicroPython bridge

This project lets you interact with MicroPython devices by having access to an equivalent Python object on your host computer. All of its functions are dynamically generated based on the signatures found in the source code of the MicroPython project.
Sending and receiving Python objects is seamlessly supported, as long as those objects have a string representation.
How to use
Let's assume the following MicroPython file main.py
is present on the device:
# main.py (MicroPython)
import machine
# create a pin object
p = machine.Pin(4, machine.Pin.OUT)
def led_on():
# turn the LED on
p.on()
The following Python code on the host computer lets you instantiate the device and call the function that was automatically discovered:
# test_led.py (Python)
import mpybridge
# instantiate the device on serial port COM1
d = mpybridge.Device("COM1")
# prints available functions
print(d)
# call the function on the device
d.led_on()
Function parameters and return values are supported, but they need to have valid string representations. This is the case for all built-in types.
Here is a more complex example of a MicroPython script:
# main.py (MicroPython)
import machine
# create a pin object
pins = {}
def setup_inputs(pin_list):
global pins
# create a dictionary of {pin number: pin object}
pins = {p: machine.Pin(p, machine.Pin.IN) for p in pin_list}
def read_inputs():
# return a dictionary of {pin number: pin value}
return {p: pins[p].value() for p in pins.keys()}
A host program can send Python objects, and receive some in return. In this case, an entire dictionary of pin values is returned:
# test_pins.py (Python)
import mpybridge
# instantiate the device on serial port COM5
d = mpybridge.Device("COM5")
# setup 3 pins at once
d.setup_inputs([2, 6, 15])
# return values of all requested pins
values = d.read_inputs()
print(values) # prints {2: 1, 6: 0, 15: 1}
Prints handling
By default, print statements from the device are relayed and displayed as:
MPY_PRINT@COM1:hello world!
These messages can be suppressed by passing show_prints=False
when instantiating the device.
For more complex message exchange, it's best to pass a string to the host computer with a return
statement.
Exceptions handling
Exceptions on the MicroPython side are turned into Python exceptions, which can help you debug the embedded code. You can catch them and react accordingly:
# test_exceptions.py (Python)
import mpybridge
d = mpybridge.Device("COM5")
try:
d.read_inputs()
except mpybridge.MicroPythonError as e:
print(f"Error on the device: {e}")
Upload code
This module includes a basic tool to update the main file on the MicroPython device:
# test_upload.py (Python)
import mpybridge
# instantiate device, skip init in case main file is missing
d = mpybridge.Device("COM34", init=False)
# upload main file
d.upload("embedded/main.py", "main.py")
# see new function list
print(d)
The object is automatically updated after each upload. By default, functions are searched in a file named main.py
. You can have other files on the device, such as libraries, but those functions won't appear in the generated Python object.
Results
To evaluate the speed of the transmission back and forth, a simple round-trip time estimation was performed between an rp2040 and a typical laptop. The result show an average of ~3.4ms per function call in the most basic scenario:

License
This project is provided under the MIT License.