Source code for circuitpython_kernel.board
# -*- coding: utf-8 -*-
"""Serial Connection to a Board"""
import logging
from serial import Serial
from serial.tools.list_ports import comports
from serial.serialutil import SerialException
# Create BOARD_LOGGER for debug messages.
BOARD_LOGGER = logging.getLogger(__name__)
# Vendor IDs
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'
CHAR_CTRL_B = b'\x02'
CHAR_CTRL_C = b'\x03'
CHAR_CTRL_D = b'\x04'
# repl messages
MSG_NEWLINE = b"\r\n"
MSG_RAWREPL = b"raw REPL; CTRL-B to exit"
MSG_RAWREPL_PROMPT = b"\r\n>"
MSG_SOFT_REBOOT = b'soft reboot\r\n'
MSG_RELOAD = b'Use CTRL-D to reload.'
[docs]class BoardError(Exception):
"""Errors relating to board connections"""
def __init__(self, msg):
# pylint: disable=useless-super-delegation
super().__init__(msg)
[docs]class Board:
"""Connect to CP VM, reconnect if connection lost"""
def __init__(self):
self.connected = False
self.serial = None
[docs] def write(self, msg):
"""Writes to CircuitPython Board.
"""
try:
self.serial.write(msg)
except SerialException as serial_error:
self.connected = False
raise BoardError(f"cannot write to board: {serial_error}")
[docs] def read_until(self, msg):
"""Reads board until end of `msg`.
"""
try:
return self.serial.read_until(msg)
except SerialException as serial_error:
self.connected = False
raise BoardError(f"cannot read from board: {serial_error}")
[docs] def read_all(self):
"""Attempts to read all incoming msgs from board.
"""
try:
return self.serial.read_all()
except SerialException as serial_error:
self.connected = False
raise BoardError(f"cannot read from board: {serial_error}")
[docs] def close(self):
"""Close serial connection with board.
"""
if self.serial and self.connected:
try:
self.connected = False
self.serial.close()
except SerialException:
pass
[docs] 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 ...
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)
BOARD_LOGGER.debug("* soft reset complete, in raw repl")
[docs] 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)
BOARD_LOGGER.debug('* entered raw repl, returning to kernel...')
[docs] def connect(self):
"""(re)connect to board and enter raw repl
"""
if self.connected:
return
# pylint : disable=too-many-function-args
device = self._find_board()
try:
BOARD_LOGGER.debug(f'connect: open {device}')
self.serial = Serial(device, 115200, parity='N')
except:
raise BoardError(f"failed to access {device}")
# open the port
if not self.serial.is_open:
try:
BOARD_LOGGER.debug('* opening board ...')
self.serial.open()
BOARD_LOGGER.debug('* board opened')
except SerialException as serial_error:
raise BoardError(f"failed to open {device}, {serial_error}")
else:
BOARD_LOGGER.debug('serial already open')
# enter the REPL
try:
self.enter_raw_repl()
self.connected = True
except:
raise BoardError(f"failed to enter raw repl with {device}")
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}")
BOARD_LOGGER.debug(f"Connected? {self.connected}")
return port.device
raise BoardError("found no board")