1#!/usr/bin/env python3 2# Copyright 2020 The Pigweed Authors 3# 4# Licensed under the Apache License, Version 2.0 (the "License"); you may not 5# use this file except in compliance with the License. You may obtain a copy of 6# the License at 7# 8# https://www.apache.org/licenses/LICENSE-2.0 9# 10# Unless required by applicable law or agreed to in writing, software 11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13# License for the specific language governing permissions and limitations under 14# the License. 15"""Detects attached Teensy boards connected via usb.""" 16 17import logging 18import re 19import subprocess 20import typing 21 22from pathlib import Path 23 24import pw_arduino_build.log 25 26_LOG = logging.getLogger('teensy_detector') 27 28 29class UnknownArduinoCore(Exception): 30 """Exception raised when a given core can not be found.""" 31 32 33def log_subprocess_output(level, output): 34 """Logs subprocess output line-by-line.""" 35 36 lines = output.decode('utf-8', errors='replace').splitlines() 37 for line in lines: 38 _LOG.log(level, line) 39 40 41class BoardInfo(typing.NamedTuple): 42 """Information about a connected dev board.""" 43 44 dev_name: str 45 usb_device_path: str 46 protocol: str 47 label: str 48 arduino_upload_tool_name: str 49 50 def test_runner_args(self) -> list[str]: 51 return [ 52 "--set-variable", 53 f"serial.port.protocol={self.protocol}", 54 "--set-variable", 55 f"serial.port={self.usb_device_path}", 56 "--set-variable", 57 f"serial.port.label={self.dev_name}", 58 ] 59 60 61def detect_boards(arduino_package_path=False) -> list: 62 """Detect attached boards, returning a list of Board objects.""" 63 64 teensy_core = Path() 65 if arduino_package_path: 66 teensy_core = Path(arduino_package_path) 67 else: 68 teensy_core = Path("third_party/arduino/cores/teensy") 69 if not teensy_core.exists(): 70 teensy_core = Path( 71 "third_party/pigweed/third_party/arduino/cores/teensy" 72 ) 73 74 if not teensy_core.exists(): 75 raise UnknownArduinoCore 76 77 teensy_device_line_regex = re.compile( 78 r"^(?P<address>[^ ]+) (?P<dev_name>[^ ]+) " 79 r"\((?P<label>[^)]+)\) ?(?P<rest>.*)$" 80 ) 81 82 boards = [] 83 detect_command = [ 84 (teensy_core / "tools" / "teensy-tools" / "1.58.0" / "teensy_ports") 85 .absolute() 86 .as_posix(), 87 "-L", 88 ] 89 90 # TODO(tonymd): teensy_ports -L on windows does not return the right port 91 # string Example: 92 # 93 # $ teensy_ports -L 94 # Port_#0001.Hub_#0003 COM3 (Teensy 3.6) Serial 95 # 96 # So we get "-port=Port_#0001.Hub_#0003" 97 # But it should be "-port=usb:0/140000/0/1" 98 99 process = subprocess.run( 100 detect_command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT 101 ) 102 if process.returncode != 0: 103 _LOG.error("Command failed with exit code %d.", process.returncode) 104 _LOG.error("Full command:") 105 _LOG.error("") 106 _LOG.error(" %s", " ".join(detect_command)) 107 _LOG.error("") 108 _LOG.error("Process output:") 109 log_subprocess_output(logging.ERROR, process.stdout) 110 _LOG.error('') 111 for line in process.stdout.decode("utf-8", errors="replace").splitlines(): 112 device_match_result = teensy_device_line_regex.match(line) 113 if device_match_result: 114 teensy_device = device_match_result.groupdict() 115 boards.append( 116 BoardInfo( 117 dev_name=teensy_device["dev_name"], 118 usb_device_path=teensy_device["address"], 119 protocol="Teensy", 120 label=teensy_device["label"], 121 arduino_upload_tool_name="teensyloader", 122 ) 123 ) 124 return boards 125 126 127def main(): 128 """This detects and then displays all attached discovery boards.""" 129 130 pw_arduino_build.log.install(logging.INFO) 131 132 boards = detect_boards() 133 if not boards: 134 _LOG.info("No attached boards detected") 135 for idx, board in enumerate(boards): 136 _LOG.info("Board %d:", idx) 137 _LOG.info(" - Name: %s", board.label) 138 _LOG.info(" - Port: %s", board.dev_name) 139 _LOG.info(" - Address: %s", board.usb_device_path) 140 _LOG.info( 141 " - Test runner args: %s", " ".join(board.test_runner_args()) 142 ) 143 144 145if __name__ == "__main__": 146 main() 147