From ca5196da571020de510531e349a8dabce440288d Mon Sep 17 00:00:00 2001 From: Ted Schundler Date: Sat, 3 Feb 2024 19:24:28 -0800 Subject: [PATCH 1/2] Improve reboot and enter REPL reliability The previous code did not work when the microcontroller is already in REPL mode, and soft reboot did not work at all with latest CircuitPython. --- circuitpython_kernel/board.py | 61 +++++++++++++++++++++++++++-------- 1 file changed, 48 insertions(+), 13 deletions(-) diff --git a/circuitpython_kernel/board.py b/circuitpython_kernel/board.py index 96f5160..f7cfd04 100644 --- a/circuitpython_kernel/board.py +++ b/circuitpython_kernel/board.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- """Serial Connection to a Board""" import logging +import time from serial import Serial from serial.tools.list_ports import comports from serial.serialutil import SerialException @@ -8,6 +9,9 @@ # Create BOARD_LOGGER for debug messages. BOARD_LOGGER = logging.getLogger(__name__) +# Timeouts +ENTER_REPL_TIMEOUT = 5.0 +REBOOT_WAIT = 0.5 # Vendor IDs ADAFRUIT_VID = 0x239A # SAMD @@ -24,9 +28,11 @@ # repl messages MSG_NEWLINE = b"\r\n" MSG_RAWREPL = b"raw REPL; CTRL-B to exit" -MSG_RAWREPL_PROMPT = b"\r\n>" +MSG_RAWREPL_BARE_PROMPT = b">" +MSG_RAWREPL_PROMPT = MSG_NEWLINE + MSG_RAWREPL_BARE_PROMPT MSG_SOFT_REBOOT = b'soft reboot\r\n' MSG_RELOAD = b'Use CTRL-D to reload.' +MSG_AUTORELOAD = b'Auto-reload is on.' class BoardError(Exception): """Errors relating to board connections""" @@ -61,6 +67,21 @@ def read_until(self, msg): self.connected = False raise BoardError(f"cannot read from board: {serial_error}") + def read_until_any(self, *msgs): + """Reads board until the tokens `msgs`. + """ + content = bytearray() + while True: + try: + got = self.serial.read(1) + except SerialException as serial_error: + self.connected = False + raise BoardError(f"cannot read from board: {serial_error}") + + content.extend(got) + for msg in msgs: + if content.endswith(msg): + return content def read_all(self): """Attempts to read all incoming msgs from board. @@ -90,24 +111,38 @@ def softreset(self): self.enter_raw_repl() # now do the soft reset ... BOARD_LOGGER.debug("* ^D, soft reset") - serial.write(CHAR_CTRL_D) - serial.read_until(MSG_SOFT_REBOOT) - serial.read_until(MSG_RELOAD) - serial.write(b'\n') - serial.read_until(MSG_RAWREPL) - serial.read_until(MSG_RAWREPL_PROMPT) + self.write(CHAR_CTRL_D) + self.read_until(MSG_SOFT_REBOOT) + BOARD_LOGGER.debug("* waiting for fresh boot ...") + self.read_until_any(MSG_RELOAD, MSG_AUTORELOAD) + BOARD_LOGGER.debug("* saw boot message, sleeping before interrupt ...") + time.sleep(REBOOT_WAIT) + self.enter_raw_repl() BOARD_LOGGER.debug("* soft reset complete, in raw repl") def enter_raw_repl(self): """Enters the RAW circuitpython repl. """ BOARD_LOGGER.debug('* enter raw repl ...') - serial = self.serial - serial.write(CHAR_CTRL_C) - serial.write(CHAR_CTRL_A) - # wait for prompt - serial.read_until(MSG_RAWREPL) - serial.read_until(MSG_RAWREPL_PROMPT) + self.write(CHAR_CTRL_C) + self.write(CHAR_CTRL_A) + BOARD_LOGGER.debug('* waiting for prompt ...') + # just waiting for the ">" prompt, but sometimes there may be other text before it, which includes a ">", so read_until isn't correct. + start = time.monotonic() + while True: + msg = self.read_all() + + if not msg: + if time.monotonic() - start > ENTER_REPL_TIMEOUT: + raise BoardError("no response from board") + time.sleep(0.01) + continue + + BOARD_LOGGER.debug(f"got: {msg}") + + if msg.endswith(MSG_RAWREPL_BARE_PROMPT): + break + BOARD_LOGGER.debug('* entered raw repl, returning to kernel...') def connect(self): From 4588eed7ca8e2e5a92fa8bf2a7324b9e326353dc Mon Sep 17 00:00:00 2001 From: Ted Schundler Date: Sat, 3 Feb 2024 19:35:43 -0800 Subject: [PATCH 2/2] flake8 fixes --- circuitpython_kernel/board.py | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/circuitpython_kernel/board.py b/circuitpython_kernel/board.py index f7cfd04..c24642d 100644 --- a/circuitpython_kernel/board.py +++ b/circuitpython_kernel/board.py @@ -14,10 +14,10 @@ REBOOT_WAIT = 0.5 # Vendor IDs -ADAFRUIT_VID = 0x239A # SAMD -ESP8266_VID = 0x10C4 # Huzzah ESP8266 -PICO_VID = 0x239A # PICO PI -TEENSY_VID = 0x16C0 #PJRC Teensy 4.1 +ADAFRUIT_VID = 0x239A # SAMD +ESP8266_VID = 0x10C4 # Huzzah ESP8266 +PICO_VID = 0x239A # PICO PI +TEENSY_VID = 0x16C0 # PJRC Teensy 4.1 # repl commands CHAR_CTRL_A = b'\x01' @@ -34,6 +34,7 @@ MSG_RELOAD = b'Use CTRL-D to reload.' MSG_AUTORELOAD = b'Auto-reload is on.' + class BoardError(Exception): """Errors relating to board connections""" def __init__(self, msg): @@ -57,7 +58,6 @@ def write(self, msg): self.connected = False raise BoardError(f"cannot write to board: {serial_error}") - def read_until(self, msg): """Reads board until end of `msg`. """ @@ -106,7 +106,6 @@ def softreset(self): """Resets the circuitpython board (^D) from a jupyter cell. """ - serial = self.serial # in case the VM is in a weird state ... self.enter_raw_repl() # now do the soft reset ... @@ -127,7 +126,8 @@ def enter_raw_repl(self): self.write(CHAR_CTRL_C) self.write(CHAR_CTRL_A) BOARD_LOGGER.debug('* waiting for prompt ...') - # just waiting for the ">" prompt, but sometimes there may be other text before it, which includes a ">", so read_until isn't correct. + # just waiting for the ">" prompt, but sometimes there may be other + # text before it, which includes a ">", so read_until isn't correct. start = time.monotonic() while True: msg = self.read_all() @@ -155,8 +155,8 @@ def connect(self): try: BOARD_LOGGER.debug(f'connect: open {device}') self.serial = Serial(device, 115200, parity='N') - except: - raise BoardError(f"failed to access {device}") + except Exception as e: + raise BoardError(f"failed to access {device}: {e}") # open the port if not self.serial.is_open: try: @@ -172,17 +172,18 @@ def connect(self): try: self.enter_raw_repl() self.connected = True - except: - raise BoardError(f"failed to enter raw repl with {device}") - + except Exception as e: + raise BoardError(f"failed to enter raw repl with {device}: {e}") def _find_board(self): """Find serial port where an Adafruit board is connected""" for port in comports(): # print out each device BOARD_LOGGER.debug(port.device) - if port.vid == ADAFRUIT_VID or port.vid == ESP8266_VID or port.vid == PICO_VID or port.vid == TEENSY_VID: - BOARD_LOGGER.debug(f"CircuitPython Board Found at: {port.device}") + if port.vid == ADAFRUIT_VID or port.vid == ESP8266_VID or \ + port.vid == PICO_VID or port.vid == TEENSY_VID: + BOARD_LOGGER.debug( + f"CircuitPython Board Found at: {port.device}") BOARD_LOGGER.debug(f"Connected? {self.connected}") return port.device raise BoardError("found no board")