Created
November 8, 2024 23:12
-
-
Save markusand/d5a7b9806dba059a565dfdd947d1747b to your computer and use it in GitHub Desktop.
Lightweight version of pyboard. Includes replacement of module constants declaration for execfile()
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
Minimal pyboard.py implementation for communicating with MicroPython boards | |
""" | |
import os | |
import time | |
import re | |
import serial | |
import serial.tools.list_ports | |
class PyboardError(Exception): | |
"""Exception raised for errors in the pyboard.""" | |
def parse_error(error: PyboardError): | |
"""Parse a PyboardError into a human-readable message""" | |
message = str(error).split(r"\r\n")[-2:][0] # Get the second-to-last or last line | |
return message.split(":")[-1].strip() | |
class Pyboard: | |
"""Class for communicating with MicroPython boards""" | |
def __init__(self, device, baudrate=115200, wait=0): | |
"""Initialize connection to the board""" | |
try: | |
self._serial = serial.Serial(device, baudrate, timeout=1) | |
except serial.SerialException as e: | |
raise PyboardError(f"Failed to open serial port {device}: {str(e)}") from e | |
if wait: | |
time.sleep(wait) | |
self.enter_repl() | |
def close(self): | |
"""Close the serial connection""" | |
if self._serial: | |
self.exit_repl() | |
self._serial.close() | |
def read_until(self, min_bytes, ending, timeout=10): | |
"""Read from serial until ending sequence is found""" | |
if not self._serial: | |
raise PyboardError("Board not connected") | |
data = self._serial.read(min_bytes) | |
start_time = time.time() | |
while True: | |
if time.time() - start_time > timeout: | |
raise PyboardError("Timeout waiting for board response") | |
if data.endswith(ending): | |
break | |
if self._serial.in_waiting > 0: | |
data += self._serial.read(1) | |
start_time = time.time() | |
return data | |
def enter_repl(self): | |
"""Enter raw REPL mode""" | |
if not self._serial: | |
raise PyboardError("Board not connected") | |
self._serial.write(b"\x03\x03") # CTRL-C twice | |
time.sleep(0.1) | |
self._serial.write(b"\x01") # CTRL-A | |
self.read_until(1, b"raw REPL; CTRL-B to exit\r\n>") | |
def exit_repl(self): | |
"""Exit raw REPL mode""" | |
if not self._serial: | |
raise PyboardError("Board not connected") | |
self._serial.write(b"\x02") # CTRL-B | |
time.sleep(0.1) | |
def exec(self, command, timeout=10): | |
"""Execute command in raw REPL mode""" | |
if not self._serial: | |
raise PyboardError("Board not connected") | |
self._serial.reset_input_buffer() # Flush any data that might be in the buffer | |
self._serial.write(command.encode("utf8") + b"\x04") | |
if self._serial.read(2) != b"OK": | |
raise PyboardError(f"Could not execute the command {command}") | |
data = self.read_until(1, b"\04") | |
if not data.endswith(b"\x04"): | |
raise PyboardError("Timeout waiting for first EOF") | |
data = data[:-1] | |
error = self.read_until(1, b"\x04", timeout=timeout) | |
if not error.endswith(b"\x04"): | |
raise PyboardError("Timeout waiting for second EOF") | |
if error[:-1]: | |
raise PyboardError( | |
f"Error with command {command} because {error.decode('utf-8')[:-1]}" | |
) | |
return data.decode("utf8").strip() | |
def execfile(self, filepath, **kwargs): | |
"""Execute a local MicroPython script file on the board with optional arguments""" | |
if not os.path.exists(filepath): | |
raise PyboardError(f"File not found: {filepath}") | |
try: | |
with open(filepath, "r", encoding="utf8") as file: | |
data = file.read() | |
# Remove comments and docstrings | |
data = re.sub(r'""".*?"""|#.*$', "", data, flags=re.MULTILINE) | |
# Replace module constant assignments | |
if kwargs: | |
for k, v in kwargs.items(): | |
pattern = rf"^{k}\s*=\s*.*" | |
data = re.sub(pattern, f"{k} = {v}", data, flags=re.MULTILINE) | |
return self.exec(data) | |
except IOError as e: | |
raise PyboardError(f"Failed to read file {filepath}: {e}") from e | |
except PyboardError as e: | |
raise PyboardError(f"Error executing {filepath}: {e}") from e | |
except Exception as e: | |
raise PyboardError(f"Unexpected error executing {filepath}: {e}") from e | |
def reset(self): | |
"""Reset the board""" | |
if not self._serial: | |
raise PyboardError("Board not connected") | |
self._serial.write(b"\x04") # CTRL-D | |
time.sleep(0.5) | |
self.enter_repl() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment