diff --git a/README.md b/README.md index 780a9749fbff1e61c26d3288cf8ab7fea34e9ff7..5f9e698fc64513cfa3d87a8c392cbf315a61c900 100644 --- a/README.md +++ b/README.md @@ -26,10 +26,10 @@ The following Python code on the host computer lets you instantiate the device a ```python # test_led.py (Python) -import mpy_bridge +import mpybridge # instantiate the device on serial port COM1 -d = mpy_bridge.Device("COM1") +d = mpybridge.Device("COM1") # prints available functions print(d) @@ -63,10 +63,10 @@ A host program can send Python objects, and receive some in return. In this case ```python # test_pins.py (Python) -import mpy_bridge +import mpybridge # instantiate the device on serial port COM5 -d = mpy_bridge.Device("COM5") +d = mpybridge.Device("COM5") # setup 3 pins at once d.setup_inputs([2, 6, 15]) @@ -74,7 +74,7 @@ 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} +print(values) # prints {2: 1, 6: 0, 15: 1} ``` ## Prints handling @@ -95,13 +95,13 @@ Exceptions on the MicroPython side are turned into Python exceptions, which can ```python # test_exceptions.py (Python) -import mpy_bridge +import mpybridge -d = mpy_bridge.Device("COM5") +d = mpybridge.Device("COM5") try: d.read_inputs() -except mpy_bridge.MicroPythonError as e: +except mpybridge.MicroPythonError as e: print(f"Error on the device: {e}") ``` @@ -111,10 +111,10 @@ This module includes a basic tool to update the main file on the MicroPython dev ```python # test_upload.py (Python) -import mpy_bridge +import mpybridge # instantiate device, skip init in case main file is missing -d = mpy_bridge.Device("COM34", init=False) +d = mpybridge.Device("COM34", init=False) # upload main file d.upload("embedded/main.py", "main.py") diff --git a/examples/test_blink.py b/examples/test_blink.py index aa81277ab6e973dd7cf5335985a022da04f57415..5c536a8647806fea5a4a7e110efc3f6e0bd29c54 100644 --- a/examples/test_blink.py +++ b/examples/test_blink.py @@ -1,10 +1,10 @@ -import mpy_bridge +import mpybridge import time def main(): # open device, read functions - d = mpy_bridge.Device("COM39") + d = mpybridge.Device("COM39") # print out functions print(d) diff --git a/examples/test_time.py b/examples/test_time.py index 5329ad9925479ebd64d76726934f55bce3b08024..075d4f91917dd750381f8a969f7995b218a23e57 100644 --- a/examples/test_time.py +++ b/examples/test_time.py @@ -1,11 +1,11 @@ import datetime -import mpy_bridge +import mpybridge import numpy as np import matplotlib.pyplot as plt def main(): - d = mpy_bridge.Device("COM39") + d = mpybridge.Device("COM39") n_exp = 1024 results = [] diff --git a/examples/test_upload.py b/examples/test_upload.py index 8c3e3a2bf2664f0eceee67e03192976df7a574fe..eee62965d159093952ac35587075447635612256 100644 --- a/examples/test_upload.py +++ b/examples/test_upload.py @@ -1,9 +1,9 @@ -import mpy_bridge +import mpybridge def main(): # Open device, skip reading its functions (the main file might be absent) - d = mpy_bridge.Device("COM39", init=False) + d = mpybridge.Device("COM39", init=False) # Upload main file d.upload("micropython/main.py", "main.py") diff --git a/mpy_bridge.py b/mpybridge.py similarity index 66% rename from mpy_bridge.py rename to mpybridge.py index 6fe86ec0819be44e060be1d7581e64c8b30e6284..1db90e2ad7986c74bba66407729fcedc7bf008b2 100644 --- a/mpy_bridge.py +++ b/mpybridge.py @@ -8,6 +8,7 @@ # warranty is provided, and users accept all liability. import os.path import serial +import serial.tools.list_ports import time import re import ast @@ -17,61 +18,77 @@ class MicroPythonError(RuntimeError): pass +class MicroPythonFileError(MicroPythonError): + pass + + class Device: - init_delay = 0.1 + init_delay = 0.15 + timeout_delay = 2.0 - def __init__(self, port, main_filename="main.py"): - self.ser_obj = serial.Serial(port) + def __init__(self, port, main_filename="main.py", show_prints=True, init=True): + self.ser_obj = serial.Serial(port, timeout=self.timeout_delay) self.function_args = {} self.function_callable = {} self.main_filename = main_filename self.main_module = main_filename.replace(".py", "") - # initialize - self.init() - self.read_functions() + self.show_prints = show_prints + self.reset() + if init: + self.init() - def init(self): + def reset(self): s = self.ser_obj s.write(b'\x04') s.write(b'\r\n\x03\r\n') s.write(b'\x01') time.sleep(self.init_delay) - # clean slate + s.flushInput() + # wipe out previous state + for name in self.function_args: + delattr(self, name) + self.function_args = {} + self.function_callable = {} + + def init(self): + # read functions + self.read_functions() # make sure main file is imported self.run(f'import {self.main_module}') - def run(self, cmd, show=False, end="\n"): + def run(self, cmd, end="\n", ignore_error=False): s = self.ser_obj s.write((cmd+end).encode("utf-8")) s.write(b'\x04') # ^d reset - # >OK<RETURN>\x04 - txt_ret = s.read_until(b"\x04")[3:-1].decode("utf-8") + # OK<RETURN>\x04 + txt_ret = s.read_until(b"\x04")[2:-1].decode("utf-8") - # <ERROR>\x04 - txt_err = s.read_until(b"\x04")[:-1].decode("utf-8") + # <ERROR>\x04> + txt_err = s.read_until(b"\x04>")[:-2].decode("utf-8") - if len(txt_err) > 0: + if len(txt_err) > 0 and not ignore_error: raise MicroPythonError(txt_err) - if show: - print(f"RETURN: '{txt_ret.rstrip()}'") - return txt_ret.rstrip() def run_func(self, func_name, *args, **kwargs): args_list = list(repr(x) for x in args) kwargs_list = list(f"{a}={repr(b)}" for a, b in kwargs.items()) - cmd_txt = f"print(repr({self.main_module}.{func_name}({','.join(args_list+kwargs_list)})))" - ret_txt = self.run(cmd_txt) - return ast.literal_eval(ret_txt) + cmd_txt = f"_ret = {self.main_module}.{func_name}({','.join(args_list+kwargs_list)})" + ret_print = self.run(cmd_txt) + if self.show_prints and len(ret_print) > 0: + print(f"MPY_PRINT@{self.ser_obj.port}:{ret_print}") + ret_val = self.run(f"print(repr(_ret))") + return ast.literal_eval(ret_val) def read_functions(self): + # open file try: self.run(f'f=open("{self.main_filename}","rb")') - except MicroPythonError: - raise FileNotFoundError(f"Could not find {self.main_filename} on device!") + except MicroPythonError as e: + raise MicroPythonFileError(str(e)) # read main txt file main_txt = ast.literal_eval(self.run('print(f.read())')).decode("utf-8") @@ -86,6 +103,13 @@ class Device: self.function_callable[name] = func setattr(self, name, func) + def testREPL(self): + # reduce waiting time for this simple test + txt = self.run("print(6*7)") + if len(txt) == 0: + return False + return ast.literal_eval(txt) == 42 + def __str__(self): txt = f"MicroPython Device at {self.ser_obj.port}, available functions:\n" if len(self.function_args) == 0: @@ -102,17 +126,12 @@ class Device: self.close() def update(self): - for name in self.function_args: - delattr(self, name) - self.function_args = {} - self.function_callable = {} self.init() - self.read_functions() def upload_main(self, filename): self.upload(filename, self.main_filename) - def upload(self, filename, destination=None, update=True): + def upload(self, filename, destination=None, init=True): if destination is None: _, destination = os.path.split(filename) with open(filename, "r") as f: @@ -120,8 +139,9 @@ class Device: self.run(f'f = open("{destination}", "wb")') self.run(f'f.write({repr(file_txt)})') self.run(f'f.close()') - if update: - self.update() + self.reset() + if init: + self.init() def remove(self, filename): self.run('import os')